前面我们讲了server的启动过程,同时介绍了其中非常重要的几个概念,ChannelPipeline,ChannelHandlerContext等。接下来我们看看server启动起来以后是如何运转的。
先回忆下之前的几个重要的点,如果要监听某个端口,首先用ServerBootstrap引导启动,启动时创建一个ServerSocketChannel,并注册到bossGroup的EventLoop中,关注的事件为OP_ACCEPT。boss EventLoop开始运行,并不停的尝试从Selector中查看是否有准备好的连接。由于ServerSocketChannel关注的是OP_ACCEPT,因此每当有客户端连接到服务端的时候,boss EventLoop都可以select到一个SelectionKey,然后进入以下方法:
//NioEventLoop
private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
// 如果key无效了则关闭连接
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// 检查readOps,避免由jdk bug导致readOps为0而产生自旋(cpu 100%)
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
// 调用unsafe.read方法,这个也是我们今天分析的入口
unsafe.read();
if (!ch.isOpen()) {
// 连接已经关闭则直接返回,就不用处理后面的write事件了
return;
}
}
。。。省略不会触发的几句代码。。。
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// 取消此key对 OP_CONNECT的关注,否则Selector.select(..)不会阻塞而直接返回,导致cpu 100%
// 此bug见 https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
注意还有一种情况是processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task),这是扩展的情况,暂时无人实现,先不分析。
这里引出了本次分析的第一个入口点,ch.unsafe().read()。NioServerSocketChannel对应的unsafe实现为NioMessageUnsafe,我们来看看它的read方法做了哪些事情(去掉部分无关代码):
// NioMessageUnsafe
private final List<Object> readBuf = new ArrayList<Object>();
public void read() {
final ChannelConfig config = config();
// 下面有个循环操作,这里的值代表循环的最大次数,对于NioServerSocketChannel来说也就是单次最大接受的连接数,默认为16,
// 可以在启动的时候通过初始化时调用引导类的setOption(ChannelOption.MAX_MESSAGES_PER_READ, 32)修改这个值。
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
final ChannelPipeline pipeline = pipeline();
boolean closed = false;
Throwable exception = null;
try {
try {
for (;;) {
// 此处会调用到NioServerSocketChannel中的doReadMessages方法
int localRead = doReadMessages(readBuf);
// 读到的值为0没获取到连接(可能是已经关闭了),注意NioServerSocketChannel中的doReadMessages只会返回0,1,
// -1在其他场景中出现,后面再分析
if (localRead == 0) {
break;
}
// 每次读取成功readBuf中就会多一个连接,达到阈值则先跳出循环,剩下的数据下次循环时再取
if (readBuf.size() >= maxMessagesPerRead) {
break;
}
}
} catch (Throwable t) {
exception = t;