借鉴bio的不足思考nio

bio和nio?

bio是阻塞io,accept接受连接是阻塞方法,read读取数据也是阻塞方法。
nio是非阻塞io,可以通过channel.configureBlocking(false);来设置为非阻塞。

bio的例子及其思考

bio写的服务器代码如下:

	public static void main(String[] args) throws IOException {
        final ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(9999));
        System.out.println("server start...");
        System.out.println("wait for connect...");
        while(true){
            final Socket socket = serverSocket.accept();//阻塞,直到有客户端链接
            System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
            InputStream ins = socket.getInputStream();
            byte[]  bytes = new byte[1024];
            while(true){
                  System.out.println("wait for client send data...");
                  ins.read(bytes);//阻塞,直到这个客户端发数据
                  System.out.println(new String(bytes));
            }
        }
    }

客户端如下

	public static void main(String[] args) throws IOException {
         Socket socket = new Socket();
         socket.connect(new InetSocketAddress("localhost",9999));
         Scanner scanner = new Scanner(System.in);
         while(true){
             String next = scanner.next();
             socket.getOutputStream().write(next.getBytes());
         }
    }

单线程的服务器,两个while true保证不断有人链接服务器和不断可以接受客户端数据。
可是这段代码其实有很严重的bug,设想A客户链接服务器,发送hello给服务器,
但是此时B客户想来链接服务器,却怎么也连不上。因为服务器一直在第二个while true里等待A发数据。

所以使用bio+单线程是不可能实现多客户端的链接的。常规bio做法就是一个连接一个线程,代码如下。

	public static void main(String[] args) throws IOException {
        final ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(9999));
        System.out.println("server start...");
        System.out.println("wait for connect...");
        while(true){
            final Socket socket = serverSocket.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
                        InputStream ins = socket.getInputStream();
                        byte[]  bytes = new byte[1024];
                        while(true){
                            System.out.println("wait for client send data...");
                            ins.read(bytes);
                            System.out.println(new String(bytes));
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

如果我们设想accept和read不是阻塞方法,例如
accept方法不阻塞,有人连接时返回Socket 对象,无人链接时返回null。
read方法不阻塞,有数据时返回值>0,无数据时返回值==0
这时候我们再去思考单线程实现多链接就不是没有可能的了。

基于设想的服务器代码如下:
假设accept和read是我们假设的非阻塞方法,下面这段代码是可以做到单线程多连接的。
可惜这两方法是阻塞的,于是我们迫切的需要一种非阻塞的方式去支持单线程多连接,而nio支持非阻塞。

	public static void main(String[] args) throws IOException, InterruptedException {
        List<Socket> socketList = new ArrayList<>();
        final ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(9999));
        System.out.println("server start...");
        System.out.println("wait for connect...");
        while(true){
            final Socket socket = serverSocket.accept();//设想不阻塞
            if(socket==null){
                Thread.sleep(1000);
                System.out.println("no one conn");
            }else{
                System.out.println("connect success,客户端ip:"+socket.getInetAddress()+" port:"+socket.getPort());
                socketList.add(socket);
            }

            for (Socket s:socketList) {
                InputStream ins = socket.getInputStream();
                byte[]  bytes = new byte[1024];
                int total = ins.read(bytes);//设想不阻塞
                if(total!=0){
                    System.out.println(new String(bytes));
                }
            }
        }
    }

nio单线程多连接

这段代码和我们上面的设想代码几乎思路是一样的

	public static void main(String[] args) throws IOException, InterruptedException {
        List<SocketChannel> channels = new ArrayList<>();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(9999));
        System.out.println("server start");
        System.out.println("wait for connect");
        ssc.configureBlocking(false);
        while(true){
            SocketChannel sc = ssc.accept();//非阻塞
            if(sc==null){
                Thread.sleep(1000);
                System.out.println("no conn");
            }else{
                sc.configureBlocking(false);
                channels.add(sc);
                System.out.println("connect success:"+sc.getRemoteAddress());
            }
            for (SocketChannel channel:channels) {
                int total = channel.read(buffer);//非阻塞
                if(total!=0){
                    buffer.flip();
                    System.out.println("receive data from "+channel.getRemoteAddress()+" data is:"+new String(buffer.array()));
                }
            }
        }
    }