├── .gitignore ├── Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端) ├── 1477130291691.png ├── 1477130295919.png ├── 1477130299722.png ├── HeadContext.png ├── Netty 客户端的连接时序图.png ├── Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端).md ├── NioEventLoopGroup 类层次结构.png ├── NioSocketChannel 类层次结构.png └── TailContext.png ├── Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端) ├── 1477377831087.png ├── 1477378182291.png ├── Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端).md ├── NioServerSocketChannel 类层次结构.png ├── Server 端 bossGroup 与 workerGroup 的图示.png ├── Server 端 handler 与 childHandler 图示.png └── 新客户端连接之后, NioSocketChannel 对应的 pipeline.png ├── Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop ├── Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop.md ├── NioEventLoop 与 Channel 的关联过程.png ├── NioEventLoop 实例化顺序图.png ├── NioEventLoop 的启动过程.png ├── NioEventLoop.png ├── NioEventLoopGroup 初始化顺序图.png ├── NioEventLoopGroup.png ├── Reactor 主从多线程模型.png ├── Reactor 单线程模型.png └── Reactor 多线程模型.png ├── Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline ├── Channel 事件流 之 Inbound 事件.png ├── Channel 事件流 之 Outbound 事件.png ├── Channel 组成.png ├── ChannelInitializer 类层次结构图.png ├── ChannelPipeline 初始化过程.png ├── EchoClientHandler 的名字.png ├── HeadContext 类层次结构.png ├── Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (一).md ├── Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (二).md ├── TailContext 类层次结构.png ├── 自定义 ChannelHandler 分析1.png ├── 自定义 ChannelHandler 分析2.png └── 自定义 ChannelHandler 分析3.png ├── Netty 源码分析之 番外篇 Java NIO 的前生今世 ├── Netty 源码分析之 番外篇 Java NIO 的前生今世.md └── Selector 图解.png ├── Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建 ├── EchoServer 运行.png ├── Github Netty 源码 clone.png ├── Netty Maven 依赖.png ├── Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建.md └── Netty 源码工程结构.png ├── README.md ├── issues └── 记一次有趣的 Netty 源码问题 │ ├── 关于 Channel 注册与绑定的时序问题.png │ └── 记一次有趣的 Netty 源码问题.md └── 源码之下无秘密 ── 做最好的 Netty 源码分析教程.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.jar 4 | *.log 5 | target/ 6 | logs/ 7 | .classpath 8 | .project 9 | .settings/ 10 | .DS_Store 11 | rebel.xml 12 | .zip 13 | -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130291691.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130291691.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130295919.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130295919.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130299722.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/1477130299722.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/HeadContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/HeadContext.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/Netty 客户端的连接时序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/Netty 客户端的连接时序图.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端).md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端) 2 | @(Netty)[Java, Netty, Netty 源码分析] 3 | 4 | [TOC] 5 | 6 | 7 | 这一章是 Netty 源码分析系列的第一章, 我打算在这一章中, 展示一下 Netty 的客户端和服务端的初始化和启动的流程, 给读者一个对 Netty 源码有一个大致的框架上的认识, 而不会深入每个功能模块. 8 | 本章会从 Bootstrap/ServerBootstrap 类 入手, 分析 Netty 程序的初始化和启动的流程. 9 | ## Bootstrap 10 | Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化. 11 | 下面我以 Netty 源码例子中的 Echo 服务器作为例子, 从客户端和服务器端分别分析一下Netty 的程序是如何启动的. 12 | ## 客户端部分 13 | ### 连接源码 14 | 首先, 让我们从客户端方面的代码开始 15 | 下面是源码**example/src/main/java/io/netty/example/echo/EchoClient.java** 的客户端部分的启动代码: 16 | ``` 17 | EventLoopGroup group = new NioEventLoopGroup(); 18 | try { 19 | Bootstrap b = new Bootstrap(); 20 | b.group(group) 21 | .channel(NioSocketChannel.class) 22 | .option(ChannelOption.TCP_NODELAY, true) 23 | .handler(new ChannelInitializer() { 24 | @Override 25 | public void initChannel(SocketChannel ch) throws Exception { 26 | ChannelPipeline p = ch.pipeline(); 27 | p.addLast(new EchoClientHandler()); 28 | } 29 | }); 30 | 31 | // Start the client. 32 | ChannelFuture f = b.connect(HOST, PORT).sync(); 33 | 34 | // Wait until the connection is closed. 35 | f.channel().closeFuture().sync(); 36 | } finally { 37 | // Shut down the event loop to terminate all threads. 38 | group.shutdownGracefully(); 39 | } 40 | ``` 41 | 从上面的客户端代码虽然简单, 但是却展示了 Netty 客户端初始化时所需的所有内容: 42 | 1. EventLoopGroup: 不论是服务器端还是客户端, 都必须指定 EventLoopGroup. 在这个例子中, 指定了 NioEventLoopGroup, 表示一个 NIO 的EventLoopGroup. 43 | 2. ChannelType: 指定 Channel 的类型. 因为是客户端, 因此使用了 NioSocketChannel. 44 | 3. Handler: 设置数据的处理器. 45 | 46 | 下面我们深入代码, 看一下客户端通过 Bootstrap 启动后, 都做了哪些工作. 47 | 48 | ### NioSocketChannel 的初始化过程 49 | 在 Netty 中, Channel 是一个 Socket 的抽象, 它为用户提供了关于 Socket 状态(是否是连接还是断开) 以及对 Socket 的读写等操作. 每当 Netty 建立了一个连接后, 都会有一个对应的 Channel 实例. 50 | NioSocketChannel 的类层次结构如下: 51 | ![Alt text](./NioSocketChannel 类层次结构.png) 52 | 53 | 54 | 这一小节我们着重分析一下 Channel 的初始化过程. 55 | #### ChannelFactory 和 Channel 类型的确定 56 | 除了 TCP 协议以外, Netty 还支持很多其他的连接协议, 并且每种协议还有 NIO(异步 IO) 和 OIO(Old-IO, 即传统的阻塞 IO) 版本的区别. 不同协议不同的阻塞类型的连接都有不同的 Channel 类型与之对应下面是一些常用的 Channel 类型: 57 | - NioSocketChannel, 代表异步的客户端 TCP Socket 连接. 58 | - NioServerSocketChannel, 异步的服务器端 TCP Socket 连接. 59 | - NioDatagramChannel, 异步的 UDP 连接 60 | - NioSctpChannel, 异步的客户端 Sctp 连接. 61 | - NioSctpServerChannel, 异步的 Sctp 服务器端连接. 62 | - OioSocketChannel, 同步的客户端 TCP Socket 连接. 63 | - OioServerSocketChannel, 同步的服务器端 TCP Socket 连接. 64 | - OioDatagramChannel, 同步的 UDP 连接 65 | - OioSctpChannel, 同步的 Sctp 服务器端连接. 66 | - OioSctpServerChannel, 同步的客户端 TCP Socket 连接. 67 | 68 | 那么我们是如何设置所需要的 Channel 的类型的呢? 答案是 channel() 方法的调用. 69 | 回想一下我们在客户端连接代码的初始化 Bootstrap 中, 会调用 channel() 方法, 传入 NioSocketChannel.class, 这个方法其实就是初始化了一个 BootstrapChannelFactory: 70 | ``` 71 | public B channel(Class channelClass) { 72 | if (channelClass == null) { 73 | throw new NullPointerException("channelClass"); 74 | } 75 | return channelFactory(new BootstrapChannelFactory(channelClass)); 76 | } 77 | ``` 78 | 而 BootstrapChannelFactory 实现了 ChannelFactory 接口, 它提供了唯一的方法, 即 **newChannel**. ChannelFactory, 顾名思义, 就是产生 Channel 的工厂类. 79 | 进入到 BootstrapChannelFactory.newChannel 中, 我们看到其实现代码如下: 80 | ``` 81 | @Override 82 | public T newChannel() { 83 | // 删除 try 块 84 | return clazz.newInstance(); 85 | } 86 | ``` 87 | 根据上面代码的提示, 我们就可以确定: 88 | - Bootstrap 中的 ChannelFactory 的实现是 BootstrapChannelFactory 89 | - 生成的 Channel 的具体类型是 NioSocketChannel. 90 | Channel 的实例化过程, 其实就是调用的 ChannelFactory#newChannel 方法, 而实例化的 Channel 的具体的类型又是和在初始化 Bootstrap 时传入的 channel() 方法的参数相关. 因此对于我们这个例子中的客户端的 Bootstrap 而言, 生成的的 Channel 实例就是 NioSocketChannel. 91 | 92 | #### Channel 实例化 93 | 前面我们已经知道了如何确定一个 Channel 的类型, 并且了解到 Channel 是通过工厂方法 ChannelFactory.newChannel() 来实例化的, 那么 ChannelFactory.newChannel() 方法在哪里调用呢? 94 | 继续跟踪, 我们发现其调用链是: 95 | ``` 96 | Bootstrap.connect -> Bootstrap.doConnect -> AbstractBootstrap.initAndRegister 97 | ``` 98 | 在 AbstractBootstrap.initAndRegister 中就调用了 **channelFactory().newChannel()** 来获取一个新的 NioSocketChannel 实例, 其源码如下: 99 | ``` 100 | final ChannelFuture initAndRegister() { 101 | // 去掉非关键代码 102 | final Channel channel = channelFactory().newChannel(); 103 | init(channel); 104 | ChannelFuture regFuture = group().register(channel); 105 | } 106 | ``` 107 | 在 **newChannel** 中, 通过类对象的 newInstance 来获取一个新 Channel 实例, 因而会调用NioSocketChannel 的默认构造器. 108 | NioSocketChannel 默认构造器代码如下: 109 | ``` 110 | public NioSocketChannel() { 111 | this(newSocket(DEFAULT_SELECTOR_PROVIDER)); 112 | } 113 | ``` 114 | `这里的代码比较关键`, 我们看到, 在这个构造器中, 会调用 **newSocket** 来打开一个新的 Java NIO SocketChannel: 115 | ``` 116 | private static SocketChannel newSocket(SelectorProvider provider) { 117 | ... 118 | return provider.openSocketChannel(); 119 | } 120 | ``` 121 | 接着会调用父类, 即 AbstractNioByteChannel 的构造器: 122 | ``` 123 | AbstractNioByteChannel(Channel parent, SelectableChannel ch) 124 | ``` 125 | 并传入参数 parent 为 null, ch 为刚才使用 newSocket 创建的 Java NIO SocketChannel, 因此生成的 NioSocketChannel 的 parent channel 是空的. 126 | ``` 127 | protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { 128 | super(parent, ch, SelectionKey.OP_READ); 129 | } 130 | ``` 131 | 接着会继续调用父类 AbstractNioChannel 的构造器, 并传入了参数 **readInterestOp = SelectionKey.OP_READ**: 132 | ``` 133 | protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { 134 | super(parent); 135 | this.ch = ch; 136 | this.readInterestOp = readInterestOp; 137 | // 省略 try 块 138 | // 配置 Java NIO SocketChannel 为非阻塞的. 139 | ch.configureBlocking(false); 140 | } 141 | ``` 142 | 然后继续调用父类 AbstractChannel 的构造器: 143 | ``` 144 | protected AbstractChannel(Channel parent) { 145 | this.parent = parent; 146 | unsafe = newUnsafe(); 147 | pipeline = new DefaultChannelPipeline(this); 148 | } 149 | ``` 150 | 151 | 到这里, 一个完整的 NioSocketChannel 就初始化完成了, 我们可以稍微总结一下构造一个 NioSocketChannel 所需要做的工作: 152 | - 调用 NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打开一个新的 Java NIO SocketChannel 153 | - AbstractChannel(Channel parent) 中初始化 AbstractChannel 的属性: 154 | - parent 属性置为 null 155 | - unsafe 通过newUnsafe() 实例化一个 unsafe 对象, 它的类型是 AbstractNioByteChannel.NioByteUnsafe 内部类 156 | - pipeline 是 new DefaultChannelPipeline(this) 新创建的实例. `这里体现了:Each channel has its own pipeline and it is created automatically when a new channel is created.` 157 | - AbstractNioChannel 中的属性: 158 | - SelectableChannel ch 被设置为 Java SocketChannel, 即 NioSocketChannel#newSocket 返回的 Java NIO SocketChannel. 159 | - readInterestOp 被设置为 SelectionKey.OP_READ 160 | - SelectableChannel ch 被配置为非阻塞的 **ch.configureBlocking(false)** 161 | - NioSocketChannel 中的属性: 162 | - SocketChannelConfig config = new NioSocketChannelConfig(this, socket.socket()) 163 | 164 | ### 关于 unsafe 字段的初始化 165 | 我们简单地提到了, 在实例化 NioSocketChannel 的过程中, 会在父类 AbstractChannel 的构造器中, 调用 newUnsafe() 来获取一个 unsafe 实例. 那么 unsafe 是怎么初始化的呢? 它的作用是什么? 166 | 其实 unsafe 特别关键, 它封装了对 Java 底层 Socket 的操作, 因此实际上是沟通 Netty 上层和 Java 底层的重要的桥梁. 167 | 168 | 那么我们就来看一下 Unsafe 接口所提供的方法吧: 169 | ``` 170 | interface Unsafe { 171 | SocketAddress localAddress(); 172 | SocketAddress remoteAddress(); 173 | void register(EventLoop eventLoop, ChannelPromise promise); 174 | void bind(SocketAddress localAddress, ChannelPromise promise); 175 | void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); 176 | void disconnect(ChannelPromise promise); 177 | void close(ChannelPromise promise); 178 | void closeForcibly(); 179 | void deregister(ChannelPromise promise); 180 | void beginRead(); 181 | void write(Object msg, ChannelPromise promise); 182 | void flush(); 183 | ChannelPromise voidPromise(); 184 | ChannelOutboundBuffer outboundBuffer(); 185 | } 186 | ``` 187 | 一看便知, 这些方法其实都会对应到相关的 Java 底层的 Socket 的操作. 188 | 回到 AbstractChannel 的构造方法中, 在这里调用了 newUnsafe() 获取一个新的 unsafe 对象, 而 newUnsafe 方法在 NioSocketChannel 中被重写了: 189 | ``` 190 | @Override 191 | protected AbstractNioUnsafe newUnsafe() { 192 | return new NioSocketChannelUnsafe(); 193 | } 194 | ``` 195 | NioSocketChannel.newUnsafe 方法会返回一个 NioSocketChannelUnsafe 实例. 从这里我们就可以确定了, 在实例化的 NioSocketChannel 中的 unsafe 字段, 其实是一个 NioSocketChannelUnsafe 的实例. 196 | ### 关于 pipeline 的初始化 197 | 上面我们分析了一个 Channel (在这个例子中是 NioSocketChannel) 的大体初始化过程, 但是我们漏掉了一个关键的部分, 即 ChannelPipeline 的初始化. 198 | 根据 `Each channel has its own pipeline and it is created automatically when a new channel is created.`, 我们知道, 在实例化一个 Channel 时, 必然伴随着实例化一个 ChannelPipeline. 而我们确实在 AbstractChannel 的构造器看到了 pipeline 字段被初始化为 DefaultChannelPipeline 的实例. 那么我们就来看一下, DefaultChannelPipeline 构造器做了哪些工作吧: 199 | ``` 200 | public DefaultChannelPipeline(AbstractChannel channel) { 201 | if (channel == null) { 202 | throw new NullPointerException("channel"); 203 | } 204 | this.channel = channel; 205 | 206 | tail = new TailContext(this); 207 | head = new HeadContext(this); 208 | 209 | head.next = tail; 210 | tail.prev = head; 211 | } 212 | ``` 213 | 我们调用 DefaultChannelPipeline 的构造器, 传入了一个 channel, 而这个 channel 其实就是我们实例化的 NioSocketChannel, DefaultChannelPipeline 会将这个 NioSocketChannel 对象保存在channel 字段中. DefaultChannelPipeline 中, 还有两个特殊的字段, 即 head 和 tail, 而这两个字段是一个双向链表的头和尾. 其实在 DefaultChannelPipeline 中, 维护了一个以 AbstractChannelHandlerContext 为节点的双向链表, 这个链表是 Netty 实现 Pipeline 机制的关键. 关于 DefaultChannelPipeline 中的双向链表以及它所起的作用, 我在这里暂时不表, 在 **Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline** 中会有详细的分析. 214 | 215 | HeadContext 的继承层次结构如下所示: 216 | ![Alt text](./HeadContext.png) 217 | TailContext 的继承层次结构如下所示: 218 | ![Alt text](./TailContext.png) 219 | 220 | 我们可以看到, 链表中 head 是一个 **ChannelOutboundHandler**, 而 tail 则是一个 **ChannelInboundHandler**. 221 | 接着看一下 HeadContext 的构造器: 222 | ``` 223 | HeadContext(DefaultChannelPipeline pipeline) { 224 | super(pipeline, null, HEAD_NAME, false, true); 225 | unsafe = pipeline.channel().unsafe(); 226 | } 227 | ``` 228 | 它调用了父类 AbstractChannelHandlerContext 的构造器, 并传入参数 inbound = false, outbound = true. 229 | TailContext 的构造器与 HeadContext 的相反, 它调用了父类 AbstractChannelHandlerContext 的构造器, 并传入参数 inbound = true, outbound = false. 230 | 即 header 是一个 outboundHandler, 而 tail 是一个inboundHandler, 关于这一点, 大家要特别注意, 因为在分析到 Netty Pipeline 时, 我们会反复用到 inbound 和 outbound 这两个属性. 231 | 232 | ### 关于 EventLoop 初始化 233 | 回到最开始的 EchoClient.java 代码中, 我们在一开始就实例化了一个 NioEventLoopGroup 对象, 因此我们就从它的构造器中追踪一下 EventLoop 的初始化过程. 234 | 首先来看一下 NioEventLoopGroup 的类继承层次: 235 | ![Alt text](./NioEventLoopGroup 类层次结构.png) 236 | 237 | NioEventLoop 有几个重载的构造器, 不过内容都没有什么区别, 最终都是调用的父类MultithreadEventLoopGroup构造器: 238 | ``` 239 | protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) { 240 | super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args); 241 | } 242 | ``` 243 | 其中有一点有意思的地方是, 如果我们传入的线程数 nThreads 是0, 那么 Netty 会为我们设置默认的线程数 DEFAULT_EVENT_LOOP_THREADS, 而这个默认的线程数是怎么确定的呢? 244 | 其实很简单, 在静态代码块中, 会首先确定 DEFAULT_EVENT_LOOP_THREADS 的值: 245 | ``` 246 | static { 247 | DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( 248 | "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); 249 | } 250 | ``` 251 | Netty 会首先从系统属性中获取 "io.netty.eventLoopThreads" 的值, 如果我们没有设置它的话, 那么就返回默认值: 处理器核心数 * 2. 252 | 253 | 回到MultithreadEventLoopGroup构造器中, 这个构造器会继续调用父类 MultithreadEventExecutorGroup 的构造器: 254 | ``` 255 | protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) { 256 | // 去掉了参数检查, 异常处理 等代码. 257 | children = new SingleThreadEventExecutor[nThreads]; 258 | if (isPowerOfTwo(children.length)) { 259 | chooser = new PowerOfTwoEventExecutorChooser(); 260 | } else { 261 | chooser = new GenericEventExecutorChooser(); 262 | } 263 | 264 | for (int i = 0; i < nThreads; i ++) { 265 | children[i] = newChild(threadFactory, args); 266 | } 267 | } 268 | ``` 269 | 根据代码, 我们就很清楚 MultithreadEventExecutorGroup 中的处理逻辑了: 270 | - 创建一个大小为 nThreads 的 SingleThreadEventExecutor 数组 271 | - 根据 nThreads 的大小, 创建不同的 Chooser, 即如果 nThreads 是 2 的幂, 则使用 PowerOfTwoEventExecutorChooser, 反之使用 GenericEventExecutorChooser. 不论使用哪个 Chooser, 它们的功能都是一样的, 即从 children 数组中选出一个合适的 EventExecutor 实例. 272 | - 调用 newChhild 方法初始化 children 数组. 273 | 274 | 根据上面的代码, 我们知道, MultithreadEventExecutorGroup 内部维护了一个 EventExecutor 数组, Netty 的 EventLoopGroup 的实现机制其实就建立在 MultithreadEventExecutorGroup 之上. 每当 Netty 需要一个 EventLoop 时, 会调用 next() 方法获取一个可用的 EventLoop. 275 | 上面代码的最后一部分是 newChild 方法, 这个是一个抽象方法, 它的任务是实例化 EventLoop 对象. 我们跟踪一下它的代码, 可以发现, 这个方法在 NioEventLoopGroup 类中实现了, 其内容很简单: 276 | ``` 277 | @Override 278 | protected EventExecutor newChild( 279 | ThreadFactory threadFactory, Object... args) throws Exception { 280 | return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]); 281 | } 282 | ``` 283 | 其实就是实例化一个 NioEventLoop 对象, 然后返回它. 284 | 285 | 最后总结一下整个 EventLoopGroup 的初始化过程吧: 286 | - EventLoopGroup(其实是MultithreadEventExecutorGroup) 内部维护一个类型为 EventExecutor children 数组, 其大小是 nThreads, 这样就构成了一个线程池 287 | - 如果我们在实例化 NioEventLoopGroup 时, 如果指定线程池大小, 则 nThreads 就是指定的值, 反之是处理器核心数 * 2 288 | - MultithreadEventExecutorGroup 中会调用 newChild 抽象方法来初始化 children 数组 289 | - 抽象方法 newChild 是在 NioEventLoopGroup 中实现的, 它返回一个 NioEventLoop 实例. 290 | - NioEventLoop 属性: 291 | - SelectorProvider provider 属性: NioEventLoopGroup 构造器中通过 SelectorProvider.provider() 获取一个 SelectorProvider 292 | - Selector selector 属性: NioEventLoop 构造器中通过调用通过 selector = provider.openSelector() 获取一个 selector 对象. 293 | 294 | ### channel 的注册过程 295 | 在前面的分析中, 我们提到, channel 会在 Bootstrap.initAndRegister 中进行初始化, 但是这个方法还会将初始化好的 Channel 注册到 EventGroup 中. 接下来我们就来分析一下 Channel 注册的过程. 296 | 回顾一下 AbstractBootstrap.initAndRegister 方法: 297 | ``` 298 | final ChannelFuture initAndRegister() { 299 | // 去掉非关键代码 300 | final Channel channel = channelFactory().newChannel(); 301 | init(channel); 302 | ChannelFuture regFuture = group().register(channel); 303 | } 304 | ``` 305 | 当Channel 初始化后, 会紧接着调用 group().register() 方法来注册 Channel, 我们继续跟踪的话, 会发现其调用链如下: 306 | AbstractBootstrap.initAndRegister -> MultithreadEventLoopGroup.register -> SingleThreadEventLoop.register -> AbstractUnsafe.register 307 | 通过跟踪调用链, 最终我们发现是调用到了 unsafe 的 register 方法, 那么接下来我们就仔细看一下 AbstractUnsafe.register 方法中到底做了什么: 308 | ``` 309 | @Override 310 | public final void register(EventLoop eventLoop, final ChannelPromise promise) { 311 | // 省略条件判断和错误处理 312 | AbstractChannel.this.eventLoop = eventLoop; 313 | register0(promise); 314 | } 315 | ``` 316 | 首先, 将 eventLoop 赋值给 Channel 的 eventLoop 属性, 而我们知道这个 eventLoop 对象其实是 MultithreadEventLoopGroup.next() 方法获取的, 根据我们前面 **关于 EventLoop 初始化** 小节中, 我们可以确定 next() 方法返回的 eventLoop 对象是 NioEventLoop 实例. 317 | register 方法接着调用了 register0 方法: 318 | ``` 319 | private void register0(ChannelPromise promise) { 320 | boolean firstRegistration = neverRegistered; 321 | doRegister(); 322 | neverRegistered = false; 323 | registered = true; 324 | safeSetSuccess(promise); 325 | pipeline.fireChannelRegistered(); 326 | // Only fire a channelActive if the channel has never been registered. This prevents firing 327 | // multiple channel actives if the channel is deregistered and re-registered. 328 | if (firstRegistration && isActive()) { 329 | pipeline.fireChannelActive(); 330 | } 331 | } 332 | ``` 333 | register0 又调用了 AbstractNioChannel.doRegister: 334 | ``` 335 | @Override 336 | protected void doRegister() throws Exception { 337 | // 省略错误处理 338 | selectionKey = javaChannel().register(eventLoop().selector, 0, this); 339 | } 340 | ``` 341 | javaChannel() 这个方法在前面我们已经知道了, 它返回的是一个 Java NIO SocketChannel, 这里我们将这个 SocketChannel 注册到与 eventLoop 关联的 selector 上了. 342 | 343 | 我们总结一下 Channel 的注册过程: 344 | - 首先在 AbstractBootstrap.initAndRegister中, 通过 group().register(channel), 调用 MultithreadEventLoopGroup.register 方法 345 | - 在MultithreadEventLoopGroup.register 中, 通过 next() 获取一个可用的 SingleThreadEventLoop, 然后调用它的 register 346 | - 在 SingleThreadEventLoop.register 中, 通过 channel.unsafe().register(this, promise) 来获取 channel 的 unsafe() 底层操作对象, 然后调用它的 register. 347 | - 在 AbstractUnsafe.register 方法中, 调用 register0 方法注册 Channel 348 | - 在 AbstractUnsafe.register0 中, 调用 AbstractNioChannel.doRegister 方法 349 | - AbstractNioChannel.doRegister 方法通过 javaChannel().register(eventLoop().selector, 0, this) 将 Channel 对应的 Java NIO SockerChannel 注册到一个 eventLoop 的 Selector 中, 并且将当前 Channel 作为 attachment. 350 | 351 | 总的来说, Channel 注册过程所做的工作就是将 Channel 与对应的 EventLoop 关联, 因此这也体现了, 在 Netty 中, 每个 Channel 都会关联一个特定的 EventLoop, 并且这个 Channel 中的所有 IO 操作都是在这个 EventLoop 中执行的; 当关联好 Channel 和 EventLoop 后, 会继续调用底层的 Java NIO SocketChannel 的 register 方法, 将底层的 Java NIO SocketChannel 注册到指定的 selector 中. 通过这两步, 就完成了 Netty Channel 的注册过程. 352 | 353 | ### handler 的添加过程 354 | Netty 的一个强大和灵活之处就是基于 Pipeline 的自定义 handler 机制. 基于此, 我们可以像添加插件一样自由组合各种各样的 handler 来完成业务逻辑. 例如我们需要处理 HTTP 数据, 那么就可以在 pipeline 前添加一个 Http 的编解码的 Handler, 然后接着添加我们自己的业务逻辑的 handler, 这样网络上的数据流就向通过一个管道一样, 从不同的 handler 中流过并进行编解码, 最终在到达我们自定义的 handler 中. 355 | 既然说到这里, 有些读者朋友肯定会好奇, 既然这个 pipeline 机制是这么的强大, 那么它是怎么实现的呢? 不过我这里不打算详细展开 Netty 的 ChannelPipeline 的实现机制(具体的细节会在后续的章节中展示), 我在这一小节中, 从简单的入手, 展示一下我们自定义的 handler 是如何以及何时添加到 ChannelPipeline 中的. 356 | 首先让我们看一下如下的代码片段: 357 | ``` 358 | ... 359 | .handler(new ChannelInitializer() { 360 | @Override 361 | public void initChannel(SocketChannel ch) throws Exception { 362 | ChannelPipeline p = ch.pipeline(); 363 | if (sslCtx != null) { 364 | p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT)); 365 | } 366 | //p.addLast(new LoggingHandler(LogLevel.INFO)); 367 | p.addLast(new EchoClientHandler()); 368 | } 369 | }); 370 | ``` 371 | 这个代码片段就是实现了 handler 的添加功能. 我们看到, Bootstrap.handler 方法接收一个 ChannelHandler, 而我们传递的是一个 派生于 ChannelInitializer 的匿名类, 它正好也实现了 ChannelHandler 接口. 我们来看一下, ChannelInitializer 类内到底有什么玄机: 372 | ``` 373 | @Sharable 374 | public abstract class ChannelInitializer extends ChannelInboundHandlerAdapter { 375 | 376 | private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class); 377 | protected abstract void initChannel(C ch) throws Exception; 378 | 379 | @Override 380 | @SuppressWarnings("unchecked") 381 | public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { 382 | initChannel((C) ctx.channel()); 383 | ctx.pipeline().remove(this); 384 | ctx.fireChannelRegistered(); 385 | } 386 | ... 387 | } 388 | ``` 389 | ChannelInitializer 是一个抽象类, 它有一个抽象的方法 initChannel, 我们正是实现了这个方法, 并在这个方法中添加的自定义的 handler 的. 那么 initChannel 是哪里被调用的呢? 答案是 ChannelInitializer.channelRegistered 方法中. 390 | 我们来关注一下 channelRegistered 方法. 从上面的源码中, 我们可以看到, 在 channelRegistered 方法中, 会调用 initChannel 方法, 将自定义的 handler 添加到 ChannelPipeline 中, 然后调用 ctx.pipeline().remove(this) 将自己从 ChannelPipeline 中删除. 上面的分析过程, 可以用如下图片展示: 391 | 一开始, ChannelPipeline 中只有三个 handler, head, tail 和我们添加的 ChannelInitializer. 392 | ![Alt text](./1477130291691.png) 393 | 接着 initChannel 方法调用后, 添加了自定义的 handler: 394 | ![Alt text](./1477130295919.png) 395 | 最后将 ChannelInitializer 删除: 396 | ![Alt text](./1477130299722.png) 397 | 398 | 分析到这里, 我们已经简单了解了自定义的 handler 是如何添加到 ChannelPipeline 中的, 不过限于主题与篇幅的原因, 我没有在这里详细展开 ChannelPipeline 的底层机制, 我打算在下一篇 **Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline** 中对这个问题进行深入的探讨. 399 | 400 | ### 客户端连接分析 401 | 经过上面的各种分析后, 我们大致了解了 Netty 初始化时, 所做的工作, 那么接下来我们就直奔主题, 分析一下客户端是如何发起 TCP 连接的. 402 | 403 | 首先, 客户端通过调用 **Bootstrap** 的 **connect** 方法进行连接. 404 | 在 connect 中, 会进行一些参数检查后, 最终调用的是 **doConnect0** 方法, 其实现如下: 405 | ``` 406 | private static void doConnect0( 407 | final ChannelFuture regFuture, final Channel channel, 408 | final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 409 | 410 | // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up 411 | // the pipeline in its channelRegistered() implementation. 412 | channel.eventLoop().execute(new Runnable() { 413 | @Override 414 | public void run() { 415 | if (regFuture.isSuccess()) { 416 | if (localAddress == null) { 417 | channel.connect(remoteAddress, promise); 418 | } else { 419 | channel.connect(remoteAddress, localAddress, promise); 420 | } 421 | promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 422 | } else { 423 | promise.setFailure(regFuture.cause()); 424 | } 425 | } 426 | }); 427 | } 428 | ``` 429 | 在 doConnect0 中, 会在 event loop 线程中调用 Channel 的 connect 方法, 而这个 Channel 的具体类型是什么呢? 我们在 Channel 初始化这一小节中已经分析过了, 这里 channel 的类型就是 **NioSocketChannel**. 430 | 进行跟踪到 channel.connect 中, 我们发现它调用的是 DefaultChannelPipeline#connect, 而, pipeline 的 connect 代码如下: 431 | ``` 432 | @Override 433 | public ChannelFuture connect(SocketAddress remoteAddress) { 434 | return tail.connect(remoteAddress); 435 | } 436 | ``` 437 | 而 tail 字段, 我们已经分析过了, 是一个 TailContext 的实例, 而 TailContext 又是 AbstractChannelHandlerContext 的子类, 并且没有实现 connect 方法, 因此这里调用的其实是 AbstractChannelHandlerContext.connect, 我们看一下这个方法的实现: 438 | ``` 439 | @Override 440 | public ChannelFuture connect( 441 | final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 442 | 443 | // 删除的参数检查的代码 444 | final AbstractChannelHandlerContext next = findContextOutbound(); 445 | EventExecutor executor = next.executor(); 446 | if (executor.inEventLoop()) { 447 | next.invokeConnect(remoteAddress, localAddress, promise); 448 | } else { 449 | safeExecute(executor, new OneTimeTask() { 450 | @Override 451 | public void run() { 452 | next.invokeConnect(remoteAddress, localAddress, promise); 453 | } 454 | }, promise, null); 455 | } 456 | 457 | return promise; 458 | } 459 | ``` 460 | 上面的代码中有一个关键的地方, 即 **final AbstractChannelHandlerContext next = findContextOutbound()**, 这里调用 **findContextOutbound** 方法, 从 DefaultChannelPipeline 内的双向链表的 tail 开始, 不断向前寻找第一个 outbound 为 true 的 AbstractChannelHandlerContext, 然后调用它的 invokeConnect 方法, 其代码如下: 461 | ``` 462 | private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { 463 | // 忽略 try 块 464 | ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise); 465 | } 466 | ``` 467 | 还记得我们在 "关于 pipeline 的初始化" 这一小节分析的的内容吗? 我们提到, 在 DefaultChannelPipeline 的构造器中, 会实例化两个对象: head 和 tail, 并形成了双向链表的头和尾. head 是 HeadContext 的实例, 它实现了 ChannelOutboundHandler 接口, 并且它的 outbound 字段为 true. 因此在 findContextOutbound 中, 找到的 AbstractChannelHandlerContext 对象其实就是 head. 进而在 invokeConnect 方法中, 我们向上转换为 ChannelOutboundHandler 就是没问题的了. 468 | 而又因为 HeadContext 重写了 connect 方法, 因此实际上调用的是 HeadContext.connect. 我们接着跟踪到 HeadContext.connect, 其代码如下: 469 | ``` 470 | @Override 471 | public void connect( 472 | ChannelHandlerContext ctx, 473 | SocketAddress remoteAddress, SocketAddress localAddress, 474 | ChannelPromise promise) throws Exception { 475 | unsafe.connect(remoteAddress, localAddress, promise); 476 | } 477 | ``` 478 | 这个 connect 方法很简单, 仅仅调用了 unsafe 的 connect 方法. 而 unsafe 又是什么呢? 479 | 回顾一下 HeadContext 的构造器, 我们发现 unsafe 是 pipeline.channel().unsafe() 返回的, 而 Channel 的 unsafe 字段, 在这个例子中, 我们已经知道了, 其实是 AbstractNioByteChannel.NioByteUnsafe 内部类. 兜兜转转了一大圈, 我们找到了创建 Socket 连接的关键代码. 480 | 进行跟踪 NioByteUnsafe -> AbstractNioUnsafe.connect: 481 | ``` 482 | @Override 483 | public final void connect( 484 | final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 485 | boolean wasActive = isActive(); 486 | if (doConnect(remoteAddress, localAddress)) { 487 | fulfillConnectPromise(promise, wasActive); 488 | } else { 489 | ... 490 | } 491 | } 492 | ``` 493 | AbstractNioUnsafe.connect 的实现如上代码所示, 在这个 connect 方法中, 调用了 doConnect 方法, `注意, 这个方法并不是 AbstractNioUnsafe 的方法, 而是 AbstractNioChannel 的抽象方法.` doConnect 方法是在 NioSocketChannel 中实现的, 因此进入到 **NioSocketChannel.doConnect** 中: 494 | ``` 495 | @Override 496 | protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { 497 | if (localAddress != null) { 498 | javaChannel().socket().bind(localAddress); 499 | } 500 | 501 | boolean success = false; 502 | try { 503 | boolean connected = javaChannel().connect(remoteAddress); 504 | if (!connected) { 505 | selectionKey().interestOps(SelectionKey.OP_CONNECT); 506 | } 507 | success = true; 508 | return connected; 509 | } finally { 510 | if (!success) { 511 | doClose(); 512 | } 513 | } 514 | } 515 | ``` 516 | 我们终于看到的最关键的部分了, 庆祝一下! 517 | 上面的代码不用多说, 首先是获取 Java NIO SocketChannel, 即我们已经分析过的, 从 NioSocketChannel.newSocket 返回的 SocketChannel 对象; 然后是调用 SocketChannel.connect 方法完成 Java NIO 层面上的 Socket 的连接. 518 | 最后, 上面的代码流程可以用如下时序图直观地展示: 519 | ![Alt text](./Netty 客户端的连接时序图.png) 520 | -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/NioEventLoopGroup 类层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/NioEventLoopGroup 类层次结构.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/NioSocketChannel 类层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/NioSocketChannel 类层次结构.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/TailContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)/TailContext.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/1477377831087.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/1477377831087.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/1477378182291.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/1477378182291.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端).md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端) 2 | @(Netty)[Java, Netty, Netty 源码分析] 3 | 4 | [TOC] 5 | 6 | 7 | ## 服务器端 8 | 在分析客户端的代码时, 我们已经对 Bootstrap 启动 Netty 有了一个大致的认识, 那么接下来分析服务器端时, 就会相对简单一些了. 9 | 首先还是来看一下服务器端的启动代码: 10 | ``` 11 | public final class EchoServer { 12 | 13 | static final boolean SSL = System.getProperty("ssl") != null; 14 | static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); 15 | 16 | public static void main(String[] args) throws Exception { 17 | // Configure SSL. 18 | final SslContext sslCtx; 19 | if (SSL) { 20 | SelfSignedCertificate ssc = new SelfSignedCertificate(); 21 | sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); 22 | } else { 23 | sslCtx = null; 24 | } 25 | 26 | // Configure the server. 27 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 28 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 29 | try { 30 | ServerBootstrap b = new ServerBootstrap(); 31 | b.group(bossGroup, workerGroup) 32 | .channel(NioServerSocketChannel.class) 33 | .option(ChannelOption.SO_BACKLOG, 100) 34 | .handler(new LoggingHandler(LogLevel.INFO)) 35 | .childHandler(new ChannelInitializer() { 36 | @Override 37 | public void initChannel(SocketChannel ch) throws Exception { 38 | ChannelPipeline p = ch.pipeline(); 39 | if (sslCtx != null) { 40 | p.addLast(sslCtx.newHandler(ch.alloc())); 41 | } 42 | //p.addLast(new LoggingHandler(LogLevel.INFO)); 43 | p.addLast(new EchoServerHandler()); 44 | } 45 | }); 46 | 47 | // Start the server. 48 | ChannelFuture f = b.bind(PORT).sync(); 49 | 50 | // Wait until the server socket is closed. 51 | f.channel().closeFuture().sync(); 52 | } finally { 53 | // Shut down all event loops to terminate all threads. 54 | bossGroup.shutdownGracefully(); 55 | workerGroup.shutdownGracefully(); 56 | } 57 | } 58 | } 59 | ``` 60 | 和客户端的代码相比, 没有很大的差别, 基本上也是进行了如下几个部分的初始化: 61 | 1. EventLoopGroup: 不论是服务器端还是客户端, 都必须指定 EventLoopGroup. 在这个例子中, 指定了 NioEventLoopGroup, 表示一个 NIO 的EventLoopGroup, 不过服务器端需要指定两个 EventLoopGroup, 一个是 bossGroup, 用于处理客户端的连接请求; 另一个是 workerGroup, 用于处理与各个客户端连接的 IO 操作. 62 | 2. ChannelType: 指定 Channel 的类型. 因为是服务器端, 因此使用了 NioServerSocketChannel. 63 | 3. Handler: 设置数据的处理器. 64 | 65 | ### Channel 的初始化过程 66 | 我们在分析客户端的 Channel 初始化过程时, 已经提到, Channel 是对 Java 底层 Socket 连接的抽象, 并且知道了客户端的 Channel 的具体类型是 NioSocketChannel, 那么自然的, 服务器端的 Channel 类型就是 NioServerSocketChannel 了. 67 | 那么接下来我们按照分析客户端的流程对服务器端的代码也同样地分析一遍, 这样也方便我们对比一下服务器端和客户端有哪些不一样的地方. 68 | #### Channel 类型的确定 69 | 同样的分析套路, 我们已经知道了, 在客户端中, Channel 的类型其实是在初始化时, 通过 Bootstrap.channel() 方法设置的, 服务器端自然也不例外. 70 | 在服务器端, 我们调用了 ServerBootstarap.channel(NioServerSocketChannel.class), 传递了一个 NioServerSocketChannel Class 对象. 这样的话, 按照和分析客户端代码一样的流程, 我们就可以确定, NioServerSocketChannel 的实例化是通过 BootstrapChannelFactory 工厂类来完成的, 而 BootstrapChannelFactory 中的 clazz 字段被设置为了 NioServerSocketChannel.class, 因此当调用 BootstrapChannelFactory.newChannel() 时: 71 | ``` 72 | @Override 73 | public T newChannel() { 74 | // 删除 try 块 75 | return clazz.newInstance(); 76 | } 77 | ``` 78 | 就获取到了一个 NioServerSocketChannel 的实例. 79 | 80 | 最后我们也来总结一下: 81 | - ServerBootstrap 中的 ChannelFactory 的实现是 BootstrapChannelFactory 82 | - 生成的 Channel 的具体类型是 NioServerSocketChannel. 83 | Channel 的实例化过程, 其实就是调用的 ChannelFactory.newChannel 方法, 而实例化的 Channel 的具体的类型又是和在初始化 ServerBootstrap 时传入的 channel() 方法的参数相关. 因此对于我们这个例子中的服务器端的 ServerBootstrap 而言, 生成的的 Channel 实例就是 NioServerSocketChannel. 84 | 85 | #### NioServerSocketChannel 的实例化过程 86 | 首先还是来看一下 NioServerSocketChannel 的实例化过程. 87 | 下面是 NioServerSocketChannel 的类层次结构图: 88 | ![Alt text](./NioServerSocketChannel 类层次结构.png) 89 | 90 | 首先, 我们来看一下它的默认的构造器. 和 NioSocketChannel 类似, 构造器都是调用了 newSocket 来打开一个 Java 的 NIO Socket, 不过需要注意的是, 客户端的 newSocket 调用的是 openSocketChannel, 而服务器端的 newSocket 调用的是 openServerSocketChannel. 顾名思义, 一个是客户端的 Java SocketChannel, 一个是服务器端的 Java ServerSocketChannel. 91 | ``` 92 | private static ServerSocketChannel newSocket(SelectorProvider provider) { 93 | return provider.openServerSocketChannel(); 94 | } 95 | 96 | public NioServerSocketChannel() { 97 | this(newSocket(DEFAULT_SELECTOR_PROVIDER)); 98 | } 99 | ``` 100 | 接下来会调用重载的构造器: 101 | ``` 102 | public NioServerSocketChannel(ServerSocketChannel channel) { 103 | super(null, channel, SelectionKey.OP_ACCEPT); 104 | config = new NioServerSocketChannelConfig(this, javaChannel().socket()); 105 | } 106 | ``` 107 | 这个构造其中, 调用父类构造器时, 传入的参数是 **SelectionKey.OP_ACCEPT**. 作为对比, 我们回想一下, 在客户端的 Channel 初始化时, 传入的参数是 **SelectionKey.OP_READ**. 有 Java NIO Socket 开发经验的朋友就知道了, Java NIO 是一种 Reactor 模式, 我们通过 selector 来实现 I/O 的多路复用复用. 在一开始时, 服务器端需要监听客户端的连接请求, 因此在这里我们设置了 **SelectionKey.OP_ACCEPT**, 即通知 selector 我们对客户端的连接请求感兴趣. 108 | 109 | 接着和客户端的分析一下, 会逐级地调用父类的构造器 NioServerSocketChannel <- AbstractNioMessageChannel <- AbstractNioChannel <- AbstractChannel. 110 | 同样的, 在 AbstractChannel 中会实例化一个 unsafe 和 pipeline: 111 | ``` 112 | protected AbstractChannel(Channel parent) { 113 | this.parent = parent; 114 | unsafe = newUnsafe(); 115 | pipeline = new DefaultChannelPipeline(this); 116 | } 117 | ``` 118 | 不过, 这里有一点需要注意的是, 客户端的 unsafe 是一个 AbstractNioByteChannel#NioByteUnsafe 的实例, 而在服务器端时, 因为 AbstractNioMessageChannel 重写了newUnsafe 方法: 119 | ``` 120 | @Override 121 | protected AbstractNioUnsafe newUnsafe() { 122 | return new NioMessageUnsafe(); 123 | } 124 | ``` 125 | 因此在服务器端, unsafe 字段其实是一个 AbstractNioMessageChannel#AbstractNioUnsafe 的实例. 126 | 我们来总结一下, 在 NioServerSocketChannsl 实例化过程中, 所需要做的工作: 127 | - 调用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打开一个新的 Java NIO ServerSocketChannel 128 | - AbstractChannel(Channel parent) 中初始化 AbstractChannel 的属性: 129 | - parent 属性置为 null 130 | - unsafe 通过newUnsafe() 实例化一个 unsafe 对象, 它的类型是 AbstractNioMessageChannel#AbstractNioUnsafe 内部类 131 | - pipeline 是 new DefaultChannelPipeline(this) 新创建的实例. 132 | - AbstractNioChannel 中的属性: 133 | - SelectableChannel ch 被设置为 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel. 134 | - readInterestOp 被设置为 SelectionKey.OP_ACCEPT 135 | - SelectableChannel ch 被配置为非阻塞的 **ch.configureBlocking(false)** 136 | - NioServerSocketChannel 中的属性: 137 | - ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket()) 138 | 139 | ### ChannelPipeline 初始化 140 | 服务器端和客户端的 ChannelPipeline 的初始化一致, 因此就不再单独分析了. 141 | ### Channel 的注册 142 | 服务器端和客户端的 Channel 的注册过程一致, 因此就不再单独分析了. 143 | ### 关于 bossGroup 与 workerGroup 144 | 在客户端的时候, 我们只提供了一个 EventLoopGroup 对象, 而在服务器端的初始化时, 我们设置了两个 EventLoopGroup, 一个是 bossGroup, 另一个是 workerGroup. 那么这两个 EventLoopGroup 都是干什么用的呢? 其实呢, bossGroup 是用于服务端 的 accept 的, 即用于处理客户端的连接请求. 我们可以把 Netty 比作一个饭店, bossGroup 就像一个像一个前台接待, 当客户来到饭店吃时, 接待员就会引导顾客就坐, 为顾客端茶送水等. 而 workerGroup, 其实就是实际上干活的啦, 它们负责客户端连接通道的 IO 操作: 当接待员 招待好顾客后, 就可以稍做休息, 而此时后厨里的厨师们(workerGroup)就开始忙碌地准备饭菜了. 145 | 关于 bossGroup 与 workerGroup 的关系, 我们可以用如下图来展示: 146 | ![Alt text](./Server 端 bossGroup 与 workerGroup 的图示.png) 147 | 148 | 首先, 服务器端 bossGroup 不断地监听是否有客户端的连接, 当发现有一个新的客户端连接到来时, bossGroup 就会为此连接初始化各项资源, 然后从 workerGroup 中选出一个 EventLoop 绑定到此客户端连接中. 那么接下来的服务器与客户端的交互过程就全部在此分配的 EventLoop 中了. 149 | 150 | 口说无凭, 我们还是以源码说话吧. 151 | 首先在ServerBootstrap 初始化时, 调用了 **b.group(bossGroup, workerGroup)** 设置了两个 EventLoopGroup, 我们跟踪进去看一下: 152 | ``` 153 | public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { 154 | super.group(parentGroup); 155 | ... 156 | this.childGroup = childGroup; 157 | return this; 158 | } 159 | ``` 160 | 显然, 这个方法初始化了两个字段, 一个是 **group = parentGroup**, 它是在 super.group(parentGroup) 中初始化的, 另一个是 **childGroup = childGroup**. 接着我们启动程序调用了 b.bind 方法来监听一个本地端口. bind 方法会触发如下的调用链: 161 | ``` 162 | AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister 163 | ``` 164 | AbstractBootstrap.initAndRegister 是我们的老朋友了, 我们在分析客户端程序时, 和它打过很多交到了, 我们再来回顾一下这个方法吧: 165 | ``` 166 | final ChannelFuture initAndRegister() { 167 | final Channel channel = channelFactory().newChannel(); 168 | ... 省略异常判断 169 | init(channel); 170 | ChannelFuture regFuture = group().register(channel); 171 | return regFuture; 172 | } 173 | ``` 174 | 这里 group() 方法返回的是上面我们提到的 bossGroup, 而这里的 channel 我们也已经分析过了, 它是一个是一个 NioServerSocketChannsl 实例, 因此我们可以知道, group().register(channel) 将 bossGroup 和 NioServerSocketChannsl 关联起来了. 175 | 那么 workerGroup 是在哪里与 NioSocketChannel 关联的呢? 176 | 我们继续看 **init(channel)** 方法: 177 | ``` 178 | @Override 179 | void init(Channel channel) throws Exception { 180 | ... 181 | ChannelPipeline p = channel.pipeline(); 182 | 183 | final EventLoopGroup currentChildGroup = childGroup; 184 | final ChannelHandler currentChildHandler = childHandler; 185 | final Entry, Object>[] currentChildOptions; 186 | final Entry, Object>[] currentChildAttrs; 187 | 188 | p.addLast(new ChannelInitializer() { 189 | @Override 190 | public void initChannel(Channel ch) throws Exception { 191 | ChannelPipeline pipeline = ch.pipeline(); 192 | ChannelHandler handler = handler(); 193 | if (handler != null) { 194 | pipeline.addLast(handler); 195 | } 196 | pipeline.addLast(new ServerBootstrapAcceptor( 197 | currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); 198 | } 199 | }); 200 | } 201 | ``` 202 | init 方法在 ServerBootstrap 中重写了, 从上面的代码片段中我们看到, 它为 pipeline 中添加了一个 ChannelInitializer, 而这个 ChannelInitializer 中添加了一个关键的 **ServerBootstrapAcceptor** handler. 关于 handler 的添加与初始化的过程, 我们留待下一小节中分析, 我们现在关注一下 ServerBootstrapAcceptor 类. 203 | ServerBootstrapAcceptor 中重写了 channelRead 方法, 其主要代码如下: 204 | ``` 205 | @Override 206 | @SuppressWarnings("unchecked") 207 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 208 | final Channel child = (Channel) msg; 209 | child.pipeline().addLast(childHandler); 210 | ... 211 | childGroup.register(child).addListener(...); 212 | } 213 | ``` 214 | ServerBootstrapAcceptor 中的 childGroup 是构造此对象是传入的 currentChildGroup, 即我们的 workerGroup, 而 Channel 是一个 NioSocketChannel 的实例, 因此这里的 childGroup.register 就是将 workerGroup 中的摸个 EventLoop 和 NioSocketChannel 关联了. 既然这样, 那么现在的问题是, ServerBootstrapAcceptor.channelRead 方法是怎么被调用的呢? 其实当一个 client 连接到 server 时, Java 底层的 NIO ServerSocketChannel 会有一个 **SelectionKey.OP_ACCEPT** 就绪事件, 接着就会调用到 NioServerSocketChannel.doReadMessages: 215 | ``` 216 | @Override 217 | protected int doReadMessages(List buf) throws Exception { 218 | SocketChannel ch = javaChannel().accept(); 219 | ... 省略异常处理 220 | buf.add(new NioSocketChannel(this, ch)); 221 | return 1; 222 | } 223 | ``` 224 | 在 doReadMessages 中, 通过 javaChannel().accept() 获取到客户端新连接的 SocketChannel, 接着就实例化一个 **NioSocketChannel**, 并且传入 NioServerSocketChannel 对象(即 this), 由此可知, 我们创建的这个 NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 实例 . 225 | 接下来就经由 Netty 的 ChannelPipeline 机制, 将读取事件逐级发送到各个 handler 中, 于是就会触发前面我们提到的 ServerBootstrapAcceptor.channelRead 方法啦. 226 | ### handler 的添加过程 227 | 服务器端的 handler 的添加过程和客户端的有点区别, 和 EventLoopGroup 一样, 服务器端的 handler 也有两个, 一个是通过 handler() 方法设置 handler 字段, 另一个是通过 childHandler() 设置 childHandler 字段. 通过前面的 bossGroup 和 workerGroup 的分析, 其实我们在这里可以大胆地猜测: handler 字段与 accept 过程有关, 即这个 handler 负责处理客户端的连接请求; 而 childHandler 就是负责和客户端的连接的 IO 交互. 228 | 那么实际上是不是这样的呢? 来, 我们继续通过代码证明. 229 | 230 | 在 **关于 bossGroup 与 workerGroup** 小节中, 我们提到, ServerBootstrap 重写了 init 方法, 在这个方法中添加了 handler: 231 | ``` 232 | @Override 233 | void init(Channel channel) throws Exception { 234 | ... 235 | ChannelPipeline p = channel.pipeline(); 236 | 237 | final EventLoopGroup currentChildGroup = childGroup; 238 | final ChannelHandler currentChildHandler = childHandler; 239 | final Entry, Object>[] currentChildOptions; 240 | final Entry, Object>[] currentChildAttrs; 241 | 242 | p.addLast(new ChannelInitializer() { 243 | @Override 244 | public void initChannel(Channel ch) throws Exception { 245 | ChannelPipeline pipeline = ch.pipeline(); 246 | ChannelHandler handler = handler(); 247 | if (handler != null) { 248 | pipeline.addLast(handler); 249 | } 250 | pipeline.addLast(new ServerBootstrapAcceptor( 251 | currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); 252 | } 253 | }); 254 | } 255 | ``` 256 | 上面代码的 **initChannel** 方法中, 首先通过 handler() 方法获取一个 handler, 如果获取的 handler 不为空,则添加到 pipeline 中. 然后接着, 添加了一个 ServerBootstrapAcceptor 实例. 那么这里 handler() 方法返回的是哪个对象呢? 其实它返回的是 handler 字段, 而这个字段就是我们在服务器端的启动代码中设置的: 257 | ``` 258 | b.group(bossGroup, workerGroup) 259 | ... 260 | .handler(new LoggingHandler(LogLevel.INFO)) 261 | ``` 262 | 那么这个时候, pipeline 中的 handler 情况如下: 263 | ![Alt text](./1477377831087.png) 264 | 265 | 根据我们原来分析客户端的经验, 我们指定, 当 channel 绑定到 eventLoop 后(在这里是 NioServerSocketChannel 绑定到 bossGroup)中时, 会在 pipeline 中发出 **fireChannelRegistered** 事件, 接着就会触发 ChannelInitializer.initChannel 方法的调用. 266 | 因此在绑定完成后, 此时的 pipeline 的内如如下: 267 | ![Alt text](./1477378182291.png) 268 | 269 | 前面我们在分析 bossGroup 和 workerGroup 时, 已经知道了在 ServerBootstrapAcceptor.channelRead 中会为新建的 Channel 设置 handler 并注册到一个 eventLoop 中, 即: 270 | ``` 271 | @Override 272 | @SuppressWarnings("unchecked") 273 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 274 | final Channel child = (Channel) msg; 275 | child.pipeline().addLast(childHandler); 276 | ... 277 | childGroup.register(child).addListener(...); 278 | } 279 | ``` 280 | 而这里的 childHandler 就是我们在服务器端启动代码中设置的 handler: 281 | ``` 282 | b.group(bossGroup, workerGroup) 283 | ... 284 | .childHandler(new ChannelInitializer() { 285 | @Override 286 | public void initChannel(SocketChannel ch) throws Exception { 287 | ChannelPipeline p = ch.pipeline(); 288 | if (sslCtx != null) { 289 | p.addLast(sslCtx.newHandler(ch.alloc())); 290 | } 291 | //p.addLast(new LoggingHandler(LogLevel.INFO)); 292 | p.addLast(new EchoServerHandler()); 293 | } 294 | }); 295 | ``` 296 | 后续的步骤就没有什么好说的了, 当这个客户端连接 Channel 注册后, 就会触发 ChannelInitializer.initChannel 方法的调用, 此后的客户端的 ChannelPipeline 状态如下: 297 | ![Alt text](./新客户端连接之后, NioSocketChannel 对应的 pipeline.png) 298 | 299 | 300 | 最后我们来总结一下服务器端的 handler 与 childHandler 的区别与联系: 301 | - 在服务器 NioServerSocketChannel 的 pipeline 中添加的是 handler 与 ServerBootstrapAcceptor. 302 | - 当有新的客户端连接请求时, ServerBootstrapAcceptor.channelRead 中负责新建此连接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 对应的 pipeline 中, 并将此 channel 绑定到 workerGroup 中的某个 eventLoop 中. 303 | - handler 是在 accept 阶段起作用, 它处理客户端的连接请求. 304 | - childHandler 是在客户端连接建立以后起作用, 它负责客户端连接的 IO 交互. 305 | 306 | 下面我们用一幅图来总结一下服务器端的 handler 添加流程: 307 | ![Alt text](./Server 端 handler 与 childHandler 图示.png) 308 | 309 | ## 后记 310 | 这是 **Netty 源码分析** 系列教程的第一篇, 按我的计划, 这一篇文章是一个简述性质的, 即这里会涉及到 Netty 各个功能模块, 但是我只是简单地提了一下, 而没有深入地探索它们内部的实现机理. 之所以这样做, 第一, 是因为如果一上来就从细节分析, 那么未免会陷入各种琐碎的细节中难以自拔; 第二, 我想给读者展示一个一个完整的 Netty 的运行流程, 让读者从一个整体上对 Netty 有一个感性的认识. 311 | 此篇文章涉及的模块比较多, 面比较广, 因此写起来难免有一点跳跃, 并且我感觉写着写着见见有点不知所云, 逻辑混乱了, 汗. 唉, 还是感觉自己功力不够, hold 不住. 312 | 接下来的几篇文章, 我会根据 Netty 的各个模块深入分析一下, 希望以后的文章能够组织的调理更加清晰一些. -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/NioServerSocketChannel 类层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/NioServerSocketChannel 类层次结构.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/Server 端 bossGroup 与 workerGroup 的图示.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/Server 端 bossGroup 与 workerGroup 的图示.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/Server 端 handler 与 childHandler 图示.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/Server 端 handler 与 childHandler 图示.png -------------------------------------------------------------------------------- /Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/新客户端连接之后, NioSocketChannel 对应的 pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (服务器端)/新客户端连接之后, NioSocketChannel 对应的 pipeline.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop.md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop 2 | @(Netty)[Netty, 源码分析] 3 | 4 | [TOC] 5 | 6 | 7 | ---------- 8 | 9 | ## 简述 10 | 这一章是 Netty 源码分析 的第三章, 我将在这一章中大家一起探究一下 Netty 的 EventLoop 的底层原理, 让大家对 Netty 的线程模型有更加深入的了解. 11 | 12 | ## NioEventLoopGroup 13 | 在 [Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)](https://segmentfault.com/a/1190000007282789) 章节中我们已经知道了, 一个 Netty 程序启动时, 至少要指定一个 EventLoopGroup(如果使用到的是 NIO, 那么通常是 NioEventLoopGroup), 那么这个 NioEventLoopGroup 在 Netty 中到底扮演着什么角色呢? 我们知道, Netty 是 Reactor 模型的一个实现, 那么首先从 Reactor 的线程模型开始吧. 14 | 15 | ### 关于 Reactor 的线程模型 16 | 首先我们来看一下 Reactor 的线程模型. 17 | Reactor 的线程模型有三种: 18 | - 单线程模型 19 | - 多线程模型 20 | - 主从多线程模型 21 | 22 | 首先来看一下 **单线程模型**: 23 | ![Alt text](./Reactor 单线程模型.png) 24 | 25 | 所谓单线程, 即 acceptor 处理和 handler 处理都在一个线程中处理. 这个模型的坏处显而易见: 当其中某个 handler 阻塞时, 会导致其他所有的 client 的 handler 都得不到执行, 并且更严重的是, handler 的阻塞也会导致整个服务不能接收新的 client 请求(因为 acceptor 也被阻塞了). 因为有这么多的缺陷, 因此单线程Reactor 模型用的比较少. 26 | 27 | 那么什么是 **多线程模型** 呢? Reactor 的多线程模型与单线程模型的区别就是 acceptor 是一个单独的线程处理, 并且有一组特定的 NIO 线程来负责各个客户端连接的 IO 操作. Reactor 多线程模型如下: 28 | ![Alt text](./Reactor 多线程模型.png) 29 | 30 | Reactor 多线程模型 有如下特点: 31 | - 有专门一个线程, 即 Acceptor 线程用于监听客户端的TCP连接请求. 32 | - 客户端连接的 IO 操作都是由一个特定的 NIO 线程池负责. 每个客户端连接都与一个特定的 NIO 线程绑定, 因此在这个客户端连接中的所有 IO 操作都是在同一个线程中完成的. 33 | - 客户端连接有很多, 但是 NIO 线程数是比较少的, 因此一个 NIO 线程可以同时绑定到多个客户端连接中. 34 | 35 | 接下来我们再来看一下 Reactor 的主从多线程模型. 36 | 一般情况下, Reactor 的多线程模式已经可以很好的工作了, 但是我们考虑一下如下情况: 如果我们的服务器需要同时处理大量的客户端连接请求或我们需要在客户端连接时, 进行一些权限的检查, 那么单线程的 Acceptor 很有可能就处理不过来, 造成了大量的客户端不能连接到服务器. 37 | Reactor 的主从多线程模型就是在这样的情况下提出来的, 它的特点是: 服务器端接收客户端的连接请求不再是一个线程, 而是由一个独立的线程池组成. 它的线程模型如下: 38 | ![Alt text](./Reactor 主从多线程模型.png) 39 | 40 | 可以看到, Reactor 的主从多线程模型和 Reactor 多线程模型很类似, 只不过 Reactor 的主从多线程模型的 acceptor 使用了线程池来处理大量的客户端请求. 41 | 42 | ### NioEventLoopGroup 与 Reactor 线程模型的对应 43 | 我们介绍了三种 Reactor 的线程模型, 那么它们和 NioEventLoopGroup 又有什么关系呢? 其实, 不同的设置 NioEventLoopGroup 的方式就对应了不同的 Reactor 的线程模型. 44 | 45 | #### 单线程模型 46 | 来看一下下面的例子: 47 | ``` 48 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 49 | ServerBootstrap b = new ServerBootstrap(); 50 | b.group(bossGroup) 51 | .channel(NioServerSocketChannel.class) 52 | ... 53 | ``` 54 | 注意, 我们实例化了一个 NioEventLoopGroup, 构造器参数是1, 表示 NioEventLoopGroup 的线程池大小是1. 然后接着我们调用 **b.group(bossGroup)** 设置了服务器端的 EventLoopGroup. 有些朋友可能会有疑惑: 我记得在启动服务器端的 Netty 程序时, 是需要设置 bossGroup 和 workerGroup 的, 为什么这里就只有一个 bossGroup? 55 | 其实很简单, ServerBootstrap 重写了 group 方法: 56 | ``` 57 | @Override 58 | public ServerBootstrap group(EventLoopGroup group) { 59 | return group(group, group); 60 | } 61 | ``` 62 | 因此当传入一个 group 时, 那么 bossGroup 和 workerGroup 就是同一个 NioEventLoopGroup 了. 63 | 这时候呢, 因为 bossGroup 和 workerGroup 就是同一个 NioEventLoopGroup, 并且这个 NioEventLoopGroup 只有一个线程, 这样就会导致 Netty 中的 acceptor 和后续的所有客户端连接的 IO 操作都是在一个线程中处理的. 那么对应到 Reactor 的线程模型中, 我们这样设置 NioEventLoopGroup 时, 就相当于 **Reactor 单线程模型**. 64 | #### 多线程模型 65 | 同理, 再来看一下下面的例子: 66 | ``` 67 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 68 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 69 | ServerBootstrap b = new ServerBootstrap(); 70 | b.group(bossGroup, workerGroup) 71 | .channel(NioServerSocketChannel.class) 72 | ... 73 | ``` 74 | bossGroup 中只有一个线程, 而 workerGroup 中的线程是 CPU 核心数乘以2, 因此对应的到 Reactor 线程模型中, 我们知道, 这样设置的 NioEventLoopGroup 其实就是 **Reactor 多线程模型**. 75 | #### 主从多线程模型 76 | 77 | ~~相信读者朋友都想到了, 实现主从线程模型的例子如下:~~ 78 | ``` 79 | EventLoopGroup bossGroup = new NioEventLoopGroup(4); 80 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 81 | ServerBootstrap b = new ServerBootstrap(); 82 | b.group(bossGroup, workerGroup) 83 | .channel(NioServerSocketChannel.class) 84 | ... 85 | ``` 86 | ~~bossGroup 线程池中的线程数我们设置为4, 而 workerGroup 中的线程是 CPU 核心数乘以2, 因此对应的到 Reactor 线程模型中, 我们知道, 这样设置的 NioEventLoopGroup 其实就是 **Reactor 主从多线程模型**.~~ 87 | 88 | ----- 89 | 根据 @labmem 的提示, Netty 的服务器端的 acceptor 阶段, 没有使用到多线程, 因此上面的 `主从多线程模型` 在 Netty 的服务器端是不存在的. 90 | 91 | 服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费. 92 | 93 | 经 Google, Netty 中的 bossGroup 为什么使用线程池的原因大家众所纷纭, 不过我在 [stackoverflow](http://stackoverflow.com/questions/34275138/why-do-we-really-need-multiple-netty-boss-threads) 上找到一个比较靠谱的答案: 94 | >the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps, but I don't see the reason for it. 95 | 因此上面的 `主从多线程模型` 分析是有问题, 抱歉. 96 | 97 | 98 | ### NioEventLoopGroup 类层次结构 99 | ![Alt text](./NioEventLoopGroup.png) 100 | 101 | ### NioEventLoopGroup 实例化过程 102 | 在前面 [Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)](https://segmentfault.com/a/1190000007282789#articleHeader7) 章节中, 我们已经简单地介绍了一下 NioEventLoopGroup 的初始化过程, 这里再回顾一下: 103 | ![Alt text](./NioEventLoopGroup 初始化顺序图.png) 104 | [点此下载原图](https://github.com/yongshun/learn_netty_source_code/blob/master/Netty%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%20%E4%B8%89%20%E6%88%91%E5%B0%B1%E6%98%AF%E5%A4%A7%E5%90%8D%E9%BC%8E%E9%BC%8E%E7%9A%84%20EventLoop/NioEventLoopGroup%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A1%BA%E5%BA%8F%E5%9B%BE.png) 105 | 106 | 即: 107 | - EventLoopGroup(其实是MultithreadEventExecutorGroup) 内部维护一个类型为 EventExecutor children 数组, 其大小是 nThreads, 这样就构成了一个线程池 108 | - 如果我们在实例化 NioEventLoopGroup 时, 如果指定线程池大小, 则 nThreads 就是指定的值, 反之是处理器核心数 * 2 109 | - MultithreadEventExecutorGroup 中会调用 newChild 抽象方法来初始化 children 数组 110 | - 抽象方法 newChild 是在 NioEventLoopGroup 中实现的, 它返回一个 NioEventLoop 实例. 111 | - NioEventLoop 属性: 112 | - SelectorProvider provider 属性: NioEventLoopGroup 构造器中通过 SelectorProvider.provider() 获取一个 SelectorProvider 113 | - Selector selector 属性: NioEventLoop 构造器中通过调用通过 selector = provider.openSelector() 获取一个 selector 对象. 114 | 115 | ## NioEventLoop 116 | NioEventLoop 继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor. SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程. 因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变. 117 | ### NioEventLoop 类层次结构 118 | ![Alt text](./NioEventLoop.png) 119 | NioEventLoop 的类层次结构图还是比较复杂的, 不过我们只需要关注几个重要的点即可. 首先 NioEventLoop 的继承链如下: 120 | ``` 121 | NioEventLoop -> SingleThreadEventLoop -> SingleThreadEventExecutor -> AbstractScheduledEventExecutor 122 | ``` 123 | 在 AbstractScheduledEventExecutor 中, Netty 实现了 NioEventLoop 的 schedule 功能, 即我们可以通过调用一个 NioEventLoop 实例的 schedule 方法来运行一些定时任务. 而在 SingleThreadEventLoop 中, 又实现了任务队列的功能, 通过它, 我们可以调用一个 NioEventLoop 实例的 execute 方法来向任务队列中添加一个 task, 并由 NioEventLoop 进行调度执行. 124 | 125 | 通常来说, NioEventLoop 肩负着两种任务, 第一个是作为 IO 线程, 执行与 Channel 相关的 IO 操作, 包括 调用 select 等待就绪的 IO 事件、读写数据与数据的处理等; 而第二个任务是作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提交的定时任务也是这个线程执行的. 126 | ### NioEventLoop 的实例化过程 127 | 128 | ![Alt text](./NioEventLoop 实例化顺序图.png) 129 | [点此下载原图](https://github.com/yongshun/learn_netty_source_code/blob/master/Netty%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%20%E4%B8%89%20%E6%88%91%E5%B0%B1%E6%98%AF%E5%A4%A7%E5%90%8D%E9%BC%8E%E9%BC%8E%E7%9A%84%20EventLoop/NioEventLoop%20%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%A1%BA%E5%BA%8F%E5%9B%BE.png) 130 | 131 | 132 | 从上图可以看到, SingleThreadEventExecutor 有一个名为 **thread** 的 Thread 类型字段, 这个字段就代表了与 SingleThreadEventExecutor 关联的本地线程. 133 | 下面是这个构造器的代码: 134 | ``` 135 | protected SingleThreadEventExecutor( 136 | EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) { 137 | this.parent = parent; 138 | this.addTaskWakesUp = addTaskWakesUp; 139 | 140 | thread = threadFactory.newThread(new Runnable() { 141 | @Override 142 | public void run() { 143 | boolean success = false; 144 | updateLastExecutionTime(); 145 | try { 146 | SingleThreadEventExecutor.this.run(); 147 | success = true; 148 | } catch (Throwable t) { 149 | logger.warn("Unexpected exception from an event executor: ", t); 150 | } finally { 151 | // 省略清理代码 152 | ... 153 | } 154 | } 155 | }); 156 | threadProperties = new DefaultThreadProperties(thread); 157 | taskQueue = newTaskQueue(); 158 | } 159 | ``` 160 | 在 SingleThreadEventExecutor 构造器中, 通过 **threadFactory.newThread** 创建了一个新的 Java 线程. 在这个线程中所做的事情主要就是调用 **SingleThreadEventExecutor.this.run()** 方法, 而因为 NioEventLoop 实现了这个方法, 因此根据多态性, 其实调用的是 **NioEventLoop.run()** 方法. 161 | 162 | ### EventLoop 与 Channel 的关联 163 | Netty 中, 每个 Channel 都有且仅有一个 EventLoop 与之关联, 它们的关联过程如下: 164 | ![Alt text](./NioEventLoop 与 Channel 的关联过程.png) 165 | [点此下载原图](https://github.com/yongshun/learn_netty_source_code/blob/master/Netty%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%20%E4%B8%89%20%E6%88%91%E5%B0%B1%E6%98%AF%E5%A4%A7%E5%90%8D%E9%BC%8E%E9%BC%8E%E7%9A%84%20EventLoop/NioEventLoop%20%E4%B8%8E%20Channel%20%E7%9A%84%E5%85%B3%E8%81%94%E8%BF%87%E7%A8%8B.png) 166 | 167 | 168 | 从上图中我们可以看到, 当调用了 **AbstractChannel#AbstractUnsafe.register** 后, 就完成了 Channel 和 EventLoop 的关联. register 实现如下: 169 | ``` 170 | @Override 171 | public final void register(EventLoop eventLoop, final ChannelPromise promise) { 172 | // 删除条件检查. 173 | ... 174 | AbstractChannel.this.eventLoop = eventLoop; 175 | 176 | if (eventLoop.inEventLoop()) { 177 | register0(promise); 178 | } else { 179 | try { 180 | eventLoop.execute(new OneTimeTask() { 181 | @Override 182 | public void run() { 183 | register0(promise); 184 | } 185 | }); 186 | } catch (Throwable t) { 187 | ... 188 | } 189 | } 190 | } 191 | ``` 192 | 在 **AbstractChannel#AbstractUnsafe.register** 中, 会将一个 EventLoop 赋值给 AbstractChannel 内部的 eventLoop 字段, 到这里就完成了 EventLoop 与 Channel 的关联过程. 193 | 194 | ### EventLoop 的启动 195 | 在前面我们已经知道了, NioEventLoop 本身就是一个 SingleThreadEventExecutor, 因此 NioEventLoop 的启动, 其实就是 NioEventLoop 所绑定的本地 Java 线程的启动. 196 | 依照这个思想, 我们只要找到在哪里调用了 SingleThreadEventExecutor 的 thread 字段的 **start()** 方法就可以知道是在哪里启动的这个线程了. 197 | 从代码中搜索, thread.start() 被封装到 **SingleThreadEventExecutor.startThread()** 方法中了: 198 | ``` 199 | private void startThread() { 200 | if (STATE_UPDATER.get(this) == ST_NOT_STARTED) { 201 | if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) { 202 | thread.start(); 203 | } 204 | } 205 | } 206 | ``` 207 | **STATE_UPDATER** 是 SingleThreadEventExecutor 内部维护的一个属性, 它的作用是标识当前的 thread 的状态. 在初始的时候, `STATE_UPDATER == ST_NOT_STARTED`, 因此第一次调用 startThread() 方法时, 就会进入到 if 语句内, 进而调用到 thread.start(). 208 | 而这个关键的 **startThread()** 方法又是在哪里调用的呢? 经过方法调用关系搜索, 我们发现, startThread 是在 SingleThreadEventExecutor.execute 方法中调用的: 209 | ``` 210 | @Override 211 | public void execute(Runnable task) { 212 | if (task == null) { 213 | throw new NullPointerException("task"); 214 | } 215 | 216 | boolean inEventLoop = inEventLoop(); 217 | if (inEventLoop) { 218 | addTask(task); 219 | } else { 220 | startThread(); // 调用 startThread 方法, 启动EventLoop 线程. 221 | addTask(task); 222 | if (isShutdown() && removeTask(task)) { 223 | reject(); 224 | } 225 | } 226 | 227 | if (!addTaskWakesUp && wakesUpForTask(task)) { 228 | wakeup(inEventLoop); 229 | } 230 | } 231 | ``` 232 | 既然如此, 那现在我们的工作就变为了寻找 `在哪里第一次调用了 SingleThreadEventExecutor.execute() 方法.` 233 | 如果留心的读者可能已经注意到了, 我们在 **EventLoop 与 Channel 的关联** 这一小节时, 有提到到在注册 channel 的过程中, 会在 **AbstractChannel#AbstractUnsafe.register** 中调用 eventLoop.execute 方法, 在 EventLoop 中进行 Channel 注册代码的执行, AbstractChannel#AbstractUnsafe.register 部分代码如下: 234 | ``` 235 | if (eventLoop.inEventLoop()) { 236 | register0(promise); 237 | } else { 238 | try { 239 | eventLoop.execute(new OneTimeTask() { 240 | @Override 241 | public void run() { 242 | register0(promise); 243 | } 244 | }); 245 | } catch (Throwable t) { 246 | ... 247 | } 248 | } 249 | ``` 250 | 很显然, 一路从 Bootstrap.bind 方法跟踪到 AbstractChannel#AbstractUnsafe.register 方法, 整个代码都是在主线程中运行的, 因此上面的 **eventLoop.inEventLoop()** 就为 false, 于是进入到 else 分支, 在这个分支中调用了 **eventLoop.execute**. eventLoop 是一个 NioEventLoop 的实例, 而 NioEventLoop 没有实现 execute 方法, 因此调用的是 **SingleThreadEventExecutor.execute:** 251 | ``` 252 | @Override 253 | public void execute(Runnable task) { 254 | ... 255 | boolean inEventLoop = inEventLoop(); 256 | if (inEventLoop) { 257 | addTask(task); 258 | } else { 259 | startThread(); 260 | addTask(task); 261 | if (isShutdown() && removeTask(task)) { 262 | reject(); 263 | } 264 | } 265 | 266 | if (!addTaskWakesUp && wakesUpForTask(task)) { 267 | wakeup(inEventLoop); 268 | } 269 | } 270 | ``` 271 | 我们已经分析过了, **inEventLoop == false**, 因此执行到 else 分支, 在这里就调用了 **startThread()** 方法来启动 SingleThreadEventExecutor 内部关联的 Java 本地线程了. 272 | 总结一句话, 当 EventLoop.execute **第一次被调用**时, 就会触发 **startThread()** 的调用, 进而导致了 EventLoop 所对应的 Java 线程的启动. 273 | 我们将 **EventLoop 与 Channel 的关联** 小节中的时序图补全后, 就得到了 EventLoop 启动过程的时序图: 274 | ![Alt text](./NioEventLoop 的启动过程.png) 275 | 276 | [点此下载原图](https://github.com/yongshun/learn_netty_source_code/blob/master/Netty%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B%20%E4%B8%89%20%E6%88%91%E5%B0%B1%E6%98%AF%E5%A4%A7%E5%90%8D%E9%BC%8E%E9%BC%8E%E7%9A%84%20EventLoop/NioEventLoop%20%E7%9A%84%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.png) 277 | 278 | 279 | 280 | ## Netty 的 IO 处理循环 281 | `在 Netty 中, 一个 EventLoop 需要负责两个工作, 第一个是作为 IO 线程, 负责相应的 IO 操作; 第二个是作为任务线程, 执行 taskQueue 中的任务.` 接下来我们先从 IO 操纵方面入手, 看一下 TCP 数据是如何从 Java NIO Socket 传递到我们的 handler 中的. 282 | 283 | Netty 是 Reactor 模型的一个实现, 并且是基于 Java NIO 的, 那么从 **Java NIO 的前生今世 之四 NIO Selector 详解** 中我们知道, Netty 中必然有一个 Selector 线程, 用于不断调用 Java NIO 的 Selector.select 方法, 查询当前是否有就绪的 IO 事件. 回顾一下在 Java NIO 中所讲述的 Selector 的使用流程: 284 | 1. 通过 Selector.open() 打开一个 Selector. 285 | 2. 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set) 286 | 3. 不断重复: 287 | - 调用 select() 方法 288 | - 调用 selector.selectedKeys() 获取 selected keys 289 | - 迭代每个 selected key: 290 | - 1) 从 selected key 中获取 对应的 Channel 和附加信息(如果有的话) 291 | - 2) 判断是哪些 IO 事件已经就绪了, 然后处理它们. **如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.** 292 | - 3) 根据需要更改 selected key 的监听事件. 293 | - 4) 将已经处理过的 key 从 selected keys 集合中删除. 294 | 295 | 296 | 上面的使用流程用代码来体现就是: 297 | ``` 298 | /** 299 | * @author xiongyongshun 300 | * @Email yongshun1228@gmail.com 301 | * @version 1.0 302 | * @created 16/8/1 13:13 303 | */ 304 | public class NioEchoServer { 305 | private static final int BUF_SIZE = 256; 306 | private static final int TIMEOUT = 3000; 307 | 308 | public static void main(String args[]) throws Exception { 309 | // 打开服务端 Socket 310 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 311 | 312 | // 打开 Selector 313 | Selector selector = Selector.open(); 314 | 315 | // 服务端 Socket 监听8080端口, 并配置为非阻塞模式 316 | serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 317 | serverSocketChannel.configureBlocking(false); 318 | 319 | // 将 channel 注册到 selector 中. 320 | // 通常我们都是先注册一个 OP_ACCEPT 事件, 然后在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 321 | // 注册到 Selector 中. 322 | serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 323 | 324 | while (true) { 325 | // 通过调用 select 方法, 阻塞地等待 channel I/O 可操作 326 | if (selector.select(TIMEOUT) == 0) { 327 | System.out.print("."); 328 | continue; 329 | } 330 | 331 | // 获取 I/O 操作就绪的 SelectionKey, 通过 SelectionKey 可以知道哪些 Channel 的哪类 I/O 操作已经就绪. 332 | Iterator keyIterator = selector.selectedKeys().iterator(); 333 | 334 | while (keyIterator.hasNext()) { 335 | 336 | // 当获取一个 SelectionKey 后, 就要将它删除, 表示我们已经对这个 IO 事件进行了处理. 337 | keyIterator.remove(); 338 | 339 | SelectionKey key = keyIterator.next(); 340 | 341 | if (key.isAcceptable()) { 342 | // 当 OP_ACCEPT 事件到来时, 我们就有从 ServerSocketChannel 中获取一个 SocketChannel, 343 | // 代表客户端的连接 344 | // 注意, 在 OP_ACCEPT 事件中, 从 key.channel() 返回的 Channel 是 ServerSocketChannel. 345 | // 而在 OP_WRITE 和 OP_READ 中, 从 key.channel() 返回的是 SocketChannel. 346 | SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); 347 | clientChannel.configureBlocking(false); 348 | //在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 注册到 Selector 中. 349 | // 注意, 这里我们如果没有设置 OP_READ 的话, 即 interest set 仍然是 OP_CONNECT 的话, 那么 select 方法会一直直接返回. 350 | clientChannel.register(key.selector(), OP_READ, ByteBuffer.allocate(BUF_SIZE)); 351 | } 352 | 353 | if (key.isReadable()) { 354 | SocketChannel clientChannel = (SocketChannel) key.channel(); 355 | ByteBuffer buf = (ByteBuffer) key.attachment(); 356 | long bytesRead = clientChannel.read(buf); 357 | if (bytesRead == -1) { 358 | clientChannel.close(); 359 | } else if (bytesRead > 0) { 360 | key.interestOps(OP_READ | SelectionKey.OP_WRITE); 361 | System.out.println("Get data length: " + bytesRead); 362 | } 363 | } 364 | 365 | if (key.isValid() && key.isWritable()) { 366 | ByteBuffer buf = (ByteBuffer) key.attachment(); 367 | buf.flip(); 368 | SocketChannel clientChannel = (SocketChannel) key.channel(); 369 | 370 | clientChannel.write(buf); 371 | 372 | if (!buf.hasRemaining()) { 373 | key.interestOps(OP_READ); 374 | } 375 | buf.compact(); 376 | } 377 | } 378 | } 379 | } 380 | } 381 | ``` 382 | 383 | 还记得不, 上面操作的第一步 **通过 Selector.open() 打开一个 Selector** 我们已经在第一章的 **Channel 实例化** 这一小节中已经提到了, Netty 中是通过调用 SelectorProvider.openSocketChannel() 来打开一个新的 Java NIO SocketChannel: 384 | ``` 385 | private static SocketChannel newSocket(SelectorProvider provider) { 386 | ... 387 | return provider.openSocketChannel(); 388 | } 389 | ``` 390 | 第二步 **将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)** 的操作我们在第一章 **channel 的注册过程** 中也分析过了, 我们在来回顾一下, 在客户端的 Channel 注册过程中, 会有如下调用链: 391 | ``` 392 | Bootstrap.initAndRegister -> 393 | AbstractBootstrap.initAndRegister -> 394 | MultithreadEventLoopGroup.register -> 395 | SingleThreadEventLoop.register -> 396 | AbstractUnsafe.register -> 397 | AbstractUnsafe.register0 -> 398 | AbstractNioChannel.doRegister 399 | ``` 400 | 在 AbstractUnsafe.register 方法中调用了 register0 方法: 401 | ``` 402 | @Override 403 | public final void register(EventLoop eventLoop, final ChannelPromise promise) { 404 | // 省略条件判断和错误处理 405 | AbstractChannel.this.eventLoop = eventLoop; 406 | register0(promise); 407 | } 408 | ``` 409 | register0 方法代码如下: 410 | ``` 411 | private void register0(ChannelPromise promise) { 412 | boolean firstRegistration = neverRegistered; 413 | doRegister(); 414 | neverRegistered = false; 415 | registered = true; 416 | safeSetSuccess(promise); 417 | pipeline.fireChannelRegistered(); 418 | // Only fire a channelActive if the channel has never been registered. This prevents firing 419 | // multiple channel actives if the channel is deregistered and re-registered. 420 | if (firstRegistration && isActive()) { 421 | pipeline.fireChannelActive(); 422 | } 423 | } 424 | ``` 425 | register0 又调用了 AbstractNioChannel.doRegister: 426 | ``` 427 | @Override 428 | protected void doRegister() throws Exception { 429 | // 省略错误处理 430 | selectionKey = javaChannel().register(eventLoop().selector, 0, this); 431 | } 432 | ``` 433 | 在这里 javaChannel() 返回的是一个 Java NIO SocketChannel 对象, 我们将此 SocketChannel 注册到前面第一步获取的 Selector 中. 434 | 435 | 那么接下来的第三步的循环是在哪里实现的呢? 第三步的操作就是我们今天分析的关键, 下面我会一步一步向读者展示出来. 436 | 437 | ### thread 的 run 循环 438 | 在 **EventLoop 的启动** 一小节中, 我们已经了解到了, 当 EventLoop.execute **第一次被调用**时, 就会触发 **startThread()** 的调用, 进而导致了 EventLoop 所对应的 Java 线程的启动. 接着我们来更深入一些, 来看一下此线程启动后都会做什么东东吧. 439 | 下面是此线程的 run() 方法, 我已经把一些异常处理和收尾工作的代码都去掉了. 这个 run 方法可以说是十分简单, 主要就是调用了 **SingleThreadEventExecutor.this.run()** 方法. 而 SingleThreadEventExecutor.run() 是一个抽象方法, 它的实现在 NioEventLoop 中. 440 | ``` 441 | thread = threadFactory.newThread(new Runnable() { 442 | @Override 443 | public void run() { 444 | boolean success = false; 445 | updateLastExecutionTime(); 446 | try { 447 | SingleThreadEventExecutor.this.run(); 448 | success = true; 449 | } catch (Throwable t) { 450 | logger.warn("Unexpected exception from an event executor: ", t); 451 | } finally { 452 | ... 453 | } 454 | } 455 | }); 456 | ``` 457 | 继续跟踪到 NioEventLoop.run() 方法, 其源码如下: 458 | ``` 459 | @Override 460 | protected void run() { 461 | for (;;) { 462 | boolean oldWakenUp = wakenUp.getAndSet(false); 463 | try { 464 | if (hasTasks()) { 465 | selectNow(); 466 | } else { 467 | select(oldWakenUp); 468 | if (wakenUp.get()) { 469 | selector.wakeup(); 470 | } 471 | } 472 | 473 | cancelledKeys = 0; 474 | needsToSelectAgain = false; 475 | final int ioRatio = this.ioRatio; 476 | if (ioRatio == 100) { 477 | processSelectedKeys(); 478 | runAllTasks(); 479 | } else { 480 | final long ioStartTime = System.nanoTime(); 481 | 482 | processSelectedKeys(); 483 | 484 | final long ioTime = System.nanoTime() - ioStartTime; 485 | runAllTasks(ioTime * (100 - ioRatio) / ioRatio); 486 | } 487 | 488 | if (isShuttingDown()) { 489 | closeAll(); 490 | if (confirmShutdown()) { 491 | break; 492 | } 493 | } 494 | } catch (Throwable t) { 495 | ... 496 | } 497 | } 498 | } 499 | ``` 500 | 啊哈, 看到了上面代码的 **for(;;)** 所构成的死循环了没? 原来 NioEventLoop 事件循环的核心就是这里! 501 | 现在我们把上面所提到的 Selector 使用步骤的第三步的部分也找到了. 502 | 这个 run 方法可以说是 Netty NIO 的核心, 属于重中之重, 把它分析明白了, 那么对 Netty 的事件循环机制也就了解了大部分了. 让我们一鼓作气, 继续分析下去吧! 503 | 504 | ### IO 事件的轮询 505 | 首先, 在 run 方法中, 第一步是调用 **hasTasks()** 方法来判断当前任务队列中是否有任务: 506 | ``` 507 | protected boolean hasTasks() { 508 | assert inEventLoop(); 509 | return !taskQueue.isEmpty(); 510 | } 511 | ``` 512 | 这个方法很简单, 仅仅是检查了一下 **taskQueue** 是否为空. 至于 taskQueue 是什么呢, 其实它就是存放一系列的需要由此 EventLoop 所执行的任务列表. 关于 taskQueue, 我们这里暂时不表, 等到后面再来详细分析它. 513 | 当 taskQueue 不为空时, 就执行到了 if 分支中的 selectNow() 方法. 然而当 taskQueue 为空时, 执行的是 select(oldWakenUp) 方法. 那么 **selectNow()** 和 **select(oldWakenUp)** 之间有什么区别呢? 来看一下, selectNow() 的源码如下: 514 | ``` 515 | void selectNow() throws IOException { 516 | try { 517 | selector.selectNow(); 518 | } finally { 519 | // restore wakup state if needed 520 | if (wakenUp.get()) { 521 | selector.wakeup(); 522 | } 523 | } 524 | } 525 | ``` 526 | 首先调用了 **selector.selectNow()** 方法, 这里 selector 是什么大家还有印象不? 我们在第一章 **Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)** 时对它有过介绍, 这个 **selector** 字段正是 Java NIO 中的多路复用器 **Selector**. 那么这里 **selector.selectNow()** 就很好理解了, selectNow() 方法会检查当前是否有就绪的 IO 事件, 如果有, 则返回就绪 IO 事件的个数; 如果没有, 则返回0. `注意, selectNow() 是立即返回的, 不会阻塞当前线程.` 当 selectNow() 调用后, finally 语句块中会检查 wakenUp 变量是否为 true, 当为 true 时, 调用 selector.wakeup() 唤醒 select() 的阻塞调用. 527 | 528 | 529 | 看了 if 分支的 selectNow 方法后, 我们再来看一下 else 分支的 **select(oldWakenUp)** 方法. 530 | 其实 else 分支的 **select(oldWakenUp)** 方法的处理逻辑比较复杂, 而我们这里的目的暂时不是分析这个方法调用的具体工作, 因此我这里长话短说, 只列出我们我们关注的内如: 531 | ``` 532 | private void select(boolean oldWakenUp) throws IOException { 533 | Selector selector = this.selector; 534 | try { 535 | ... 536 | int selectedKeys = selector.select(timeoutMillis); 537 | ... 538 | } catch (CancelledKeyException e) { 539 | ... 540 | } 541 | } 542 | ``` 543 | 在这个 select 方法中, 调用了 **selector.select(timeoutMillis)**, 而这个调用是会阻塞住当前线程的, timeoutMillis 是阻塞的超时时间. 544 | 到来这里, 我们可以看到, 当 **hasTasks()** 为真时, 调用的的 **selectNow()** 方法是不会阻塞当前线程的, 而当 **hasTasks()** 为假时, 调用的 **select(oldWakenUp)** 是会阻塞当前线程的. 545 | 这其实也很好理解: 当 taskQueue 中没有任务时, 那么 Netty 可以阻塞地等待 IO 就绪事件; 而当 taskQueue 中有任务时, 我们自然地希望所提交的任务可以尽快地执行, 因此 Netty 会调用非阻塞的 selectNow() 方法, 以保证 taskQueue 中的任务尽快可以执行. 546 | 547 | ### IO 事件的处理 548 | 在 NioEventLoop.run() 方法中, 第一步是通过 select/selectNow 调用查询当前是否有就绪的 IO 事件. 那么当有 IO 事件就绪时, 第二步自然就是处理这些 IO 事件啦. 549 | 首先让我们来看一下 NioEventLoop.run 中循环的剩余部分: 550 | ``` 551 | final int ioRatio = this.ioRatio; 552 | if (ioRatio == 100) { 553 | processSelectedKeys(); 554 | runAllTasks(); 555 | } else { 556 | final long ioStartTime = System.nanoTime(); 557 | 558 | processSelectedKeys(); 559 | 560 | final long ioTime = System.nanoTime() - ioStartTime; 561 | runAllTasks(ioTime * (100 - ioRatio) / ioRatio); 562 | } 563 | ``` 564 | 上面列出的代码中, 有两个关键的调用, 第一个是 **processSelectedKeys()** 调用, 根据字面意思, 我们可以猜出这个方法肯定是查询就绪的 IO 事件, 然后处理它; 第二个调用是 **runAllTasks()**, 这个方法我们也可以一眼就看出来它的功能就是运行 taskQueue 中的任务. 565 | 这里的代码还有一个十分有意思的地方, 即 **ioRatio**. 那什么是 **ioRatio**呢? 它表示的是此线程分配给 IO 操作所占的时间比(即运行 processSelectedKeys 耗时在整个循环中所占用的时间). 例如 ioRatio 默认是 50, 则表示 IO 操作和执行 task 的所占用的线程执行时间比是 1 : 1. 当知道了 IO 操作耗时和它所占用的时间比, 那么执行 task 的时间就可以很方便的计算出来了: 566 | ``` 567 | 设 IO 操作耗时为 ioTime, ioTime 占的时间比例为 ioRatio, 则: 568 | ioTime / ioRatio = taskTime / taskRatio 569 | taskRatio = 100 - ioRatio 570 | => taskTime = ioTime * (100 - ioRatio) / ioRatio 571 | ``` 572 | 根据上面的公式, 当我们设置 ioRate = 70 时, 则表示 IO 运行耗时占比为70%, 即假设某次循环一共耗时为 100ms, 那么根据公式, 我们知道 **processSelectedKeys()** 方法调用所耗时大概为70ms(即 IO 耗时), 而 **runAllTasks()** 耗时大概为 30ms(即执行 task 耗时). 573 | 当 ioRatio 为 100 时, Netty 就不考虑 IO 耗时的占比, 而是分别调用 **processSelectedKeys()**、**runAllTasks()**; 而当 ioRatio 不为 100时, 则执行到 else 分支, 在这个分支中, 首先记录下 **processSelectedKeys()** 所执行的时间(即 IO 操作的耗时), 然后根据公式, 计算出执行 task 所占用的时间, 然后以此为参数, 调用 **runAllTasks()**. 574 | 575 | 我们这里先分析一下 **processSelectedKeys()** 方法调用, **runAllTasks()** 我们留到下一节再分析. 576 | **processSelectedKeys()** 方法的源码如下: 577 | ``` 578 | private void processSelectedKeys() { 579 | if (selectedKeys != null) { 580 | processSelectedKeysOptimized(selectedKeys.flip()); 581 | } else { 582 | processSelectedKeysPlain(selector.selectedKeys()); 583 | } 584 | } 585 | ``` 586 | 这个方法中, 会根据 **selectedKeys** 字段是否为空, 而分别调用 **processSelectedKeysOptimized** 或 **processSelectedKeysPlain**. **selectedKeys** 字段是在调用 openSelector() 方法时, 根据 JVM 平台的不同, 而有设置不同的值, 在我所调试这个值是不为 null 的. 其实 **processSelectedKeysOptimized** 方法 **processSelectedKeysPlain** 没有太大的区别, 为了简单起见, 我们以 **processSelectedKeysOptimized** 为例分析一下源码的工作流程吧. 587 | ``` 588 | private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) { 589 | for (int i = 0;; i ++) { 590 | final SelectionKey k = selectedKeys[i]; 591 | if (k == null) { 592 | break; 593 | } 594 | selectedKeys[i] = null; 595 | 596 | final Object a = k.attachment(); 597 | 598 | if (a instanceof AbstractNioChannel) { 599 | processSelectedKey(k, (AbstractNioChannel) a); 600 | } else { 601 | @SuppressWarnings("unchecked") 602 | NioTask task = (NioTask) a; 603 | processSelectedKey(k, task); 604 | } 605 | ... 606 | } 607 | } 608 | ``` 609 | 其实你别看它代码挺多的, 但是关键的点就两个: 迭代 **selectedKeys** 获取就绪的 IO 事件, 然后为每个事件都调用 **processSelectedKey** 来处理它. 610 | 这里正好完美对应上了我们提到的 Selector 的使用流程中的第三步里操作. 611 | 还有一点需要注意的是, 我们可以调用 **selectionKey.attach(object)** 给一个 selectionKey 设置一个附加的字段, 然后可以通过 **Object attachedObj = selectionKey.attachment()** 获取它. 上面代代码正是通过了 **k.attachment()** 来获取一个附加在 selectionKey 中的对象, 那么这个对象是什么呢? 它又是在哪里设置的呢? 我们再来回忆一下 SocketChannel 是如何注册到 Selector 中的: 612 | 在客户端的 Channel 注册过程中, 会有如下调用链: 613 | ``` 614 | Bootstrap.initAndRegister -> 615 | AbstractBootstrap.initAndRegister -> 616 | MultithreadEventLoopGroup.register -> 617 | SingleThreadEventLoop.register -> 618 | AbstractUnsafe.register -> 619 | AbstractUnsafe.register0 -> 620 | AbstractNioChannel.doRegister 621 | ``` 622 | 最后的 AbstractNioChannel.doRegister 方法会调用 **SocketChannel.register** 方法注册一个 SocketChannel 到指定的 Selector: 623 | ``` 624 | @Override 625 | protected void doRegister() throws Exception { 626 | // 省略错误处理 627 | selectionKey = javaChannel().register(eventLoop().selector, 0, this); 628 | } 629 | ``` 630 | 特别注意一下 **register** 的第三个参数, 这个参数是设置 selectionKey 的附加对象的, 和调用 **selectionKey.attach(object)** 的效果一样. 而调用 **register** 所传递的第三个参数是 **this**, 它其实就是一个 `NioSocketChannel` 的实例. 那么这里就很清楚了, 我们在将 SocketChannel 注册到 Selector 中时, 将 SocketChannel 所对应的 NioSocketChannel 以附加字段的方式添加到了selectionKey 中. 631 | 再回到 **processSelectedKeysOptimized** 方法中, 当我们获取到附加的对象后, 我们就调用 **processSelectedKey** 来处理这个 IO 事件: 632 | ``` 633 | final Object a = k.attachment(); 634 | 635 | if (a instanceof AbstractNioChannel) { 636 | processSelectedKey(k, (AbstractNioChannel) a); 637 | } else { 638 | @SuppressWarnings("unchecked") 639 | NioTask task = (NioTask) a; 640 | processSelectedKey(k, task); 641 | } 642 | ``` 643 | **processSelectedKey** 方法源码如下: 644 | ``` 645 | private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { 646 | final NioUnsafe unsafe = ch.unsafe(); 647 | ... 648 | try { 649 | int readyOps = k.readyOps(); 650 | 651 | // 可读事件 652 | if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { 653 | unsafe.read(); 654 | if (!ch.isOpen()) { 655 | // Connection already closed - no need to handle write. 656 | return; 657 | } 658 | } 659 | 660 | // 可写事件 661 | if ((readyOps & SelectionKey.OP_WRITE) != 0) { 662 | // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write 663 | ch.unsafe().forceFlush(); 664 | } 665 | 666 | // 连接建立事件 667 | if ((readyOps & SelectionKey.OP_CONNECT) != 0) { 668 | // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking 669 | // See https://github.com/netty/netty/issues/924 670 | int ops = k.interestOps(); 671 | ops &= ~SelectionKey.OP_CONNECT; 672 | k.interestOps(ops); 673 | 674 | unsafe.finishConnect(); 675 | } 676 | } catch (CancelledKeyException ignored) { 677 | unsafe.close(unsafe.voidPromise()); 678 | } 679 | } 680 | ``` 681 | 这个代码是不是很熟悉啊? 完全是 Java NIO 的 Selector 的那一套处理流程嘛! 682 | **processSelectedKey** 中处理了三个事件, 分别是: 683 | - OP_READ, 可读事件, 即 Channel 中收到了新数据可供上层读取. 684 | - OP_WRITE, 可写事件, 即上层可以向 Channel 写入数据. 685 | - OP_CONNECT, 连接建立事件, 即 TCP 连接已经建立, Channel 处于 active 状态. 686 | 687 | 下面我们分别根据这三个事件来看一下 Netty 是怎么处理的吧. 688 | #### OP_READ 处理 689 | 当就绪的 IO 事件是 **OP_READ**, 代码会调用 **unsafe.read()** 方法, 即: 690 | ``` 691 | // 可读事件 692 | if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { 693 | unsafe.read(); 694 | if (!ch.isOpen()) { 695 | // Connection already closed - no need to handle write. 696 | return; 697 | } 698 | } 699 | ``` 700 | unsafe 这个字段, 我们已经和它打了太多的交道了, 在第一章 **Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)** 中我们已经对它进行过浓墨重彩地分析了, 最后我们确定了它是一个 **NioSocketChannelUnsafe** 实例, 负责的是 Channel 的底层 IO 操作. 701 | 我们可以利用 Intellij IDEA 提供的 **Go To Implementations** 功能, 寻找到这个方法的实现. 最后我们发现这个方法没有在 **NioSocketChannelUnsafe** 中实现, 而是在它的父类 **AbstractNioByteChannel** 实现的, 它的实现源码如下: 702 | ``` 703 | @Override 704 | public final void read() { 705 | ... 706 | ByteBuf byteBuf = null; 707 | int messages = 0; 708 | boolean close = false; 709 | try { 710 | int totalReadAmount = 0; 711 | boolean readPendingReset = false; 712 | do { 713 | byteBuf = allocHandle.allocate(allocator); 714 | int writable = byteBuf.writableBytes(); 715 | int localReadAmount = doReadBytes(byteBuf); 716 | 717 | // 检查读取结果. 718 | ... 719 | 720 | pipeline.fireChannelRead(byteBuf); 721 | byteBuf = null; 722 | 723 | ... 724 | 725 | totalReadAmount += localReadAmount; 726 | 727 | // 检查是否是配置了自动读取, 如果不是, 则立即退出循环. 728 | ... 729 | } while (++ messages < maxMessagesPerRead); 730 | 731 | pipeline.fireChannelReadComplete(); 732 | allocHandle.record(totalReadAmount); 733 | 734 | if (close) { 735 | closeOnRead(pipeline); 736 | close = false; 737 | } 738 | } catch (Throwable t) { 739 | handleReadException(pipeline, byteBuf, t, close); 740 | } finally { 741 | } 742 | } 743 | ``` 744 | **read()** 源码比较长, 我为了篇幅起见, 删除了部分代码, 只留下了主干. 不过我建议读者朋友们自己一定要看一下 **read()** 源码, 这对理解 Netty 的 EventLoop 十分有帮助. 745 | 上面 **read** 方法其实归纳起来, 可以认为做了如下工作: 746 | 1. 分配 ByteBuf 747 | 2. 从 SocketChannel 中读取数据 748 | 3. 调用 **pipeline.fireChannelRead** 发送一个 inbound 事件. 749 | 750 | 前面两点没什么好说的, 第三点 **pipeline.fireChannelRead** 读者朋友们看到了有没有会心一笑地感觉呢? 反正我看到这里时是有的. **pipeline.fireChannelRead** 正好就是我们在第二章 **Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (二)** 中分析的 **inbound** 事件起点. 当调用了 **pipeline.fireIN_EVT()** 后, 那么就产生了一个 **inbound** 事件, 此事件会以 **head -> customContext -> tail** 的方向依次流经 ChannelPipeline 中的各个 handler. 751 | 调用了 **pipeline.fireChannelRead** 后, 就是 ChannelPipeline 中所需要做的工作了, 这些我们已经在第二章中有过详细讨论, 这里就展开了. 752 | 753 | #### OP_WRITE 处理 754 | **OP_WRITE** 可写事件代码如下. 这里代码比较简单, 没有详细分析的必要了. 755 | ``` 756 | if ((readyOps & SelectionKey.OP_WRITE) != 0) { 757 | // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write 758 | ch.unsafe().forceFlush(); 759 | } 760 | ``` 761 | #### OP_CONNECT 处理 762 | 最后一个事件是 **OP_CONNECT**, 即 TCP 连接已建立事件. 763 | ``` 764 | if ((readyOps & SelectionKey.OP_CONNECT) != 0) { 765 | // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking 766 | // See https://github.com/netty/netty/issues/924 767 | int ops = k.interestOps(); 768 | ops &= ~SelectionKey.OP_CONNECT; 769 | k.interestOps(ops); 770 | 771 | unsafe.finishConnect(); 772 | } 773 | ``` 774 | **OP_CONNECT** 事件的处理中, 只做了两件事情: 775 | 1. 正如代码中的注释所言, 我们需要将 **OP_CONNECT** 从就绪事件集中清除, 不然会一直有 **OP_CONNECT** 事件. 776 | 2. 调用 unsafe.finishConnect() 通知上层连接已建立 777 | 778 | 779 | unsafe.finishConnect() 调用最后会调用到 **pipeline().fireChannelActive()**, 产生一个 **inbound** 事件, 通知 pipeline 中的各个 handler TCP 通道已建立(即 **ChannelInboundHandler.channelActive** 方法会被调用) 780 | 781 | 到了这里, 我们整个 NioEventLoop 的 IO 操作部分已经了解完了, 接下来的一节我们要重点分析一下 **Netty 的任务队列机制**. 782 | ## Netty 的任务队列机制 783 | 我们已经提到过, 在Netty 中, 一个 NioEventLoop 通常需要肩负起两种任务, 第一个是作为 IO 线程, 处理 IO 操作; 第二个就是作为任务线程, 处理 taskQueue 中的任务. 这一节的重点就是分析一下 NioEventLoop 的任务队列机制的. 784 | ### Task 的添加 785 | #### 普通 Runnable 任务 786 | NioEventLoop 继承于 SingleThreadEventExecutor, 而 `SingleThreadEventExecutor` 中有一个 **Queue taskQueue** 字段, 用于存放添加的 Task. 在 Netty 中, 每个 Task 都使用一个实现了 Runnable 接口的实例来表示. 787 | 例如当我们需要将一个 Runnable 添加到 taskQueue 中时, 我们可以进行如下操作: 788 | ``` 789 | EventLoop eventLoop = channel.eventLoop(); 790 | eventLoop.execute(new Runnable() { 791 | @Override 792 | public void run() { 793 | System.out.println("Hello, Netty!"); 794 | } 795 | }); 796 | ``` 797 | 当调用 execute 后, 实际上是调用到了 SingleThreadEventExecutor.execute() 方法, 它的实现如下: 798 | ``` 799 | @Override 800 | public void execute(Runnable task) { 801 | if (task == null) { 802 | throw new NullPointerException("task"); 803 | } 804 | 805 | boolean inEventLoop = inEventLoop(); 806 | if (inEventLoop) { 807 | addTask(task); 808 | } else { 809 | startThread(); 810 | addTask(task); 811 | if (isShutdown() && removeTask(task)) { 812 | reject(); 813 | } 814 | } 815 | 816 | if (!addTaskWakesUp && wakesUpForTask(task)) { 817 | wakeup(inEventLoop); 818 | } 819 | } 820 | ``` 821 | 而添加任务的 **addTask** 方法的源码如下: 822 | ``` 823 | protected void addTask(Runnable task) { 824 | if (task == null) { 825 | throw new NullPointerException("task"); 826 | } 827 | if (isShutdown()) { 828 | reject(); 829 | } 830 | taskQueue.add(task); 831 | } 832 | ``` 833 | 因此实际上, `taskQueue` 是存放着待执行的任务的队列. 834 | 835 | #### schedule 任务 836 | 除了通过 execute 添加普通的 Runnable 任务外, 我们还可以通过调用 eventLoop.scheduleXXX 之类的方法来添加一个定时任务. 837 | EventLoop 中实现任务队列的功能在超类 `SingleThreadEventExecutor` 实现的, 而 schedule 功能的实现是在 `SingleThreadEventExecutor` 的父类, 即 `AbstractScheduledEventExecutor` 中实现的. 838 | 在 `AbstractScheduledEventExecutor` 中, 有以 scheduledTaskQueue 字段: 839 | ``` 840 | Queue> scheduledTaskQueue; 841 | ``` 842 | 843 | scheduledTaskQueue 是一个队列(Queue), 其中存放的元素是 **ScheduledFutureTask**. 而 **ScheduledFutureTask** 我们很容易猜到, 它是对 Schedule 任务的一个抽象. 844 | 我们来看一下 `AbstractScheduledEventExecutor` 所实现的 **schedule** 方法吧: 845 | ``` 846 | @Override 847 | public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { 848 | ObjectUtil.checkNotNull(command, "command"); 849 | ObjectUtil.checkNotNull(unit, "unit"); 850 | if (delay < 0) { 851 | throw new IllegalArgumentException( 852 | String.format("delay: %d (expected: >= 0)", delay)); 853 | } 854 | return schedule(new ScheduledFutureTask( 855 | this, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); 856 | } 857 | ``` 858 | 这是其中一个重载的 schedule, 当一个 Runnable 传递进来后, 会被封装为一个 **ScheduledFutureTask** 对象, 这个对象会记录下这个 Runnable 在何时运行、已何种频率运行等信息. 859 | 当构建了 **ScheduledFutureTask** 后, 会继续调用 另一个重载的 schedule 方法: 860 | ``` 861 | ScheduledFuture schedule(final ScheduledFutureTask task) { 862 | if (inEventLoop()) { 863 | scheduledTaskQueue().add(task); 864 | } else { 865 | execute(new OneTimeTask() { 866 | @Override 867 | public void run() { 868 | scheduledTaskQueue().add(task); 869 | } 870 | }); 871 | } 872 | 873 | return task; 874 | } 875 | ``` 876 | 在这个方法中, ScheduledFutureTask 对象就会被添加到 **scheduledTaskQueue** 中了. 877 | ### 任务的执行 878 | 当一个任务被添加到 taskQueue 后, 它是怎么被 EventLoop 执行的呢? 879 | 让我们回到 NioEventLoop.run() 方法中, 在这个方法里, 会分别调用 **processSelectedKeys()** 和 **runAllTasks()** 方法, 来进行 IO 事件的处理和 task 的处理. **processSelectedKeys()** 方法我们已经分析过了, 下面我们来看一下 **runAllTasks()** 中到底有什么名堂吧. 880 | runAllTasks 方法有两个重载的方法, 一个是无参数的, 另一个有一个参数的. 首先来看一下无参数的 runAllTasks: 881 | ``` 882 | protected boolean runAllTasks() { 883 | fetchFromScheduledTaskQueue(); 884 | Runnable task = pollTask(); 885 | if (task == null) { 886 | return false; 887 | } 888 | 889 | for (;;) { 890 | try { 891 | task.run(); 892 | } catch (Throwable t) { 893 | logger.warn("A task raised an exception.", t); 894 | } 895 | 896 | task = pollTask(); 897 | if (task == null) { 898 | lastExecutionTime = ScheduledFutureTask.nanoTime(); 899 | return true; 900 | } 901 | } 902 | } 903 | ``` 904 | 我们前面已经提到过, EventLoop 可以通过调用 **EventLoop.execute** 来将一个 Runnable 提交到 taskQueue 中, 也可以通过调用 **EventLoop.schedule** 来提交一个 schedule 任务到 **scheduledTaskQueue** 中. 在此方法的一开始调用的 **fetchFromScheduledTaskQueue()** 其实就是将 **scheduledTaskQueue** 中已经可以执行的(即定时时间已到的 schedule 任务) 拿出来并添加到 taskQueue 中, 作为可执行的 task 等待被调度执行. 905 | 它的源码如下: 906 | ``` 907 | private void fetchFromScheduledTaskQueue() { 908 | if (hasScheduledTasks()) { 909 | long nanoTime = AbstractScheduledEventExecutor.nanoTime(); 910 | for (;;) { 911 | Runnable scheduledTask = pollScheduledTask(nanoTime); 912 | if (scheduledTask == null) { 913 | break; 914 | } 915 | taskQueue.add(scheduledTask); 916 | } 917 | } 918 | } 919 | ``` 920 | 接下来 **runAllTasks()** 方法就会不断调用 **task = pollTask()** 从 **taskQueue** 中获取一个可执行的 task, 然后调用它的 **run()** 方法来运行此 task. 921 | 922 | >`注意`, 因为 EventLoop 既需要执行 IO 操作, 又需要执行 task, 因此我们在调用 EventLoop.execute 方法提交任务时, 不要提交耗时任务, 更不能提交一些会造成阻塞的任务, 不然会导致我们的 IO 线程得不到调度, 影响整个程序的并发量. 923 | -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 与 Channel 的关联过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 与 Channel 的关联过程.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 实例化顺序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 实例化顺序图.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 的启动过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop 的启动过程.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoop.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoopGroup 初始化顺序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoopGroup 初始化顺序图.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoopGroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/NioEventLoopGroup.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 主从多线程模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 主从多线程模型.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 单线程模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 单线程模型.png -------------------------------------------------------------------------------- /Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 多线程模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop/Reactor 多线程模型.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 事件流 之 Inbound 事件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 事件流 之 Inbound 事件.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 事件流 之 Outbound 事件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 事件流 之 Outbound 事件.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 组成.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Channel 组成.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/ChannelInitializer 类层次结构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/ChannelInitializer 类层次结构图.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/ChannelPipeline 初始化过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/ChannelPipeline 初始化过程.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/EchoClientHandler 的名字.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/EchoClientHandler 的名字.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/HeadContext 类层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/HeadContext 类层次结构.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (一).md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (一) 2 | 3 | @(Netty)[Netty 源码分析] 4 | 5 | [TOC] 6 | 7 | 8 | ---------- 9 | 10 | ## 前言 11 | 这篇是 **Netty 源码分析** 的第二篇, 在这篇文章中, 我会为读者详细地分析 Netty 中的 ChannelPipeline 机制. 12 | 13 | ## Channel 与 ChannelPipeline 14 | 相信大家都知道了, 在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系如下: 15 | ![Alt text](./Channel 组成.png) 16 | 17 | 18 | 通过上图我们可以看到, 一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表. 这个链表的头是 HeadContext, 链表的尾是 TailContext, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler. 19 | 上面的图示给了我们一个对 ChannelPipeline 的直观认识, 但是实际上 Netty 实现的 Channel 是否真的是这样的呢? 我们继续用源码说话. 20 | 21 | 在第一章 **Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头** 中, 我们已经知道了一个 Channel 的初始化的基本过程, 下面我们再回顾一下. 22 | 下面的代码是 AbstractChannel 构造器: 23 | ``` 24 | protected AbstractChannel(Channel parent) { 25 | this.parent = parent; 26 | unsafe = newUnsafe(); 27 | pipeline = new DefaultChannelPipeline(this); 28 | } 29 | ``` 30 | AbstractChannel 有一个 pipeline 字段, 在构造器中会初始化它为 DefaultChannelPipeline的实例. 这里的代码就印证了一点: `每个 Channel 都有一个 ChannelPipeline`. 31 | 接着我们跟踪一下 DefaultChannelPipeline 的初始化过程. 32 | 首先进入到 DefaultChannelPipeline 构造器中: 33 | ``` 34 | public DefaultChannelPipeline(AbstractChannel channel) { 35 | if (channel == null) { 36 | throw new NullPointerException("channel"); 37 | } 38 | this.channel = channel; 39 | 40 | tail = new TailContext(this); 41 | head = new HeadContext(this); 42 | 43 | head.next = tail; 44 | tail.prev = head; 45 | } 46 | ``` 47 | 在 DefaultChannelPipeline 构造器中, 首先将与之关联的 Channel 保存到字段 channel 中, 然后实例化两个 ChannelHandlerContext, 一个是 HeadContext 实例 head, 另一个是 TailContext 实例 tail. 接着将 head 和 tail 互相指向, 构成一个双向链表. 48 | `特别注意到, 我们在开始的示意图中, head 和 tail 并没有包含 ChannelHandler`, 这是因为 HeadContext 和 TailContext 继承于 AbstractChannelHandlerContext 的同时也实现了 ChannelHandler 接口了, 因此它们有 Context 和 Handler 的双重属性. 49 | ## ChannelPipeline 的初始化再探 50 | 在第一章的时候, 我们已经对 ChannelPipeline 的初始化有了一个大致的了解, 不过当时重点毕竟不在 ChannelPipeline 这里, 因此没有深入地分析它的初始化过程. 那么下面我们就来看一下具体的 ChannelPipeline 的初始化都做了哪些工作吧. 51 | 52 | ### ChannelPipeline 实例化过程 53 | 我们再来回顾一下, 在实例化一个 Channel 时, 会伴随着一个 ChannelPipeline 的实例化, 并且此 Channel 会与这个 ChannelPipeline 相互关联, 这一点可以通过NioSocketChannel 的父类 AbstractChannel 的构造器予以佐证: 54 | ``` 55 | protected AbstractChannel(Channel parent) { 56 | this.parent = parent; 57 | unsafe = newUnsafe(); 58 | pipeline = new DefaultChannelPipeline(this); 59 | } 60 | ``` 61 | 当实例化一个 Channel(这里以 EchoClient 为例, 那么 Channel 就是 NioSocketChannel), 其 pipeline 字段就是我们新创建的 DefaultChannelPipeline 对象, 那么我们就来看一下 DefaultChannelPipeline 的构造方法吧: 62 | ``` 63 | public DefaultChannelPipeline(AbstractChannel channel) { 64 | if (channel == null) { 65 | throw new NullPointerException("channel"); 66 | } 67 | this.channel = channel; 68 | 69 | tail = new TailContext(this); 70 | head = new HeadContext(this); 71 | 72 | head.next = tail; 73 | tail.prev = head; 74 | } 75 | ``` 76 | 可以看到, 在 DefaultChannelPipeline 的构造方法中, 将传入的 channel 赋值给字段 this.channel, 接着又实例化了两个特殊的字段: **tail** 与 **head**. 这两个字段是一个双向链表的头和尾. 其实在 DefaultChannelPipeline 中, 维护了一个以 AbstractChannelHandlerContext 为节点的双向链表, 这个链表是 Netty 实现 Pipeline 机制的关键. 77 | 再回顾一下 head 和 tail 的类层次结构: 78 | ![@HeadContext 类层次结构](./HeadContext 类层次结构.png) 79 | ![@TailContext 类层次结构](./TailContext 类层次结构.png) 80 | 81 | 从类层次结构图中可以很清楚地看到, head 实现了 **ChannelInboundHandler**, 而 tail 实现了 **ChannelOutboundHandler** 接口, 并且它们都实现了 **ChannelHandlerContext** 接口, 因此可以说 **head 和 tail 即是一个 ChannelHandler, 又是一个 ChannelHandlerContext.** 82 | 83 | 接着看一下 HeadContext 的构造器: 84 | ``` 85 | HeadContext(DefaultChannelPipeline pipeline) { 86 | super(pipeline, null, HEAD_NAME, false, true); 87 | unsafe = pipeline.channel().unsafe(); 88 | } 89 | ``` 90 | 它调用了父类 AbstractChannelHandlerContext 的构造器, 并传入参数 inbound = false, outbound = true. 91 | TailContext 的构造器与 HeadContext 的相反, 它调用了父类 AbstractChannelHandlerContext 的构造器, 并传入参数 inbound = true, outbound = false. 92 | 即 header 是一个 outboundHandler, 而 tail 是一个inboundHandler, 关于这一点, 大家要特别注意, 因为在后面的分析中, 我们会反复用到 inbound 和 outbound 这两个属性. 93 | 94 | ### 链表的节点 ChannelHandlerContext 95 | 96 | ### ChannelInitializer 的添加 97 | 上面一小节中, 我们已经分析了 Channel 的组成, 其中我们了解到, 最开始的时候 ChannelPipeline 中含有两个 ChannelHandlerContext(同时也是 ChannelHandler), 但是这个 Pipeline并不能实现什么特殊的功能, 因为我们还没有给它添加自定义的 ChannelHandler. 98 | 通常来说, 我们在初始化 Bootstrap, 会添加我们自定义的 ChannelHandler, 就以我们熟悉的 EchoClient 来举例吧: 99 | ``` 100 | Bootstrap b = new Bootstrap(); 101 | b.group(group) 102 | .channel(NioSocketChannel.class) 103 | .option(ChannelOption.TCP_NODELAY, true) 104 | .handler(new ChannelInitializer() { 105 | @Override 106 | public void initChannel(SocketChannel ch) throws Exception { 107 | ChannelPipeline p = ch.pipeline(); 108 | p.addLast(new EchoClientHandler()); 109 | } 110 | }); 111 | ``` 112 | 上面代码的初始化过程, 相信大家都不陌生. 在调用 handler 时, 传入了 ChannelInitializer 对象, 它提供了一个 initChannel 方法供我们初始化 ChannelHandler. 那么这个初始化过程是怎样的呢? 下面我们就来揭开它的神秘面纱. 113 | 114 | ChannelInitializer 实现了 ChannelHandler, 那么它是在什么时候添加到 ChannelPipeline 中的呢? 进行了一番搜索后, 我们发现它是在 Bootstrap.init 方法中添加到 ChannelPipeline 中的. 115 | 其代码如下: 116 | ``` 117 | @Override 118 | @SuppressWarnings("unchecked") 119 | void init(Channel channel) throws Exception { 120 | ChannelPipeline p = channel.pipeline(); 121 | p.addLast(handler()); 122 | ... 123 | } 124 | ``` 125 | 上面的代码将 handler() 返回的 ChannelHandler 添加到 Pipeline 中, 而 handler() 返回的是handler 其实就是我们在初始化 Bootstrap 调用 handler 设置的 ChannelInitializer 实例, 因此这里就是将 ChannelInitializer 插入到了 Pipeline 的末端. 126 | 此时 Pipeline 的结构如下图所示: 127 | ![Alt text](./ChannelPipeline 初始化过程.png) 128 | 129 | 130 | 有朋友可能就有疑惑了, 我明明插入的是一个 ChannelInitializer 实例, 为什么在 ChannelPipeline 中的双向链表中的元素却是一个 ChannelHandlerContext? 为了解答这个问题, 我们继续在代码中寻找答案吧. 131 | 我们刚才提到, 在 Bootstrap.init 中会调用 p.addLast() 方法, 将 ChannelInitializer 插入到链表末端: 132 | ``` 133 | @Override 134 | public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) { 135 | synchronized (this) { 136 | checkDuplicateName(name); // 检查此 handler 是否有重复的名字 137 | 138 | AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); 139 | addLast0(name, newCtx); 140 | } 141 | 142 | return this; 143 | } 144 | ``` 145 | addLast 有很多重载的方法, 我们关注这个比较重要的方法就可以了. 146 | 上面的 addLast 方法中, 首先检查这个 ChannelHandler 的名字是否是重复的, 如果不重复的话, 则为这个 Handler 创建一个对应的 DefaultChannelHandlerContext 实例, 并与之关联起来(Context 中有一个 handler 属性保存着对应的 Handler 实例). 判断此 Handler 是否重名的方法很简单: Netty 中有一个 **name2ctx** Map 字段, key 是 handler 的名字, 而 value 则是 handler 本身. 因此通过如下代码就可以判断一个 handler 是否重名了: 147 | ``` 148 | private void checkDuplicateName(String name) { 149 | if (name2ctx.containsKey(name)) { 150 | throw new IllegalArgumentException("Duplicate handler name: " + name); 151 | } 152 | } 153 | ``` 154 | 为了添加一个 handler 到 pipeline 中, 必须把此 handler 包装成 ChannelHandlerContext. 因此在上面的代码中我们可以看到新实例化了一个 newCtx 对象, 并将 handler 作为参数传递到构造方法中. 那么我们来看一下实例化的 DefaultChannelHandlerContext 到底有什么玄机吧. 155 | 首先看它的构造器: 156 | ``` 157 | DefaultChannelHandlerContext( 158 | DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) { 159 | super(pipeline, group, name, isInbound(handler), isOutbound(handler)); 160 | if (handler == null) { 161 | throw new NullPointerException("handler"); 162 | } 163 | this.handler = handler; 164 | } 165 | ``` 166 | DefaultChannelHandlerContext 的构造器中, 调用了两个很有意思的方法: **isInbound** 与 **isOutbound**, 这两个方法是做什么的呢? 167 | ``` 168 | private static boolean isInbound(ChannelHandler handler) { 169 | return handler instanceof ChannelInboundHandler; 170 | } 171 | 172 | private static boolean isOutbound(ChannelHandler handler) { 173 | return handler instanceof ChannelOutboundHandler; 174 | } 175 | ``` 176 | 从源码中可以看到, 当一个 handler 实现了 ChannelInboundHandler 接口, 则 isInbound 返回真; 相似地, 当一个 handler 实现了 ChannelOutboundHandler 接口, 则 isOutbound 就返回真. 177 | 而这两个 boolean 变量会传递到父类 AbstractChannelHandlerContext 中, 并初始化父类的两个字段: **inbound** 与 **outbound**. 178 | 那么这里的 ChannelInitializer 所对应的 DefaultChannelHandlerContext 的 inbound 与 inbound 字段分别是什么呢? 那就看一下 ChannelInitializer 到底实现了哪个接口不就行了? 如下是 ChannelInitializer 的类层次结构图: 179 | ![Alt text](./ChannelInitializer 类层次结构图.png) 180 | 可以清楚地看到, ChannelInitializer 仅仅实现了 ChannelInboundHandler 接口, 因此这里实例化的 DefaultChannelHandlerContext 的 inbound = true, outbound = false. 181 | 不就是 inbound 和 outbound 两个字段嘛, 为什么需要这么大费周章地分析一番? 其实这两个字段关系到 pipeline 的事件的流向与分类, 因此是十分关键的, 不过我在这里先卖个关子, 后面我们再来详细分析这两个字段所起的作用. 在这里, 读者只需要记住, **ChannelInitializer 所对应的 DefaultChannelHandlerContext 的 inbound = true, outbound = false** 即可. 182 | 183 | 当创建好 Context 后, 就将这个 Context 插入到 Pipeline 的双向链表中: 184 | ``` 185 | private void addLast0(final String name, AbstractChannelHandlerContext newCtx) { 186 | checkMultiplicity(newCtx); 187 | 188 | AbstractChannelHandlerContext prev = tail.prev; 189 | newCtx.prev = prev; 190 | newCtx.next = tail; 191 | prev.next = newCtx; 192 | tail.prev = newCtx; 193 | 194 | name2ctx.put(name, newCtx); 195 | 196 | callHandlerAdded(newCtx); 197 | } 198 | ``` 199 | 显然, 这个代码就是典型的双向链表的插入操作了. 当调用了 addLast 方法后, Netty 就会将此 handler 添加到双向链表中 tail 元素之前的位置. 200 | 201 | ## 自定义 ChannelHandler 的添加过程 202 | 在上一小节中, 我们已经分析了一个 ChannelInitializer 如何插入到 Pipeline 中的, 接下来就来探讨一下 ChannelInitializer 在哪里被调用, ChannelInitializer 的作用, 以及我们自定义的 ChannelHandler 是如何插入到 Pipeline 中的. 203 | 204 | 在 **Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 (客户端)** 一章的 **channel 的注册过程** 小节中, 我们已经分析过 Channel 的注册过程了, 这里我们再简单地复习一下: 205 | - 首先在 AbstractBootstrap.initAndRegister中, 通过 group().register(channel), 调用 MultithreadEventLoopGroup.register 方法 206 | - 在MultithreadEventLoopGroup.register 中, 通过 next() 获取一个可用的 SingleThreadEventLoop, 然后调用它的 register 207 | - 在 SingleThreadEventLoop.register 中, 通过 channel.unsafe().register(this, promise) 来获取 channel 的 unsafe() 底层操作对象, 然后调用它的 register. 208 | - 在 AbstractUnsafe.register 方法中, 调用 register0 方法注册 Channel 209 | - 在 AbstractUnsafe.register0 中, 调用 AbstractNioChannel#doRegister 方法 210 | - AbstractNioChannel.doRegister 方法通过 javaChannel().register(eventLoop().selector, 0, this) 将 Channel 对应的 Java NIO SockerChannel 注册到一个 eventLoop 的 Selector 中, 并且将当前 Channel 作为 attachment. 211 | 212 | 而我们自定义 ChannelHandler 的添加过程, 发生在 AbstractUnsafe.register0 中, 在这个方法中调用了 pipeline.fireChannelRegistered() 方法, 其实现如下: 213 | ``` 214 | @Override 215 | public ChannelPipeline fireChannelRegistered() { 216 | head.fireChannelRegistered(); 217 | return this; 218 | } 219 | ``` 220 | 上面的代码很简单, 就是调用了 head.fireChannelRegistered() 方法而已. 221 | >`关于上面代码的 head.fireXXX 的调用形式, 是 Netty 中 Pipeline 传递事件的常用方式, 我们以后会经常看到.` 222 | 223 | 还记得 head 的 类层次结构图不, head 是一个 AbstractChannelHandlerContext 实例, 并且它没有重写 fireChannelRegistered 方法, 因此 head.fireChannelRegistered 其实是调用的 AbstractChannelHandlerContext.fireChannelRegistered: 224 | ``` 225 | @Override 226 | public ChannelHandlerContext fireChannelRegistered() { 227 | final AbstractChannelHandlerContext next = findContextInbound(); 228 | EventExecutor executor = next.executor(); 229 | if (executor.inEventLoop()) { 230 | next.invokeChannelRegistered(); 231 | } else { 232 | executor.execute(new OneTimeTask() { 233 | @Override 234 | public void run() { 235 | next.invokeChannelRegistered(); 236 | } 237 | }); 238 | } 239 | return this; 240 | } 241 | ``` 242 | 这个方法的第一句是调用 findContextInbound 获取一个 Context, 那么它返回的 Context 到底是什么呢? 我们跟进代码中看一下: 243 | ``` 244 | private AbstractChannelHandlerContext findContextInbound() { 245 | AbstractChannelHandlerContext ctx = this; 246 | do { 247 | ctx = ctx.next; 248 | } while (!ctx.inbound); 249 | return ctx; 250 | } 251 | ``` 252 | 很显然, 这个代码会从 head 开始遍历 Pipeline 的双向链表, 然后找到第一个属性 inbound 为 true 的 ChannelHandlerContext 实例. 想起来了没? 我们在前面分析 ChannelInitializer 时, 花了大量的笔墨来分析了 inbound 和 outbound 属性, 你看现在这里就用上了. 回想一下, ChannelInitializer 实现了 ChannelInboudHandler, 因此它所对应的 ChannelHandlerContext 的 inbound 属性就是 true, 因此这里返回就是 ChannelInitializer 实例所对应的 ChannelHandlerContext. 即: 253 | ![Alt text](./自定义 ChannelHandler 分析1.png) 254 | 255 | 256 | 当获取到 inbound 的 Context 后, 就调用它的 invokeChannelRegistered 方法: 257 | ``` 258 | private void invokeChannelRegistered() { 259 | try { 260 | ((ChannelInboundHandler) handler()).channelRegistered(this); 261 | } catch (Throwable t) { 262 | notifyHandlerException(t); 263 | } 264 | } 265 | ``` 266 | 我们已经强调过了, 每个 ChannelHandler 都与一个 ChannelHandlerContext 关联, 我们可以通过 ChannelHandlerContext 获取到对应的 ChannelHandler. 因此很显然了, 这里 handler() 返回的, 其实就是一开始我们实例化的 ChannelInitializer 对象, 并接着调用了 ChannelInitializer.channelRegistered 方法. 看到这里, 读者朋友是否会觉得有点眼熟呢? ChannelInitializer.channelRegistered 这个方法我们在第一章的时候已经大量地接触了, 但是我们并没有深入地分析这个方法的调用过程, 那么在这里读者朋友应该对它的调用有了更加深入的了解了吧. 267 | 268 | 那么这个方法中又有什么玄机呢? 继续看代码: 269 | ``` 270 | @Override 271 | @SuppressWarnings("unchecked") 272 | public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { 273 | initChannel((C) ctx.channel()); 274 | ctx.pipeline().remove(this); 275 | ctx.fireChannelRegistered(); 276 | } 277 | ``` 278 | initChannel 这个方法我们很熟悉了吧, 它就是我们在初始化 Bootstrap 时, 调用 handler 方法传入的匿名内部类所实现的方法: 279 | ``` 280 | .handler(new ChannelInitializer() { 281 | @Override 282 | public void initChannel(SocketChannel ch) throws Exception { 283 | ChannelPipeline p = ch.pipeline(); 284 | p.addLast(new EchoClientHandler()); 285 | } 286 | }); 287 | ``` 288 | 因此当调用了这个方法后, 我们自定义的 ChannelHandler 就插入到 Pipeline 了, 此时的 Pipeline 如下图所示: 289 | ![Alt text](./自定义 ChannelHandler 分析2.png) 290 | 291 | 292 | 当添加了自定义的 ChannelHandler 后, 会删除 ChannelInitializer 这个 ChannelHandler, 即 "ctx.pipeline().remove(this)", 因此最后的 Pipeline 如下: 293 | ![Alt text](./自定义 ChannelHandler 分析3.png) 294 | 295 | 好了, 到了这里, 我们的 **自定义 ChannelHandler 的添加过程** 也分析的查不多了. 296 | -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (二).md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (二) 2 | 3 | @(Netty)[Netty 源码分析] 4 | 5 | [TOC] 6 | 7 | 8 | ---------- 9 | 接上篇 **Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline (一)** 10 | ## ChannelHandler 的名字 11 | 我们注意到, pipeline.addXXX 都有一个重载的方法, 例如 addLast, 它有一个重载的版本是: 12 | ``` 13 | ChannelPipeline addLast(String name, ChannelHandler handler); 14 | ``` 15 | 第一个参数指定了所添加的 handler 的名字(更准确地说是 ChannelHandlerContext 的名字, 不过我们通常是以 handler 作为叙述的对象, 因此说成 handler 的名字便于理解). 那么 handler 的名字有什么用呢? 如果我们不设置name, 那么 handler 会有怎样的名字? 16 | 为了解答这些疑惑, 老规矩, 依然是从源码中找到答案. 17 | 我们还是以 addLast 方法为例: 18 | ``` 19 | @Override 20 | public ChannelPipeline addLast(String name, ChannelHandler handler) { 21 | return addLast(null, name, handler); 22 | } 23 | ``` 24 | 这个方法会调用重载的 addLast 方法: 25 | ``` 26 | @Override 27 | public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) { 28 | synchronized (this) { 29 | checkDuplicateName(name); 30 | 31 | AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); 32 | addLast0(name, newCtx); 33 | } 34 | 35 | return this; 36 | } 37 | ``` 38 | 第一个参数被设置为 null, 我们不关系它. 第二参数就是这个 handler 的名字. 看代码可知, 在添加一个 handler 之前, 需要调用 **checkDuplicateName** 方法来确定此 handler 的名字是否和已添加的 handler 的名字重复. 而这个 checkDuplicateName 方法我们在前面已经有提到, 这里再回顾一下: 39 | ``` 40 | private void checkDuplicateName(String name) { 41 | if (name2ctx.containsKey(name)) { 42 | throw new IllegalArgumentException("Duplicate handler name: " + name); 43 | } 44 | } 45 | ``` 46 | Netty 判断一个 handler 的名字是否重复的依据很简单: DefaultChannelPipeline 中有一个 类型为 Map 的 **name2ctx** 字段, 它的 key 是一个 handler 的名字, 而 value 则是这个 handler 所对应的 ChannelHandlerContext. 每当新添加一个 handler 时, 就会 put 到 name2ctx 中. 因此检查 name2ctx 中是否包含这个 name 即可. 47 | 当没有重名的 handler 时, 就为这个 handler 生成一个关联的 DefaultChannelHandlerContext 对象, 然后就将 name 和 newCtx 作为 key-value 对 放到 name2Ctx 中. 48 | 49 | ### 自动生成 handler 的名字 50 | 如果我们调用的是如下的 addLast 方法 51 | ``` 52 | ChannelPipeline addLast(ChannelHandler... handlers); 53 | ``` 54 | 那么 Netty 会调用 generateName 为我们的 handler 自动生成一个名字: 55 | ``` 56 | private String generateName(ChannelHandler handler) { 57 | WeakHashMap, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)]; 58 | Class handlerType = handler.getClass(); 59 | String name; 60 | synchronized (cache) { 61 | name = cache.get(handlerType); 62 | if (name == null) { 63 | name = generateName0(handlerType); 64 | cache.put(handlerType, name); 65 | } 66 | } 67 | 68 | synchronized (this) { 69 | // It's not very likely for a user to put more than one handler of the same type, but make sure to avoid 70 | // any name conflicts. Note that we don't cache the names generated here. 71 | if (name2ctx.containsKey(name)) { 72 | String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'. 73 | for (int i = 1;; i ++) { 74 | String newName = baseName + i; 75 | if (!name2ctx.containsKey(newName)) { 76 | name = newName; 77 | break; 78 | } 79 | } 80 | } 81 | } 82 | 83 | return name; 84 | } 85 | ``` 86 | 而 generateName 会接着调用 **generateName0** 来实际产生一个 handler 的名字: 87 | ``` 88 | private static String generateName0(Class handlerType) { 89 | return StringUtil.simpleClassName(handlerType) + "#0"; 90 | } 91 | ``` 92 | 自动生成的名字的规则很简单, 就是 handler 的简单类名加上 "#0", 因此我们的 EchoClientHandler 的名字就是 "EchoClientHandler#0", 这一点也可以通过调试窗口佐证: 93 | ![Alt text](./EchoClientHandler 的名字.png) 94 | 95 | ## 关于 Pipeline 的事件传输机制 96 | 前面章节中, 我们知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量, 分别用于标识 Context 所对应的 handler 的类型, 即: 97 | - inbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelInboundHandler 方法. 98 | - outbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelOutboundHandler 方法. 99 | 100 | 读者朋友肯定很疑惑了吧: 那究竟这两个字段有什么作用呢? 其实这还要从 ChannelPipeline 的传输的事件类型说起. 101 | **Netty 的事件可以分为 Inbound 和 Outbound 事件.** 102 | 103 | 如下是从 Netty 官网上拷贝的一个图示: 104 | ``` 105 | I/O Request 106 | via Channel or 107 | ChannelHandlerContext 108 | | 109 | +---------------------------------------------------+---------------+ 110 | | ChannelPipeline | | 111 | | \|/ | 112 | | +---------------------+ +-----------+----------+ | 113 | | | Inbound Handler N | | Outbound Handler 1 | | 114 | | +----------+----------+ +-----------+----------+ | 115 | | /|\ | | 116 | | | \|/ | 117 | | +----------+----------+ +-----------+----------+ | 118 | | | Inbound Handler N-1 | | Outbound Handler 2 | | 119 | | +----------+----------+ +-----------+----------+ | 120 | | /|\ . | 121 | | . . | 122 | | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()| 123 | | [ method call] [method call] | 124 | | . . | 125 | | . \|/ | 126 | | +----------+----------+ +-----------+----------+ | 127 | | | Inbound Handler 2 | | Outbound Handler M-1 | | 128 | | +----------+----------+ +-----------+----------+ | 129 | | /|\ | | 130 | | | \|/ | 131 | | +----------+----------+ +-----------+----------+ | 132 | | | Inbound Handler 1 | | Outbound Handler M | | 133 | | +----------+----------+ +-----------+----------+ | 134 | | /|\ | | 135 | +---------------+-----------------------------------+---------------+ 136 | | \|/ 137 | +---------------+-----------------------------------+---------------+ 138 | | | | | 139 | | [ Socket.read() ] [ Socket.write() ] | 140 | | | 141 | | Netty Internal I/O Threads (Transport Implementation) | 142 | +-------------------------------------------------------------------+ 143 | ``` 144 | 从上图可以看出, inbound 事件和 outbound 事件的流向是不一样的, inbound 事件的流行是从下至上, 而 outbound 刚好相反, 是从上到下. 并且 inbound 的传递方式是通过调用相应的 **ChannelHandlerContext.fireIN_EVT()** 方法, 而 outbound 方法的的传递方式是通过调用 **ChannelHandlerContext.OUT_EVT()** 方法. 例如 **ChannelHandlerContext.fireChannelRegistered()** 调用会发送一个 **ChannelRegistered** 的 inbound 给下一个ChannelHandlerContext, 而 **ChannelHandlerContext.bind** 调用会发送一个 **bind** 的 outbound 事件给 下一个 ChannelHandlerContext. 145 | 146 | Inbound 事件传播方法有: 147 | ``` 148 | ChannelHandlerContext.fireChannelRegistered() 149 | ChannelHandlerContext.fireChannelActive() 150 | ChannelHandlerContext.fireChannelRead(Object) 151 | ChannelHandlerContext.fireChannelReadComplete() 152 | ChannelHandlerContext.fireExceptionCaught(Throwable) 153 | ChannelHandlerContext.fireUserEventTriggered(Object) 154 | ChannelHandlerContext.fireChannelWritabilityChanged() 155 | ChannelHandlerContext.fireChannelInactive() 156 | ChannelHandlerContext.fireChannelUnregistered() 157 | ``` 158 | Oubound 事件传输方法有: 159 | ``` 160 | ChannelHandlerContext.bind(SocketAddress, ChannelPromise) 161 | ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise) 162 | ChannelHandlerContext.write(Object, ChannelPromise) 163 | ChannelHandlerContext.flush() 164 | ChannelHandlerContext.read() 165 | ChannelHandlerContext.disconnect(ChannelPromise) 166 | ChannelHandlerContext.close(ChannelPromise) 167 | ``` 168 | `注意, 如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.` 169 | 例如: 170 | ``` 171 | public class MyInboundHandler extends ChannelInboundHandlerAdapter { 172 | @Override 173 | public void channelActive(ChannelHandlerContext ctx) { 174 | System.out.println("Connected!"); 175 | ctx.fireChannelActive(); 176 | } 177 | } 178 | 179 | public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter { 180 | @Override 181 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) { 182 | System.out.println("Closing .."); 183 | ctx.close(promise); 184 | } 185 | } 186 | ``` 187 | 上面的例子中, MyInboundHandler 收到了一个 channelActive 事件, 它在处理后, 如果希望将事件继续传播下去, 那么需要接着调用 ctx.fireChannelActive(). 188 | 189 | ### Outbound 操作(outbound operations of a channel) 190 | `Outbound 事件都是请求事件(request event)`, 即请求某件事情的发生, 然后通过 Outbound 事件进行通知. 191 | Outbound 事件的传播方向是 tail -> customContext -> head. 192 | 193 | 我们接下来以 connect 事件为例, 分析一下 Outbound 事件的传播机制. 194 | 首先, 当用户调用了 Bootstrap.connect 方法时, 就会触发一个 **Connect 请求事件**, 此调用会触发如下调用链: 195 | ``` 196 | Bootstrap.connect -> Bootstrap.doConnect -> Bootstrap.doConnect0 -> AbstractChannel.connect 197 | ``` 198 | 继续跟踪的话, 我们就发现, AbstractChannel.connect 其实由调用了 DefaultChannelPipeline.connect 方法: 199 | ``` 200 | @Override 201 | public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 202 | return pipeline.connect(remoteAddress, promise); 203 | } 204 | ``` 205 | 而 pipeline.connect 的实现如下: 206 | ``` 207 | @Override 208 | public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 209 | return tail.connect(remoteAddress, promise); 210 | } 211 | ``` 212 | 可以看到, 当 outbound 事件(这里是 connect 事件)传递到 Pipeline 后, 它其实是以 tail 为起点开始传播的. 213 | 而 tail.connect 其实调用的是 AbstractChannelHandlerContext.connect 方法: 214 | ``` 215 | @Override 216 | public ChannelFuture connect( 217 | final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 218 | ... 219 | final AbstractChannelHandlerContext next = findContextOutbound(); 220 | EventExecutor executor = next.executor(); 221 | ... 222 | next.invokeConnect(remoteAddress, localAddress, promise); 223 | ... 224 | return promise; 225 | } 226 | ``` 227 | findContextOutbound() 顾名思义, 它的作用是以当前 Context 为起点, 向 Pipeline 中的 Context 双向链表的前端寻找第一个 **outbound** 属性为真的 Context(即关联着 ChannelOutboundHandler 的 Context), 然后返回. 228 | 它的实现如下: 229 | ``` 230 | private AbstractChannelHandlerContext findContextOutbound() { 231 | AbstractChannelHandlerContext ctx = this; 232 | do { 233 | ctx = ctx.prev; 234 | } while (!ctx.outbound); 235 | return ctx; 236 | } 237 | ``` 238 | 当我们找到了一个 outbound 的 Context 后, 就调用它的 invokeConnect 方法, 这个方法中会调用 Context 所关联着的 ChannelHandler 的 connect 方法: 239 | ``` 240 | private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { 241 | try { 242 | ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise); 243 | } catch (Throwable t) { 244 | notifyOutboundHandlerException(t, promise); 245 | } 246 | } 247 | ``` 248 | 如果用户没有重写 ChannelHandler 的 connect 方法, 那么会调用 ChannelOutboundHandlerAdapter 所实现的方法: 249 | ``` 250 | @Override 251 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, 252 | SocketAddress localAddress, ChannelPromise promise) throws Exception { 253 | ctx.connect(remoteAddress, localAddress, promise); 254 | } 255 | ``` 256 | 我们看到, ChannelOutboundHandlerAdapter.connect 仅仅调用了 ctx.connect, 而这个调用又回到了: 257 | ``` 258 | Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect 259 | ``` 260 | 这样的循环中, 直到 connect 事件传递到DefaultChannelPipeline 的双向链表的头节点, 即 head 中. 为什么会传递到 head 中呢? 回想一下, head 实现了 ChannelOutboundHandler, 因此它的 outbound 属性是 true. 261 | `因为 head 本身既是一个 ChannelHandlerContext, 又实现了 ChannelOutboundHandler 接口`, 因此当 connect 消息传递到 head 后, 会将消息转递到对应的 ChannelHandler 中处理, 而恰好, head 的 handler() 返回的就是 head 本身: 262 | ``` 263 | @Override 264 | public ChannelHandler handler() { 265 | return this; 266 | } 267 | ``` 268 | 因此最终 connect 事件是在 head 中处理的. head 的 connect 事件处理方法如下: 269 | ``` 270 | @Override 271 | public void connect( 272 | ChannelHandlerContext ctx, 273 | SocketAddress remoteAddress, SocketAddress localAddress, 274 | ChannelPromise promise) throws Exception { 275 | unsafe.connect(remoteAddress, localAddress, promise); 276 | } 277 | ``` 278 | 到这里, 整个 Connect 请求事件就结束了. 279 | 下面以一幅图来描述一个整个 Connect 请求事件的处理过程: 280 | ![Alt text](./Channel 事件流 之 Outbound 事件.png) 281 | 282 | 283 | 我们仅仅以 Connect 请求事件为例, 分析了 Outbound 事件的传播过程, 但是其实所有的 outbound 的事件传播都遵循着一样的传播规律, 读者可以试着分析一下其他的 outbound 事件, 体会一下它们的传播过程. 284 | 285 | ### Inbound 事件 286 | Inbound 事件和 Outbound 事件的处理过程有点镜像. 287 | `Inbound 事件是一个通知事件`, 即某件事已经发生了, 然后通过 Inbound 事件进行通知. Inbound 通常发生在 Channel 的状态的改变或 IO 事件就绪. 288 | Inbound 的特点是它传播方向是 head -> customContext -> tail. 289 | 290 | 既然上面我们分析了 Connect 这个 Outbound 事件, 那么接着分析 Connect 事件后会发生什么 Inbound 事件, 并最终找到 Outbound 和 Inbound 事件之间的联系. 291 | 292 | 当 Connect 这个 Outbound 传播到 unsafe 后, 其实是在 AbstractNioUnsafe.connect 方法中进行处理的: 293 | ``` 294 | @Override 295 | public final void connect( 296 | final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 297 | ... 298 | if (doConnect(remoteAddress, localAddress)) { 299 | fulfillConnectPromise(promise, wasActive); 300 | } else { 301 | ... 302 | } 303 | ... 304 | } 305 | ``` 306 | 在 AbstractNioUnsafe.connect 中, 首先调用 doConnect 方法进行实际上的 Socket 连接, 当连接上后, 会调用 fulfillConnectPromise 方法: 307 | ``` 308 | private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { 309 | ... 310 | // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, 311 | // because what happened is what happened. 312 | if (!wasActive && isActive()) { 313 | pipeline().fireChannelActive(); 314 | } 315 | ... 316 | } 317 | ``` 318 | 我们看到, 在 fulfillConnectPromise 中, 会通过调用 pipeline().fireChannelActive() 将通道激活的消息(即 Socket 连接成功)发送出去. 319 | `而这里, 当调用 pipeline.fireXXX 后, 就是 Inbound 事件的起点.` 320 | 因此当调用了 pipeline().fireChannelActive() 后, 就产生了一个 ChannelActive Inbound 事件, 我们就从这里开始看看这个 Inbound 事件是怎么传播的吧. 321 | ``` 322 | @Override 323 | public ChannelPipeline fireChannelActive() { 324 | head.fireChannelActive(); 325 | 326 | if (channel.config().isAutoRead()) { 327 | channel.read(); 328 | } 329 | 330 | return this; 331 | } 332 | ``` 333 | 哈哈, 果然, 在 fireChannelActive 方法中, 调用的是 head.fireChannelActive, `因此可以证明了, Inbound 事件在 Pipeline 中传输的起点是 head.` 334 | 那么, 在 head.fireChannelActive() 中又做了什么呢? 335 | ``` 336 | @Override 337 | public ChannelHandlerContext fireChannelActive() { 338 | final AbstractChannelHandlerContext next = findContextInbound(); 339 | EventExecutor executor = next.executor(); 340 | ... 341 | next.invokeChannelActive(); 342 | ... 343 | return this; 344 | } 345 | ``` 346 | 上面的代码应该很熟悉了吧. 回想一下在 Outbound 事件(例如 Connect 事件)的传输过程中时, 我们也有类似的操作: 347 | - 首先调用 findContextInbound, 从 Pipeline 的双向链表中中找到第一个属性 inbound 为真的 Context, 然后返回 348 | - 调用这个 Context 的 invokeChannelActive 349 | 350 | invokeChannelActive 方法如下: 351 | ``` 352 | private void invokeChannelActive() { 353 | try { 354 | ((ChannelInboundHandler) handler()).channelActive(this); 355 | } catch (Throwable t) { 356 | notifyHandlerException(t); 357 | } 358 | } 359 | ``` 360 | 这个方法和 Outbound 的对应方法(例如 invokeConnect) 如出一辙. 同 Outbound 一样, 如果用户没有重写 channelActive 方法, 那么会调用 ChannelInboundHandlerAdapter 的 channelActive 方法: 361 | ``` 362 | @Override 363 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 364 | ctx.fireChannelActive(); 365 | } 366 | ``` 367 | 同样地, 在 ChannelInboundHandlerAdapter.channelActive 中, 仅仅调用了 ctx.fireChannelActive 方法, 因此就会有如下循环: 368 | ``` 369 | Context.fireChannelActive -> Connect.findContextInbound -> nextContext.invokeChannelActive -> nextHandler.channelActive -> nextContext.fireChannelActive 370 | ``` 371 | 这样的循环中. 同理, tail 本身 既实现了 **ChannelInboundHandler** 接口, 又实现了 **ChannelHandlerContext** 接口, 因此当 channelActive 消息传递到 tail 后, 会将消息转递到对应的 ChannelHandler 中处理, 而恰好, tail 的 handler() 返回的就是 tail 本身: 372 | ``` 373 | @Override 374 | public ChannelHandler handler() { 375 | return this; 376 | } 377 | ``` 378 | 因此 channelActive Inbound 事件最终是在 tail 中处理的, 我们看一下它的处理方法: 379 | ``` 380 | @Override 381 | public void channelActive(ChannelHandlerContext ctx) throws Exception { } 382 | ``` 383 | TailContext.channelActive 方法是空的. 如果读者自行查看 TailContext 的 Inbound 处理方法时, 会发现, 它们的实现都是空的. 可见, 如果是 Inbound, 当用户没有实现自定义的处理器时, 那么默认是不处理的. 384 | 385 | 用一幅图来总结一下 Inbound 的传输过程吧: 386 | ![Alt text](./Channel 事件流 之 Inbound 事件.png) 387 | 388 | 389 | 390 | ### 总结 391 | 对于 Outbound事件: 392 | - Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求) 393 | - Outbound 事件的发起者是 Channel 394 | - Outbound 事件的处理者是 unsafe 395 | - Outbound 事件在 Pipeline 中的传输方向是 tail -> head. 396 | - 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止. 397 | - Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT 398 | 399 | 对于 Inbound 事件: 400 | - Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层. 401 | - Inbound 事件发起者是 unsafe 402 | - Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现. 403 | - Inbound 事件在 Pipeline 中传输方向是 head -> tail 404 | - 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止. 405 | - Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT 406 | 407 | 408 | outbound 和 inbound 事件十分的镜像, 并且 Context 与 Handler 直接的调用关系是否容易混淆, 因此读者在阅读这里的源码时, 需要特别的注意. -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/TailContext 类层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/TailContext 类层次结构.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析1.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析2.png -------------------------------------------------------------------------------- /Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 二 贯穿Netty 的大动脉 ── ChannelPipeline/自定义 ChannelHandler 分析3.png -------------------------------------------------------------------------------- /Netty 源码分析之 番外篇 Java NIO 的前生今世/Netty 源码分析之 番外篇 Java NIO 的前生今世.md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 番外篇 Java NIO 的前生今世 2 | @(Netty)[Netty 源码分析, ChannelFuture, ChannelPromise] 3 | 4 | [TOC] 5 | 6 | 7 | ---------- 8 | 9 | ## 简介 10 | Java NIO 是由 Java 1.4 引进的异步 IO. 11 | Java NIO 由以下几个核心部分组成: 12 | - Channel 13 | - Buffer 14 | - Selector 15 | 16 | ### NIO 和 IO 的对比 17 | IO 和 NIO 的区别主要体现在三个方面: 18 | - IO 基于流(Stream oriented), 而 NIO 基于 Buffer (Buffer oriented) 19 | - IO 操作是阻塞的, 而 NIO 操作是非阻塞的 20 | - IO 没有 selector 概念, 而 NIO 有 selector 概念. 21 | 22 | 23 | #### 基于 Stream 与基于 Buffer 24 | 传统的 IO 是面向字节流或字符流的, 而在 NIO 中, 我们抛弃了传统的 IO 流, 而是引入了 **Channel** 和 **Buffer** 的概念. 在 NIO 中, 我只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel. 25 | 那么什么是 **基于流** 呢? 在一般的 Java IO 操作中, 我们以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 因此我们也就不能随意改变读取指针的位置. 26 | 而 **基于 Buffer** 就显得有点不同了. 我们首先需要从 Channel 中读取数据到 Buffer 中, 当 Buffer 中有数据后, 我们就可以对这些数据进行操作了. 不像 IO 那样是顺序操作, NIO 中我们可以随意地读取任意位置的数据. 27 | 28 | #### 阻塞和非阻塞 29 | Java 提供的各种 Stream 操作都是阻塞的, 例如我们调用一个 read 方法读取一个文件的内容, 那么调用 read 的线程会被阻塞住, 直到 read 操作完成. 30 | 而 NIO 的非阻塞模式允许我们非阻塞地进行 IO 操作. 例如我们需要从网络中读取数据, 在 NIO 的非阻塞模式中, 当我们调用 read 方法时, 如果此时有数据, 则 read 读取并返回; 如果此时没有数据, 则 read 直接返回, 而不会阻塞当前线程. 31 | #### selector 32 | selector 是 NIO 中才有的概念, 它是 Java NIO 之所以可以非阻塞地进行 IO 操作的关键. 33 | 通过 Selector, 一个线程可以监听多个 Channel 的 IO 事件, 当我们向一个 Selector 中注册了 Channel 后, Selector 内部的机制就可以自动地为我们不断地查询(select) 这些注册的 Channel 是否有已就绪的 IO 事件(例如可读, 可写, 网络连接完成等). 通过这样的 Selector 机制, 我们就可以很简单地使用一个线程高效地管理多个 Channel 了. 34 | 35 | ## Java NIO Channel 36 | 通常来说, 所有的 NIO 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream. 37 | java Stream 和 NIO Channel 对比 38 | - 我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写. 39 | - Channel 可以异步地读写, 而 Stream 是阻塞的同步读写. 40 | - Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中. 41 | 42 | 43 | Channel 类型有: 44 | - FileChannel, 文件操作 45 | - DatagramChannel, UDP 操作 46 | - SocketChannel, TCP 操作 47 | - ServerSocketChannel, TCP 操作, 使用在服务器端. 48 | 49 | 50 | 这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO. 51 | 基本的 Channel 使用例子: 52 | ``` 53 | public static void main( String[] args ) throws Exception 54 | { 55 | RandomAccessFile aFile = new RandomAccessFile("/Users/xiongyongshun/settings.xml", "rw"); 56 | FileChannel inChannel = aFile.getChannel(); 57 | 58 | ByteBuffer buf = ByteBuffer.allocate(48); 59 | 60 | int bytesRead = inChannel.read(buf); 61 | while (bytesRead != -1) { 62 | buf.flip(); 63 | 64 | while(buf.hasRemaining()){ 65 | System.out.print((char) buf.get()); 66 | } 67 | 68 | buf.clear(); 69 | bytesRead = inChannel.read(buf); 70 | } 71 | aFile.close(); 72 | } 73 | ``` 74 | ### FileChannel 75 | FileChannel 是操作文件的Channel, 我们可以通过 FileChannel 从一个文件中读取数据, 也可以将数据写入到文件中. 76 | **`注意`**, FileChannel 不能设置为非阻塞模式. 77 | #### 打开 FileChannel 78 | ``` 79 | RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); 80 | FileChannel inChannel = aFile.getChannel(); 81 | ``` 82 | #### 从 FileChannel 中读取数据 83 | ``` 84 | ByteBuffer buf = ByteBuffer.allocate(48); 85 | int bytesRead = inChannel.read(buf); 86 | ``` 87 | #### 写入数据 88 | ``` 89 | String newData = "New String to write to file..." + System.currentTimeMillis(); 90 | 91 | ByteBuffer buf = ByteBuffer.allocate(48); 92 | buf.clear(); 93 | buf.put(newData.getBytes()); 94 | 95 | buf.flip(); 96 | 97 | while(buf.hasRemaining()) { 98 | channel.write(buf); 99 | } 100 | ``` 101 | #### 关闭 102 | 当我们对 FileChannel 的操作完成后, 必须将其关闭 103 | ``` 104 | channel.close(); 105 | ``` 106 | #### 设置 position 107 | ``` 108 | long pos channel.position(); 109 | channel.position(pos +123); 110 | ``` 111 | #### 文件大小 112 | 我们可以通过 channel.size()获取关联到这个 Channel 中的文件的大小. **注意**, 这里返回的是文件的大小, 而不是 Channel 中剩余的元素个数. 113 | #### 截断文件 114 | ``` 115 | channel.truncate(1024); 116 | ``` 117 | 将文件的大小截断为1024字节. 118 | #### 强制写入 119 | 我们可以强制将缓存的未写入的数据写入到文件中: 120 | ``` 121 | channel.force(true); 122 | ``` 123 | 124 | ### SocketChannel 125 | SocketChannel 是一个客户端用来进行 TCP 连接的 Channel. 126 | 创建一个 SocketChannel 的方法有两种: 127 | - 打开一个 SocketChannel, 然后将其连接到某个服务器中 128 | - 当一个 ServerSocketChannel 接受到连接请求时, 会返回一个 SocketChannel 对象. 129 | #### 打开 SocketChannel 130 | ``` 131 | SocketChannel socketChannel = SocketChannel.open(); 132 | socketChannel.connect(new InetSocketAddress("http://example.com", 80)); 133 | ``` 134 | #### 关闭 135 | ``` 136 | socketChannel.close(); 137 | ``` 138 | #### 读取数据 139 | ``` 140 | ByteBuffer buf = ByteBuffer.allocate(48); 141 | int bytesRead = socketChannel.read(buf); 142 | ``` 143 | 如果 read()返回 **-1**, 那么表示连接中断了. 144 | #### 写入数据 145 | ``` 146 | String newData = "New String to write to file..." + System.currentTimeMillis(); 147 | 148 | ByteBuffer buf = ByteBuffer.allocate(48); 149 | buf.clear(); 150 | buf.put(newData.getBytes()); 151 | 152 | buf.flip(); 153 | 154 | while(buf.hasRemaining()) { 155 | channel.write(buf); 156 | } 157 | ``` 158 | #### 非阻塞模式 159 | 我们可以设置 SocketChannel 为异步模式, 这样我们的 connect, read, write 都是异步的了. 160 | ##### 连接 161 | ``` 162 | socketChannel.configureBlocking(false); 163 | socketChannel.connect(new InetSocketAddress("http://example.com", 80)); 164 | 165 | while(! socketChannel.finishConnect() ){ 166 | //wait, or do something else... 167 | } 168 | ``` 169 | 在异步模式中, 或许连接还没有建立, connect 方法就返回了, 因此我们需要检查当前是否是连接到了主机, 因此通过一个 while 循环来判断. 170 | ##### 读写 171 | 在异步模式下, 读写的方式是一样的. 172 | 在读取时, 因为是异步的, 因此我们必须检查 read 的返回值, 来判断当前是否读取到了数据. 173 | 174 | ### ServerSocketChannel 175 | ServerSocketChannel 顾名思义, 是用在服务器为端的, 可以监听客户端的 TCP 连接, 例如: 176 | ``` 177 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 178 | serverSocketChannel.socket().bind(new InetSocketAddress(9999)); 179 | while(true){ 180 | SocketChannel socketChannel = 181 | serverSocketChannel.accept(); 182 | 183 | //do something with socketChannel... 184 | } 185 | ``` 186 | #### 打开 关闭 187 | ``` 188 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 189 | ``` 190 | ``` 191 | serverSocketChannel.close(); 192 | ``` 193 | #### 监听连接 194 | 我们可以使用ServerSocketChannel.accept()方法来监听客户端的 TCP 连接请求, accept()方法会阻塞, 直到有连接到来, 当有连接时, 这个方法会返回一个 SocketChannel 对象: 195 | ``` 196 | while(true){ 197 | SocketChannel socketChannel = 198 | serverSocketChannel.accept(); 199 | 200 | //do something with socketChannel... 201 | } 202 | ``` 203 | #### 非阻塞模式 204 | 在非阻塞模式下, accept()是非阻塞的, 因此如果此时没有连接到来, 那么 accept()方法会返回null: 205 | ``` 206 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 207 | 208 | serverSocketChannel.socket().bind(new InetSocketAddress(9999)); 209 | serverSocketChannel.configureBlocking(false); 210 | 211 | while(true){ 212 | SocketChannel socketChannel = 213 | serverSocketChannel.accept(); 214 | 215 | if(socketChannel != null){ 216 | //do something with socketChannel... 217 | } 218 | } 219 | ``` 220 | ### DatagramChannel 221 | DatagramChannel 是用来处理 UDP 连接的. 222 | #### 打开 223 | ``` 224 | DatagramChannel channel = DatagramChannel.open(); 225 | channel.socket().bind(new InetSocketAddress(9999)); 226 | ``` 227 | #### 读取数据 228 | ``` 229 | ByteBuffer buf = ByteBuffer.allocate(48); 230 | buf.clear(); 231 | 232 | channel.receive(buf); 233 | ``` 234 | #### 发送数据 235 | ``` 236 | String newData = "New String to write to file..." 237 | + System.currentTimeMillis(); 238 | 239 | ByteBuffer buf = ByteBuffer.allocate(48); 240 | buf.clear(); 241 | buf.put(newData.getBytes()); 242 | buf.flip(); 243 | 244 | int bytesSent = channel.send(buf, new InetSocketAddress("example.com", 80)); 245 | ``` 246 | #### 连接到指定地址 247 | 因为 UDP 是非连接的, 因此这个的 connect 并不是向 TCP 一样真正意义上的连接, 而是它会讲 DatagramChannel 锁住, 因此我们仅仅可以从指定的地址中读取或写入数据. 248 | ``` 249 | channel.connect(new InetSocketAddress("example.com", 80)); 250 | ``` 251 | 252 | ## Java NIO Buffer 253 | 当我们需要与 NIO Channel 进行交互时, 我们就需要使用到 NIO Buffer, 即数据从 Buffer读取到 Channel 中, 并且从 Channel 中写入到 Buffer 中. 254 | 实际上, 一个 Buffer 其实就是一块内存区域, 我们可以在这个内存区域中进行数据的读写. NIO Buffer 其实是这样的内存块的一个封装, 并提供了一些操作方法让我们能够方便地进行数据的读写. 255 | Buffer 类型有: 256 | - ByteBuffer 257 | - CharBuffer 258 | - DoubleBuffer 259 | - FloatBuffer 260 | - IntBuffer 261 | - LongBuffer 262 | - ShortBuffer 263 | 264 | 265 | 这些 Buffer 覆盖了能从 IO 中传输的所有的 Java 基本数据类型. 266 | ### NIO Buffer 的基本使用 267 | 使用 NIO Buffer 的步骤如下: 268 | - 将数据写入到 Buffer 中. 269 | - 调用 Buffer.flip()方法, 将 NIO Buffer 转换为读模式. 270 | - 从 Buffer 中读取数据 271 | - 调用 Buffer.clear() 或 Buffer.compact()方法, 将 Buffer 转换为写模式. 272 | 273 | 当我们将数据写入到 Buffer 中时, Buffer 会记录我们已经写了多少的数据, 当我们需要从 Buffer 中读取数据时, 必须调用 Buffer.flip()将 Buffer 切换为读模式. 274 | 一旦读取了所有的 Buffer 数据, 那么我们必须清理 Buffer, 让其从新可写, 清理 Buffer 可以调用 Buffer.clear() 或 Buffer.compact(). 275 | 例如: 276 | ``` 277 | public class Test { 278 | public static void main(String[] args) { 279 | IntBuffer intBuffer = IntBuffer.allocate(2); 280 | intBuffer.put(12345678); 281 | intBuffer.put(2); 282 | intBuffer.flip(); 283 | System.err.println(intBuffer.get()); 284 | System.err.println(intBuffer.get()); 285 | } 286 | } 287 | ``` 288 | 上述中, 我们分配两个单位大小的 IntBuffer, 因此它可以写入两个 int 值. 289 | 我们使用 put 方法将 int 值写入, 然后使用 flip 方法将 buffer 转换为读模式, 然后连续使用 get 方法从 buffer 中获取这两个 int 值. 290 | `每当调用一次 get 方法读取数据时, buffer 的读指针都会向前移动一个单位长度(在这里是一个 int 长度)` 291 | ### Buffer 属性 292 | 一个 Buffer 有三个属性: 293 | - capacity 294 | - position 295 | - limit 296 | 297 | 298 | 其中 **position** 和 **limit** 的含义与 Buffer 处于读模式或写模式有关, 而 capacity 的含义与 Buffer 所处的模式无关. 299 | 300 | #### Capacity 301 | 一个内存块会有一个固定的大小, 即容量(capacity), 我们最多写入**capacity** 个单位的数据到 Buffer 中, 例如一个 DoubleBuffer, 其 Capacity 是100, 那么我们最多可以写入100个 double 数据. 302 | #### Position 303 | 当从一个 Buffer 中写入数据时, 我们是从 Buffer 的一个确定的位置(position)开始写入的. 在最初的状态时, position 的值是0. 每当我们写入了一个单位的数据后, position 就会递增一. 304 | 当我们从 Buffer 中读取数据时, 我们也是从某个特定的位置开始读取的. **当我们调用了 filp()方法将 Buffer 从写模式转换到读模式时, position 的值会自动被设置为0,** 每当我们读取一个单位的数据, position 的值递增1. 305 | **position** 表示了读写操作的位置指针. 306 | #### limit 307 | `limit - position 表示此时还可以写入/读取多少单位的数据.` 308 | 例如在写模式, 如果此时 limit 是10, position 是2, 则表示已经写入了2个单位的数据, 还可以写入 10 - 2 = 8 个单位的数据. 309 | #### 例子: 310 | ``` 311 | public class Test { 312 | public static void main(String args[]) { 313 | IntBuffer intBuffer = IntBuffer.allocate(10); 314 | intBuffer.put(10); 315 | intBuffer.put(101); 316 | System.err.println("Write mode: "); 317 | System.err.println("\tCapacity: " + intBuffer.capacity()); 318 | System.err.println("\tPosition: " + intBuffer.position()); 319 | System.err.println("\tLimit: " + intBuffer.limit()); 320 | 321 | intBuffer.flip(); 322 | System.err.println("Read mode: "); 323 | System.err.println("\tCapacity: " + intBuffer.capacity()); 324 | System.err.println("\tPosition: " + intBuffer.position()); 325 | System.err.println("\tLimit: " + intBuffer.limit()); 326 | } 327 | } 328 | ``` 329 | 这里我们首先写入两个 int 值, 此时 capacity = 10, position = 2, limit = 10. 330 | 然后我们调用 flip 转换为读模式, 此时 capacity = 10, position = 0, limit = 2; 331 | ### 分配 Buffer 332 | 为了获取一个 Buffer 对象, 我们首先需要分配内存空间. 每个类型的 Buffer 都有一个 allocate()方法, 我们可以通过这个方法分配 Buffer: 333 | ``` 334 | ByteBuffer buf = ByteBuffer.allocate(48); 335 | ``` 336 | 这里我们分配了48 * sizeof(Byte)字节的内存空间. 337 | ``` 338 | CharBuffer buf = CharBuffer.allocate(1024); 339 | ``` 340 | 这里我们分配了大小为1024个字符的 Buffer, 即 这个 Buffer 可以存储1024 个 Char, 其大小为 1024 * 2 个字节. 341 | ### 关于 Direct Buffer 和 Non-Direct Buffer 的区别 342 | **Direct Buffer:** 343 | - 所分配的内存不在 JVM 堆上, 不受 GC 的管理.(但是 Direct Buffer 的 Java 对象是由 GC 管理的, 因此当发生 GC, 对象被回收时, Direct Buffer 也会被释放) 344 | - 因为 Direct Buffer 不在 JVM 堆上分配, 因此 Direct Buffer 对应用程序的内存占用的影响就不那么明显(实际上还是占用了这么多内存, 但是 JVM 不好统计到非 JVM 管理的内存.) 345 | - 申请和释放 Direct Buffer 的开销比较大. 因此正确的使用 Direct Buffer 的方式是在初始化时申请一个 Buffer, 然后不断复用此 buffer, 在程序结束后才释放此 buffer. 346 | - 使用 Direct Buffer 时, 当进行一些底层的系统 IO 操作时, 效率会比较高, 因为此时 JVM 不需要拷贝 buffer 中的内存到中间临时缓冲区中. 347 | 348 | **Non-Direct Buffer:** 349 | - 直接在 JVM 堆上进行内存的分配, 本质上是 byte[] 数组的封装. 350 | - 因为 Non-Direct Buffer 在 JVM 堆中, 因此当进行操作系统底层 IO 操作中时, 会将此 buffer 的内存复制到中间临时缓冲区中. 因此 Non-Direct Buffer 的效率就较低. 351 | 352 | 353 | ### 写入数据到 Buffer 354 | ``` 355 | int bytesRead = inChannel.read(buf); //read into buffer. 356 | buf.put(127); 357 | ``` 358 | ### 从 Buffer 中读取数据 359 | ``` 360 | //read from buffer into channel. 361 | int bytesWritten = inChannel.write(buf); 362 | byte aByte = buf.get(); 363 | ``` 364 | ### 重置 position 365 | Buffer.rewind()方法可以重置 position 的值为0, 因此我们可以重新读取/写入 Buffer 了. 366 | 如果是读模式, 则重置的是读模式的 position, 如果是写模式, 则重置的是写模式的 position. 367 | 例如: 368 | ``` 369 | public class Test { 370 | public static void main(String[] args) { 371 | IntBuffer intBuffer = IntBuffer.allocate(2); 372 | intBuffer.put(1); 373 | intBuffer.put(2); 374 | System.err.println("position: " + intBuffer.position()); 375 | 376 | intBuffer.rewind(); 377 | System.err.println("position: " + intBuffer.position()); 378 | intBuffer.put(1); 379 | intBuffer.put(2); 380 | System.err.println("position: " + intBuffer.position()); 381 | 382 | 383 | intBuffer.flip(); 384 | System.err.println("position: " + intBuffer.position()); 385 | intBuffer.get(); 386 | intBuffer.get(); 387 | System.err.println("position: " + intBuffer.position()); 388 | 389 | intBuffer.rewind(); 390 | System.err.println("position: " + intBuffer.position()); 391 | } 392 | } 393 | ``` 394 | `rewind() 主要针对于读模式. 在读模式时, 读取到 limit 后, 可以调用 rewind() 方法, 将读 position 置为0.` 395 | ### 关于 mark()和 reset() 396 | 我们可以通过调用 Buffer.mark()将当前的 position 的值保存起来, 随后可以通过调用 Buffer.reset()方法将 position 的值回复回来. 397 | 例如: 398 | ``` 399 | public class Test { 400 | public static void main(String[] args) { 401 | IntBuffer intBuffer = IntBuffer.allocate(2); 402 | intBuffer.put(1); 403 | intBuffer.put(2); 404 | intBuffer.flip(); 405 | System.err.println(intBuffer.get()); 406 | System.err.println("position: " + intBuffer.position()); 407 | intBuffer.mark(); 408 | System.err.println(intBuffer.get()); 409 | 410 | System.err.println("position: " + intBuffer.position()); 411 | intBuffer.reset(); 412 | System.err.println("position: " + intBuffer.position()); 413 | System.err.println(intBuffer.get()); 414 | } 415 | } 416 | ``` 417 | 这里我们写入两个 int 值, 然后首先读取了一个值. 此时读 position 的值为1. 418 | 接着我们调用 mark() 方法将当前的 position 保存起来(在读模式, 因此保存的是读的 position), 然后再次读取, 此时 position 就是2了. 419 | 接着使用 reset() 恢复原来的读 position, 因此读 position 就为1, 可以再次读取数据. 420 | 421 | ### flip, rewind 和 clear 的区别 422 | #### flip 423 | 方法源码: 424 | ``` 425 | public final Buffer flip() { 426 | limit = position; 427 | position = 0; 428 | mark = -1; 429 | return this; 430 | } 431 | ``` 432 | Buffer 的读/写模式共用一个 position 和 limit 变量. 433 | 当从写模式变为读模式时, 原先的 **写 position** 就变成了读模式的 **limit**. 434 | #### rewind 435 | 方法源码 436 | ``` 437 | public final Buffer rewind() { 438 | position = 0; 439 | mark = -1; 440 | return this; 441 | } 442 | ``` 443 | rewind, 即倒带, 这个方法仅仅是将 position 置为0. 444 | #### clear 445 | 方法源码: 446 | ``` 447 | public final Buffer clear() { 448 | position = 0; 449 | limit = capacity; 450 | mark = -1; 451 | return this; 452 | } 453 | ``` 454 | 根据源码我们可以知道, clear 将 positin 设置为0, 将 limit 设置为 capacity. 455 | clear 方法使用场景: 456 | - 在一个已经写满数据的 buffer 中, 调用 clear, 可以从头读取 buffer 的数据. 457 | - 为了将一个 buffer 填充满数据, 可以调用 clear, 然后一直写入, 直到达到 limit. 458 | 459 | 460 | ##### 例子: 461 | ``` 462 | IntBuffer intBuffer = IntBuffer.allocate(2); 463 | intBuffer.flip(); 464 | System.err.println("position: " + intBuffer.position()); 465 | System.err.println("limit: " + intBuffer.limit()); 466 | System.err.println("capacity: " + intBuffer.capacity()); 467 | 468 | // 这里不能读, 因为 limit == position == 0, 没有数据. 469 | //System.err.println(intBuffer.get()); 470 | 471 | intBuffer.clear(); 472 | System.err.println("position: " + intBuffer.position()); 473 | System.err.println("limit: " + intBuffer.limit()); 474 | System.err.println("capacity: " + intBuffer.capacity()); 475 | 476 | // 这里可以读取数据了, 因为 clear 后, limit == capacity == 2, position == 0, 477 | // 即使我们没有写入任何的数据到 buffer 中. 478 | System.err.println(intBuffer.get()); // 读取到0 479 | System.err.println(intBuffer.get()); // 读取到0 480 | ``` 481 | ### Buffer 的比较 482 | 我们可以通过 equals() 或 compareTo() 方法比较两个 Buffer, 当且仅当如下条件满足时, 两个 Buffer 是相等的: 483 | - 两个 Buffer 是相同类型的 484 | - 两个 Buffer 的剩余的数据个数是相同的 485 | - 两个 Buffer 的剩余的数据都是相同的. 486 | 487 | 通过上述条件我们可以发现, 比较两个 Buffer 时, 并不是 Buffer 中的每个元素都进行比较, 而是比较 Buffer 中剩余的元素. 488 | 489 | ## Selector 490 | Selector 允许一个单一的线程来操作多个 Channel. 如果我们的应用程序中使用了多个 Channel, 那么使用 Selector 很方便的实现这样的目的, 但是因为在一个线程中使用了多个 Channel, 因此也会造成了每个 Channel 传输效率的降低. 491 | 使用 Selector 的图解如下: 492 | ![Alt text](./Selector 图解.png) 493 | 494 | 495 | 为了使用 Selector, 我们首先需要将 Channel 注册到 Selector 中, 随后调用 Selector 的 select()方法, 这个方法会阻塞, 直到注册在 Selector 中的 Channel 发送可读写事件. 当这个方法返回后, 当前的这个线程就可以处理 Channel 的事件了. 496 | ### 创建选择器 497 | 通过 Selector.open()方法, 我们可以创建一个选择器: 498 | ``` 499 | Selector selector = Selector.open(); 500 | ``` 501 | ### 将 Channel 注册到选择器中 502 | 为了使用选择器管理 Channel, 我们需要将 Channel 注册到选择器中: 503 | ``` 504 | channel.configureBlocking(false); 505 | 506 | SelectionKey key = channel.register(selector, SelectionKey.OP_READ); 507 | ``` 508 | >**`注意`**, 如果一个 Channel 要注册到 Selector 中, 那么这个 Channel 必须是非阻塞的, 即channel.configureBlocking(false); 509 | >因为 Channel 必须要是非阻塞的, 因此 FileChannel 是不能够使用选择器的, 因为 FileChannel 都是阻塞的. 510 | 511 | 注意到, 在使用 Channel.register()方法时, 第二个参数指定了我们对 Channel 的什么类型的事件感兴趣, 这些事件有: 512 | - Connect, 即连接事件(TCP 连接), 对应于SelectionKey.OP_CONNECT 513 | - Accept, 即确认事件, 对应于SelectionKey.OP_ACCEPT 514 | - Read, 即读事件, 对应于SelectionKey.OP_READ, 表示 buffer 可读. 515 | - Write, 即写事件, 对应于SelectionKey.OP_WRITE, 表示 buffer 可写. 516 | 517 | 一个 Channel发出一个事件也可以称为** 对于某个事件, Channel 准备好了**. 因此一个 Channel 成功连接到了另一个服务器也可以被称为** connect ready**. 518 | 我们可以使用或运算**|**来组合多个事件, 例如: 519 | ``` 520 | int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 521 | ``` 522 | `注意, 一个 Channel 仅仅可以被注册到一个 Selector 一次, 如果将 Channel 注册到 Selector 多次, 那么其实就是相当于更新 SelectionKey 的 interest set`. 例如: 523 | ``` 524 | channel.register(selector, SelectionKey.OP_READ); 525 | channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); 526 | ``` 527 | 上面的 channel 注册到同一个 Selector 两次了, 那么第二次的注册其实就是相当于更新这个 Channel 的 interest set 为 SelectionKey.OP_READ | SelectionKey.OP_WRITE. 528 | ### 关于 SelectionKey 529 | 如上所示, 当我们使用 register 注册一个 Channel 时, 会返回一个 SelectionKey 对象, 这个对象包含了如下内容: 530 | - interest set, 即我们感兴趣的事件集, 即在调用 register 注册 channel 时所设置的 interest set. 531 | - ready set 532 | - channel 533 | - selector 534 | - attached object, 可选的附加对象 535 | 536 | 537 | #### interest set 538 | 我们可以通过如下方式获取 interest set: 539 | ``` 540 | int interestSet = selectionKey.interestOps(); 541 | 542 | boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; 543 | boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; 544 | boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; 545 | boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; 546 | ``` 547 | #### ready set 548 | 代表了 Channel 所准备好了的操作. 549 | 我们可以像判断 interest set 一样操作 Ready set, 但是我们还可以使用如下方法进行判断: 550 | ``` 551 | int readySet = selectionKey.readyOps(); 552 | 553 | selectionKey.isAcceptable(); 554 | selectionKey.isConnectable(); 555 | selectionKey.isReadable(); 556 | selectionKey.isWritable(); 557 | ``` 558 | #### Channel 和 Selector 559 | 我们可以通过 SelectionKey 获取相对应的 Channel 和 Selector: 560 | ``` 561 | Channel channel = selectionKey.channel(); 562 | Selector selector = selectionKey.selector(); 563 | ``` 564 | #### Attaching Object 565 | 我们可以在selectionKey中附加一个对象: 566 | ``` 567 | selectionKey.attach(theObject); 568 | Object attachedObj = selectionKey.attachment(); 569 | ``` 570 | 或者在注册时直接附加: 571 | ``` 572 | SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject); 573 | ``` 574 | 575 | ### 通过 Selector 选择 Channel 576 | 我们可以通过 Selector.select()方法获取对某件事件准备好了的 Channel, 即如果我们在注册 Channel 时, 对其的**可写**事件感兴趣, 那么当 select()返回时, 我们就可以获取 Channel 了. 577 | >**`注意`**, select()方法返回的值表示有多少个 Channel 可操作. 578 | 579 | 580 | ### 获取可操作的 Channel 581 | 如果 select()方法返回值表示有多个 Channel 准备好了, 那么我们可以通过 Selected key set 访问这个 Channel: 582 | ``` 583 | Set selectedKeys = selector.selectedKeys(); 584 | 585 | Iterator keyIterator = selectedKeys.iterator(); 586 | 587 | while(keyIterator.hasNext()) { 588 | 589 | SelectionKey key = keyIterator.next(); 590 | 591 | if(key.isAcceptable()) { 592 | // a connection was accepted by a ServerSocketChannel. 593 | 594 | } else if (key.isConnectable()) { 595 | // a connection was established with a remote server. 596 | 597 | } else if (key.isReadable()) { 598 | // a channel is ready for reading 599 | 600 | } else if (key.isWritable()) { 601 | // a channel is ready for writing 602 | } 603 | 604 | keyIterator.remove(); 605 | } 606 | ``` 607 | 608 | 609 | `注意, 在每次迭代时, 我们都调用 "keyIterator.remove()" 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.` 610 | 611 | 612 | 例如此时我们收到 OP_ACCEPT 通知, 然后我们进行相关处理, 但是并没有将这个 Key 从 SelectedKeys 中删除, 那么下一次 select() 返回时 我们还可以在 SelectedKeys 中获取到 OP_ACCEPT 的 key. 613 | 614 | 615 | `注意, 我们可以动态更改 SekectedKeys 中的 key 的 interest set.` 616 | 617 | 例如在 OP_ACCEPT 中, 我们可以将 interest set 更新为 OP_READ, 这样 Selector 就会将这个 Channel 的 读 IO 就绪事件包含进来了. 618 | 619 | ### Selector 的基本使用流程 620 | 1. 通过 Selector.open() 打开一个 Selector. 621 | 2. 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set) 622 | 3. 不断重复: 623 | - 调用 select() 方法 624 | - 调用 selector.selectedKeys() 获取 selected keys 625 | - 迭代每个 selected key: 626 | - *从 selected key 中获取 对应的 Channel 和附加信息(如果有的话) 627 | - *判断是哪些 IO 事件已经就绪了, 然后处理它们. **如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.** 628 | - *根据需要更改 selected key 的监听事件. 629 | - *将已经处理过的 key 从 selected keys 集合中删除. 630 | 631 | 632 | ### 关闭 Selector 633 | 当调用了 Selector.close()方法时, 我们其实是关闭了 Selector 本身并且将所有的 SelectionKey 失效, 但是并不会关闭 Channel. 634 | 635 | 636 | ### 完整的 Selector 例子 637 | ``` 638 | public class NioEchoServer { 639 | private static final int BUF_SIZE = 256; 640 | private static final int TIMEOUT = 3000; 641 | 642 | public static void main(String args[]) throws Exception { 643 | // 打开服务端 Socket 644 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 645 | 646 | // 打开 Selector 647 | Selector selector = Selector.open(); 648 | 649 | // 服务端 Socket 监听8080端口, 并配置为非阻塞模式 650 | serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 651 | serverSocketChannel.configureBlocking(false); 652 | 653 | // 将 channel 注册到 selector 中. 654 | // 通常我们都是先注册一个 OP_ACCEPT 事件, 然后在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 655 | // 注册到 Selector 中. 656 | serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 657 | 658 | while (true) { 659 | // 通过调用 select 方法, 阻塞地等待 channel I/O 可操作 660 | if (selector.select(TIMEOUT) == 0) { 661 | System.out.print("."); 662 | continue; 663 | } 664 | 665 | // 获取 I/O 操作就绪的 SelectionKey, 通过 SelectionKey 可以知道哪些 Channel 的哪类 I/O 操作已经就绪. 666 | Iterator keyIterator = selector.selectedKeys().iterator(); 667 | 668 | while (keyIterator.hasNext()) { 669 | 670 | // 当获取一个 SelectionKey 后, 就要将它删除, 表示我们已经对这个 IO 事件进行了处理. 671 | keyIterator.remove(); 672 | 673 | SelectionKey key = keyIterator.next(); 674 | 675 | if (key.isAcceptable()) { 676 | // 当 OP_ACCEPT 事件到来时, 我们就有从 ServerSocketChannel 中获取一个 SocketChannel, 677 | // 代表客户端的连接 678 | // 注意, 在 OP_ACCEPT 事件中, 从 key.channel() 返回的 Channel 是 ServerSocketChannel. 679 | // 而在 OP_WRITE 和 OP_READ 中, 从 key.channel() 返回的是 SocketChannel. 680 | SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); 681 | clientChannel.configureBlocking(false); 682 | //在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 注册到 Selector 中. 683 | // 注意, 这里我们如果没有设置 OP_READ 的话, 即 interest set 仍然是 OP_CONNECT 的话, 那么 select 方法会一直直接返回. 684 | clientChannel.register(key.selector(), OP_READ, ByteBuffer.allocate(BUF_SIZE)); 685 | } 686 | 687 | if (key.isReadable()) { 688 | SocketChannel clientChannel = (SocketChannel) key.channel(); 689 | ByteBuffer buf = (ByteBuffer) key.attachment(); 690 | long bytesRead = clientChannel.read(buf); 691 | if (bytesRead == -1) { 692 | clientChannel.close(); 693 | } else if (bytesRead > 0) { 694 | key.interestOps(OP_READ | SelectionKey.OP_WRITE); 695 | System.out.println("Get data length: " + bytesRead); 696 | } 697 | } 698 | 699 | if (key.isValid() && key.isWritable()) { 700 | ByteBuffer buf = (ByteBuffer) key.attachment(); 701 | buf.flip(); 702 | SocketChannel clientChannel = (SocketChannel) key.channel(); 703 | 704 | clientChannel.write(buf); 705 | 706 | if (!buf.hasRemaining()) { 707 | key.interestOps(OP_READ); 708 | } 709 | buf.compact(); 710 | } 711 | } 712 | } 713 | } 714 | } 715 | ``` -------------------------------------------------------------------------------- /Netty 源码分析之 番外篇 Java NIO 的前生今世/Selector 图解.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 番外篇 Java NIO 的前生今世/Selector 图解.png -------------------------------------------------------------------------------- /Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/EchoServer 运行.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/EchoServer 运行.png -------------------------------------------------------------------------------- /Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Github Netty 源码 clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Github Netty 源码 clone.png -------------------------------------------------------------------------------- /Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Netty Maven 依赖.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Netty Maven 依赖.png -------------------------------------------------------------------------------- /Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建.md: -------------------------------------------------------------------------------- 1 | # Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建 2 | @(Netty)[Java, Netty, Netty 源码分析, Bootstrap] 3 | [TOC] 4 | 5 | 6 | ---------- 7 | 8 | ## 代码下载 9 | 首先到 Netty 的 [Github 仓库](https://github.com/netty/netty) 中, 点击右边绿色的按钮: 10 | ![Alt text](./Github Netty 源码 clone.png) 11 | 拷贝 git 地址: git@github.com:netty/netty.git 12 | 然后在终端中输入如下命令, 克隆 Netty 工程: 13 | ``` 14 | /Users/xiongyongshun/works/learn_netty 15 | >>> git clone git@github.com:netty/netty.git 16 | Cloning into 'netty'... 17 | ``` 18 | Netty 工程源码较大, 加上国内网络问题, 下载 Netty 源码可能会比较耗时. 19 | 当有如下输出时, 表示克隆成功了: 20 | ``` 21 | /Users/xiongyongshun/works/learn_netty 22 | >>> git clone git@github.com:netty/netty.git 23 | Cloning into 'netty'... 24 | remote: Counting objects: 210696, done. 25 | remote: Compressing objects: 100% (93/93), done. 26 | remote: Total 210696 (delta 33), reused 0 (delta 0), pack-reused 210587 27 | Receiving objects: 100% (210696/210696), 40.38 MiB | 155.00 KiB/s, done. 28 | Resolving deltas: 100% (93595/93595), done. 29 | Checking connectivity... done. 30 | ``` 31 | 如果有朋友实在下载太慢, 可以使用如下命令直接下载某个分支: 32 | ``` 33 | git clone -b netty-4.0.33.Final --single-branch git@github.com:netty/netty.git 34 | ``` 35 | 当下载好后, 可以创建我们自己的一个分支, 用于代码的学习: 36 | ``` 37 | git checkout netty-4.0.33.Final 38 | git checkout -b learn_netty_4.0.33 39 | ``` 40 | 我们从 Netty 的 4.0.33.Final tag 中创建了自己的分支 **learn_netty_4.0.33**, 便于我们的源码的学习. 41 | 42 | ## IntelliJ IDEA 配置 43 | 用 IntelliJ IDEA 打开工程, 工程结构如下: 44 | ![Alt text](./Netty 源码工程结构.png) 45 | 一般情况下, 此时我们就可以打开 example 目录, 运行下面的例子了. 46 | 不过有些读者可能会遇到 Maven 依赖下不全的问题, 此时我们可以点击右边的 Maven Project 标签, 然后点击蓝色的刷新按钮: 47 | ![Alt text](./Netty Maven 依赖.png). 48 | 49 | 接下来我们可以运行 example 下的 EchoServer 看看: 50 | ![Alt text](./EchoServer 运行.png) 51 | 如上图所示, 点击左边的绿色三角箭头即可运行了. -------------------------------------------------------------------------------- /Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Netty 源码工程结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建/Netty 源码工程结构.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 在工作中, 虽然我经常使用到 Netty 库, 但是很多时候对 Netty 的一些概念还是处于知其然, 不知其所以然的状态, 因此就萌生了学习 Netty 源码的想法. 2 | 刚开始看源码的时候, 自然是比较痛苦的, 主要原因有两个: 第一, 网上没有和详尽的 Netty 源码分析的教程; 第二, 我也是第一次系统地学习这么大代码量的源码. 由于这两个原因, 最开始时, 看代码的进度很慢, 甚至一度想放弃了, 不过最后很庆幸自己能够坚持下去, 并因此从 Netty 源码中学到了很多宝贵的知识. 3 | 4 | 下面我将自己在 Netty 源码学习过程记录下来, 整理成博客, 与大家分享交流, 共同学习. 由于本人才疏学浅, 文章中难免有不少错误之处, 期待能得到大家的建议和斧正. 5 | 6 | 最后, 忘了提了, 我使用的 Netty 版本: **4.0.33.Final** 7 | -------------------------------------------------------------------------------- /issues/记一次有趣的 Netty 源码问题/关于 Channel 注册与绑定的时序问题.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongshun/learn_netty_source_code/f129f37978e29746f07ea6a8baef2479ee3b0593/issues/记一次有趣的 Netty 源码问题/关于 Channel 注册与绑定的时序问题.png -------------------------------------------------------------------------------- /issues/记一次有趣的 Netty 源码问题/记一次有趣的 Netty 源码问题.md: -------------------------------------------------------------------------------- 1 | # 记一次有趣的 Netty 源码问题 2 | 3 | 4 | 5 | [TOC] 6 | 7 | 8 | ---------- 9 | 10 | ## 背景 11 | 起因是一个朋友问我的一个关于 [ServerBootstrap 启动的问题](http://stackoverflow.com/questions/40482830/is-there-a-possibility-that-pipeline-firechannelactive-is-executed-twice-when). 12 | [相关 issue](https://github.com/yongshun/learn_netty_source_code/issues/1) 13 | 14 | 他的问题我复述一下: 15 | ServerBootstrap 的绑定流程如下: 16 | ```java 17 | ServerBootstrap.bind -> 18 | AbstractBootstrap.bind -> 19 | AbstractBootstrap.doBind -> 20 | AbstractBootstrap.initAndRegister -> 21 | AbstractChannel#AbstractUnsafe.register -> 22 | eventLoop.execute( () -> AbstractUnsafe.register0) 23 | doBind0() -> 24 | channel.eventLoop().execute( () -> channel.bind) -> 25 | AbstractUnsafe.bind 26 | ``` 27 | 在 **AbstractUnsafe.register0** 中可能会调用 **pipeline.fireChannelActive()**, 即: 28 | ```java 29 | private void register0(ChannelPromise promise) { 30 | try { 31 | ... 32 | boolean firstRegistration = neverRegistered; 33 | doRegister(); 34 | ... 35 | if (firstRegistration && isActive()) { 36 | pipeline.fireChannelActive(); 37 | } 38 | } catch (Throwable t) { 39 | ... 40 | } 41 | } 42 | ``` 43 | 并且在 **AbstractUnsafe.bind** 中也会有 **pipeline.fireChannelActive()** 的调用, 即: 44 | ```java 45 | public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { 46 | ... 47 | boolean wasActive = isActive(); 48 | try { 49 | doBind(localAddress); 50 | } catch (Throwable t) { 51 | ... 52 | } 53 | 54 | if (!wasActive && isActive()) { 55 | invokeLater(new OneTimeTask() { 56 | @Override 57 | public void run() { 58 | pipeline.fireChannelActive(); 59 | } 60 | }); 61 | } 62 | ... 63 | } 64 | ``` 65 | 66 | 那么有没有可能造成了两次的 **pipeline.fireChannelActive()** 调用? 67 | 68 | 我的回答是不会. 为什么呢? 对于直接想知道答案的朋友可以直接阅读到最后面的 **回答** 与 **总结** 两节.. 69 | 70 | 下面我们就来根据代码详细分析一下. 71 | ## 分析 72 | 首先, 根据我们上面所列出的调用流程, 会有 **AbstractBootstrap.doBind** 的调用, 它的代码如下: 73 | ```java 74 | private ChannelFuture doBind(final SocketAddress localAddress) { 75 | // 步骤1 76 | final ChannelFuture regFuture = initAndRegister(); 77 | ... 78 | // 步骤2 79 | if (regFuture.isDone()) { 80 | ... 81 | doBind0(regFuture, channel, localAddress, promise); 82 | ... 83 | } else { 84 | regFuture.addListener(new ChannelFutureListener() { 85 | @Override 86 | public void operationComplete(ChannelFuture future) throws Exception { 87 | ... 88 | doBind0(regFuture, channel, localAddress, promise); 89 | } 90 | }); 91 | } 92 | } 93 | ``` 94 | 首先在 doBind 中, 执行步骤1, 即调用 **initAndRegister** 方法, 这个方法会最终调用到**AbstractChannel#AbstractUnsafe.register**. 而在 **AbstractChannel#AbstractUnsafe.register** 中, 会通过 eventLoop.execute 的形式将 AbstractUnsafe.register0 的调用提交到任务队列中(即提交到 eventLoop 线程中, 而当前代码所在的线程是 main 线程), 即: 95 | ```java 96 | Override 97 | public final void register(EventLoop eventLoop, final ChannelPromise promise) { 98 | // 当前线程是主线程, 因此这个判断是 false 99 | if (eventLoop.inEventLoop()) { 100 | register0(promise); 101 | } else { 102 | try { 103 | eventLoop.execute(new OneTimeTask() { 104 | @Override 105 | public void run() { 106 | // register0 在 eventLoop 线程中执行. 107 | register0(promise); 108 | } 109 | }); 110 | } catch (Throwable t) { 111 | ... 112 | } 113 | } 114 | } 115 | ``` 116 | 接着 AbstractBootstrap.initAndRegister 返回, 回到 **AbstractBootstrap.doBind** 中, 于是执行到步骤2. 注意, 因为 **AbstractUnsafe.register0** 是在 **eventLoop** 中执行的, 因此有可能主线程执行到步骤2 时, **AbstractUnsafe.register0** 已经执行完毕了, 此时必然有 **regFuture.isDone() == true**; 但也有可能 **AbstractUnsafe.register0** 没有来得及执行, 因此此时 **regFuture.isDone() == false**. 所以上面的步骤2 考虑到了这两种情况, 因此分别针对这两种情况做了区分, 即: 117 | ```java 118 | // 步骤2 119 | if (regFuture.isDone()) { 120 | ... 121 | doBind0(regFuture, channel, localAddress, promise); 122 | ... 123 | } else { 124 | regFuture.addListener(new ChannelFutureListener() { 125 | @Override 126 | public void operationComplete(ChannelFuture future) throws Exception { 127 | ... 128 | doBind0(regFuture, channel, localAddress, promise); 129 | } 130 | }); 131 | } 132 | ``` 133 | 134 | 一般情况下, **regFuture.isDone()** 为 false, 因为绑定操作是比较费时的, 因此很大几率会执行到 else 分支, 并且 if 分支和 else 分支从结果上说没有不同, 而且 if 分支逻辑还更简单一些, 因此我们以 else 分支来分析吧. 在 else 分支中, 会为 **regFuture** 设置一个回调监听器. **regFuture** 是一个 `ChannelFuture`, 而 `ChannelFuture` 代表了一个 Channel 的异步 IO 的操作结果, 因此这里 `regFuture` 代表了 `Channel 注册(register)` 的这个异步 IO 的操作结果. 135 | Netty 这里之所以要为 `regFuture` 设置一个回调监听器, 是为了保证 register 和 bind 的时序上的正确性: `Channel 的注册必须要发生在 Channel 的绑定之前`. 136 | (关于时序的正确性的问题, 我们在后面有证明) 137 | 138 | 139 | 接下来我们来看一下 `AbstractUnsafe.register0` 方法: 140 | ```java 141 | private void register0(ChannelPromise promise) { 142 | try { 143 | .... 144 | // neverRegistered 一开始是 true, 因此 firstRegistration == true 145 | boolean firstRegistration = neverRegistered; 146 | doRegister(); 147 | neverRegistered = false; 148 | registered = true; 149 | safeSetSuccess(promise); 150 | pipeline.fireChannelRegistered(); 151 | // Only fire a channelActive if the channel has never been registered. This prevents firing 152 | // multiple channel actives if the channel is deregistered and re-registered. 153 | // firstRegistration == true, 而 isActive() == false, 154 | // 因此不会执行到 pipeline.fireChannelActive() 155 | if (firstRegistration && isActive()) { 156 | pipeline.fireChannelActive(); 157 | } 158 | } catch (Throwable t) { 159 | // Close the channel directly to avoid FD leak. 160 | closeForcibly(); 161 | closeFuture.setClosed(); 162 | safeSetFailure(promise, t); 163 | } 164 | } 165 | ``` 166 | `注意, 我需要再强调一下, 这里 AbstractUnsafe.register0 是在 eventLoop 中执行的.` 167 | AbstractUnsafe.register0 中会调用 **doRegister()** 注册 **NioServerSocketChannel**, 然后调用 **safeSetSuccess()** 设置 `promise` 的状态为成功. 而这个 `promise` 变量是什么呢? 我将 AbstractBootstrap.doBind 的调用链写详细一些: 168 | ```java 169 | AbstractBootstrap.doBind -> 170 | AbstractBootstrap.initAndRegister -> 171 | MultithreadEventLoopGroup.register -> 172 | SingleThreadEventLoop.register -> 173 | AbstractChannel#AbstractUnsafe.register -> 174 | eventLoop.execute( () -> AbstractUnsafe.register0) 175 | ``` 176 | 在 SingleThreadEventLoop.register 中会实例化一个 DefaultChannelPromise, 即: 177 | ```java 178 | @Override 179 | public ChannelFuture register(Channel channel) { 180 | return register(channel, new DefaultChannelPromise(channel, this)); 181 | } 182 | ``` 183 | 接着调用重载的 SingleThreadEventLoop.register 方法: 184 | ```java 185 | @Override 186 | public ChannelFuture register(final Channel channel, final ChannelPromise promise) { 187 | if (channel == null) { 188 | throw new NullPointerException("channel"); 189 | } 190 | if (promise == null) { 191 | throw new NullPointerException("promise"); 192 | } 193 | 194 | channel.unsafe().register(this, promise); 195 | return promise; 196 | } 197 | ``` 198 | 我们看到, 实例化的 **DefaultChannelPromise** 最终会以方法返回值的方式返回到调用方, 即返回到 **AbstractBootstrap.doBind** 中: 199 | ```java 200 | final ChannelFuture regFuture = initAndRegister(); 201 | ``` 202 | 因此我们这里有一个共识: regFuture 是一个在 **SingleThreadEventLoop.register** 中实例化的 `DefaultChannelPromise` 对象. 203 | 204 | 205 | 再回到 **SingleThreadEventLoop.register** 中, 在这里会调用 **channel.unsafe().register(this, promise)**, 将 promise 对象传递到 **AbstractChannel#AbstractUnsafe.register** 中, 因此在 **AbstractUnsafe.register0** 中的 `promise` 就是 **AbstractBootstrap.doBind** 中的 `regFuture`. 206 | `promise == regFuture` 很关键. 207 | 208 | 既然我们已经确定了 `promise` 的身份, 那么调用的 **safeSetSuccess(promise);** 我们也知道是干嘛的了. **safeSetSuccess** 方法设置一个 Promise 的状态为`成功态`, 而 Promise 的 `成功态` 是最终状态, 即此时 `promise.isDone() == true`. 那么 设置 promise 为`成功态`后, 会发生什么呢? 209 | 还记得不 `promise == regFuture`, 而我们在 **AbstractBootstrap.doBind** 的 else 分支中设置了一个回调监听器: 210 | ```java 211 | final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); 212 | regFuture.addListener(new ChannelFutureListener() { 213 | @Override 214 | public void operationComplete(ChannelFuture future) throws Exception { 215 | Throwable cause = future.cause(); 216 | if (cause != null) { 217 | // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an 218 | // IllegalStateException once we try to access the EventLoop of the Channel. 219 | promise.setFailure(cause); 220 | } else { 221 | // Registration was successful, so set the correct executor to use. 222 | // See https://github.com/netty/netty/issues/2586 223 | promise.executor = channel.eventLoop(); 224 | } 225 | doBind0(regFuture, channel, localAddress, promise); 226 | } 227 | }); 228 | ``` 229 | 因此当 **safeSetSuccess(promise);** 调用时, 根据 Netty 的 Promise/Future 机制, 会触发上面的 **operationComplete** 回调, 在回调中调用 `doBind0` 方法: 230 | ```java 231 | private static void doBind0( 232 | final ChannelFuture regFuture, final Channel channel, 233 | final SocketAddress localAddress, final ChannelPromise promise) { 234 | // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up 235 | // the pipeline in its channelRegistered() implementation. 236 | channel.eventLoop().execute(new Runnable() { 237 | @Override 238 | public void run() { 239 | if (regFuture.isSuccess()) { 240 | channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 241 | } else { 242 | promise.setFailure(regFuture.cause()); 243 | } 244 | } 245 | }); 246 | } 247 | ``` 248 | `注意到, 有一个关键的地方, 代码中将 **channel.bind** 的调用放到了 eventLoop 中执行`. doBind0 返回后, 代码继续执行 `AbstractUnsafe.register0` 方法的剩余部分代码, 即: 249 | ```java 250 | private void register0(ChannelPromise promise) { 251 | try { 252 | .... 253 | safeSetSuccess(promise); 254 | // safeSetSuccess 返回后, 继续执行如下代码 255 | pipeline.fireChannelRegistered(); 256 | // Only fire a channelActive if the channel has never been registered. This prevents firing 257 | // multiple channel actives if the channel is deregistered and re-registered. 258 | // firstRegistration == true, 而 isActive() == false, 259 | // 因此不会执行到 pipeline.fireChannelActive() 260 | if (firstRegistration && isActive()) { 261 | pipeline.fireChannelActive(); 262 | } 263 | } catch (Throwable t) { 264 | // Close the channel directly to avoid FD leak. 265 | closeForcibly(); 266 | closeFuture.setClosed(); 267 | safeSetFailure(promise, t); 268 | } 269 | } 270 | ``` 271 | 272 | 当 `AbstractUnsafe.register0` 方法执行完毕后, 才执行到 **channel.bind** 方法. 273 | 274 | 275 | 而 **channel.bind** 方法最终会调用到 **AbstractChannel#AbstractUnsafe.bind** 方法, 源码如下: 276 | ```java 277 | @Override 278 | public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { 279 | boolean wasActive = isActive(); 280 | logger.info("---wasActive: {}---", wasActive); 281 | 282 | try { 283 | // 调用 NioServerSocketChannel.bind 方法, 284 | // 将底层的 Java NIO SocketChannel 绑定到指定的端口. 285 | // 当 SocketChannel 绑定到端口后, isActive() 才为真. 286 | doBind(localAddress); 287 | } catch (Throwable t) { 288 | ... 289 | } 290 | 291 | boolean activeNow = isActive(); 292 | logger.info("---activeNow: {}---", activeNow); 293 | 294 | // 这里 wasActive == false 295 | // isActive() == true 296 | if (!wasActive && isActive()) { 297 | invokeLater(new OneTimeTask() { 298 | @Override 299 | public void run() { 300 | pipeline.fireChannelActive(); 301 | } 302 | }); 303 | } 304 | 305 | safeSetSuccess(promise); 306 | } 307 | ``` 308 | 上面的代码中, 调用了 **doBind(localAddress)** 将底层的 Java NIO SocketChannel 绑定到指定的端口. 并且`当 SocketChannel 绑定到端口后, isActive() 才为真.` 309 | 因此我们知道, 如果 SocketChannel 第一次绑定时, 在调用 doBind 前, `wasActive == false == isActive()`, 而当调用了 **doBind** 后, `isActive() == true`, 因此第一次绑定端口时, if 判断成立, 会调用 **pipeline.fireChannelActive()**. 310 | 311 | ## 关于 Channel 注册与绑定的时序问题 312 | 我们在前的分析中, 直接认定了 `Channel 注册` 在 `Channel 的绑定` 之前完成, 那么依据是什么呢? 313 | 其实所有的关键在于 [EventLoop 的任务队列机制](https://segmentfault.com/a/1190000007403937#articleHeader4). 314 | 不要闲我啰嗦哦. 我们需要继续回到 `AbstractUnsafe.register0` 的调用中(再次强调一下, 在 eventLoop 线程中执行AbstractUnsafe.register0), 这个方法我们已经分析了, 它会调用 safeSetSuccess(promise), 并由 Netty 的 Promise/Future 机制, 导致了AbstractBootstrap.doBind 中的 regFuture 所设置的回调监听器的 `operationComplete` 方法调用, 而 `operationComplete` 中调用了 **AbstractBootstrap.doBind0**: 315 | ```java 316 | private static void doBind0( 317 | final ChannelFuture regFuture, final Channel channel, 318 | final SocketAddress localAddress, final ChannelPromise promise) { 319 | channel.eventLoop().execute(new Runnable() { 320 | @Override 321 | public void run() { 322 | if (regFuture.isSuccess()) { 323 | channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 324 | } else { 325 | promise.setFailure(regFuture.cause()); 326 | } 327 | } 328 | }); 329 | } 330 | ``` 331 | 在 **doBind0** 中, 根据 [EventLoop 的任务队列机制](https://segmentfault.com/a/1190000007403937#articleHeader4), 会使用 eventLoop().execute 将 **channel.bind** 封装为一个 Task, 放到 eventLoop 的 taskQueue 中. 332 | 如下用一幅图表示上面的过程: 333 | ![Alt text](./关于 Channel 注册与绑定的时序问题.png) 334 | 而当 channel.bind 被调度时, `AbstractUnsafe.register0` 早就已经调用结束了. 335 | 336 | 因此由于 EventLoop 的任务队列机制, 我们知道, 在执行 AbstractUnsafe.register0 时, 是在 EventLoop 线程中的, 而 **channel.bind** 的调用是以 task 的形式添加到 taskQueue 队列的末尾, 因此必然是有 EventLoop 线程先执行完 **AbstractUnsafe.register0** 方法后, 才有机会从 taskQueue 中取出一个 task 来执行, 因此这个机制从根本上保证了 `Channel 注册发生在绑定` 之前. 337 | ## 回答 338 | 你的疑惑是, **AbstractChannel#AbstractUnsafe.register0** 中, 可能会调用 **pipeline.fireChannelActive()**, 即: 339 | ```java 340 | private void register0(ChannelPromise promise) { 341 | try { 342 | ... 343 | boolean firstRegistration = neverRegistered; 344 | doRegister(); 345 | ... 346 | if (firstRegistration && isActive()) { 347 | pipeline.fireChannelActive(); 348 | } 349 | } catch (Throwable t) { 350 | ... 351 | } 352 | } 353 | ``` 354 | 并且在 **AbstractChannel#AbstractUnsafe.bind** 中也可能会调用到**pipeline.fireChannelActive()**, 即: 355 | ```java 356 | public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { 357 | ... 358 | boolean wasActive = isActive(); 359 | try { 360 | doBind(localAddress); 361 | } catch (Throwable t) { 362 | ... 363 | } 364 | 365 | if (!wasActive && isActive()) { 366 | invokeLater(new OneTimeTask() { 367 | @Override 368 | public void run() { 369 | pipeline.fireChannelActive(); 370 | } 371 | }); 372 | } 373 | ... 374 | } 375 | ``` 376 | 我觉得是 `不会`. 因为根据上面我们分析的结果可知, Netty 的 Promise/Future 与 EventLoop 的任务队列机制保证了 NioServerSocketChannel 的注册和 Channel 的绑定的时序: `Channel 的注册必须要发生在 Channel 的绑定之前`, 而当一个 NioServerSocketChannel 没有绑定到具体的端口前, 它是`不活跃的(Inactive)`, 进而在 `register0` 中, **if (firstRegistration && isActive())** 就不成立, 因此就不会执行到 **pipeline.fireChannelActive()** 了. 377 | 而执行完注册操作后, 在 **AbstractChannel#AbstractUnsafe.bind** 才会调用**pipeline.fireChannelActive()**, 因此最终只有一次 fireChannelActive 调用. 378 | 379 | ## 总结 380 | 有两点需要注意的: 381 | - isActive() == true 成立的关键是此 NioServerSocketChannel 已经绑定到端口上了. 382 | - 由 Promise/Future 与 EventLoop 机制, 导致了 **Channel 的注册** 发生在 **Channel 的绑定** 之前, 因此在 **AbstractChannel#AbstractUnsafe.register0** 中的 isActive() == false, if 判断不成立, 最终就是 register0 中的 **pipeline.fireChannelActive()** 不会被调用. -------------------------------------------------------------------------------- /源码之下无秘密 ── 做最好的 Netty 源码分析教程.md: -------------------------------------------------------------------------------- 1 | # 源码之下无秘密 ── 做最好的 Netty 源码分析教程 2 | 3 | 4 | @(Netty)[Java, Netty, Netty 源码分析] 5 | 6 | [TOC] 7 | 8 | 9 | ---------- 10 | ## 背景 11 | 在工作中, 虽然我经常使用到 Netty 库, 但是很多时候对 Netty 的一些概念还是处于知其然, 不知其所以然的状态, 因此就萌生了学习 Netty 源码的想法. 12 | 刚开始看源码的时候, 自然是比较痛苦的, 主要原因有两个: 第一, 网上没有和详尽的 Netty 源码分析的教程; 第二, 我也是第一次系统地学习这么大代码量的源码. 由于这两个原因, 最开始时, 看代码的进度很慢, 甚至一度想放弃了, 不过最后很庆幸自己能够坚持下去, 并因此从 Netty 源码中学到了很多宝贵的知识. 13 | 14 | 下面我将自己在 Netty 源码学习过程记录下来, 整理成博客, 与大家分享交流, 共同学习. 由于本人才疏学浅, 文章中难免有不少错误之处, 期待能得到大家的建议和斧正. 15 | 16 | 最后, 忘了提了, 我使用的 Netty 版本: **4.0.33.Final** 17 | 18 | PS. 不小心做了一次标题党, 不过正如标题所言, 即使不是最好的, 那也要尽力 **做到最好的**! 19 | 20 | ## 目录 21 | ### Netty 源码分析之 番外篇 Java NIO 的前生今世 22 | ### Netty 源码分析之 零 磨刀不误砍柴工 源码分析环境搭建 23 | ### Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头 24 | ### Netty 源码分析之 二 贯穿 Netty 的大动脉 ── ChannelPipeline 25 | ### Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop 26 | ### Netty 源码分析之 四 Promise 与 Future: 双子星的秘密 --------------------------------------------------------------------------------