文章目录
1. 客户端接收连接:
在Netty服务端启动的时候,会先启动一个Reactor线程池,去绑定启动的地址和端口,并且线程池中的channel复用器会循环监听客户端的各种事件。实际会调用NioEventLoop中的run方法,代码如下(只保留具体执行代码):
@Override
Protected void run(){
for(;;){
// Nio 事件监听和执行
processSelectedKeys();
// 使用eventLoop执行的任务(使用者加入的)
runAllTasks();
}
主要看一下 processSelectedKeys()方法,这里包含了服务端对客户端的连接和读取方法。
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); // 获取channel的unsafe类
……(省略代码)
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
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(); // 连接客户端
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush(); // 写操作
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read(); // 读操作或者接受连接操作 ||
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
当客户端向服务端发起连接,最终调用unsafe类的read方法,因为此时的channel是NioServerSocketChannel.class 实际调用是NioMessageUnsafe.class:
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config(); // DefaultChannelConfig
final ChannelPipeline pipeline = pipeline(); // DefaultChannelPipeline
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); // io.netty.channel.AdaptiveRecvByteBufAllocator.HandleImpl
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
int localRead = doReadMessages(readBuf); // accept客户端连接,生成ServerSocketChannel 加入readBuf中
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead); // 计数
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i)); // 调用channelRead方法,其中将调用ServerBootstrapAcceptor的channelRead方法方法,将ServerSocketChannel注册到childGroup上去。
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete(); // 调用读完毕
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception); // 调用异常
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
可以看到的是,当有客户端发起连接时,服务端这边会调用ServerBootstrapAcceptor中的方法,将客户端连接注册到另一个Reactor线程池中去,这样的话服务端的接受连接和读写操作很好的分开开来,接受连接的线程池可以继续接收客户端连接,而不需要等待此次连接读写完成。
2. 读操作:
与上述代码一致,只是这次使用的时NioSocketChannl,并且Reactor线程池也是不同的,最终也会调用unsafe(NioByteUnsafe)的read方法
@Override
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator); // 默认使用直接内存创建的byteBuf
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf); // channelHandler责任链中channelRead方法(业务逻辑等等)
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}