NioSocketChannel之服务端视角
上篇文章我们对NioServerSocketChannel进行了分析,了解了它是如何接收客户端连接的。本篇我们将详细的了解接收到的连接的生命周期。需要说明的是由于采用的是服务端视角,因此一个连接的生命周期主要包括:
- 创建
- 读取数据
- 写数据
- 关闭
创建
创建过程主要是做一些基础属性的初始化(废话):
public NioSocketChannel(Channel parent, SocketChannel socket) {
// parent自然就是NioServerSocketChannel, socket是accept()到的连接
super(parent, socket);
//创建一份配置,从前一篇NioServerSocketChannel的介绍中我们知道稍后会用
//Bootstrap/ServerBootstrap的childOptions对这个config进行具体的设置
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
//一开始只关注OP_READ
super(parent, ch, SelectionKey.OP_READ);
}
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
// 配置为非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
protected AbstractChannel(Channel parent) {
this.parent = parent;
// 分配一个全局唯一的id,默认为MAC+进程id+自增的序列号+时间戳相关的数值+随机值
id = DefaultChannelId.newInstance();
// 初始化Unsafe, NioSocketChannel对应的是NioSocketChannelUnsafe
unsafe = newUnsafe();
// 初始化pipeline,在后面的阶段里pipeline被用户定义的ChannelInitializer改掉
pipeline = new DefaultChannelPipeline(this);
}
创建过程很简单,这里不再做过多分析,直接进入读取数据的环节。
读取数据
前一篇文章介绍了在接收到连接时会触发fireChannelActive事件,该事件会使channel被注册到EventLoop的Selector中,此EventLoop后面会循环的从Selector中获取准备好的连接,并调用unsafe.read(); NioSocketChannel对应的unsafe为NioSocketChannelUnsafe,我们来看看它的read()方法是如何实现的:
// read()方法在其父类NioByteUnsafe中
public final void read() {
final ChannelConfig config = config();
// 如果autoRead为false且读未被挂起,则移除对OP_READ的关注,即暂时不接收数据
if (!config.isAutoRead() && !isReadPending()) {
removeReadOp();
return;
}
final ChannelPipeline pipeline = pipeline();
// 获取到配置的allocator,allocator是用来分配ByteBuf的
final ByteBufAllocator allocator = config.getAllocator();
// 从一个channel中读取消息的最大次数
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
// allocHandle主要用于预估本次ByteBuf的初始大小,避免分配太多导致浪费或者分配过小放不下单次读取的数据而需要多次读取
RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
ByteBuf byteBuf = null;
int messages = 0;
boolean close = false;
try {
int totalReadAmount = 0;
boolean readPendingReset = false;
do {
// 分配一个ByteBuf
byteBuf = allocHandle.allocate(allocator);
int writable = byteBuf.writableBytes();
// 读取数据到ByteBuf中(最大为writable)
int localReadAmount = doReadBytes(byteBuf);
if (localReadAmount <= 0) {
// 未读取到数据则直接释放该ByteBuf,如果返回-1表示读取出错,后面会关闭该连接
byteBuf.release();
byteBuf = null;
close = localReadAmount < 0;
break;
}
if (!readPendingReset) {
readPendingReset = true;
setReadPending(false);
}
// 通过pipleline通知有数据到达,channelRead属于inbound事件,
// 数据会从第一个handler开始处理并往后传递(是否需要传递由handler自已定)
pipeline.fireChannelRead(byteBuf);
// 这里直接赋null,释放逻辑需要handler实现
byteBuf = null;
// 本次read方法已经接收的数据超过Integer.MAX_VALUE,避免溢出,不再读取数据,
if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
totalReadAmount = Integer.MAX_VALUE;
break;
}
totalReadAmount += localReadAmount;
// 如果autoRead为false则停止读取,默认为true,在流量控制的时候可能会被设置为false
if (!config.isAutoRead()) {
break;
}
// 本次读取的数据长度小于分配的ByteBuf的大小,说明准备好的数据都已经读完了
if (localReadAmount < writable) {
break;
}
} while (++ messages < maxMessagesPerRead);
// 本轮数据读取完毕
pipeline.fireChannelReadComplete();
// 记录本次读取到的数据长度(用于计算下次分配ByteBuf时的初始化大小)
allocHandle.record(totalReadAmount);
// 如果读取的时候发生错误则关闭连接
if (close) {
closeOnRead(pipeline);
close = false;
}
} catch (Throwable t) {
// 读取异常
handleReadException(pipeline, byteBuf, t, close);
} finally {
if (!config.isAutoRead() && !isReadPending()) {
removeReadOp();
}
}
}
}
private void handleReadException(ChannelPipeline pipeline,
ByteBuf byteBuf, Throwable cause, boolean close) {
// 如果读取到了数据则继续处理,否则释放ByteBuf
if (byteBuf != null) {
if (byteBuf.isReadable()) {
setReadPending(false);
pipeline.fireChannelRead(byteBuf);
} else {
byteBuf.release();
}
}
// 触发读取完成事件,触发异常捕获事件。需要的时候关闭连接
pipeline.fireChannelReadComplete();
pipeline.fireExceptionCaught(cause);
if (close || cause instanceof IOException) {
closeOnRead(pipeline);
}
}
可以看到,读取的过程貌似不是很复杂,循环读取数据,每读到一次数据就调用pipeline.fireChannelRead来处理,本次循环结束后调用pipeline.fireChannelReadComplete。这中间有一个细节需要理解:ByteBuf的分配。分配代码:
ByteBufAllocator allocator = config.getAllocator();
RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
byteBuf = allocHandle.allocate(allocator);
...
allocHandle.record(totalReadAmount);
默认的allocator为PooledByteBufAllocator.DEFAULT,是一个全局的PooledByteBufAllocator实例,它维护了一个内存池,通过它从内存池中分配ByteBuf(可以通过-Dio.netty.allocator.type=unpooled来使用非内存池模式的allocator)。
默认的allocHandle为AdaptiveRecvByteBufAllocator.HandleImpl。该类的作用是通过历史分配的ByteBuf size来计算下次分配的size。allocHandle.allocate方法主要作用就是提供一个nextReceiveBufferSize,其他的就由allocator(连接池管理)处理:
public ByteBuf allocate(ByteBufAllocator alloc) {