<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                很多人說BIO不好,會“block”,但到底什么是IO的Block呢?考慮下面兩種情況: * 用系統調用`read`從socket里讀取一段數據 * 用系統調用`read`從一個磁盤文件讀取一段數據到內存 如果你的直覺告訴你,這兩種都算“Block”,那么很遺憾,你的理解與[Linux](https://so.csdn.net/so/search?from=pc_blog_highlight&q=Linux)不同。Linux認為: * 對于第一種情況,算作block,因為Linux無法知道網絡上對方是否會發數據。如果沒數據發過來,對于調用`read`的程序來說,就只能“等”。 * 對于第二種情況,**不算做block**。 是的,對于磁盤文件IO,Linux總是不視作Block。 你可能會說,這不科學啊,磁盤讀寫偶爾也會因為硬件而卡殼啊,怎么能不算Block呢?但實際就是不算。 > 一個解釋是,**所謂“Block”是指操作系統可以預見這個Block會發生才會主動Block**。例如當讀取TCP連接的數據時,如果發現Socket buffer里沒有數據就可以確定定對方還沒有發過來,于是Block;而對于普通磁盤文件的讀寫,也許磁盤運作期間會抖動,會短暫暫停,但是操作系統無法預見這種情況,只能視作不會Block,照樣執行。 基于這個基本的設定,在討論IO時,一定要嚴格區分網絡IO和磁盤文件IO。NIO和后文講到的IO多路復用只對網絡IO有意義。 > 嚴格的說,O\_NONBLOCK和IO多路復用,對標準輸入輸出描述符、管道和FIFO也都是有效的。但本文側重于討論高性能網絡服務器下各種IO的含義和關系,所以本文做了簡化,只提及網絡IO和磁盤文件IO兩種情況。 # 1. 為什么需要IO模型 1.cpu基于內存計算(內存存取速度遠高于外部設備),所以在進行數據讀取時,首先將數據從外部設備讀取到內存,這個拷貝過程可能比較耗時,這時cpu應該空閑等待,還是處理其他請求,這時io模型需要解決的事情。 ## 1.2 網絡IO 對于一個網絡I/O通信過程,比如網絡數據讀取,會涉及兩個對象: * 調用這個I/O操作的用戶線程 * 操作系統內核 一個進程的地址空間分為用戶空間和內核空間,用戶線程不能直接訪問內核空間。 當用戶線程發起I/O操作后(Selector發出的select調用就是一個I/O操作),網絡數據讀取操作會經歷兩個步驟: 1. 用戶線程等待內核將數據從網卡拷貝到內核空間 2. 內核將數據從內核空間拷貝到用戶空間 有人會好奇,內核數據從內核空間拷貝到用戶空間,這樣會不會有點浪費? 畢竟實際上只有一塊內存,能否直接把內存地址指向用戶空間可以讀取? Linux中有個叫mmap的系統調用,可以將磁盤文件映射到內存,省去了內核和用戶空間的拷貝,但不支持網絡通信場景!各種I/O模型的區別就是這兩個步驟的方式不一樣。 # 2. 同步阻塞I/O **因為IO,阻塞線程** **用戶線程發起read調用后就阻塞了**,**讓出CPU**。內核等待網卡數據到來,把數據從網卡拷貝到內核空間,接著把數據拷貝到用戶空間,再把用戶線程叫醒。 ![](https://img.kancloud.cn/43/9e/439e641ca3d7a606dc88729e1b276803_640x549.png) # 3. 同步非阻塞IO 用戶進程主動發起read調用,這是個系統調用,CPU由用戶態切換到內核態,執行內核代碼。 內核發現該socket上的數據已到內核空間,將用戶線程掛起,然后把數據從內核空間拷貝到用戶空間,再喚醒用戶線程,read調用返回。 **用戶線程不斷發起read調用(此時沒有阻塞,上邊那個調用read直接阻塞了)**,數據沒到內核空間時,每次都返回失敗,直到數據到了內核空間,這次read調用后,在等待數據從內核空間拷貝到用戶空間這段時間里,線程還是阻塞的,等數據到了用戶空間再把線程叫醒。![](https://img.kancloud.cn/f8/b6/f8b6ae5b0823c22f360b61d5c11be331_640x561.png) # 4. I/O多路復用 用戶線程的讀取操作分成兩步: * **線程先發起select調用**,問內核:數據準備好了嗎? * 等內核把數據準備好了,**用戶線程再發起read調用** * 在等待數據從內核空間拷貝到用戶空間這段時間里,線程還是阻塞的 為什么叫I/O多路復用? 因為一次select調用可以向內核查多個數據通道(Channel)的狀態。 ![](https://img.kancloud.cn/7c/15/7c159c14a7cfb3e0720b3f521626455f_640x565.png) # 5. 異步I/O 用戶線程發起read調用的同時注冊一個回調函數,read立即返回,等內核將數據準備好后,再調用指定的回調函數完成處理。在這個過程中,用戶線程一直沒有阻塞。 ![](https://img.kancloud.cn/e8/ea/e8eaad8656f2cb652445cdb4ae0e8e97_640x578.png) # 6. Java nio ## 6.1 NIOClient ``` public class NIOClient { /*標識數字*/ private static int flag = 0; /*緩沖區大小*/ private static int BLOCK = 4096; /*接受數據緩沖區*/ private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩沖區*/ private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); /*服務器端地址*/ private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress( "localhost", 8888); public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 打開socket通道 SocketChannel socketChannel = SocketChannel.open(); // 設置為非阻塞方式 socketChannel.configureBlocking(false); // 打開選擇器 Selector selector = Selector.open(); // 注冊連接服務端socket動作 socketChannel.register(selector, SelectionKey.OP_CONNECT); // 連接 socketChannel.connect(SERVER_ADDRESS); // 分配緩沖區大小內存 Set<SelectionKey> selectionKeys; Iterator<SelectionKey> iterator; SelectionKey selectionKey; SocketChannel client; String receiveText; String sendText; int count=0; while (true) { //選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。 //此方法執行處于阻塞模式的選擇操作。 selector.select(); //返回此選擇器的已選擇鍵集。 selectionKeys = selector.selectedKeys(); //System.out.println(selectionKeys.size()); iterator = selectionKeys.iterator(); while (iterator.hasNext()) { selectionKey = iterator.next(); if (selectionKey.isConnectable()) { System.out.println("client connect"); client = (SocketChannel) selectionKey.channel(); // 判斷此通道上是否正在進行連接操作。 // 完成套接字通道的連接過程。 if (client.isConnectionPending()) { client.finishConnect(); System.out.println("完成連接!"); sendbuffer.clear(); sendbuffer.put("Hello,Server".getBytes()); sendbuffer.flip(); client.write(sendbuffer); } client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { client = (SocketChannel) selectionKey.channel(); //將緩沖區清空以備下次讀取 receivebuffer.clear(); //讀取服務器發送來的數據到緩沖區中 count=client.read(receivebuffer); if(count>0){ receiveText = new String( receivebuffer.array(),0,count); System.out.println("客戶端接受服務器端數據--:"+receiveText); client.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { sendbuffer.clear(); client = (SocketChannel) selectionKey.channel(); sendText = "message from client--" + (flag++); sendbuffer.put(sendText.getBytes()); //將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位 sendbuffer.flip(); client.write(sendbuffer); System.out.println("客戶端向服務器端發送數據--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } selectionKeys.clear(); } } } ``` ## 6.2 NIOServer ``` public class NIOServer { /*標識數字*/ private int flag = 0; /*緩沖區大小*/ private int BLOCK = 4096; /*接受數據緩沖區*/ private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩沖區*/ private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); private Selector selector; public NIOServer(int port) throws IOException { /** * 以下的所有說明均已linux系統底層進行說明: * nio 的底層實現是 epoll 模式,采用多路復用技術,對nio的代碼進行深入分析,結合epoll的底層實現 * 進行詳細的說明 * 1.linux網絡編程是兩個進程之間的通信,跨集群合網絡 * 2.開啟一個socket線程,在linux系統上任何操作均以文件句柄數表示,默認情況下 * 一個線程可以打開1024個句柄,也就說最多同時支持1024個網絡連接請求。阿里云默認打開65535個文件 * 句柄,通常情況下,1G內存最多可以打開10w個句柄數 * * */ // 打開服務器套接字通道 // 底層: 在linux上面開啟socket服務,啟動一個線程。綁定ip地址和端口號 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 服務器配置為非阻塞 serverSocketChannel.configureBlocking(false); // 檢索與此通道關聯的服務器套接字 ServerSocket serverSocket = serverSocketChannel.socket(); // 進行服務的綁定 serverSocket.bind(new InetSocketAddress(port)); // 通過open()方法找到Selector // 底層: 開啟epoll,為當前socket服務創建epoll服務,epoll_create selector = Selector.open(); // 注冊到selector,等待連接 /** * 底層: * 1.將當前的epoll,服務器地址,端口號綁定,如果有連接請求,直接添加到epoll中,epoll的底層是紅黑樹, * 可以快速的實現連接的查找和狀態更新。如果有新的連接過來,直接存放到epoll中。如果有連接過期,中斷, * 會從epoll中刪除。 * 2.通過epoll_ctl添加到epoll的同時,會注冊一個回調函數給內核,當網卡有數據來的時候,會通知內核,內核 * 調用回調函數,將當前內核數據的事件狀態添加到list鏈表中 */ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server Start----8888:"); } // 監聽 private void listen() throws IOException { while (true) { // 選擇一組鍵,并且相應的通道已經打開 /** * epoll底層維護一個鏈表,rdlist,基于事件驅動模式,當網卡有數據請求過來,會發起硬件中斷,通知內核已經有來了。內核調用 * 回調函數,將當前的事件添加到rdlist中,將當前可用的rdlist列表發送給用戶態,用戶去遍歷rdlist中的事件,進行處理 */ selector.select(); // 返回此選擇器的已選擇鍵集。 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); // 獲得當前epoll的rdlist復制到用戶態,遍歷,同事刪除當前rdlist中的事件 iterator.remove(); handleKey(selectionKey); } } } // 處理請求 private void handleKey(SelectionKey selectionKey) throws IOException { // 接受請求 ServerSocketChannel server = null; SocketChannel client = null; String receiveText; String sendText; int count=0; // 測試此鍵的通道是否已準備好接受新的套接字連接。 if (selectionKey.isAcceptable()) { // 返回為之創建此鍵的通道。 server = (ServerSocketChannel) selectionKey.channel(); // 接受到此通道套接字的連接。 // 此方法返回的套接字通道(如果有)將處于阻塞模式。 client = server.accept(); // 配置為非阻塞 client.configureBlocking(false); // 注冊到selector,等待連接 client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 返回為之創建此鍵的通道。 client = (SocketChannel) selectionKey.channel(); //將緩沖區清空以備下次讀取 receivebuffer.clear(); //讀取服務器發送來的數據到緩沖區中 count = client.read(receivebuffer); if (count > 0) { receiveText = new String( receivebuffer.array(),0,count); System.out.println("服務器端接受客戶端數據--:"+receiveText); client.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { //將緩沖區清空以備下次寫入 sendbuffer.clear(); // 返回為之創建此鍵的通道。 client = (SocketChannel) selectionKey.channel(); sendText="message from server--" + flag++; //向緩沖區中輸入數據 sendbuffer.put(sendText.getBytes()); //將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位 sendbuffer.flip(); //輸出到通道 client.write(sendbuffer); System.out.println("服務器端向客戶端發送數據--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub int port = 8888; NIOServer server = new NIOServer(port); server.listen(); } } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看