Netty写数据原理

写出数据

EchoServerHandler中打两个断点

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
         // 读取数据后写回客户端
         ctx.write(msg); // 在这里打个断点
    }
  
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush(); // 在这里打个断点
    }
}

ctx.write(msg)并不会立即把数据发送出去,ctx.flush()才会把数据发送出去。

ctx.write(msg)

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
    // ①
    @Override
    public ChannelFuture write(Object msg) {
        return write(msg, newPromise());
    }
    @Override
    public ChannelPromise newPromise() {
        return new DefaultChannelPromise(channel(), executor());
    }
    // ②
    @Override
    public ChannelFuture write(final Object msg, final ChannelPromise promise) {
        // 第二个参数为flush,这里传入的是false
        // 也就是默认不进行真正地发送
        write(msg, false, promise);
        return promise;
    }
    // ③
    private void write(Object msg, boolean flush, ChannelPromise promise) {
        ObjectUtil.checkNotNull(msg, "msg");
        // 异常校验......省略
        // 寻找下一个可用的ChannelOutboundHandler类型的ChannelHandlerContext
        // 在ChannelPipeline中也就是prev指针标记的那个
        // 这里找到的就是LoggingHandler对应的那个ChannelHandlerContext
        final AbstractChannelHandlerContext next = findContextOutbound(flush ?
                (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
        // 这里的m其实就是msg
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                // flush为false,所以走到了这里
                // 调用context的invokeWrite()方法
                next.invokeWrite(m, promise);
            }
        } else {
            final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
            if (!safeExecute(executor, task, promise, m, !flush)) {
                task.cancel();
            }
        }
    }
    // ④
    void invokeWrite(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            // 走的是这里
            invokeWrite0(msg, promise);
        } else {
            write(msg, promise);
        }
    }
    // ⑤
    private void invokeWrite0(Object msg, ChannelPromise promise) {
        try {
            // 调用Handler的write()方法
            ((ChannelOutboundHandler) handler()).write(this, msg, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }
}

然后会走到LoggingHandlerwrite方法

public class LoggingHandler extends ChannelDuplexHandler {
    // ⑥
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "WRITE", msg));
        }
        // 又调回了第②步中的方法, 继续寻找下一个可用的ChannelOutboundHandler类型的ChannelHandlerContext
        ctx.write(msg, promise);
    }
}

可以知道,写数据在ChannelPipeline中的传递,也是通过ChannelHandlerContext进行,
每次寻找下一个可用的ChannelOutboundHandler类型的ChannelHandlerContext,调用它里面的ChannelHandlerwrite()方法。
然后,在ChannelHandler里面调用ctx.write(msg, promise)才能让这个链往下传递,继续寻找下一个可用的ChannelOutboundHandler类型的ChannelHandlerContext

对于接收数据,如果需要数据在ChannelPipeline中传递,就调用ctx.fireChannelRead(msg)方法;
对于写出数据,如果需要数据在ChannelPipeline中传递,就调用ctx.write(msg)或者ctx.write(msg, promise)方法。

通过上面的分析,最后一定会走到ChannelPipelineHeadContextwrite()方法,在这个方法中打一个断点:

public class DefaultChannelPipeline implements ChannelPipeline {
    final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            // unsafe 为 io.netty.channel.socket.nio.NioSocketChannel.NioSocketChannelUnsafe
            unsafe.write(msg, promise);
        }
    }
}

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    protected abstract class AbstractUnsafe implements Unsafe {
        @Override
        public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop();
            // 出站缓存
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                try {
                    // release message now to prevent resource-leak
                    ReferenceCountUtil.release(msg);
                } finally {
                    safeSetFailure(promise,
                            newClosedChannelException(initialCloseCause, "write(Object, ChannelPromise)"));
                }
                return;
            }

            int size;
            try {
                // 过滤消息
                msg = filterOutboundMessage(msg);
                // 计算消息大小
                size = pipeline.estimatorHandle().size(msg);
                if (size < 0) {
                    size = 0;
                }
            } catch (Throwable t) {
                try {
                    ReferenceCountUtil.release(msg);
                } finally {
                    safeSetFailure(promise, t);
                }
                return;
            }
            // 消息添加到缓存
            outboundBuffer.addMessage(msg, size, promise);
        }
    }
}

public final class ChannelOutboundBuffer {
    public void addMessage(Object msg, int size, ChannelPromise promise) {
        // 将msg封装成一个Entry
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
        // 这里就是一个单链表的添加过程了
        if (tailEntry == null) {
            flushedEntry = null;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
        }
        tailEntry = entry;
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }

        incrementPendingOutboundBytes(entry.pendingSize, false);
    }
}

因此,ctx.write()方法最终只是把消息添加到了一个叫做ChannelOutboundBuffer的缓存中,并没有真正地发送出去。


ctx.flush()

ctx.flush()的调用过程跟ctx.write(msg)是类似的,直接来到HeadContextflush()方法。

public class DefaultChannelPipeline implements ChannelPipeline {
    final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {
        @Override
        public void flush(ChannelHandlerContext ctx) {
            // unsafe 为 io.netty.channel.socket.nio.NioSocketChannel.NioSocketChannelUnsafe
            unsafe.flush();
        }
    }
}

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    protected abstract class AbstractUnsafe implements Unsafe {
        @Override
        public final void flush() {
            assertEventLoop();
            // ctx.write(msg)就是把数据写到this.outboundBuffer里面的
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }

            outboundBuffer.addFlush();
            flush0();
        }
    }
}

public abstract class AbstractNioChannel extends AbstractChannel {
    protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe {
        @Override
        protected final void flush0() {
            // Flush immediately only when there's no pending flush.
            // If there's a pending flush operation, event loop will call forceFlush() later,
            // and thus there's no need to call it now.
            if (!isFlushPending()) {
                super.flush0();
            }
        }
    }
}

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    protected abstract class AbstractUnsafe implements Unsafe {
        protected void flush0() {
            // 省略......
            final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            // 省略......
            try {
                doWrite(outboundBuffer);
            } catch (Throwable t) {
                handleWriteError(t);
            } finally {
                inFlush0 = false;
            }
        }
    }
}

doWrite

最后进入到NioSocketChanneldoWrite方法

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        // ch 为 java.nio.channels.SocketChannel
        SocketChannel ch = javaChannel();
        int writeSpinCount = config().getWriteSpinCount();
        do {
            if (in.isEmpty()) {
                clearOpWrite();
                return;
            }
            // Ensure the pending writes are made of ByteBufs only.
            int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
            // ①从ChannelOutboundBuffer中取出ByteBuffer
            // 前面分析write()的时候放里面放的实际是ByeBuf
            // 因为ByteBuf实际上是对ByteBuffer的包装
            // 所以这里取出来的时候直接就转换成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
            switch (nioBufferCnt) {
                case 0:
                    // We have something else beside ByteBuffers to write so fallback to normal writes.
                    writeSpinCount -= doWrite0(in);
                    break;
                case 1: {
                    // 我们只写了一条数据 所以会走到这里来
                    ByteBuffer buffer = nioBuffers[0];
                    int attemptedBytes = buffer.remaining();
                    // ② 调用SocketChannel的write()方法写出数据
                    final int localWrittenBytes = ch.write(buffer);
                    if (localWrittenBytes <= 0) {
                        incompleteWrite(true);
                        return;
                    }
                    adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes);
                    --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();
                    // ② 调用SocketChannel的write()方法写出数据
                    final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                    if (localWrittenBytes <= 0) {
                        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);
                    --writeSpinCount;
                    break;
                }
            }
        } while (writeSpinCount > 0);

        incompleteWrite(writeSpinCount < 0);
    }
}

这里有两个关键方法:

  • 先从ChannelOutboundBuffer中取出ByteBuffer

  • 再通过Java原生的SocketChannel写出数据

总结

  1. 调用ctx.write()方法时,只是把数据添加到ChannelOutboundBuffer缓存中

  2. 调用ctx.flush()方法时,才把数据从ChannelOutboundBuffer取出来

  3. 调用Java原生的SocketChannel把数据发送出去

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值