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 ***​ 之间标注的内容

循环遍历发送缓冲区,对消息的发送结果进行判断,下面具体展开进行说明:

  1. 从ChannelOutboundBuffer弹出第一条发送的ByteBuf,然后获取该ByteBuf的读索引和可读字节数;

  2. 对可读字节数和发送的总字节数进行比较,如果发送的字节数大于可读的字节数,说明当前的ByteBuf已经被完全发送出去,更新ChannelOutboundBuffer的发送进度信息,

    将已经发送的ByteBuf删除,释放相关资源。最后,发送的字节数要减去第一条发送的字节数,得到后续消息发送的总字节数,然后循环判断第二条、第三条消息...

  3. 如果可读的消息大于已经发送的总字节数,说明这条消息没有被完整的发送出去,仅仅发送了部分数据包,也就是出现了所谓的“写半包”问题。此时,需要更新可读的索引

    为当前索引+已经发送的总字节数,然后更新ChannelOutboundBuffer的发送进度信息,退出循环;

  4. 如果可读字节数等于已经发送的总字节数,则说明最后一次发送的消息是个整包消息,没有剩余的半包消息待发送。更新发送进度信息,将最后一条已发送的消息从缓冲区中删除,

    最后退出循环。

循环发送操作完成之后,更新SocketChannel的操作位位OP_WRITE,由多路复用器在下一次轮询中触发SocketChannel,继续处理没有发送完成的半包消息。

 

posted @ 2018-09-10 00:49  17退而结网  阅读(130)  评论(0编辑  收藏  举报