加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Netty - 粘包和半包(下)

发布时间:2019-10-27 18:23:06 所属栏目:教程 来源:健健壮
导读:接上篇《TCP 粘包和半包 介绍及解决(上)》 上一篇介绍了粘包和半包及其通用的解决方案,今天重点来看一下 Netty 是如何实现封装成帧(Framing)方案的。 解码核心流程 之前介绍过三种解码器FixedLengthFrameDecoder、DelimiterBasedFrameDecoder、LengthFiel
副标题[/!--empirenews.page--]

接上篇《TCP 粘包和半包 介绍及解决(上)》

上一篇介绍了粘包和半包及其通用的解决方案,今天重点来看一下 Netty 是如何实现封装成帧(Framing)方案的。

Netty - 粘包和半包(下)

解码核心流程

之前介绍过三种解码器FixedLengthFrameDecoder、DelimiterBasedFrameDecoder、LengthFieldBasedFrameDecoder,它们都继承自ByteToMessageDecoder,而ByteToMessageDecoder继承自ChannelInboundHandlerAdapter,其核心方法为channelRead。因此,我们来看看ByteToMessageDecoder的channelRead方法:

  1. @Override 
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  3.  if (msg instanceof ByteBuf) { 
  4.  CodecOutputList out = CodecOutputList.newInstance(); 
  5.  try { 
  6.  // 将传入的消息转化为data 
  7.  ByteBuf data = (ByteBuf) msg; 
  8.  // 最终实现的目标是将数据全部放进cumulation中 
  9.  first = cumulation == null; 
  10.  // 第一笔数据直接放入 
  11.  if (first) { 
  12.  cumulation = data; 
  13.  } else { 
  14.  // 不是第一笔数据就进行追加 
  15.  cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); 
  16.  } 
  17.  // 解码 
  18.  callDecode(ctx, cumulation, out); 
  19.  } 
  20.  // 以下代码省略,因为不属于解码过程 
  21.  } 

再来看看callDecode方法:

  1. protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { 
  2.  try { 
  3.  while (in.isReadable()) { 
  4.  int outoutSize = out.size(); 
  5.  if (outSize > 0) { 
  6.  // 以下代码省略,因为初始状态时,outSize 只可能是0,不可能进入这里 
  7.  } 
  8.  int oldInputLength = in.readableBytes(); 
  9.  // 在进行 decode 时,不执行handler的remove操作。 
  10.  // 只有当 decode 执行完之后,开始清理数据。 
  11.  decodeRemovalReentryProtection(ctx, in, out); 
  12.  // 省略以下代码,因为后面的内容也不是解码的过程 

再来看看decodeRemovalReentryProtection方法:

  1. final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) 
  2.  throws Exception { 
  3.  // 设置当前状态为正在解码 
  4.  decodeState = STATE_CALLING_CHILD_DECODE; 
  5.  try { 
  6.  // 解码 
  7.  decode(ctx, in, out); 
  8.  } finally { 
  9.  // 执行hander的remove操作 
  10.  boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING; 
  11.  decodeState = STATE_INIT; 
  12.  if (removePending) { 
  13.  handlerRemoved(ctx); 
  14.  } 
  15.  } 
  16. // 子类都重写了该方法,每种实现都会有自己特殊的解码方式 
  17. protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception; 

从上面的过程可以总结出,在解码之前,需要先将数据写入cumulation,当解码结束后,需要通过 handler 进行移除。

具体解码过程

刚刚说到decode方法在子类中都有实现,那针对我们说的三种解码方式,一一看其实现。

1. FixedLengthFrameDecoder

其源码为:

  1. @Override 
  2. protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 
  3.  Object decodedecoded = decode(ctx, in); 
  4.  if (decoded != null) { 
  5.  out.add(decoded); 
  6.  } 
  7. protected Object decode( 
  8.  @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception { 
  9.  // 收集到的数据是否小于固定长度,小于就代表无法解析 
  10.  if (in.readableBytes() < frameLength) { 
  11.  return null; 
  12.  } else { 
  13.  return in.readRetainedSlice(frameLength); 
  14.  } 

就和这个类的名字一样简单,就是固定长度进行解码,因此,在设置该解码器的时候,需要在构造方式里传入frameLength。

2. DelimiterBasedFrameDecoder

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读