首先我们来思考一个问题,什么是Channel?
Channel是Netty抽象出来的对网络I/O进行读/写的相关接口,与NIO中的Channel接口类似。
那么Channel有哪些主要功能?
- 网络I/O的读/写
- 客户端发起连接
- 主动关闭连接、关闭链路
- 获取通信双方的地址
说明:
Netty支持除了TCP以外的多种协议。不同协议、不同阻塞类型的连接会有所不同的Channel类型与之对应。
下面我们来看一下常见的几种Channel:
1. AbstractChannel
- 首先他有几个主要属性:
EventLoop:每个Channel对应一条EventLoop线程。
DefaultChannelPipeline:一个Handler的容器,也可以将其理解为一个Handler链。Handler主要处理数据的编/解码和业务逻辑。
Unsafe:实现具体的连接与读/写数据,如网络的读/写、链路关闭、发起连接等。命名为Unsafe表示不对外提供使用,并非不安全。
我们来看一下AbstractChannel的功能图:
接下来我们来看源码:
- 先看一些全局变量:
private final Channel parent;
private final ChannelId id;
// 实现具体的连接读/写数据,如网络的读/写、链路关闭、发起连接等,unsafe表示不对外提供使用,并非不安全
private final Unsafe unsafe;
// handler调用链
private final DefaultChannelPipeline pipeline;
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
private final CloseFuture closeFuture = new CloseFuture(this);
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
// 每个channel对应一个eventloop线程
private volatile EventLoop eventLoop;
private volatile boolean registered;
private boolean closeInitiated;
private Throwable initialCloseCause;
- AbstractChannel中有一个子类AbstractUnsafe,这个类大量采用了“模板设计模式”,具体的实现由子类完成,例如其中的bind()方法:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
// 模板设计模式,调用子类NioServerSocketChannel的doBind()方法
doBind(localAddress);
} catch (Throwable t) {
// 绑定失败回调
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// 从非活跃状态到活跃状态触发了active事件
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
// 绑定成功回调通知
safeSetSuccess(promise);
}
2. AbstractNioChannel
AbstractNioChannel 继承了 AbstractChannel,不过在其基础上又增加了一些属性和方法。
// 真正用到的NIO channel
private final SelectableChannel ch;
// 监听感兴趣的事件
protected final int readInterestOp;
// 注册到Selector后获取Key
volatile SelectionKey selectionKey;
我们来看其中的一个主要方法,doRegister():
/**
* 在AbstractUnsafe的register0()方法中调用
* @throws Exception
*/
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
/**
* 通过javaChannel()方法获取具体的Nio Channel
* 把Channel注册到其EventLoop线程的Selector上
* 对于注册后返回的selectionKey,需要为其设置Channel感兴趣的事件
*/
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
// 由于尚未调用select.select(...)
// 因此可能仍在缓存而未删除但已取消selectionKey
// 强制调用 selector.selectNow方法
// 将已经取消的selectionKey 从Selector上删除
eventLoop().selectNow();
selected = true;
} else {
// 只有第一次跑出此异常, 才能调用selector.selectorNow进行取消
// 如果调用selector.selectNow 还有取消的缓存,则可能是JDK的一个bug
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
在AbstractNioChannel中有个非常重要的类——AbstractNioUnsafe,它继承了AbstractUnsafe,并且实现了其中的connect,flush0()等方法。
我们来探究一下connect方法:
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
// 设置任务为不可取消状态,并确定channel已打开
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
try {
// 确保没有正在进行的连接
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
}
// 获取之前的状态
boolean wasActive = isActive();
/**
* 在远程连接时,会出现以下三种结果。
* 1. 连接成功,返回true
* 2. 暂时没有连接上,服务端没有返回ACK应答,连接结果不确定,返回false
* 3. 连接失败,直接抛出I/O异常
* 由于协议和I/O模型不同,连接的方式也不一样,因此具体实现由子类实现
*/
if (doConnect(remoteAddress, localAddress)) {
/**
* 连接成功后会触发ChannelActive事件
* 最终会将NioSocketChannel中的selectionKey
* 设置为SelectionKey.OP_READ
* 用于监听网络读操作位
*/
fulfillConnectPromise(promise, wasActive);
} else {
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// Schedule connect timeout.
// 获取连接超时事件
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
// 根据连接超时事件设置定时任务
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
// 到达连接超时时间触发校验
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
if (connectPromise != null && !connectPromise.isDone()
&& connectPromise.tryFailure(new ConnectTimeoutException(
"connection timed out: " + remoteAddress))) {
// 如果发现连接并没有完成,则关闭连接句柄,释放资源
// 设置异常堆栈并发起取消注册操作
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
// 增加连接结果监听器
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 如果接收到连接完成的通知,则判断连接是否被取消
// 如果被取消则关闭连接句柄,释放资源,发起取消注册操作
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
// 关闭连接句柄,释放资源,发起取消注册操作
// 从多路复用器上移除
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}
其中finishConnect()方法的解读如下:
@Override
public final void finishConnect() {
// Note this method is invoked by the event loop only if the connection attempt was
// neither cancelled nor timed out.
// 只有eventLoop线程才能调用finishConnect方法
// 此方法在NioEventLoop的processSelectedKey()方法中被调用
assert eventLoop().inEventLoop();
try {
boolean wasActive = isActive();
/**
* 判断连接结果(由其子类完成)
* 通过SocketChannel的finishConnect()方法判断连接结果
* 连接成功返回true
* 连接失败抛异常
* 链路被关闭,链路中断等异常也属于连接失败
*/
doFinishConnect();
// 负责将socketChannel修改为监听读操作位
// 用来监听网络读事件
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
// 连接失败,关闭连接句柄,释放资源,并取消注册操作
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
// See https://github.com/netty/netty/issues/1770
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
}
}
2. AbstractNioByteChannel
AbstractNioByteChannel 类继承了AbstractNioChannel,因此也具备连接、注册等功能,但I/O读写依然交给子类。
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
private static final String EXPECTED_TYPES =
" (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " +
StringUtil.simpleClassName(FileRegion.class) + ')';
// 匿名内部类
// 负责刷新发送缓存链表中的数据,由于write的数据没有直接写在Socket中,而是写在了ChannelOutboundBuffer缓存中,
// 所以当调用flush() 方法时,会把数据写入Socket中并向网络中发送。
private final Runnable flushTask = new Runnable() {
@Override
public void run() {
// Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
// meantime.
((AbstractNioUnsafe) unsafe()).flush0();
}
};
private boolean inputClosedSeenErrorOnRead;
我们来看它的doWrite()方法,其主要作用是从ChannelOutboundBuffer缓存中获取发送的数据,进行循环发送:
- 该写操作非常有意思,当多个线程同时进行写操作时,我们会给默认每个Channel每次循环16次去写数据,如果这16次内没有写完数据,则把选择Key的OP_WRITE事件从兴趣事件集中移除,并添加一个flushTask任务,先去执行其他任务,当检测到此任务时在发送
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
// 写请求自循环次数,默认为16次
int writeSpinCount = config().getWriteSpinCount();
do {
// 获取当前Channel的缓存ChannelOutboundBuffer中的当前刷新消息
Object msg = in.current();
// 所有消息发送成功了
if (msg == null) {
// 清除Channel选择Key兴趣事件集中的OP_WRITE写操作事件
// Wrote all messages.
clearOpWrite();
// 直接返回,没必要再添加写人物
// Directly return here so incompleteWrite(...) is not called.
return;
}
// 否则发送数据
writeSpinCount -= doWriteInternal(in, msg);
} while (writeSpinCount > 0);
/**
* 当因缓冲区满了而发送失败时
* doWriteInternal返回Integer.MAX_VALUE
* 此时writeSpinCount<0为true
* 当发送16次还未全部发送完,但每次都写成功时
* writeSpinCount 为0
*/
incompleteWrite(writeSpinCount < 0);
}
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
// 将OP_WRITE写操作事件添加到Channel的选择Key兴趣事件集中
if (setOpWrite) {
setOpWrite();
} else {
// It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
// use our write quantum. In this case we no longer want to set the write OP because the socket is still
// writable (as far as we know). We will find out next time we attempt to write if the socket is writable
// and set the write OP if necessary.
// 清除Channel选择Key兴趣事件集中的OP_WRITE写操作事件
clearOpWrite();
// 将操作任务添加到EventLoop线程上,以便后续继续发送
// Schedule flush again later so other tasks can be picked up in the meantime
eventLoop().execute(flushTask);
}
}
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (!buf.isReadable()) {
// 若可读字节数为0,则从缓冲区中移除
in.remove();
return 0;
}
// 实际发送字节数据
final int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount > 0) {
// 更新字节数据的发送进度
in.progress(localFlushedAmount);
if (!buf.isReadable()) {
// 若可读字节数为0,则从缓存区中移除
in.remove();
}
return 1;
}
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
// 如果是文件FileRegion消息
if (region.transferred() >= region.count()) {
in.remove();
return 0;
}
// 实际写操作
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount > 0) {
// 更新数据的发送进度
in.progress(localFlushedAmount);
if (region.transferred() >= region.count()) {
// 若region已全部发送成功
// 则从缓存中移除
in.remove();
}
return 1;
}
} else {
// Should not reach here.
// 不支持发送其他类型的数据
throw new Error();
}
// 当实际发送字节数为0时,返回Integer.MAX_VALUE
return WRITE_STATUS_SNDBUF_FULL;
}
接下来我们来看一下read方法:
NioByteUnsafe的read()方法的实现思路大概分为以下3步。
(1)获取Channel的配置对象、内存分配器ByteBufAllocator,并计算内存分配器RecvByte BufAllocator.Handle。
(2)进入for循环。循环体的作用:使用内存分配器获取数据容器ByteBuf,调用doReadBytes()方法将数据读取到容器中,如果本次循环没有读到数据或链路已关闭,则跳出循环。另外,当循环次数达到属性METADATA的defaultMaxMessagesPerRead次数(默认为16)时,也会跳出循环。由于TCP传输会产生粘包问题,因此每次读取都会触发channelRead事件,进而调用业务逻辑处理Handler。
(3)跳出循环后,表示本次读取已完成。调用allocHandle的readComplete()方法,并记录读取记录,用于下次分配合理内存。
具体源码查看:
@Override
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
// 获取内存分配器,默认为PooledByteBufAllocator
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
// 清空上一次读取的字节数,每次读取时重新计算
// 字节buf分配器,并计算字节buf分配器Handler
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
// 分配内存
byteBuf = allocHandle.allocate(allocator);
// doReadBytes()将数据读到容器中
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// 若本次没有读到数据或链路已关闭,则跳出循环
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// 当读到-1时,表示Channel通道已关闭
// 没必要再继续读
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
// 更新读消息计数器
allocHandle.incMessagesRead(1);
readPending = false;
// 通知通道处理读取数据,触发Channel管道的fireChannelRead时间
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
// 读取操作完毕
allocHandle.readComplete();
// 触发Channel管道的fireChannelReadComplete事件
pipeline.fireChannelReadComplete();
if (close) {
// 如果Socket通道关闭,则关闭读操作
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
// 若读操作完毕,且没有配置自动读
// 则从选择Key兴趣中移除读操作时间
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
3.AbstractNioMessageChannel
AbstractNioMessageChannel 主要是操作对象类型,因此并不存在拆包粘包问题,下面看一下它的dowrite()和read()方法:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
final SelectionKey key = selectionKey();
// 获取Key兴趣集
final int interestOps = key.interestOps();
int maxMessagesPerWrite = maxMessagesPerWrite();
while (maxMessagesPerWrite > 0) {
Object msg = in.current();
// 数据已全部发送完,从兴趣集中移除OP_WRITE事件
if (msg == null) {
break;
}
try {
boolean done = false;
// 获取配置中循环写的最大次数
for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
// 调用子类方法,若msg写成功了,则返回true
if (doWriteMessage(msg, in)) {
done = true;
break;
}
}
// 若发送成功,则将其从缓存链表中移除
// 继续发送下一个缓存节点数据
if (done) {
maxMessagesPerWrite--;
in.remove();
} else {
// 若没有成功,直接跳出循环
break;
}
} catch (Exception e) {
// 当出现异常时,判断是否继续写
if (continueOnWriteError()) {
maxMessagesPerWrite--;
in.remove(e);
} else {
throw e;
}
}
}
if (in.isEmpty()) {
// Wrote all messages.
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
} else {
// Did not write all messages.
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
}
}
那么它的read()方法:
@Override
public void read() {
assert eventLoop().inEventLoop();
// 获取Channel的配置对象
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
// 获取计算内存分配Handle
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
// 清空上次的记录
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
/**
* 调用子类的 doReadMessages()方法
* 读取数据包,并放入readBuf链表中
* 当成功读取时,返回1
*/
int localRead = doReadMessages(readBuf);
// 已无数据,跳出循环
if (localRead == 0) {
break;
}
// 链路关闭,跳出循环
if (localRead < 0) {
closed = true;
break;
}
// 记录成功读取的次数
allocHandle.incMessagesRead(localRead);
// 默认不能超过16次
} while (continueReading(allocHandle));
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
// 循环处理读取的数据包
for (int i = 0; i < size; i ++) {
readPending = false;
// 触发channelRead事件
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
// 记录当前读取记录,以便下次分配合理内存
allocHandle.readComplete();
// 触发readComplete事件
pipeline.fireChannelReadComplete();
if (exception != null) {
// 处理channel 异常关闭
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();
}
}
}片
4.NioSocketChannel源码
NioSocketChannel是AbstractNioByteChannel的子类,也是SocketChannel接口实现类。
Netty中每个Socket连接都会生成一个NioSocketChannel对象。
关于NioSocketChannel的功能图:
NioSocketChannel的继承关系图:
解释:
• SocketChannel在NioSocketChannel构造方法中由SelectorProvider.provider().openSocket Channel()创建,提供javaChannel()方法以获取SocketChannel。
• 实现doReadBytes()方法,从SocketChannel中读取数据。
• 重写doWrite()方法、实现doWriteBytes()方法,将数据写入Socket中。
• 实现doConnect()方法和客户端连接。
来看几个核心的方法:
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
// 获取计算内存分配器handle
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
// 设置尝试读取字节数为buf的可写字节数
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
// 从Channel中读取字节并写入buf中,返回读取的字节数
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
@Override
protected int doWriteBytes(ByteBuf buf) throws Exception {
// 获取buf的可读字节数
final int expectedWrittenBytes = buf.readableBytes();
// 把buf写入Socket缓存中,返回写入字节数
return buf.readBytes(javaChannel(), expectedWrittenBytes);
}
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
// 获取SocketChannel
SocketChannel ch = javaChannel();
// 获取循环写的最大次数
int writeSpinCount = config().getWriteSpinCount();
do {
// 缓存数据为空,无数据可写
if (in.isEmpty()) {
// All written so clear OP_WRITE
// 移除写时间,并直接返回
clearOpWrite();
// Directly return here so incompleteWrite(...) is not called.
return;
}
// Ensure the pending writes are made of ByteBufs only.
// 获取一次最大可写字节数
int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
/**
* 缓存由多个Entry组成,每次写时都可能写多个Entry
* 具体一次性发送多少数据
* 由byteBuffer数组的最大长度和一次最大可写字节数决定
*/
ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
int nioBufferCnt = in.nioBufferCount();
// Always use nioBuffers() to workaround data-corruption.
// See https://github.com/netty/netty/issues/2761
// 缓存中有多少个nioBuffer
switch (nioBufferCnt) {
case 0:
// We have something else beside ByteBuffers to write so fallback to normal writes.
// 非ByteBuffer数据,交给父类实现
writeSpinCount -= doWrite0(in);
break;
case 1: {
// Only one ByteBuf so use non-gathering write
// Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
// to check if the total size of all the buffers is non-zero.
ByteBuffer buffer = nioBuffers[0];
// buf可读字节数
int attemptedBytes = buffer.remaining();
// 把buf发送到socket缓存中
final int localWrittenBytes = ch.write(buffer);
// 发送失败
if (localWrittenBytes <= 0) {
// 将写事件添加到事件兴趣集中
incompleteWrite(true);
return;
}
/**
* 根据成功写入字节数和尝试写入字节数调整下次最大可写字节数
* 当两者相等时,若尝试写入字节数*2 大于当前最大写入字节数
* 则下次最大可写字节数等于尝试写入字节数*2
* 当两者不相等时,成功写入字节数小于尝试写入字节数/2
* 且尝试接入字节数大于4096时
* 下次最大可写字节数等于尝试写入字节数/2
*/
adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
// 从缓存中移除写入字节数
in.removeBytes(localWrittenBytes);
// 循环写次数减1
--writeSpinCount;
break;
}
default: {
// Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
// to check if the total size of all the buffers is non-zero.
// We limit the max amount to int above so cast is safe
// 尝试写入字节数
long attemptedBytes = in.nioBufferSize();
// 真正发送到Socket缓存中的字节数
final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
// 如果发送失败
if (localWrittenBytes <= 0) {
// 将写事件添加到事件兴趣集中
// 以便下次NioEventLoop继续触发写操作
incompleteWrite(true);
return;
}
// Casting to int is safe because we limit the total amount of data in the nioBuffers to int above.
// 调整下次最大可写字节数
adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
maxBytesPerGatheringWrite);
// 从缓存中移除发送成功的字节
in.removeBytes(localWrittenBytes);
// 循环写次数减1
--writeSpinCount;
break;
}
}
} while (writeSpinCount > 0);
/**
* 未全部发送完:
* 若writeSpinCount 《 0
* 则说明Socket缓冲区已满,未发送成功
* 若writeSpinCount = 0
* 则说明Netty缓存数据太大,写了16次还未写完
*/
incompleteWrite(writeSpinCount < 0);
}
参考书籍:《Netty源码剖析与应用》