永动机即'永远运动的机器'.
作为服务端时, 需要接收客户端的连接, 需要接收客户端的数据, 或者向客户端发送数据.
既然需要接收客户端的连接, 那么就需要一个IO线程永远的执行一个无限循环.只有一直循环着, 才能一直接收新的连接.
NioEventLoop的底层绑定一个线程, 这个线程在启动之后, 就会一直无限循环着, 而且只做三件事
1.轮询IO事件
2.处理IO事件
3.执行任务
当有新的客户端连接到服务端的时候(TCP三次握手已经完成), 服务端的IO线程就会轮询到有客户端的连接事件. 接下来就会处理这个连接事件.
IO线程会创建一个针对这个客户端与之对应的NioSocketChannel, 然后把这个Channel注册到另一类NioEventLoop(它的底层也会绑定一个线程)上.
从图中可以看出, 右侧的IO线程只会负责读写事件, 并不会负责连接事件.
当客户端给服务端发送数据的时候, 服务端IO线程轮询到读事件, 接下来就会处理这个读事件. 会读取到客户端发送过来的数据, 经过解码器解码, 再把数据传给业务Handler进行处理.
然而一般情况下, 当服务端需要向客户端写数据的时候, 直接调用相应的函数(writeAndFlush)即可, 并不会涉及到写事件. 那么什么时候才会涉及到写事件呢? 当网络出现拥堵的情况, 或者客户端没有及时处理服务端发给它的数据. 那么服务端的Socket的TCP缓冲区就会被写满, 这个时候再向缓冲区写数据就会失败, Netty就会注册一个写事件. 当TCP缓冲区可写的时候, Netty就会继续将之前没有写完的数据,再次向TCP缓冲区写.
如果把Netty的服务端比作一台大型的机器. 那么在这个机器里, 有好几个一直运作地大齿轮(每个NioEventLoop就是一个大齿轮). 这些大齿轮一直转着,一直转着, 从不停止.
以上说了服务端的三个IO事件, 分别是连接事件, 读事件, 写事件. 准确的说应该是接收连接事件, 读事件, 写事件.
因为在客户端也有三个IO事件, 分别是连接事件, 读事件, 写事件. 如下图
如上图右侧所示, 客户端轮询着连接事件, 读事件和写事件. 读写事件和服务端一样, 说一下连接事件.
Netty在进行TCP三次握手的时候, 由于网络等原因, Netty并没有一直等待着连接完成, 客户端在发起连接之后, 便注册了一个连接事件. 当TCP三次握手完成之后, IO线程轮询到了连接完成事件. 接下来就会做一些连接完成的后续工作, 同时取消连接事件. 关键代码如下
// 源码位置: io.netty.channel.socket.nio.NioSocketChannel#doConnect
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
// 连接服务端
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
// 注册感兴趣的连接事件
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
// 源码位置: io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
// 移除连接事件
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
总结: 此篇文件简单说了下Netty作为服务端和客户端的时候, IO线程一直在无限循环着, 傻傻地做着轮询IO事件, 处理IO事件, 执行任务这三件事.