Netty--write过程

示例代码

public class SessionHandler extends SimpleChannelInboundHandler {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        BalanceNetProtocol.BasePackage basePackage = (BalanceNetProtocol.BasePackage) msg;
        ctx.writeAndFlush(basePackage);

    }
...
...

write流程

AbstractChannel#writeAndFlush--->DefaultChannelPipeline#writeAndFlush。然后从tail节点向前传播一个write事件,最终到head。

    @Override
    public ChannelFuture writeAndFlush(Object msg) {
        return pipeline.writeAndFlush(msg);
    }
    @Override
    public final ChannelFuture writeAndFlush(Object msg) {
        return tail.writeAndFlush(msg);
    }

Netty的writeAndFlush流程分析
AbstractChannelHandlerContext#invokeWriteAndFlush先调用write方法,传播一个write事件,然后再调用flush方法。

    private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }

调用HeadContext中的write方法。

å¨è¿éæå¥å¾çæè¿°

 主要进行两步操作,消息过滤,如果使用的不是直接内存需要申请一块直接内存将msg包装一下,然后,添加到channelOutBounfBuffer中,这是一个链表结构。å¨è¿éæå¥å¾çæè¿°

因此在传输数据的时候,最好使用DirectByteBuf可以减少一次拷贝。为什么需要这次拷贝呢?因为堆内存是会随着垃圾回收动态变化的,数据拷贝的时候需要保证内存地址不变。å¨è¿éæå¥å¾çæè¿°
ChannelOutboundBuffer 缓存是一个链表结构,每次传入的数据都会被封装成一个 Entry 对象添加到链表中。ChannelOutboundBuffer 包含三个非常重要的指针:第一个被写到缓冲区的节点 flushedEntry、第一个未被写到缓冲区的节点 unflushedEntry和最后一个节点 tailEntry。 在初始状态下这三个指针都指向 NULL,当我们每次调用 write 方法是,都会调用 addMessage 方法改变这三个指针的指向,可以参考下图理解指针的移动过程会更加形象。å¨è¿éæå¥å¾çæè¿°

 ChannelOutboundBuffer包装了bytebuf,如果一直往里面写数据,可能会造成OOM,因此Netty里面引入了高低水位,也就是每次写一个entry后,会计算当前的字节数如果超过了高水位,就会传播一个不可写的事件。但是这个不可写的话,需要用户去手动判断 channel.isWriteAble,否则的话,还是可以继续写的å¨è¿éæå¥å¾çæè¿°

2、Flush操作
flush操作就是将缓冲区的数据写到socket缓冲区的过程。将用户缓冲区的数据写到内核缓冲区。

HeadContext的flush:å¨è¿éæå¥å¾çæè¿°

 主要包含两个重要方法,addflush和flush0。
addflush用于移动指针,并且减少channelOutBoundBuffer中的字节数。当减少到小于低水位的时候,就传播一个可写事件,同样是提供给用户处理的。å¨è¿éæå¥å¾çæè¿°

flush0 主要完成刷新的操作,这里引入了一个自选锁:

@Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = -1;

        for (;;) {
            Object msg = in.current();
            if (msg == null) {
                // Wrote all messages.
                clearOpWrite();
                break;
            }

            if (msg instanceof ByteBuf) {
                ByteBuf buf = (ByteBuf) msg;
                int readableBytes = buf.readableBytes();
                if (readableBytes == 0) {
                    in.remove();
                    continue;
                }

                boolean setOpWrite = false;
                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 {
                    incompleteWrite(setOpWrite);
                    break;
                }
            } else if (msg instanceof FileRegion) {
                FileRegion region = (FileRegion) msg;
                boolean setOpWrite = false;
                boolean done = false;
                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.transfered() >= region.count()) {
                        done = true;
                        break;
                    }
                }

                in.progress(flushedAmount);

                if (done) {
                    in.remove(); // 根据写出的数据的数量情况, 来判断操作是否完成, 如果完成则调用 in.remove()
                } else {
                    incompleteWrite(setOpWrite);
                    break;
                }
            } else {
                throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg));
            }
        }
    }

1、如果写完了调用in.remove:触发接听

public final boolean remove() {
        if (isEmpty()) {
            return false;
        }

        Entry e = buffer[flushed];
        Object msg = e.msg;
        if (msg == null) {
            return false;
        }

        ChannelPromise promise = e.promise;
        int size = e.pendingSize;

        e.clear();

        flushed = flushed + 1 & buffer.length - 1;

        if (!e.cancelled) {
            // only release message, notify and decrement if it was not canceled before.
            safeRelease(msg);
            safeSuccess(promise); // 这里, 调用了promise的trySuccess()方法, 触发Listener
            decrementPendingOutboundBytes(size);
        }

        return true;
    }

2、如果没写完  incompleteWrite(setOpWrite);setOpWrite这个是这次实际发出去的字节,如果为0,代表socket无法写数据,此时应该在通道上注册一个写事件,然后结束此次写的动作,避免浪费cpu。下次selector时,如果此通道可写了再写。如果setOpWrite不为0,代表socket可写,但是为了避免这个线程长时间被占用,把剩余的下次再写。

    protected final void incompleteWrite(boolean setOpWrite) {
        // socket不可写
        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);
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Netty是一个高性能、异步、事件驱动的网络编程框架。在Netty中,Inbound和Outbound是两个重要的概念,它们分别代表了网络数据的输入和输出。 Inbound是指网络数据从外部进入Netty过程,也就是数据的输入。当Netty接收到外部的数据,就会调用Inbound的相关方法来处理数据。例如,当Netty接收到一个新的连接请求时,就会调用Inbound的channelActive()方法来处理这个连接请求。另外,当Netty接收到一个数据包时,就会调用Inbound的channelRead()方法来处理这个数据包。 Outbound是指网络数据从Netty输出到外部的过程,也就是数据的输出。当Netty需要向外部发送数据时,就会调用Outbound的相关方法来处理数据。例如,当Netty需要向外部发送一个数据包时,就会调用Outbound的write()方法来将数据包写入输出缓冲区。另外,当Netty需要关闭一个连接时,就会调用Outbound的close()方法来关闭连接。 总之,Inbound和Outbound是Netty中非常重要的概念,它们分别代表了网络数据的输入和输出过程。理解它们的作用和使用方法,对于Netty的网络编程非常重要。 ### 回答2: Netty是一个用于构建高性能网络应用程序的开源框架。在Netty中,Inbound和Outbound是两个重要的概念,用于描述数据在网络应用程序中的流动方向。 Inbound指的是数据从外部源流入Netty应用程序,也可以理解为数据的输入。在Netty中,Inbound处理器链负责处理接收到的数据,进行解码、处理和转换等操作。它们通常用于处理网络数据协议的解析和应用逻辑的实现。例如,当网络连接建立时,Inbound处理器可以对接收到的数据进行解码,还可以执行身份认证、权限验证等操作。 Outbound指的是数据从Netty应用程序流出到外部目标,也可以理解为数据的输出。在Netty中,Outbound处理器链负责处理发送到外部目标的数据,进行编码、处理和转换等操作。它们通常用于处理网络数据协议的编码和发送。例如,在发送数据到网络连接时,Outbound处理器可以对数据进行编码,并处理流控制、拥塞控制等问题,确保数据以可靠、高效的方式发送到目标。 总结起来,Inbound处理器链用于处理从外部源流入Netty应用程序的数据,负责解码、处理和转换等操作;而Outbound处理器链用于处理从Netty应用程序流出到外部目标的数据,负责编码、处理和转换等操作。 理解Netty中的Inbound和Outbound概念对于构建高性能的网络应用程序至关重要。通过合理设计和配置Inbound和Outbound处理器链,可以实现数据的高效传输、协议的解析和发送、以及应用逻辑的实现等功能。这使得Netty成为构建高性能和可扩展的网络应用程序的首选框架之一。 ### 回答3: 在Netty中,Inbound和Outbound是两个重要的概念,它们用于描述网络通信中的消息处理流程。理解这两个概念对于使用Netty进行网络编程非常重要。 首先,Inbound(入站)指的是消息从网络到达应用程序的过程,即接收和处理来自客户端或其他网络节点的消息。在Netty中,Inbound的处理流程可以通过实现ChannelInboundHandler接口来实现。通过自定义ChannelInboundHandler,我们可以在接收到消息后进行解码、验证、处理以及生成响应等操作。 其次,Outbound(出站)指的是消息从应用程序发送到网络的过程,即将应用程序的响应发送给客户端或其他网络节点。在Netty中,Outbound的处理流程可以通过实现ChannelOutboundHandler接口来实现。通过自定义ChannelOutboundHandler,我们可以在发送消息之前进行编码、加密、压缩等操作,以及在发送消息之后进行清理等操作。 通过Inbound和Outbound的结合使用,我们可以有效地处理消息的收发。比如,当接收到Inbound消息后,我们可以在Inbound处理链中对消息进行解码,并将解码后的消息转发给Outbound处理链中的下一个节点进行处理或发送;而在Outbound处理链中,我们可以对消息进行编码,并将其发送到网络中。 此外,通过Netty提供的ChannelInitializer,我们可以对Inbound和Outbound处理链进行组合和配置,实现一个完整的消息处理流程。在这个流程中,每个Inbound处理器都可以处理来自网络的入站消息,每个Outbound处理器都可以处理应用程序的出站消息。 总之,Inbound和Outbound是Netty中用来描述消息处理流程的重要概念。理解它们的作用和用法,可以帮助我们更好地使用Netty进行网络编程,实现可靠的消息通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值