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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章