netty writeAndFlush源码分析

writeAndFlush之发送速率不匹配 背景知识 如果业务线程调用writeAndFlush()发送消息,会生成WriteAndFlushTask,交由IO线程处理,write操作将消息写入ChannelOutboundBuffer,flush操作将ChannelOutboundBuffer写入socket的发送缓冲区; ChannelOutboundBuffer是单向链表,没有容量限制; 速率不匹配造成的问题  如下图所示,当业务线程产 阅读详情

​目录

          一、 背景

          二、源码


一、背景

writeAndFlush,顾名思义,就是写入(发送缓冲区)并且刷新,熟悉netty编码的同学对这个方法一定不会感到陌生, 这方法既能将数据写到发送缓存中, 也能刷新到channel中。

        二、源码

以在动态长度解码器那节中使用的例子为入口,我们进入到writeAndFlush看看其执行逻辑。

public class LengthFieldBasedFrameDecoderTestClient {
​
    public static void main(String[] args) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline()
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        public void channelActive(ChannelHandlerContext ctx) {
                                            ByteBuf byteBuf = Unpooled.copiedBuffer("hello world".getBytes());
                                            ctx.writeAndFlush(byteBuf);
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.connect("127.0.0.1", 9000).sync();
            System.out.println("Started LengthFieldBasedFrameDecoderTestClient...");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

     进入到ChannelHandlerContext的实现类AbstractChannelHandlerContext中。

   @Override
    public ChannelFuture writeAndFlush(Object msg) {
        return writeAndFlush(msg, newPromise());
    }

newPromise()是为了写成功或者失败后的异步回调通知(DefaultChannelPromise)。其中会注册listener监听器,对处理事件进行监听。

继续进入到实现了ChannelOutboundInvoker的writeAndFlush方法。

    @Override
    public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
        if (msg == null) {
            throw new NullPointerException("msg");
        }
​
        if (!validatePromise(promise, true)) {
            ReferenceCountUtil.release(msg);
            // cancelled
            return promise;
        }
​
        write(msg, true, promise);
​
        return promise;
    }

此时的msg就是发送的消息载体。promise是刚才创建的DefaultChannelPromise。

在这个writeAndFlush方法中首先对msg进行非空判断,然后对promise进行校验,核心逻辑为write方法,直接进入。

  private void write(Object msg, boolean flush, ChannelPromise promise) {  
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

findContextOutbound()用于以当前ctx为入口一直往前找,直到找到为出站的一个ctx为止,返回找到的ctx(此时为HeadContext,也就是第一个节点了),然后获取执行器NIOEventLoop,flush此时为true,直接进入invokeWriteAndFlush()方法。

  private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            //将消息放入输出缓冲区
            invokeWrite0(msg, promise);
            //将输出缓冲区中的数据通过socket发送到网络中
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }

到这里就很明显了,如果handler准备好了,依次调用invokeWrite0、invokeFlush0写入数据输出缓冲区中(ChannelOutboundBuffer)并且flush刷新缓冲区发送到网络中。

直接进入invokeWrite0方法。

此时将HeadContext转成出站Handler-ChannelOutboundHandler并且调用其write方法。

    private void invokeWrite0(Object msg, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler()).write(this, msg, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }

再往下走就看到了熟悉的unsafe方法,netty所有io操作,底层都是依赖unsafe进行处理的。

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            unsafe.write(msg, promise);
        }

此时的unsafe为NioSocketChannelUnsafe。

进入到其write方法逻辑中。

        @Override
        public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop();
​
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                //如果outboundBuffer为null,则该通道已关闭,因此需要立即使将来失效。
                safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
                // 立即释放消息以防止资源泄漏
                ReferenceCountUtil.release(msg);
                return;
            }
​
            int size;
            try {
                //过滤待发送的消息,只允许发送ByteBuf和FileRegion
                msg = filterOutboundMessage(msg);
                //预估待发送数据的大小
                size = pipeline.estimatorHandle().size(msg);
                if (size < 0) {
                    size = 0;
                }
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                ReferenceCountUtil.release(msg);
                return;
            }
​
            outboundBuffer.addMessage(msg, size, promise);
        }

首先获得ChannelOutboundBuffer,如果outboundBuffer为null,说明通道已经关闭,立即释放消息以防止资源泄漏,然后直接return返回。

filterOutboundMessage(msg),过滤待发送的消息,只允许发送ByteBuf和FileRegion。

    @Override
    protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }
​
            return newDirectBuffer(buf);
        }
        if (msg instanceof FileRegion) {
            return msg;
        }
​
        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }

在这里会对msg类型进行判断,只有ByteBuf以及 FileRegion可以进行网络传输,其他类型的数据是不支持的,如果是非ByteBuf或者FileRegion,直接抛出异常,消息类型是不支持的。

pipeline.estimatorHandle().size(msg)对待发送的消息大小进行预估,如果消息大小小于零,直接重置为零,然后最后通过addMessage(msg, size, promise)将消息添加到buffer中。

进入到addMessage(msg, size, promise)方法。

    public void addMessage(Object msg, int size, ChannelPromise promise) {
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
        if (tailEntry == null) {
            flushedEntry = null;
            tailEntry = entry;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
            tailEntry = entry;
        }
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }
​
        // increment pending bytes after adding message to the unflushed arrays.
        // See https://github.com/netty/netty/issues/1619
        incrementPendingOutboundBytes(size, false);
    }

首先构造一个Entry实例entry,这个Entry是ChannelOutboundBuffer中自定义的一个静态内部类,内部封装了一个Entry结点信息,核心属性如下:

        private final Handle<Entry> handle;
        Entry next;
        //原始消息
        Object msg;
        ByteBuffer[] bufs;
        ByteBuffer buf;
        //异步回调通知成功或者失败
        ChannelPromise promise;
        long progress;
        //对数据本身大小的记录
        long total;
        //msg+bufs+buf待发送数据总大小
        int pendingSize;
        int count = -1;
        boolean cancelled;

ChannelOutboundBuffer中几个核心属性如下:

    // flush操作时进行刷新的ByteBuf
    private Entry flushedEntry;
    // 还未刷新的ByteBuf
    private Entry unflushedEntry;
    // 始终指向最后一个Entry
    private Entry tailEntry;
    // 还未写入的刷新Entry对象数量
    private int flushed;

此时尾部结点tailEntry是为空的,所以,新创建的entry就放到了尾部作为tailEntry。unflushedEntry结点为entry。

然后incrementPendingOutboundBytes调整buffer的大小size。

    private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
        if (size == 0) {
            return;
        }
​
        long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
        if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
            setUnwritable(invokeLater);
        }
    }

至此,write写入一个信息就完成了,其实核心就是构造一个entry结点添加到buffer单向链表尾部。

然后继续回到之前invokeWriteAndFlush的invokeFlush0方法,invokeFlush0方法主要功能就两个:

①、将消息标记为待发送。

②、刷新发送缓冲区发送消息

直接进入方法内部。

来到unsafe操作。

        @Override
        public void flush(ChannelHandlerContext ctx) throws Exception {
            unsafe.flush();
        }

接着往下走。

        @Override
        public final void flush() {
            assertEventLoop();
​
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }
            //消息标记为flushed
            outboundBuffer.addFlush();
            flush0();
        }

进入addFlush方法。

    public void addFlush() {
        // There is no need to process all entries if there was already a flush before and no new messages
        // where added in the meantime.
        //
        // See https://github.com/netty/netty/issues/2577
        Entry entry = unflushedEntry;
        if (entry != null) {
            if (flushedEntry == null) {
                // there is no flushedEntry yet, so start with the entry
                flushedEntry = entry;
            }
            do {
                //计数器
                flushed ++;
                if (!entry.promise.setUncancellable()) {
                    // 已取消,因此请确保我们释放内存并通知释放的字节
                    int pending = entry.cancel();
                    decrementPendingOutboundBytes(pending, false, true);
                }
                entry = entry.next;
            } while (entry != null);
​
            // 所有已刷新,因此重置unflushedEntry
            unflushedEntry = null;
        }
    }

将Entry entry = unflushedEntry意思是如果之前已经有刷新并且在此期间没有添加新消息,则无需处理所有entry,然后通过一个循环flushed记录还未刷新的entry数量。所有已刷新,因此重置unflushedEntry。

然后返回flush0,进入方法内部。

    protected void flush0() {
            if (inFlush0) {
                // 避免重复进入
                return;
            }
            // 没有数据,直接返回
            final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null || outboundBuffer.isEmpty()) {
                return;
            }
            //正在刷新数据
            inFlush0 = true;
​
            //如果通道处于非活动状态,则将所有请求标记为失败。
            if (!isActive()) {
                try {
                    //Channel已经打开
                    if (isOpen()) {
                        outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true);
                    } else {
                        // 不要触发channelWritabilityChanged,因为通道已经关闭
                        outboundBuffer.failFlushed(FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
                    }
                } finally {
                    inFlush0 = false;
                }
                return;
            }
​
            try {
                //将输出缓冲区中的数据通过socket传输
                doWrite(outboundBuffer);
            } catch (Throwable t) {
                if (t instanceof IOException && config().isAutoClose()) {
                    /**
                     * Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of
                     * failing all flushed messages and also ensure the actual close of the underlying transport
                     * will happen before the promises are notified.
                     *
                     * This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()}
                     * may still return {@code true} even if the channel should be closed as result of the exception.
                     */
                    close(voidPromise(), t, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
                } else {
                    outboundBuffer.failFlushed(t, true);
                }
            } finally {
                inFlush0 = false;
            }
        }

进入到核心逻辑doWrite(outboundBuffer)。

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        for (;;) {
            int size = in.size();
            if (size == 0) {
                // All written so clear OP_WRITE
                clearOpWrite();
                break;
            }
            long writtenBytes = 0;
            boolean done = false;
            boolean setOpWrite = false;
​
            ByteBuffer[] nioBuffers = in.nioBuffers();
            int nioBufferCnt = in.nioBufferCount();
            long expectedWrittenBytes = in.nioBufferSize();
            SocketChannel ch = javaChannel();
​
            // Always us nioBuffers() to workaround data-corruption.
            // See https://github.com/netty/netty/issues/2761
            switch (nioBufferCnt) {
                case 0:
                    // 除了ByteBuffers之外,其他东西
                    super.doWrite(in);
                    return;
                case 1:
                    // 只有一个ByteBuf,非聚集写入
                    ByteBuffer nioBuffer = nioBuffers[0];
                    for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) {
                        final int localWrittenBytes = ch.write(nioBuffer);
                        if (localWrittenBytes == 0) {
                            setOpWrite = true;
                            break;
                        }
                        expectedWrittenBytes -= localWrittenBytes;
                        writtenBytes += localWrittenBytes;
                        if (expectedWrittenBytes == 0) {
                            done = true;
                            break;
                        }
                    }
                    break;
                default:
                    // 多个ByteBuf,因此使用聚集写入 config().getWriteSpinCount()=16
                    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;
                        //如果16次写入之后还有数据没有写入,那么done就位false
                        if (expectedWrittenBytes == 0) {
                            done = true;
                            break;
                        }
                    }
                    break;
            }
​
            // 释放完全写入的缓冲区,并更新部分写入的缓冲区的索引。
            in.removeBytes(writtenBytes);
​
            if (!done) {
                //没有完全写入所有缓冲区。
                incompleteWrite(setOpWrite);
                break;
            }
        }
    }

首先size方法(flushed的大小)获取到ChannelOutboundBuffer的未刷新的entry数量,如果为空,说明所有的都应刷新,那么就清空写事件clearOpWrite。writtenBytes为本次循环已经写出的字节数,done标识本次循环是否写出了所有待写出的数据,setOpWrite标识是否需要设置SelectionKey.OP_WRITE事件,in.nioBuffers确保待写的写操作仅有ByteBufs

    public ByteBuffer[] nioBuffers() {
        long nioBufferSize = 0;
        int nioBufferCount = 0;
        final InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        ByteBuffer[] nioBuffers = NIO_BUFFERS.get(threadLocalMap);
        Entry entry = flushedEntry;
        while (isFlushedEntry(entry) && entry.msg instanceof ByteBuf) {
            if (!entry.cancelled) {
                ByteBuf buf = (ByteBuf) entry.msg;
                final int readerIndex = buf.readerIndex();
                final int readableBytes = buf.writerIndex() - readerIndex;
​
                if (readableBytes > 0) {
                    if (Integer.MAX_VALUE - readableBytes < nioBufferSize) {
                        // If the nioBufferSize + readableBytes will overflow an Integer we stop populate the
                        // ByteBuffer array. This is done as bsd/osx don't allow to write more bytes then
                        // Integer.MAX_VALUE with one writev(...) call and so will return 'EINVAL', which will
                        // raise an IOException. On Linux it may work depending on the
                        // architecture and kernel but to be safe we also enforce the limit here.
                        // This said writing more the Integer.MAX_VALUE is not a good idea anyway.
                        //
                        // See also:
                        // - https://www.freebsd.org/cgi/man.cgi?query=write&sektion=2
                        // - http://linux.die.net/man/2/writev
                        break;
                    }
                    nioBufferSize += readableBytes;
                    int count = entry.count;
                    if (count == -1) {
                        //noinspection ConstantValueVariableUse
                        entry.count = count =  buf.nioBufferCount();
                    }
                    int neededSpace = nioBufferCount + count;
                    if (neededSpace > nioBuffers.length) {
                        nioBuffers = expandNioBufferArray(nioBuffers, neededSpace, nioBufferCount);
                        NIO_BUFFERS.set(threadLocalMap, nioBuffers);
                    }
                    if (count == 1) {
                        ByteBuffer nioBuf = entry.buf;
                        if (nioBuf == null) {
                            // cache ByteBuffer as it may need to create a new ByteBuffer instance if its a
                            // derived buffer
                            entry.buf = nioBuf = buf.internalNioBuffer(readerIndex, readableBytes);
                        }
                        nioBuffers[nioBufferCount ++] = nioBuf;
                    } else {
                        ByteBuffer[] nioBufs = entry.bufs;
                        if (nioBufs == null) {
                            // cached ByteBuffers as they may be expensive to create in terms
                            // of Object allocation
                            entry.bufs = nioBufs = buf.nioBuffers();
                        }
                        nioBufferCount = fillBufferArray(nioBufs, nioBuffers, nioBufferCount);
                    }
                }
            }
            entry = entry.next;
        }
        this.nioBufferCount = nioBufferCount;
        this.nioBufferSize = nioBufferSize;
​
        return nioBuffers;
    }

此方法获取所有待写出的ByteBuf,然后构造出一个个ByteBuffer,因为底层还是利用jdk的ByteBuffer进行网络传输的,最终汇总成一个ByteBuffer数组进行返回。nioBufferCount为本次循环需要写出的ByteBuffer个数,然后对nioBufferCnt进行匹配。

如果为零,除了ByteBuffer,我们还有其他东西要写,所以执行doWrite(in)去写。

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = -1;
​
        boolean setOpWrite = false;
        for (;;) {
            Object msg = in.current();
            if (msg == null) {
                // Wrote all messages.
                clearOpWrite();
                // Directly return here so incompleteWrite(...) is not called.
                return;
            }
​
            if (msg instanceof ByteBuf) {
                ByteBuf buf = (ByteBuf) msg;
                int readableBytes = buf.readableBytes();
                if (readableBytes == 0) {
                    in.remove();
                    continue;
                }
​
                boolean done = false;
                long flushedAmount = 0;
                if (writeSpinCount == -1) {
                    writeSpinCount = config().getWriteSpinCount();
                }
                for (int i = writeSpinCount - 1; i >= 0; i --) {
                    int localFlushedAmount = doWriteBytes(buf);
                    if (localFlushedAmount == 0) {
                        setOpWrite = true;
                        break;
                    }
​
                    flushedAmount += localFlushedAmount;
                    if (!buf.isReadable()) {
                        done = true;
                        break;
                    }
                }
​
                in.progress(flushedAmount);
​
                if (done) {
                    in.remove();
                } else {
                    // Break the loop and so incompleteWrite(...) is called.
                    break;
                }
            } else if (msg instanceof FileRegion) {
                FileRegion region = (FileRegion) msg;
                boolean done = region.transferred() >= region.count();
​
                if (!done) {
                    long flushedAmount = 0;
                    if (writeSpinCount == -1) {
                        writeSpinCount = config().getWriteSpinCount();
                    }
​
                    for (int i = writeSpinCount - 1; i >= 0; i--) {
                        long localFlushedAmount = doWriteFileRegion(region);
                        if (localFlushedAmount == 0) {
                            setOpWrite = true;
                            break;
                        }
​
                        flushedAmount += localFlushedAmount;
                        if (region.transferred() >= region.count()) {
                            done = true;
                            break;
                        }
                    }
​
                    in.progress(flushedAmount);
                }
​
                if (done) {
                    in.remove();
                } else {
                    // Break the loop and so incompleteWrite(...) is called.
                    break;
                }
            } else {
                // Should not reach here.
                throw new Error();
            }
        }
        incompleteWrite(setOpWrite);
    }

在doWrite中核心逻辑我们看doWriteBytes(buf)和doWriteFileRegion(region)两种方式,进入到方法内部会发现其实底层还是利用了JavaChannel进行数据写入。

    @Override
    protected int doWriteBytes(ByteBuf buf) throws Exception {
        final int expectedWrittenBytes = buf.readableBytes();
        return buf.readBytes(javaChannel(), expectedWrittenBytes);
    }
​
    @Override
    protected long doWriteFileRegion(FileRegion region) throws Exception {
        final long position = region.transferred();
        return region.transferTo(javaChannel(), position);
    }

返回来如果nioBufferCnt为1,那么就使用费非聚集写入,如果大于1,就使用循环进行写入,最终释放完全写入的缓冲区,并更新部分写入的缓冲区的索引。

最终如果经过循环之后还有数据剩余,那么就包装成task交给eventLoop进行处理。

    protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();
        } else {
            // Schedule flush again later so other tasks can be picked up in the meantime
            Runnable flushTask = this.flushTask;
            if (flushTask == null) {
                flushTask = this.flushTask = new Runnable() {
                    @Override
                    public void run() {
                        flush();
                    }
                };
            }
            eventLoop().execute(flushTask);
        }
    }

个人才疏学浅、信手涂鸦,netty框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

【Netty】从0到1(八):入门-ChannelFuture 与 CloseFuture 往期系列文章请访问博主的。 阅读详情

相关推荐

Netty源码分析系列之writeAndFlush()下

write()只会将数据只是发送到了ChannelOutboundBuffer 缓冲区中,还没有被发送到操作系统的套接字中只有当调用了 flush()方法后,才会真正将数据发送到套接字中。那么 flush()方法的源码又是如何执行的呢?

天堂的博客 1572

Netty的writeAndFlush流程分析

Netty的writeAndFlush流程分析 1、Write操作 writeAndFlush是一个典型的出站操作,如果调用者是channel则从tail节点向前传播。 public final ChannelFuture writeAndFlush(Object msg) { return tail.writeAndFlush(msg); } 看TailContext的writeAndFlush方法。 会调用write方法,其中flush参数的值为true。 先调用write

博客 3379

Netty源码分析系列之writeAndFlush()上

当我们的业务逻辑处理完成后,就需要给客户端响应消息,这就涉及到服务端如何通过 channel 将响应消息写出去的流程了,同时还会涉及到消息的编码过程,因为在 TCP 协议中,数据最终是通过字节流传输的,而我们通常在业务代码中是返回一个对象,因此需要进行编码。接下来本文将会重点分析这两个的过程的源码实现。

天堂的博客 2132

Netty学习九:编解码之writeAndFlush

Netty学习系列

weixin_41402069的博客 4475

writeAndFlush处理流程剖析——数据传输

接下来我们如何将解编码后的结果发送出去呢?在 Netty 中实现数据发送非常简单,只需要调用 writeAndFlush 方法即可,这么简单的一行代码究竟 Netty 帮我们完成了哪些事情呢? 文章目录Pipeline 事件传播回顾writeAndFlush 事件传播分析写 Buffer 队列刷新 Buffer 队列总结 Pipeline 事件传播回顾 在介绍 writeAndFlush 的工作原理之前,我们首先回顾下 Pipeline 的事件传播机制,因为他们是息息相关的。根据网络数据的流向,Channe

1608

netty案例,netty4.1源码分析篇五《一行简单的writeAndFlush都做了哪些事》

前言介绍 对于使用netty的小伙伴来说,ctx.writeAndFlush()再熟悉不过了,它可以将我们的消息发送出去。那么它都执行了那些行为呢,是怎么将消息发送出去的呢。 I/O Request via {@lin...

bugstack虫洞栈 1928

Netty源码分析——ChannelHandlerContext.writeAndFlush与channel.writeAndFlush的区别

疑问 在如下代码示例中,我们经常使用channel().writeAndFlush()来返回数据,同时有一个同名方法ChannelHandlerContext.writeAndFlush(),从使用上来说,两者都是将数据写回channel,但是两者有没有什么区别呢? @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System

保暖透气大裤衩LeoLee的博客 3439

从零开始:用ADS 2023手把手教你设计2.4GHz Wi-Fi LNA(基于ATF-54143,附模型文件)

本文详细介绍了如何使用ADS 2023软件设计2.4GHz Wi-Fi频段的低噪声放大器(LNA),基于ATF-54143晶体管。内容涵盖模型导入、直流工作点确定、稳定性优化、噪声匹配等关键步骤,并提供可下载的模型文件包,帮助工程师快速掌握LNA设计技巧。

weixin_42531128的博客 320

Netty源码分析----writeAndFlush

(*文章基于Netty4.1.22版本) ctx.writeAndFlush相当于先调用ctx.write然后再调用ctx.flush,所以下面分析write和flush write write和flush会经过pipeline的每个outbound的Handler,之前文章分析过流程,这里不再分析。 write方法最终到达HeadContext的write方法,然后什么都没做,将请求...

菜鸟一名 3976

华为私有云HCS 8.1.1实战:从镜像制作到云主机发放全流程指南

本文详细解析了华为私有云HCS 8.1.1从镜像制作到云主机发放的全流程实战指南。内容涵盖制作标准化QCOW2镜像、配置云主机规格与主机组、规划外部网络,以及在运营面为租户初始化资源、配置审批流与产品目录。最后,通过用户视角演示了从创建VPC网络到申请、审批并最终绑定EIP访问云主机的完整操作步骤,为运维人员和架构师提供了一套清晰、可落地的HCS部署与管理实践方案。

weixin_29305313的博客 1127

从JX-2R-01热敏打印机芯到稳定打印:硬件设计与时序控制的实践指南

本文详细解析了JX-2R-01热敏打印机芯的硬件设计与驱动开发全流程。核心内容包括:实现严格的双电源上电时序控制、采用PWM恒流驱动步进电机确保走纸平稳,以及精确控制SPI数据加载与加热脉冲时序以获得清晰打印效果。文章提供了从电路设计、芯片选型到代码调试的实战指南,旨在帮助开发者构建稳定可靠的热敏打印模块。

weixin_29169899的博客 218

netty源码分析之writeAndFlush全解析

前言 在前面的文章中,我们已经详细阐述了事件和异常传播在netty中的实现,(netty源码分析之pipeline(一),netty源码分析之pipeline(二)),其中有一类事件我们在实际编码中用得最多,那就是 write或者writeAndFlush,也就是我们今天的主要内容 主要内容 本文分以下几个部分阐述一个java对象最后是如何转变成字节流,写到socket缓冲区中去的 pipeline中的标准链表结构 java对象编码过程 write:写队列 flush:刷新写队列 write

sicaujh的博客 1135

Zabbix华为交换机光衰告警

zabbix使用自动发现监控huawei网络设备光衰

qq_31651649的博客 4529

Netty 源码分析系列(十八)一行简单的writeAndFlush都做了哪些事?

对于使用netty的小伙伴来说,我们想通过服务端往客户端发送数据,通常我们会调用ctx.writeAndFlush(数据)的方式。那么它都执行了那些行为呢,是怎么将消息发送出去的呢。在这个类中,我们主要看方法,源码如下。do {return;通过一个无限循环,保证可以拿到所有的节点上的ByteBuf,通过这个函数获取节点,我们进一步看它的实现,如下,它只会取出我们标记的节点。下面我们看下的方法源码。

初念初恋的博客 1975

准静态电磁场

叠加法 缺点:对于连续分布的电荷,叠加法的计算比较困难 虚位移法 概念 广义坐标:确实系统中导体形状、尺寸与位置的一组独立几何量。 广义力:企图改变某一广义坐标的力。 功:广义力乘上由它引起的广义坐标的改变量。 公式 ...

qq_35912930的博客 1860

Central Finance 全景解析:用 SAP S/4HANA 构建跨系统的财务中台与渐进式转型路径

Central Finance 是基于 SAP S/4HANA 的一种部署与应用场景:在不打断现有异构源系统运营的前提下,把多套 SAP 与非 SAP 系统中的财务与管理会计数据实时/准实时复制到一套中心化的 SAP S/4HANA 中,作为统一的记账与分析底座,逐步把选定的财务流程(如预算控制、付款、固定资产结账等)迁入中心系统。这让企业既能快速获得统一报表与统一核算结构,又为未来的整体迁移留出了弹性路径。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 390

Netty源码分析(终)--关于WriteAndFlush

在 Netty 编程中, 是我们最常使用的方法之一。它代表着从用户层到内核缓冲区的一整条出站(Outbound)数据路径。本章将从源码层面,深入分析 的执行流程与关键机制。在前面讲解编解码器时,我们提到过“编码”的概念。编码指的是将服务器处理完的业务对象转换为字节码的过程。 因为数据写出属于 Outbound 事件,所以负责编码的 一般位于管道尾部()之前,如下图所示:典型的编码器(Encoder)通常继承自 。 当我们调用 时,Netty 会从管道尾部(Tail)开始,依次调用所有 Outbound

sc35262的博客 681

EPLAN中2D安装板的绘制方法示例

EPLAN中2D安装板的绘制方法示例

Robot_PLC_HMI_Automation 3215

netty源码分析之writeAndFlush全解析分析

前言 在前面的文章中,我们已经详细阐述了事件和异常传播在netty中的实现,(netty源码分析之pipeline(一),netty源码分析之pipeline(二)),其中有一类事件我们在实际编码中用得最多,那就是 write或者writeAndFlush,也就是我们今天的主要内容 主要内容 本文分以下几个部分阐述一个java对象最后是如何转变成字节流,写到socket缓冲区中去的 pip...

很多时候犯错都是在不知情的情况下发生的 4834

可编程逻辑控制器(PLC)系列:Mitsubishi MELSEC-Q_(8).MELSEC-Q系列PLC故障诊断与维护

MELSEC-Q系列PLC的故障诊断与维护是一个系统性的过程,需要工程师具备扎实的理论基础和丰富的实践经验。通过了解故障类型、使用诊断工具、遵循诊断步骤,可以有效地定位和解决故障问题。同时,定期的检查与保养、备份与恢复、优化程序和配置,以及提供良好的运行环境,可以减少故障的发生,提高系统的可靠性。希望本文能够帮助工程师在故障诊断与维护过程中更加得心应手,确保MELSEC-Q系列PLC的稳定运行。

2401_87715305的博客 1247

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

06|Netty writeAndFlush 源码分析:数据真的立刻发到网卡了吗?

这篇文章深入分析了Netty中writeAndFlush方法的执行流程和底层机制。核心要点包括: writeAndFlush不等于数据立即发送,而是先进入Netty的出站流程,再尝试刷新到socket write和flush的区别:write将数据放入出站缓冲队列,flush才真正尝试写入socket 数据会经过Pipeline中的outbound处理器链,可能进行编码/压缩等操作 最终数据进入ChannelOutboundBuffer队列,由底层Unsafe负责实际写出 socket写操作可能分批完成,未

谢文峰的博客 384

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Netty从0到1系列之writeAndFlush原理分析

本文解析Netty中writeAndFlush的工作原理,区分核心概念:write将数据暂存至内部缓冲区,flush触发网络发送,而writeAndFlush是两者的组合操作。通过类比邮箱投递形象说明缓冲区的批处理优势——减少系统调用开销。核心流程通过ChannelPipeline事件传播,依次经过业务处理器、编码器和Pipeline头节点,最终由底层Socket发送。

编程技术圈 1350

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Netty源码实战(十一) --- 设计模式的应用

1 单例模式 1.1 Netty 实例 1.1.1 ReadTimeoutException 可以看出,构造器私有,防止随意创建 static保证延迟加载 1.1.2 MqttEncoder 2 策略模式 3 装饰者模式 Netty 实践 4 观察者模式 4.1 常规操作 4.2 Netty 的实践 在调用 writeAndFlush 后,其实返回的...

JavaEdge全是干货的技术号 1362

Netty : writeAndFlush的线程安全及并发问题

使用Netty编程时,我们经常会从用户线程,而不是Netty线程池发起write操作,因为我们不能在netty的事件回调中做大量耗时操作。那么问题来了 – 1, writeAndFlush是线程安全的吗? 2, 是否使用了锁,导致并发性能下降呢   我们来看代码 – 在DefaultChannelHandlerContext中 @Override public Channe

binhualiu1983的博客 8343

Netty源码分析第7章(编码器和写数据)---->第1节: writeAndFlush的事件传播

Netty源码分析第7章(编码器和写数据)---->第1节: writeAndFlush的事件传播 Netty源码分析第七章: 编码器和写数据 概述: 上一小章我们介绍了解码器, 这一章我们介绍编码器 其实编码器和解码器比较类似, 编码器也是一个handler, 并且属于out...

weixin_30767835的博客 165
上一篇: netty零拷贝之CompositeByteBuf
下一篇: seata源码初始启动报错(protobuf)
wenyixicodedog
博客等级 码龄8年 87粉丝 116原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值