NioSocketChannel
@Override protected void doWrite(ChannelOutboundBuffer in) throws Exception { for (;;) { // Do non-gathering write for a single buffer case. final int msgCount = in.size(); if (msgCount <= 1) { super.doWrite(in); return; } // Ensure the pending writes are made of ByteBufs only. ByteBuffer[] nioBuffers = in.nioBuffers(); if (nioBuffers == null) { super.doWrite(in); return; } int nioBufferCnt = in.nioBufferCount(); long expectedWrittenBytes = in.nioBufferSize(); final SocketChannel ch = javaChannel(); long writtenBytes = 0; boolean done = false; boolean setOpWrite = false; for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) { final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); if (localWrittenBytes == 0) { setOpWrite = true; break; } expectedWrittenBytes -= localWrittenBytes; writtenBytes += localWrittenBytes; if (expectedWrittenBytes == 0) { done = true; break; } } if (done) { // Release all buffers for (int i = msgCount; i > 0; i --) { in.remove(); } // Finish the write loop if no new messages were flushed by in.remove(). if (in.isEmpty()) { clearOpWrite(); break; } } else { // Did not write all buffers completely. // Release the fully written buffers and update the indexes of the partially written buffer. // ***** 写半包判断处理 start ***** for (int i = msgCount; i > 0; i --) { final ByteBuf buf = (ByteBuf) in.current(); final int readerIndex = buf.readerIndex(); final int readableBytes = buf.writerIndex() - readerIndex; if (readableBytes < writtenBytes) { in.progress(readableBytes); in.remove(); writtenBytes -= readableBytes; } else if (readableBytes > writtenBytes) { buf.readerIndex(readerIndex + (int) writtenBytes); in.progress(writtenBytes); break; } else { // readableBytes == writtenBytes in.progress(readableBytes); in.remove(); break; } } // ***** 写半包判断处理 end ***** incompleteWrite(setOpWrite); break; } } }
关键代码见:// *** 写半包判断处理 start *** 与 // *** 写半包判断处理 end *** 之间标注的内容
循环遍历发送缓冲区,对消息的发送结果进行判断,下面具体展开进行说明:
-
从ChannelOutboundBuffer弹出第一条发送的ByteBuf,然后获取该ByteBuf的读索引和可读字节数;
-
对可读字节数和发送的总字节数进行比较,如果发送的字节数大于可读的字节数,说明当前的ByteBuf已经被完全发送出去,更新ChannelOutboundBuffer的发送进度信息,
将已经发送的ByteBuf删除,释放相关资源。最后,发送的字节数要减去第一条发送的字节数,得到后续消息发送的总字节数,然后循环判断第二条、第三条消息...
-
如果可读的消息大于已经发送的总字节数,说明这条消息没有被完整的发送出去,仅仅发送了部分数据包,也就是出现了所谓的“写半包”问题。此时,需要更新可读的索引
为当前索引+已经发送的总字节数,然后更新ChannelOutboundBuffer的发送进度信息,退出循环;
-
如果可读字节数等于已经发送的总字节数,则说明最后一次发送的消息是个整包消息,没有剩余的半包消息待发送。更新发送进度信息,将最后一条已发送的消息从缓冲区中删除,
最后退出循环。
循环发送操作完成之后,更新SocketChannel的操作位位OP_WRITE,由多路复用器在下一次轮询中触发SocketChannel,继续处理没有发送完成的半包消息。