NIO源码解析-ServerSocketChannel

前言:

    上一篇文章中探讨了关于Socket与SocketChannel的使用,都是基于客户端的视角来分析的。本文中我们分析下服务端,也就是ServerSocket和ServerSocketChannel。

    同样的需求,实现一个可简单对话的服务端即可。

1.基于ServerSocket的服务端

public class BIOServerSocket {

    private String address;
    private int port;

    public BIOServerSocket(String address, int port) {
        this.address = address;
        this.port = port;
    }

    public void startServer() {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(address, port));
            System.out.println("bio server start...");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("client connect...");

                // 写入 thread
                ServerWriteThread serverWriteThread = new ServerWriteThread(clientSocket);
                serverWriteThread.start();

                // 读取数据
                read(clientSocket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(Socket clientSocket) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
                System.out.println("receive msg: " + msg);
            }
        } catch (IOException e) {
            try {
                clientSocket.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        String address = "localhost";
        int port = 9999;

        BIOServerSocket bioServerSocket = new BIOServerSocket(address, port);
        bioServerSocket.startServer();
    }
}

/**
 * 从Scanner获取输入信息,并写回到client
 */
class ServerWriteThread extends Thread {
    private Socket socket;
    private PrintWriter writer;
    private Scanner scanner;

    public ServerWriteThread(Socket socket) throws IOException{
        this.socket = socket;
        scanner = new Scanner(System.in);
        this.writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
    }

    @Override
    public void run() {
        String msg = "";
        try {
            while ((msg = scanner.nextLine()) != null) {
                if (msg.equals("bye")) {
                    socket.close();
                    break;
                }
                writer.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

与上篇中的Socket类似,ServerSocket的使用也是比较简单的,笔者不再详述。

2.基于ServerSocketChannel的服务端

public class NIOServerSocket {

    private String address;
    private int port;
    private Selector selector;

    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

    private Scanner scanner = new Scanner(System.in);

    public NIOServerSocket(String address, int port) throws IOException {
        this.address = address;
        this.port = port;
        this.selector = Selector.open();
    }

    public void startServer() {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();

            serverSocket.bind(new InetSocketAddress(address, port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("nio server start...");
        } catch (IOException e) {
            System.out.println("nio server start error...");
            e.printStackTrace();
        }

        // 监听连接
        acceptClient();
    }

    private void acceptClient() {
        while (true) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                for (SelectionKey key : selectionKeys) {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();

                        SocketChannel clientChannel = server.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("client connect...");
                    } else if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();

                        readBuffer.clear();
                        StringBuffer sb = new StringBuffer("receive msg: ");
                        while (clientChannel.read(readBuffer) > 0) {
                            readBuffer.flip();
                            sb.append(new String(readBuffer.array(), 0, readBuffer.limit()));
                        }

                        System.out.println(sb.toString());
                        clientChannel.register(selector, SelectionKey.OP_WRITE);
                    } else if (key.isWritable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        String msg = scanner.nextLine();

                        writeBuffer.clear();
                        writeBuffer.put(msg.getBytes());
                        writeBuffer.flip();

                        clientChannel.write(writeBuffer);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                    }
                }

                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        String address = "localhost";
        int port = 9999;

        try {
            new NIOServerSocket(address, port).startServer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

同SocketChannel一样,我们也借助了Selector来实现连接事件的监听、接收客户端请求(read)事件的监听、发送响应(write)事件的监听。

当服务端接收到客户端请求后,先获取对应的SocketChannel,然后将SocketChannel注册Selector 读事件(默认客户端先发送请求)。

读取到客户端请求信息后,然后将SocketChannel注册Selector 写事件,将获取控制台(Scanner)的输出信息,并发送给客户端,之后注册读事件。(同样,也是与客户端一问一答

3.ServerSocketChannel API

    先来看下其类结构图

 

可以看到,其可进行端口绑定,socket属性设置(实现了NetworkChannel);可通过Selector进行事件注册(继承了SelectableChannel);
比较奇怪的是,没有像SocketChannel一样,实现了ByteChannel,没有读写操作。
ServerSocketChannel不支持读写操作,所有的读写都是基于SocketChannel来实现的。

3.1 非阻塞模式

    ServerSocketChannel同样可以设置非阻塞模式。在之前的SocketChannel中我们描述了三种情况下(connect、read、write)阻塞和非阻塞socket的区别。
    ServerSocketChannel的非阻塞模式主要用于接收客户端连接上(accept)
1)接收客户端连接
    A进程调用阻塞ServerSocket.accept方法,当尚无新的连接到达时,进程A则被阻塞,直到有新的连接达到;
    A进程调用非阻塞ServerSocketChannel.accept方法,当尚无新的连接到达时,则方法返回一个EWOULDBLOCK报错,并立即返回。
其他AbstractSelectableChannel NetworkChannel的继承实现则与SocketChannel中一样的使用方式,笔者不再赘述。

4.ServerSocket与ServerSocketChannel

    还是通过类注释,来分析下其不同之处(发现JDK的注释真是个好东西,之前没有好好看过,基本所有的文章分析来源都是这些注释,建议大家可以好好看看)
// A server socket waits for requests to come in over the network.
public
class ServerSocket implements java.io.Closeable {
    // Listens for a connection to be made to this socket and accepts 
    // it. The method blocks until a connection is made.
    public Socket accept() throws IOException {}
    
    public ServerSocketChannel getChannel() {
        return null;
    }
}

ServerSocket作为一个服务端点,其主要工作就是等待客户端的连接;

其accept方法用于监听连接的到来,当连接未建立成功时,accept方法会被阻塞。

当通过ServerSocket.getChannel方法来获取对应ServerSocketChannel时,直接返回一个null,说明对于手动创建的ServerSocket而言,没法获取其对应的channel,只能通过ServerSocketChannel.open方法来获取

// A selectable channel for stream-oriented listening sockets
public abstract class ServerSocketChannel
    extends AbstractSelectableChannel
    implements NetworkChannel
{
    // Accepts a connection made to this channel's socket
    /** If this channel is in non-blocking mode then this method will
     * immediately return <tt>null</tt> if there are no pending connections.
     * Otherwise it will block indefinitely until a new connection is available
     * or an I/O error occurs. */
    public abstract SocketChannel accept() throws IOException;
    
    // Retrieves a server socket associated with this channel.
    public abstract ServerSocket socket();

}

ServerSocketChannel作为一个可选择的通道(注册到Selector),用于监听socket连接;

通过accept方法来获取连接到的SocketChannel,看其注释,我们知道,若当前ServerSocketChannel是非阻塞的,且没有客户端连接上来,则直接返回null;若为阻塞类型的,则一直阻塞到有客户端连接上来为止。

ServerSocketChannel.socket方法则返回通道对应的ServerSocket

总结:虽然每个ServerSocketChannel都有一个对应的ServerSocket,但是不是每个ServerSocket都有一个对应的channel。

相对非阻塞的ServerSocketChannel,ServerSocket是阻塞的,通过accept方法就可以明显的区分开。

おすすめ

転載: blog.csdn.net/qq_26323323/article/details/120381461