NIO 看破也說破(三)—— 不同的IO模型

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上兩節我們提到了select 和 poll函數,查看man手冊:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"\nSELECT(2) Linux Programmer's Manual SELECT(2)\n\nNAME\n select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"POLL(2) Linux Programmer's Manual POLL(2)\n\nNAME\n poll, ppoll - wait for some event on a file descriptor\n\nSYNOPSIS\n #include "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"synchronous I/O multiplexing中文解釋是同步的多路複用,因此select 是一個同步的I/O多路複用模式。Unix共五種I/O模型:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞I/O"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"非阻塞I/O"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"I/O多路複用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信號驅動"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步I/O"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信號驅動和真正的異步I/O並不常用,我們重點說一下前三個。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"阻塞I/O"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ServerSocket server = new ServerSocket(8080);\nwhile (true) {\n Socket socket = server.accept();\n System.out.println(\"鏈接端口:\" + socket.getPort());\n InputStream inputStream = socket.getInputStream();\n BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));\n String str = null;\n while ((str = reader.readLine()) != null) {\n System.out.println(\"接受:\" + str);\n socket.getOutputStream().write(\"ok\\n\".getBytes());\n socket.getOutputStream().flush();\n if (\"over\".equals(str)) {\n System.out.println(\"要關閉了\");\n socket.close();\n break;\n }\n }\n System.out.println(\"===========\");\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當有數據獲取時,用戶線程要釋放cpu,直到數據由內核處理完成,整個過程用戶線程是阻塞的。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9b/9b2ff058da2afe8088283341dcf81de8.png","alt":null,"title":"阻塞IO","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶線程調用內核IO操作,需要等IO徹底完成後才返回到用戶空間,因此是阻塞IO"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"非阻塞I/O"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\nserverSocketChannel.bind(new InetSocketAddress(8888));\nserverSocketChannel.configureBlocking(false);\nwhile (true) {\n SocketChannel socketChannel = serverSocketChannel.accept();\n if (socketChannel == null) {\n System.out.println(\"沒有鏈接 \");\n continue;\n }\n System.out.printf(\"新鏈接,端口是 %s\", ((InetSocketAddress) socketChannel.\n getRemoteAddress()).getPort());\n ByteBuffer ds = ByteBuffer.allocate(10);\n socketChannel.read(ds);\n System.out.println(\"接受數據\");\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/14/14c7654f42acb3df14e4967c71b96b3f.png","alt":null,"title":"非阻塞IO","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成,因此是非阻塞的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"I/O多路複用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"非阻塞IO中需要用戶線程在每個IO通路上,各自不斷輪詢IO狀態,來判斷是否有可處理的數據。如果把一個連接的可讀可寫事件剝離出來,使用單獨的線程來對其進行管理。多個IO通路,都複用這個管理器來管理socket狀態,這個叫I/O多路複用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bcee92e2fceb35c07a3290ba3c38ec73.png","alt":null,"title":"多路複用","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多路複用在內核中提供了select,poll,epoll三種方式:"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"select"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原理示意"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/949057056cc642f67c766dbce15e79a2.png","alt":null,"title":"select","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"特點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只能處理有限(不同系統參數:1024/2048)個socket"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"select監控socket時不能準確告訴用戶是哪個socket有可用數據,需要輪詢判斷"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"poll"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原理示意"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4a/4afd9532cca7ea39a12243fbf3af8cb4.png","alt":null,"title":"poll","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"特點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與select沒區別"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"採用鏈表實現,取消了文件個數的限制"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"epoll"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原理示意"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d2/d252607396b633b65ec71653ba8b46d8.png","alt":null,"title":"epoll","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"epoll_wait 直接檢查鏈表是不是空就知道是否有文件描述符準備好了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fd 上的事件發生時,與它對應的回調函數就會被調用把fd 加入鏈表,其他處於“空閒的”狀態的則不會被加入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"epoll從上面鏈表中獲取有事件發生的fd"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"epoll準確的表述應該是I/O事件通知器:"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"EPOLL(7) Linux Programmer's Manual EPOLL(7)\n\nNAME\n epoll - I/O event notification facility\n\nSYNOPSIS\n #include "}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"特點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"沒有最大連接限制"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以直接告訴用戶程序哪一個,哪個連接有數據了"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"同步異步的概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步和異步的概念描述的是用戶線程與內核的交互方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步是指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;異步是指用戶線程發起IO請求 後仍繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此 阻塞I/O,非阻塞I/O,I/O多路複用,都屬於同步調用。只有實現了特殊API的AIO纔是異步調用,之後單開一篇講解。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"AIO(7) Linux Programmer's Manual AIO(7)\n\nNAME\n aio - POSIX asynchronous I/O overview\n\nDESCRIPTION\n The POSIX asynchronous I/O (AIO) interface allows applications to initiate one or more I/O operations that are performed asyn‐\n chronously (i.e., in the background). The application can elect to be notified of completion of the I/O operation in a variety of\n ways: by delivery of a signal, by instantiation of a thread, or no notification at all."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系列:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/0e36ad9712c8d9ad8f7a7c570","title":null},"content":[{"type":"text","text":"NIO 看破也說破(一)—— Linux/IO 基礎"}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/e8ab7c9020253b83355c10661","title":null},"content":[{"type":"text","text":"NIO 看破也說破(二)—— Java 中的兩種 BIO"}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一篇,java中reactor,簡易的netty實現"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章