《深入剖析Netty》之八:Netty编码

8 Netty编码和writeAndFlush()

如果调用writeAndFlush(),且有对应的编码器,那么Netty就能将对象变成字节流,最终写到socket底层

总过程图:涉及两个重要的自定义handler:Encoder、BizHandler;最后传到head,通过底层unsafe写到socket
在这里插入图片描述
BizHandler:

public class BizHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //...:这里可能会有一些对象的数据,这里简化,直接new
        User user = new User(19, "zhangsan");
        ctx.channel().writeAndFlush(user);
    }
}

Encoder:

/**
 * ---------------------
 *|   4    |  4  |  ?   |
 * ---------------------
 *| length | age | name |
 * ---------------------
 */
//可以使用基于长度域解码器对out进行解码
public class Encoder extends MessageToByteEncoder<User> {
    @Override
    protected void encode(ChannelHandlerContext ctx, User user, ByteBuf out) throws Exception {
        byte[] bytes = user.getName().getBytes();
        out.writeInt(4 + bytes.length);
        out.writeInt(user.getAge());
        out.writeBytes(bytes);
    }
}

问题:

1.out是如何传播到Encoder的?

通过write传:MessageToByteEncoder#write方法会调用encode,这也是我们重写此方法的原因

MessageToByteEncoder的write方法会调用encoder方法,传入ByteBuf,重写后就能拿到

2.将对象编码进out之后,是如何把out写到socket底层的?

自旋写

8.1 writeAndFlush()抽象步骤

writeAndFlush拆分为writer和flush方法,这样可以单独调用

  • 流程

1.从tail节点开始往前向outbound传播

2.先逐个调用channelHandler的write方法直到Head,表明将对象写到缓存(自定义encoder覆盖write方法)

3.write结束后,再逐个调用channelHandler的flush方法直到Head,表明对象真的要写到socket底层去了(此方法不用覆盖)

  • 解析

在逐个调用channelHandler的write方法时,需要进行“当前线程是否对应NioEventLoop”的判断逻辑

8.2 抽象编码器MessageToByteEncoder处理逻辑

MessageToByteEncoder是outboundHandler,write方法的msg其实就是要编码的对象

  • 流程

1.匹配对象(acceptOutboundMessage(0)

2.如果匹配成功,则分配内存(ByteBuf buf = allocateBuffer())

3.编码实现(encode()),自定义编码器需要重写此方法

4.释放对象

5.传播数据

6.如果出了异常,则释放buf内存

  • 解析

使用TypeParameterMatcher来判断当前handler是否能处理此msg

分配出来的内存作用:给第三步用来写入对象数据用

如果对象是ByteBuf对象,则需要释放,如果是自定义的就不用

传播到head节点,由此节点负责将数据写入socket

8.3 Head节点的write、flush

write():Head的write调用Unsafe的write,这一步称为写buffer队列

8.3.1 write()-写buffer队列

  • 流程

1.direct化ByteBuf(AbstractNioByteChannel#newDirectBuffer()),即将内存转为堆外内存,因为要通过Unsafe来操作

2.将ByteBuf封装为一个entry,然后插入写队列,通过指针来标识ByteBuf的一些状态(addMessage)

3.设置写状态,统计当前需要写出的字节,这样如果内存不足了,那么就能知道不能将新的ByteBuf插入队列,会抛出异常(64字节最大)

  • 解析

指针:tailEntry、flushedEntry、unflushedEntry

// Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
//链表结构中的第一个被刷新过的entry,即Entry(flushedEntry) --> ... 这段都是被write掉的了
private Entry flushedEntry;
//链表结构中第一个未刷新的entry
private Entry unflushedEntry;
//表示缓冲区的尾部
private Entry tailEntry;

第一次调用write()时三个指针的状态:
在这里插入图片描述
第二次调用write()时三个指针的状态:
在这里插入图片描述
第n次调用write()时三个指针的状态:
在这里插入图片描述

8.3.2 flush-刷新buffer队列

flush():Head的flush调用Unsafe的flush,这一步称为刷新buffer队列

  • 流程

1.添加刷新标志(即三个指针)并设置写状态(将刷新到socket的字节数减掉,这样就可以继续write了)

2.遍历buffer队列,过滤ByteBuf

3.调用jdk底层api进行自旋写(默认尝试16次,性能点)

  • 解析

如下图的两个写状态,如果小于64,表明不可写了,如果小于32,就把不可写改为可写(设计巧妙)
在这里插入图片描述
第一次过程后三个指针的状态:unflushedEntry为null,说明缓冲区已经没有可以flush到flushedEntry到TailEntry这一段的Bytebuf了;而flushedEntry到TailEntry这一段都是可以flush的
在这里插入图片描述
每次写完都会移除当前entry,即将flushedEntry往前移动一个位置,最后指向null

8. 总结

  • 如何把对象变成字节流,最终写到socket底层?

8.1的抽象步骤具体看就是

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值