1.Java NIO服务端创建

首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写。

示例代码(参考文献【2】):

  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.CharBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.ServerSocketChannel;
  8. import java.nio.channels.SocketChannel;
  9. import java.nio.charset.Charset;
  10. import java.nio.charset.CharsetDecoder;
  11. import java.util.Iterator;
  12. import java.util.Set;
  13.  
  14. /**
  15. * User: mihasya
  16. * Date: Jul 25, 2010
  17. * Time: 9:09:03 AM
  18. */
  19. public class JServer {
  20. public static void main (String[] args) {
  21. ServerSocketChannel sch = null;
  22. Selector sel = null;
  23.  
  24. try {
  25. // setup the socket we're listening for connections on.
  26. InetSocketAddress addr = new InetSocketAddress(8400);
  27. sch = ServerSocketChannel.open();
  28. sch.configureBlocking(false);
  29. sch.socket().bind(addr);
  30. // setup our selector and register the main socket on it
  31. sel = Selector.open();
  32. sch.register(sel, SelectionKey.OP_ACCEPT);
  33. } catch (IOException e) {
  34. System.out.println("Couldn't setup server socket");
  35. System.out.println(e.getMessage());
  36. System.exit(1);
  37. }
  38.  
  39. // fire up the listener thread, pass it our selector
  40. ListenerThread listener = new ListenerThread(sel);
  41. listener.run();
  42. }
  43.  
  44. /*
  45. * the thread is completely unnecessary, it could all just happen
  46. * in main()
  47. */
  48. class ListenerThread extends Thread {
  49. Selector sel = null;
  50. ListenerThread(Selector sel) {
  51. this.sel = sel;
  52. }
  53.  
  54. public void run() {
  55. while (true) {
  56.  
  57. // our canned response for now
  58. ByteBuffer resp = ByteBuffer.wrap(new String("got it\n").getBytes());
  59. try {
  60. // loop over all the sockets that are ready for some activity
  61. while (this.sel.select() > 0) {
  62. Set keys = this.sel.selectedKeys();
  63. Iterator i = keys.iterator();
  64. while (i.hasNext()) {
  65. SelectionKey key = (SelectionKey)i.next();
  66. if (key.isAcceptable()) {
  67. // this means that a new client has hit the port our main
  68. // socket is listening on, so we need to accept the connection
  69. // and add the new client socket to our select pool for reading
  70. // a command later
  71. System.out.println("Accepting connection!");
  72. // this will be the ServerSocketChannel we initially registered
  73. // with the selector in main()
  74. ServerSocketChannel sch = (ServerSocketChannel)key.channel();
  75. SocketChannel ch = sch.accept();
  76. ch.configureBlocking(false);
  77. ch.register(this.sel, SelectionKey.OP_READ);
  78. } else if (key.isReadable()) {
  79. // one of our client sockets has received a command and
  80. // we're now ready to read it in
  81. System.out.println("Accepting command!");
  82. SocketChannel ch = (SocketChannel)key.channel();
  83. ByteBuffer buf = ByteBuffer.allocate(200);
  84. ch.read(buf);
  85. buf.flip();
  86. Charset charset = Charset.forName("UTF-8");
  87. CharsetDecoder decoder = charset.newDecoder();
  88. CharBuffer cbuf = decoder.decode(buf);
  89. System.out.print(cbuf.toString());
  90. // re-register this socket with the selector, this time
  91. // for writing since we'll want to write something to it
  92. // on the next go-around
  93. ch.register(this.sel, SelectionKey.OP_WRITE);
  94. } else if (key.isWritable()) {
  95. // we are ready to send a response to one of the client sockets
  96. // we had read a command from previously
  97. System.out.println("Sending response!");
  98. SocketChannel ch = (SocketChannel)key.channel();
  99. ch.write(resp);
  100. resp.rewind();
  101. // we may get another command from this guy, so prepare
  102. // to read again. We could also close the channel, but
  103. // that sort of defeats the whole purpose of doing async
  104. ch.register(this.sel, SelectionKey.OP_READ);
  105. }
  106. i.remove();
  107. }
  108. }
  109. } catch (IOException e) {
  110. System.out.println("Error in poll loop");
  111. System.out.println(e.getMessage());
  112. System.exit(1);
  113. }
  114. }
  115. }
  116. }
  117. }

从上面的代码可以看出java nio的通用步骤:

1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父通道,绑定监听端口,设置客户端连接方式为非阻塞模式。

2.打开多路复用器并启动服务端监听线程,将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT状态。

3.多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手后,与客户端建立物理链路。

2. Netty服务端创建

2.1 打开ServerSocketChannel

  1. ServerBootstrap b = new ServerBootstrap();
  2. b.group(bossGroup, workerGroup)
  3. .channel(NioServerSocketChannel.class)

创建一个ServerSocketChannel的过程:

  1. /**
  2. * Create a new instance
  3. */
  4. public NioServerSocketChannel() {
  5. this(newSocket(DEFAULT_SELECTOR_PROVIDER));
  6. }

调用newSocket方法:

  1. private static ServerSocketChannel newSocket(SelectorProvider provider) {
  2. try {
  3. /**
  4. * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
  5. * {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
  6. *
  7. * See <a href="See https://github.com/netty/netty/issues/2308">#2308</a>.
  8. */
  9. return provider.openServerSocketChannel();
  10. } catch (IOException e) {
  11. throw new ChannelException(
  12. "Failed to open a server socket.", e);
  13. }
  14. }

其中的provider.openServerSocketChannel()就是java nio的实现。设置非阻塞模式包含在父类中:

  1. /**
  2. * Create a new instance
  3. *
  4. * @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
  5. * @param ch the underlying {@link SelectableChannel} on which it operates
  6. * @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
  7. */
  8. protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
  9. super(parent);
  10. this.ch = ch;
  11. this.readInterestOp = readInterestOp;
  12. try {
  13. ch.configureBlocking(false);
  14. } catch (IOException e) {
  15. try {
  16. ch.close();
  17. } catch (IOException e2) {
  18. if (logger.isWarnEnabled()) {
  19. logger.warn(
  20. "Failed to close a partially initialized socket.", e2);
  21. }
  22. }
  23.  
  24. throw new ChannelException("Failed to enter non-blocking mode.", e);
  25. }
  26. }

2.2 打开多路复用器过程

NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合,相关代码如下:

  1. @Override
  2. protected void run() {
  3. for (;;) {
  4. boolean oldWakenUp = wakenUp.getAndSet(false);
  5. try {
  6. if (hasTasks()) {
  7. selectNow();
  8. } else {
  9. select(oldWakenUp);
  10.  
  11. // 'wakenUp.compareAndSet(false, true)' is always evaluated
  12. // before calling 'selector.wakeup()' to reduce the wake-up
  13. // overhead. (Selector.wakeup() is an expensive operation.)
  14. //
  15. // However, there is a race condition in this approach.
  16. // The race condition is triggered when 'wakenUp' is set to
  17. // true too early.
  18. //
  19. // 'wakenUp' is set to true too early if:
  20. // 1) Selector is waken up between 'wakenUp.set(false)' and
  21. // 'selector.select(...)'. (BAD)
  22. // 2) Selector is waken up between 'selector.select(...)' and
  23. // 'if (wakenUp.get()) { ... }'. (OK)
  24. //
  25. // In the first case, 'wakenUp' is set to true and the
  26. // following 'selector.select(...)' will wake up immediately.
  27. // Until 'wakenUp' is set to false again in the next round,
  28. // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
  29. // any attempt to wake up the Selector will fail, too, causing
  30. // the following 'selector.select(...)' call to block
  31. // unnecessarily.
  32. //
  33. // To fix this problem, we wake up the selector again if wakenUp
  34. // is true immediately after selector.select(...).
  35. // It is inefficient in that it wakes up the selector for both
  36. // the first case (BAD - wake-up required) and the second case
  37. // (OK - no wake-up required).
  38.  
  39. if (wakenUp.get()) {
  40. selector.wakeup();
  41. }
  42. }
  43.  
  44. cancelledKeys = 0;
  45. needsToSelectAgain = false;
  46. final int ioRatio = this.ioRatio;
  47. if (ioRatio == 100) {
  48. processSelectedKeys();
  49. runAllTasks();
  50. } else {
  51. final long ioStartTime = System.nanoTime();
  52.  
  53. processSelectedKeys();
  54.  
  55. final long ioTime = System.nanoTime() - ioStartTime;
  56. runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
  57. }
  58.  
  59. if (isShuttingDown()) {
  60. closeAll();
  61. if (confirmShutdown()) {
  62. break;
  63. }
  64. }
  65. } catch (Throwable t) {
  66. logger.warn("Unexpected exception in the selector loop.", t);
  67.  
  68. // Prevent possible consecutive immediate failures that lead to
  69. // excessive CPU consumption.
  70. try {
  71. Thread.sleep(1000);
  72. } catch (InterruptedException e) {
  73. // Ignore.
  74. }
  75. }
  76. }
  77. }

2.2.1 绑定处理的key

  1. private void processSelectedKeys() {
  2. if (selectedKeys != null) {
  3. processSelectedKeysOptimized(selectedKeys.flip());
  4. } else {
  5. processSelectedKeysPlain(selector.selectedKeys());
  6. }
  7. }

以processSelectedKeysPlain为例:

  1. private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
  2. // check if the set is empty and if so just return to not create garbage by
  3. // creating a new Iterator every time even if there is nothing to process.
  4. // See https://github.com/netty/netty/issues/597
  5. if (selectedKeys.isEmpty()) {
  6. return;
  7. }
  8.  
  9. Iterator<SelectionKey> i = selectedKeys.iterator();
  10. for (;;) {
  11. final SelectionKey k = i.next();
  12. final Object a = k.attachment();
  13. i.remove();
  14.  
  15. if (a instanceof AbstractNioChannel) {
  16. processSelectedKey(k, (AbstractNioChannel) a);
  17. } else {
  18. @SuppressWarnings("unchecked")
  19. NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
  20. processSelectedKey(k, task);
  21. }
  22.  
  23. if (!i.hasNext()) {
  24. break;
  25. }
  26.  
  27. if (needsToSelectAgain) {
  28. selectAgain();
  29. selectedKeys = selector.selectedKeys();
  30.  
  31. // Create the iterator again to avoid ConcurrentModificationException
  32. if (selectedKeys.isEmpty()) {
  33. break;
  34. } else {
  35. i = selectedKeys.iterator();
  36. }
  37. }
  38. }
  39. }

2.3 绑定端口,接收请求:

  1. // Bind and start to accept incoming connections.
  2. ChannelFuture f = b.bind(PORT).sync();

调用bind程序

  1. private ChannelFuture doBind(final SocketAddress localAddress) {
  2. final ChannelFuture regFuture = initAndRegister();
  3. final Channel channel = regFuture.channel();
  4. if (regFuture.cause() != null) {
  5. return regFuture;
  6. }
  7.  
  8. if (regFuture.isDone()) {
  9. // At this point we know that the registration was complete and successful.
  10. ChannelPromise promise = channel.newPromise();
  11. doBind0(regFuture, channel, localAddress, promise);
  12. return promise;
  13. } else {
  14. // Registration future is almost always fulfilled already, but just in case it's not.
  15. final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
  16. regFuture.addListener(new ChannelFutureListener() {
  17. @Override
  18. public void operationComplete(ChannelFuture future) throws Exception {
  19. Throwable cause = future.cause();
  20. if (cause != null) {
  21. // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
  22. // IllegalStateException once we try to access the EventLoop of the Channel.
  23. promise.setFailure(cause);
  24. } else {
  25. // Registration was successful, so set the correct executor to use.
  26. // See https://github.com/netty/netty/issues/2586
  27. promise.executor = channel.eventLoop();
  28. }
  29. doBind0(regFuture, channel, localAddress, promise);
  30. }
  31. });
  32. return promise;
  33. }
  34. }

最终的绑定由dobind0来完成

  1. private static void doBind0(
  2. final ChannelFuture regFuture, final Channel channel,
  3. final SocketAddress localAddress, final ChannelPromise promise) {
  4.  
  5. // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
  6. // the pipeline in its channelRegistered() implementation.
  7. channel.eventLoop().execute(new OneTimeTask() {
  8. @Override
  9. public void run() {
  10. if (regFuture.isSuccess()) {
  11. channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
  12. } else {
  13. promise.setFailure(regFuture.cause());
  14. }
  15. }
  16. });
  17. }

具体实现:

  1. private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
  2. final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
  3. if (!k.isValid()) {
  4. // close the channel if the key is not valid anymore
  5. unsafe.close(unsafe.voidPromise());
  6. return;
  7. }
  8.  
  9. try {
  10. int readyOps = k.readyOps();
  11. // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
  12. // to a spin loop
  13. if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
  14. unsafe.read();
  15. if (!ch.isOpen()) {
  16. // Connection already closed - no need to handle write.
  17. return;
  18. }
  19. }
  20. if ((readyOps & SelectionKey.OP_WRITE) != 0) {
  21. // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
  22. ch.unsafe().forceFlush();
  23. }
  24. if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
  25. // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
  26. // See https://github.com/netty/netty/issues/924
  27. int ops = k.interestOps();
  28. ops &= ~SelectionKey.OP_CONNECT;
  29. k.interestOps(ops);
  30.  
  31. unsafe.finishConnect();
  32. }
  33. } catch (CancelledKeyException ignored) {
  34. unsafe.close(unsafe.voidPromise());
  35. }
  36. }

参考文献

【1】http://www.infoq.com/cn/articles/netty-server-create

【2】https://github.com/mihasya/sample-java-nio-server/blob/master/src/JServer.java

源码分析netty服务器创建过程vs java nio服务器创建的更多相关文章

  1. Spring Ioc源码分析系列--Bean实例化过程(一)

    Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...

  2. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  3. Spring源码分析之`BeanFactoryPostProcessor`调用过程

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...

  4. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  5. 【Netty源码分析】发送数据过程

    前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...

  6. tomcat8 源码分析 | 组件及启动过程

    tomcat 8 源码分析 ,本文主要讲解tomcat拥有哪些组件,容器,又是如何启动的 推荐访问我的个人网站,排版更好看呦: https://chenmingyu.top/tomcat-source ...

  7. spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv

    在前面源码剖析介绍中,spark 源码分析之二 -- SparkContext 的初始化过程 中的SparkEnv和 spark 源码分析之四 -- TaskScheduler的创建和启动过程 中的C ...

  8. MyBatis 源码分析 - SQL 的执行过程

    * 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...

  9. 【Canal源码分析】parser工作过程

    本文主要分析的部分是instance启动时,parser的一个启动和工作过程.主要关注的是AbstractEventParser的start()方法中的parseThread. 一.序列图 二.源码分 ...

随机推荐

  1. C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...

  2. load和initialize方法

      一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使 ...

  3. 从零开始编写自己的C#框架(24)——测试

    导航 1.前言 2.不堪回首的开发往事 3.测试推动开发的成长——将Bug消灭在自测中 4.关于软件测试 5.制定测试计划 6.编写测试用例 7.执行测试用例 8.发现并提交Bug 9.开发人员修复B ...

  4. node.js学习(二)--Node.js控制台(REPL)&&Node.js的基础和语法

    1.1.2 Node.js控制台(REPL) Node.js也有自己的虚拟的运行环境:REPL. 我们可以使用它来执行任何的Node.js或者javascript代码.还可以引入模块和使用文件系统. ...

  5. AI人工智能系列随笔:syntaxnet 初探(1)

    人工智能是 最近的一个比较火的名词,相信大家对于阿尔法狗都不陌生吧?其实我对人工智能以前也是非常抵触的,因为我认为机器人会取代人类,成为地球乃至宇宙的霸主,但是人工智能带给我的这种冲击,我个人感觉是欲 ...

  6. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  7. SOLID 设计原则

    SOLID 原则基本概念: 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象 ...

  8. 初识JavaScript

    JavaScript ECMA-262: 变量,函数,对象,数据类型....唯独没有输入和输出. Javascript:包含 ECMA-262,核心 BOM 浏览器对象模型, DOM 文档对象模型 什 ...

  9. AEAI DP V3.6.0 升级说明,开源综合应用开发平台

    AEAI DP综合应用开发平台是一款扩展开发工具,专门用于开发MIS类的Java Web应用,本次发版的AEAI DP_v3.6.0版本为AEAI DP _v3.5.0版本的升级版本,该产品现已开源并 ...

  10. Atitit 管理原理与实践attilax总结

    Atitit 管理原理与实践attilax总结 1. 管理学分类1 2. 我要学的管理学科2 3. 管理学原理2 4. 管理心理学2 5. 现代管理理论与方法2 6. <领导科学与艺术4 7. ...