NIO與IO

NIO與IO

1、阻塞IO實(shí)例:

    public void serve(int portNumber) throws IOException {
        //創(chuàng)建一個新的 ServerSocket,用以監(jiān)聽指定端口上的連接請求
        ServerSocket serverSocket = new ServerSocket(portNumber);
        //對accept()方法的調(diào)用將被阻塞,直到一個連接建立
        Socket clientSocket = serverSocket.accept();
        //這些流對象都派生于該套接字的流對象,創(chuàng)建一個緩沖區(qū)存儲輸入流
        BufferedReader in = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
        PrintWriter out =
                new PrintWriter(clientSocket.getOutputStream(), true);
        String request, response;
        //處理循環(huán)開始
        while ((request = in.readLine()) != null) {
            if ("Done".equals(request)) {
                break;
            }
            //請求被傳遞給服務(wù)器的處理方法
            response = processRequest(request);
            //服務(wù)器的響應(yīng)被發(fā)送給了客戶端
            out.println(response);
            //繼續(xù)執(zhí)行處理循環(huán)
        }
    }

    private String processRequest(String request){
        return "Processed";
    }

上面的實(shí)例只能同時處理一個連接,要管理多個并發(fā)客戶端,需要為每個新的客戶端socket創(chuàng)建一個新的Thread,如下圖:


image.png

這種方案的影響:
①在任何時候都可能有大量的線程處于休眠狀態(tài),只是等待輸入或者輸出數(shù)據(jù)就緒,這可能算是一種資源浪費(fèi)。
②需要為每個線程的調(diào)用棧都分配內(nèi)存,其默認(rèn)值大小區(qū)間為64 KB到1 MB,具體取決于操作系統(tǒng)。
③即使Java虛擬機(jī)(JVM)在物理上可以支持非常大數(shù)量的線程,但是遠(yuǎn)在到達(dá)該極限之前,上下文切換所帶來的開銷就會帶來麻煩,例如,在達(dá)到10 000個連接的時候。

雖然這種并發(fā)方案對于支撐中小數(shù)量的客戶端來說還算可以接受,但是為了支撐100 000或者更多的并發(fā)連接所需要的資源使得它很不理想

2、NIO
非阻塞設(shè)計(jì),其實(shí)際上消除了IO的那些弊端。
實(shí)例:

public void serve(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        //將服務(wù)器綁定到選定的端口
        ss.bind(address);
        //打開Selector來處理 Channel
        Selector selector = Selector.open();
        //將ServerSocket注冊到Selector以接受連接
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;){
            try {
                //等待需要處理的新事件;阻塞將一直持續(xù)到下一個傳入事件
                selector.select();
            } catch (IOException ex) {
                ex.printStackTrace();
                //handle exception
                break;
            }
            //獲取所有接收事件的SelectionKey實(shí)例
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    //檢查事件是否是一個新的已經(jīng)就緒可以被接受的連接
                    if (key.isAcceptable()) {
                        ServerSocketChannel server =
                                (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        //接受客戶端,并將它注冊到選擇器
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());
                        System.out.println(
                                "Accepted connection from " + client);
                    }
                    //檢查套接字是否已經(jīng)準(zhǔn)備好寫數(shù)據(jù)
                    if (key.isWritable()) {
                        SocketChannel client =
                                (SocketChannel) key.channel();
                        ByteBuffer buffer =
                                (ByteBuffer) key.attachment();
                        while (buffer.hasRemaining()) {
                            //將數(shù)據(jù)寫到已連接的客戶端
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        //關(guān)閉連接
                        client.close();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // ignore on close
                    }
                }
            }
        }
    }
image.png

圖1-2 使用Selector的非阻塞I/O

class java.nio.channels.Selector是Java的非阻塞I/O實(shí)現(xiàn)的關(guān)鍵。它使用了事件通知API以確定在一組非阻塞套接字中有哪些已經(jīng)就緒能夠進(jìn)行I/O相關(guān)的操作。因?yàn)榭梢栽谌魏蔚臅r間檢查任意的讀操作或者寫操作的完成狀態(tài),所以如圖1-2所示,一個單一的線程便可以處理多個并發(fā)的連接。

總體來看,與阻塞I/O模型相比,這種模型提供了更好的資源管理:

使用較少的線程便可以處理許多連接,因此也減少了內(nèi)存管理和上下文切換所帶來開銷;
當(dāng)沒有I/O操作需要處理的時候,線程也可以被用于其他任務(wù)。
盡管已經(jīng)有許多直接使用Java NIO API的應(yīng)用程序被構(gòu)建了,但是要做到如此正確和安全并不容易。特別是,在高負(fù)載下可靠和高效地處理和調(diào)度I/O操作是一項(xiàng)繁瑣而且容易出錯的任務(wù)

參考:《Netty in Action》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容