NIO和IO多路複用

NIO的引入

使用BIO進行socket通信時,accept連接read讀 均會阻塞。

這樣就會出現以下問題:

1. 第一個客戶端建立連接後,但是不發送數據,server會釋放cpu資源,並且等待客戶端發送數據

2. 第二個客戶端無法建立連接

編寫一個BIOServer

public class BIOServer {
    public static void main(String[] args) throws IOException {
        byte[] bytes = new byte[1024];
        ServerSocket serverSocket = new ServerSocket();

        // 端口號+ip,ip默認本機
        serverSocket.bind(new InetSocketAddress(9876));

        // 阻塞--程序釋放cpu資源,程序不會向下執行
        // accept,專門負責通信
        while (true) {
            System.out.println("--等待連接--");
            Socket accept = serverSocket.accept();
            System.out.println("--連接成功--");

            // 解阻塞,向下執行,read也會阻塞
            System.out.println("--開始讀數據--");
            int read = accept.getInputStream().read(bytes);
            System.out.println("--數據讀取完成:" + read + "--");

            // 字節數組轉string
            String content = new String(bytes);
            System.out.println(content);

            // 回覆,寫,略
        }
    }
}

再創建一個客戶端

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 9876));

        System.out.println("--請輸入內容--");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String next = scanner.next();
            socket.getOutputStream().write(next.getBytes());
        }
    }
}

運行BIOServer 和client,便會出現以下結果:

總結:

1. 如果採用單線程,無法處理併發

2. 解決? 引入多線程

多線程實現socket通信

client不變,新建一個BioServerBatch類,子線程自己阻塞

public class BioServerBatch {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(9876));

        while (true) {
            System.out.println("--等待連接--");
            Socket socket = serverSocket.accept();
            System.out.println("--連接成功--");

            Thread thread = new Thread(new ExecuteSocket(socket));
            thread.start();
        }
    }

    static class ExecuteSocket implements Runnable {
        private Socket socket;
        private byte[] bytes = new byte[1024];

        // 處理每個客戶端連接 -- 讀寫
        ExecuteSocket(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                System.out.println("--開始讀數據--");

                socket.getInputStream().read(bytes);
                String content = new String(bytes);

                System.out.println("--數據讀取完成:" + content + "--");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

這樣就可以實現併發通信,但是存在一個問題,比如開100個線程,真正發生讀寫的線程只有20%有效,80%的線程存在浪費現象,對服務器損耗很大。

解決?單線程實現高併發

單線程解決高併發 NIO

實現機制:

不讓程序阻塞

  1. 等待連接和讀數據不阻塞

  2. 有人來連,設置爲非阻塞讀,將當前socket放入到list,遍歷list,查看是否有人發數據

  3. 沒人來連,將當前socket放入到list,遍歷list,查看是否有人發數據

Java爲我們提供了API來實現該機制,代碼如下:

public class NIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        List<SocketChannel> list = new ArrayList<>();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.bind(new InetSocketAddress(9876));

        // 設置爲非阻塞
        serverSocketChannel.configureBlocking(false);

        while (true) {
            Selector selector = Selector.open();
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel == null) {
                System.out.println("--沒人連接--");
                Thread.sleep(1000);
                for (SocketChannel channel : list) {
                    int k =  channel.read(byteBuffer);
                    System.out.println(k);

                    if (k != 0) {
                        // 有人發數據
                        byteBuffer.flip();
                        System.out.println("========no conn=======》" + new String(byteBuffer.array()));
                    }
                }
            } else {
                System.out.println("--有人連接--");
                socketChannel.configureBlocking(false);
                list.add(socketChannel);

                // 得到套接字,循環所以的套接字,通過套接字獲取數據
                for (SocketChannel channel : list) {
                    int k = channel.read(byteBuffer);
                    if (k != 0) {
                        byteBuffer.flip();
                        System.out.println("========conn=======》" + new String(byteBuffer.array()));
                    }
                }
            }
        }
    }
}

但該程序依然存在以下問題:

每次循環的時候,都會遍歷list列表,如果列表很大,且只有20%有讀寫,性能還是較低

解決?IO多路複用

IO多路複用

將list交給操作系統來處理

具體的方法有三個:select、poll和epoll,有興趣的可以去了解一下

應用:redis、nginx和java nio在linux上的實現

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章