NIO看破也說破(四)—— Java的NIO

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java的NIO有selector,系統內核也提供了多種非阻塞IO模型,Java社區也出現了像netty這種優秀的 NIO 框架。Java的NIO 與內核的阻塞模型到底什麼關係,爲什麼Java有NIO的API還出現了netty這種框架,網上說的 reactor 到底是什麼?本文通過分析代碼,帶你一步步搞清楚Java的NIO和系統函數之間的關係,以及Java NIO 是如何一步步衍生出來netty框架。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"NIO概念"}]},{"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":"text","marks":[{"type":"italic"}],"text":"Nonblocking IO"},{"type":"text","text":" 的概念,在Java中有Java NIO 的系列包,網上的大多數資料把Java的NIO等同於 "},{"type":"text","marks":[{"type":"italic"}],"text":"Nonblocking IO"},{"type":"text","text":" ,這是錯誤的。Java 中的 NIO 指的是從1.4版本後,提供的一套可以替代標準的Java IO 的 new API。有三部分組成:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Buffer 緩衝區"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Channel 通道"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Selector 選擇器"}]}]}]},{"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":"API的具體使用不在本文贅述。"}]},{"type":"heading","attrs":{"align":null,"level":2},"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":"NIO大致分爲這幾步驟:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"獲取channel"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"設置非阻塞"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"創建多路複用器selector"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"channel和selector做關聯"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"根據selector返回的channel狀態處理邏輯"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 開啓一個channel\nServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\n// 設置爲非阻塞\nserverSocketChannel.configureBlocking(false);\n// 綁定端口\nserverSocketChannel.bind(new InetSocketAddress(PORT));\n// 打開一個多路複用器\nSelector selector = Selector.open();\n// 綁定多路複用器和channel\nserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);\n// 獲取到達的事件\nwhile (selector.select() > 0) {\n Set keys = selector.keys();\n Iterator iterator = keys.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n if (selectionKey.isAcceptable()) {\n // 處理邏輯\n }\n if (selectionKey.isReadable()) {\n // 處理邏輯\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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":"參考代碼模板,我們用 NIO 實現一個Echo Server。server代碼如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws IOException {\n Selector selector = initServer();\n while (selector.select() > 0) {\n Set set = selector.selectedKeys();\n Iterator iterator = set.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n try {\n if (selectionKey.isAcceptable()) {\n ServerSocketChannel serverSocketChannel = \n (ServerSocketChannel) selectionKey.channel();\n SocketChannel channel = serverSocketChannel.accept();\n System.out.println(\"建立鏈接:\" + channel.getRemoteAddress());\n channel.configureBlocking(false);\n ByteBuffer buffer = ByteBuffer.allocate(1024);\n channel.register(selector, SelectionKey.OP_READ, buffer);\n } else if (selectionKey.isReadable()) {\n SocketChannel channel = (SocketChannel) selectionKey.channel();\n ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();\n buffer.clear();\n StringBuilder sb = new StringBuilder();\n int read = 0;\n while ((read = channel.read(buffer)) > 0) {\n buffer.flip();\n sb.append(Charset.forName(\"UTF-8\").\n newDecoder().decode(buffer));\n buffer.clear();\n }\n System.out.printf(\"收到 %s 發來的:%s\\n\", \n channel.getRemoteAddress(), sb);\n buffer.clear();\n // 模擬server端處理耗時\n Thread.sleep((int) (Math.random() * 1000));\n buffer.put((\"收到,你發來的是:\" + sb + \"\\r\\n\").getBytes(\"utf-8\"));\n buffer.flip();\n channel.write(buffer);\n System.out.printf(\"回覆 %s 內容是: %s\\n\", \n channel.getRemoteAddress(), sb);\n channel.register(selector, SelectionKey.OP_READ, buffer.clear());\n }\n } catch (IOException | InterruptedException e) {\n selectionKey.cancel();\n selectionKey.channel().close();\n System.err.println(e.getMessage());\n }\n iterator.remove();\n }\n }\n}"}]},{"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":"寫一個client ,模擬 50 個線程同時請求 server 端,在 readHandler 中模擬了隨機sleep。client代碼:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws IOException {\n for (int i = 0; i < 50; i++) {\n new Thread(new Runnable() {\n @Override\n public void run() {\n try {\n clientHandler();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }).start();\n }\n return;\n}\n\nprivate static void clientHandler() throws IOException {\n long start = System.currentTimeMillis();\n Socket socket = new Socket();\n socket.setSoTimeout(10000);\n socket.connect(new InetSocketAddress(9999));\n OutputStream outputStream = socket.getOutputStream();\n BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));\n bw.write(\"你好,我是client \" + socket.getLocalSocketAddress() + \"\\r\\n\");\n bw.flush();\n\n InputStream inputStream = socket.getInputStream();\n BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));\n System.out.printf(\"接到服務端響應:%s,處理了%d\\r\\n\", br.readLine(), (System.currentTimeMillis() - start));\n br.close();\n inputStream.close();\n\n bw.close();\n outputStream.close();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/75/75660df7f026b638b7aa9c78098fc069.png","alt":null,"title":"Java NIO 單線程","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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Selector 實現原理"}]},{"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":"用strace啓動,"},{"type":"codeinline","content":[{"type":"text","text":"[root@f00e68119764 tmp]# strace -ff -o out /usr/lib/jvm/java-1.8.0/bin/java NIOServerSingle"}]},{"type":"text","text":" 分析執行的過程,在日誌中可以看到如下片段:"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"20083 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 4\n20084 setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0\n20085 clock_gettime(CLOCK_MONOTONIC, {tv_sec=242065, tv_nsec=887240727}) = 0\n20086 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR)\n20087 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0\n20088 bind(4, {sa_family=AF_INET, sin_port=htons(9999), sin_addr=inet_addr(\"0.0.0.0\")}, 16) = 0\n20089 listen(4, 50) = 0\n20090 getsockname(4, {sa_family=AF_INET, sin_port=htons(9999), sin_addr=inet_addr(\"0.0.0.0\")}, [16]) = 0\n20091 getsockname(4, {sa_family=AF_INET, sin_port=htons(9999), sin_addr=inet_addr(\"0.0.0.0\")}, [16]) = 0\n20092 epoll_create(256) = 7\n21100 epoll_ctl(7, EPOLL_CTL_ADD, 4, {EPOLLIN, {u32=4, u64=158913789956}}) = 0\n21101 epoll_wait(7, [{EPOLLIN, {u32=4, u64=158913789956}}], 8192, -1) = 1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出,在Java的 NIO 中(java1.8)底層是調用的系統 epoll ,關於 epoll 請"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/628ae27da9ccb37d2900e8ef4","title":null},"content":[{"type":"text","text":"出門右轉"}]},{"type":"text","text":" 這裏不再囉嗦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從源碼中也可以看出:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static Selector open() throws IOException {\n return SelectorProvider.provider().openSelector();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"openSelector是抽象方法具體實現類,在Linux上代碼如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class EPollSelectorProvider\n extends SelectorProviderImpl\n{\n public AbstractSelector openSelector() throws IOException {\n return new EPollSelectorImpl(this);\n }\n\n public Channel inheritedChannel() throws IOException {\n return InheritedChannel.getChannel();\n }\n}"}]},{"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":"跟蹤代碼可以看到最後調用 native 方法,說明:"},{"type":"text","marks":[{"type":"strong"}],"text":"Java NIO 是利用系統內核提供的能力。"}]},{"type":"heading","attrs":{"align":null,"level":2},"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":"我們把單線程示例中,readHandler 隨機 sleep,稍稍做些修改。模擬 server 端執行某一次請求時,處理過慢,如圖示:"}]},{"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":"第十五個請求過來時,隨機sleep:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 模擬server端處理耗時\nif (t.get() == 15) {\n Thread.sleep((int) (Math.random() * 10000));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果第十五個線程之後,所有 client 的執行都有一個短暫的等待"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0c/0c72de43003a224ef3dfd057938ae7c9.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},"content":[{"type":"text","text":"很容易解釋,因爲在單線程處理中,channel創建、IO讀寫均爲一個 Thread ,面對50個 client,IO時間需要排隊處理。因此我們Redis系列中也提到了在Redis中,儘量避免某一個key的操作會很耗時的情況。可以參考 "},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/91ab6a27e9bca957cab2d1819","title":""},"content":[{"type":"text","text":"出門右轉"}]},{"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":"我們對代碼做一些改造,client 端代碼不動,server 端代碼稍作調整。增加一個線程來處理讀寫時間,代碼片段如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (selectionKey.isAcceptable()) {\n ServerSocketChannel serverSocketChannel = \n (ServerSocketChannel) selectionKey.channel();\n SocketChannel channel = serverSocketChannel.accept();\n System.out.println(\"建立鏈接:\" + channel.getRemoteAddress());\n channel.configureBlocking(false);\n ByteBuffer buffer = ByteBuffer.allocate(1024);\n channel.register(selector, SelectionKey.OP_READ, buffer);\n} else if (selectionKey.isReadable()) {\n service.execute(new Runnable() {\n @Override\n public void run() {\n try {\n // 處理邏輯……………………\n } catch (IOException e) {\n e.printStackTrace();\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n }\n });\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣相當於server端有兩個線程,一個是主線程啓動的 selector 來監聽 channel 的 OP_ACCEPT 狀態,另一個線程是處理 channel 的讀寫。程序也可以繼續執行,稍稍快了一些。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/12ab13dd8c9ce27e4e84fa44d8c1e884.png","alt":null,"title":"Java NIO 多線程","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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"reactor模式"}]},{"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":"接觸 NIO 就一定聽過reactor 這個名詞,reactor 經常被混入 NIO 中,讓很多人混淆概念。Reactor 到底是什麼,"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Reactor_pattern#cite_note-1","title":null},"content":[{"type":"text","text":"維基百科"}]},{"type":"text","text":"的解釋:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers."}]}]},{"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":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"reactor design pattern (reactor是一種設計模式,不是專屬於某個語言或框架的)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"event handling pattern (事件處理模式)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"delivered concurrently to a service handler by one or more inputs(一次處理一個或多個輸入)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"demultiplexes the incoming requests and dispatches them(多路分解,分發)"}]}]}]},{"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":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws IOException {\n Selector selector = initServer();\n while (selector.select() > 0) {\n Set set = selector.selectedKeys();\n Iterator iterator = set.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n dispatcher(selectionKey);\n iterator.remove();\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"initServer的實現:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static Selector initServer() throws IOException {\n ServerSocketChannel serverChannel = ServerSocketChannel.open();\n serverChannel.configureBlocking(false);\n serverChannel.bind(new InetSocketAddress(9999));\n\n Selector selector = Selector.open();\n serverChannel.register(selector, SelectionKey.OP_ACCEPT);\n System.out.println(\"server 啓動\");\n return selector;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"dispatcher 的實現:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static void dispatcher(SelectionKey selectionKey) {\n try {\n if (selectionKey.isAcceptable()) {\n // 我只負責處理鏈接\n acceptHandler(selector, selectionKey);\n } else if (selectionKey.isReadable()) {\n // 我只處理讀寫數據\n readHandler(selector, selectionKey);\n }\n } catch (IOException | InterruptedException e) {\n selectionKey.cancel();\n selectionKey.channel().close();\n System.err.println(e.getMessage());\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"acceptHandler的實現:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();\nSocketChannel channel = serverSocketChannel.accept();\nSystem.out.println(\"建立鏈接:\" + channel.getRemoteAddress());\nchannel.configureBlocking(false);\nByteBuffer buffer = ByteBuffer.allocate(1024);\nchannel.register(selector, SelectionKey.OP_READ, buffer);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"readHandler的實現:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"SocketChannel channel = (SocketChannel) selectionKey.channel();\nByteBuffer buffer = (ByteBuffer) selectionKey.attachment();\nbuffer.clear();\nStringBuilder sb = new StringBuilder();\nint read = 0;\nwhile ((read = channel.read(buffer)) > 0) {\n buffer.flip();\n sb.append(Charset.forName(\"UTF-8\").newDecoder().decode(buffer));\n buffer.clear();\n}\nSystem.out.printf(\"收到 %s 發來的:%s\\n\", channel.getRemoteAddress(), sb);\nbuffer.clear();\n// 模擬server端處理耗時\nThread.sleep((int) (Math.random() * 1000));\nbuffer.put((\"收到,你發來的是:\" + sb + \"\\r\\n\").getBytes(\"utf-8\"));\nbuffer.flip();\nchannel.write(buffer);\nSystem.out.printf(\"回覆 %s 內容是: %s\\n\", channel.getRemoteAddress(), sb);\nchannel.register(selector, SelectionKey.OP_READ, buffer.clear());"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"單線程Reactor"}]},{"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}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"基於事件驅動( NIO 的 selector,底層對事件驅動的epoll實現 jdk1.8)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"統一分派中心(dispatcher方法)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"不同的事件處理(accept 和 read write 拆分)"}]}]}]},{"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":"已經基本上實現了 Reactor 的單線程模式,我們把示例代碼再做一些改造:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ReactorDemo {\n private Selector selector;\n\n public ReactorDemo() throws IOException {\n initServer();\n }\n private void initServer() throws IOException {\n ServerSocketChannel serverChannel = ServerSocketChannel.open();\n serverChannel.configureBlocking(false);\n serverChannel.bind(new InetSocketAddress(9999));\n\n selector = Selector.open();\n SelectionKey selectionKey = serverChannel.\n register(selector, SelectionKey.OP_ACCEPT);\n selectionKey.attach(new Acceptor());\n System.out.println(\"server 啓動\");\n }\n\n public void start() throws IOException {\n while (selector.select() > 0) {\n Set set = selector.selectedKeys();\n Iterator iterator = set.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n dispater(selectionKey);\n iterator.remove();\n }\n\n }\n }\n\n public void dispater(SelectionKey selectionKey) {\n Hander hander = (Hander) selectionKey.attachment();\n if (hander != null) {\n hander.process(selectionKey);\n }\n }\n\n private interface Hander {\n void process(SelectionKey selectionKey);\n }\n\n private class Acceptor implements Hander {\n\n @Override\n public void process(SelectionKey selectionKey) {\n try {\n ServerSocketChannel serverSocketChannel = \n (ServerSocketChannel) selectionKey.channel();\n SocketChannel channel = serverSocketChannel.accept();\n System.out.println(\"建立鏈接:\" + channel.getRemoteAddress());\n channel.configureBlocking(false);\n selectionKey.attach(new ProcessHander());\n channel.register(selector, SelectionKey.OP_READ);\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }\n\n private class ProcessHandler implements Hander {\n @Override\n public void process(SelectionKey selectionKey) {\n\n }\n }\n \n public static void main(String[] args) throws IOException {\n new ReactorDemo().start();\n }\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們實現了最基本的單Reactor的單線程模型,程序啓動後 selector 負責獲取、分離可用的 socket 交給dispatcher處理,dispatcher 交給不同的 handler 處理。其中 Acceptor 只負責 socket 鏈接,IO 的處理交給 ProcessHandler。"}]},{"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":"我把網上流傳的Reactor的圖,按自己的理解重新畫了一份"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/82b5c7e3ec790f57cbca6afed261b8ca.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":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"多線程Reactor"}]},{"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":"上面的示例代碼中,從 socket 建立到 IO 完成,只有一個線程在處理。NIO 單線程示例中我們嘗試加入線程池來加速 IO 任務的處理,reactor 模式中該如何實現呢?"}]},{"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":"簡單理解,參考 NIO 多線程加入線程池處理所有的processHandler ,可以利用 CPU 多核心加快業務處理,代碼不再贅述。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/eb3d02db85408fc538a033138e5f98e5.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":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"多Reactor模式"}]},{"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":"參考ReactorDemo ,我們的 acceptor 處理 socket 鏈接時和 handler 處理 IO 都是用的同一個 selector 。如果我們在多線程基礎上有兩個 selector ,一個只負責處理 socket 鏈接一個處理網路 IO 各司其職將會更高大的提升系統吞吐量,該怎麼實現呢?"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ReactorDemo {\n private Selector selector;\n private Selector ioSelector;\n\n\n public ReactorDemo() throws IOException {\n initServer();\n }\n\n private void initServer() throws IOException {\n ServerSocketChannel serverChannel = ServerSocketChannel.open();\n serverChannel.configureBlocking(false);\n serverChannel.bind(new InetSocketAddress(9999));\n\n selector = Selector.open();\n ioSelector = Selector.open();\n serverChannel.register(selector, SelectionKey.OP_ACCEPT);\n System.out.println(\"server 啓動\");\n }\n\n public void startServer() {\n Executors.newFixedThreadPool(1).execute(new Runnable() {\n @Override\n public void run() {\n try {\n majorListen();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n });\n Executors.newFixedThreadPool(1).execute(new Runnable() {\n @Override\n public void run() {\n try {\n subListen();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n });\n }\n\n public void majorListen() throws IOException {\n System.out.println(\"主selector啓動\");\n while (selector.select() > 0) {\n System.out.println(\"主selector有事件\");\n Set set = selector.selectedKeys();\n Iterator iterator = set.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n if (selectionKey.isAcceptable()) {\n new Acceptor().process(selectionKey);\n }\n iterator.remove();\n }\n\n }\n }\n\n public void subListen() throws IOException {\n System.out.println(\"子selector啓動\");\n while (true) {\n if (ioSelector.select(100) <= 0) {\n continue;\n }\n System.out.println(\"子selector有事件\");\n Set set = ioSelector.selectedKeys();\n Iterator iterator = set.iterator();\n while (iterator.hasNext()) {\n SelectionKey selectionKey = iterator.next();\n selectionKey.attach(new ProcessHander());\n dispater(selectionKey, true);\n iterator.remove();\n }\n\n }\n }\n\n public void dispater(SelectionKey selectionKey, boolean isSub) {\n Hander hander = (Hander) selectionKey.attachment();\n if (hander != null) {\n hander.process(selectionKey);\n }\n }\n\n private interface Hander {\n void process(SelectionKey selectionKey);\n }\n\n private class Acceptor implements Hander {\n\n @Override\n public void process(SelectionKey selectionKey) {\n try {\n ServerSocketChannel serverSocketChannel = \n (ServerSocketChannel) selectionKey.channel();\n SocketChannel channel = serverSocketChannel.accept();\n System.out.println(\"獲取一個鏈接:\" + channel.getRemoteAddress());\n channel.configureBlocking(false);\n channel.register(ioSelector, \n SelectionKey.OP_READ, ByteBuffer.allocate(1024));\n ioSelector.wakeup();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }\n\n private class ProcessHander implements Hander {\n\n @Override\n public void process(SelectionKey selectionKey) {\n try {\n SocketChannel channel = (SocketChannel) selectionKey.channel();\n ByteBuffer buffer = ByteBuffer.allocate(1024);\n buffer.clear();\n StringBuilder sb = new StringBuilder();\n int read = 0;\n if ((read = channel.read(buffer)) > 0) {\n buffer.flip();\n sb.append(Charset.forName(\"UTF-8\").newDecoder().decode(buffer));\n buffer.clear();\n } else if (read == 0) {\n return;\n } else if (read == -1) {\n if (selectionKey.isValid()) {\n selectionKey.cancel();\n channel.close();\n }\n }\n System.out.printf(\"收到 %s 發來的:%s\\n\", \n channel.getRemoteAddress(), sb);\n buffer.clear();\n buffer.put((\"收到,你發來的是:\" + sb + \"\\r\\n\").getBytes(\"utf-8\"));\n buffer.flip();\n channel.write(buffer);\n System.out.printf(\"回覆 %s 內容是: %s\\n\", \n channel.getRemoteAddress(), sb);\n channel.register(ioSelector, SelectionKey.OP_READ, buffer.clear());\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }\n\n public static void main(String[] args) throws IOException {\n new ReactorDemo().startServer();\n }\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"示例中創建了 selector 和 ioSelector ,其中 selector 只處理 socket 的建立,在 Acceptor.process 方法中把 socket 註冊給 ioSelector。在 ProcessHander.process 方法中 ioSelector 只負責處理 IO 事件。這樣,我們把 selector 進行了拆分。參考多線程實現,同理我們可以創建 N 個線程,處理 ioSelector 對應的 IO 事件。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/eb3d02db85408fc538a033138e5f98e5.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":"heading","attrs":{"align":null,"level":2},"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":"至此,我們瞭解了 Reactor 的三種模型結果,分別是單 Reactor 單線程、單 Reactor 多線程、多 Reactor 多線程。所有代碼不夠嚴謹,只爲了表示可以使用多個線程或者多個 selector 之間的關係。總結重點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"reactor 是一種設計模式"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"基於事件驅動來處理"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"利用多路分發的策略,讓不同業務處理各司其職"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"有單線程,單Reactor 多線程 和 多 Reactor 多線程,三種實現方式"}]}]}]},{"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":"http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf","title":null},"content":[{"type":"text","text":"http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/1f44643161e1666b6a30b85e7","title":null},"content":[{"type":"text","text":"NIO 看破也說破(三)—— 不同的 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/b9baa25c9d506e4a1cb459fe0","title":null},"content":[{"type":"text","text":"NIO 看破也說破(四)—— Java 的 NIO"}],"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}},{"type":"heading","attrs":{"align":null,"level":2},"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":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzI3MDYwOTYwOA==&scene=110#wechat_redirect","title":null},"content":[{"type":"text","text":"關注我"}]},{"type":"text","text":" ,如果您在 PC 上閱讀請掃碼關注我,歡迎與我交流隨時指出錯誤 "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28d49266bc303e8d1d8779b0336982db.png","alt":null,"title":"小眼睛聊技術","style":[{"key":"width","value":"25%"},{"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}},{"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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章