TCP/IP,传输的是byte[],将byte[]放入队列中。可能会发生粘包和拆包。

比如,客户端向服务端发送了2条消息,分别为D1,D2,可能产生的情况,如下图所示:

情况一:正常的。

情况二:粘包。

情况三:拆包。即:部分数据不是一次完整发送的,而是分了至少2次发送。

如本例,D2拆成了D2_1和D2_2,这是拆包。

服务端分2次收到包,第一次收到了D1和D2_1包,这是粘包;服务端第二次收到了D2_2包,这是拆包。

回到Time client例子,存在相同的问题。4字节的int很小,很少发生粘包或拆包。但是,如果并发量大时,可能会发生。
最简单的方法是创建一个内部全局的(只为了多次接收放入相同buffer)buffer,等待,直到4个字节全部接受。以下修改了TimeClientHandler解决此问题。

第一种解决方法:全局buffer,累积。

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import lombok.extern.slf4j.Slf4j;
  5. import cn.hutool.core.date.DateUtil;
  6. @Slf4j
  7. public class TimeClientHandler extends ChannelInboundHandlerAdapter {
  8. private ByteBuf buf;
  9.  
  10. @Override
  11. public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  12. buf = ctx.alloc().buffer();//()
  13. }
  14.  
  15. @Override
  16. public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
  17. buf.release();//()
  18. buf = null;
  19. }
  20.  
  21. @Override
  22. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  23. ByteBuf m = (ByteBuf) msg;
  24. buf.writeBytes(m);//()
  25. m.release();
  26. if (buf.readableBytes() >= ) {//()
  27. long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L)*1000L;
  28. log.info("{}",DateUtil.date(currentTimeMillis));
  29. ctx.close();
  30. }
  31. }
  32.  
  33. @Override
  34. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  35. cause.printStackTrace();
  36. ctx.close();
  37. }
  38.  
  39. }

1、ChannelHandler

  • ChannelHandler有2个监听器方法(生命周期):handlerAdded()和handlerRemoved()。当该handler被添加到pipeline中时,被触发;当该handler从pipeline删除时,被触发。该handler,执行顺序:channelAdd()--->channelRead()--->channelRemove()。
  • 只要任务不重,即不会阻塞很久,可以在这2个方法中做一些初始化的工作。比如本例,channelAdd()中初始分配一个4字节的ByteBuf,在channelRemove()中释放ByteBuf。

2、首先,接收到的所有字节均被写入buf。
3、然后,handler必须要检查是否够4个字节(本例),如果不够(拆包),则当有剩下的数据来时,Netty会再次调用该channelRead()方法,直到4个字节都接收到为止

第二种解决方法:使用解码器

尽管第一种方法解决了粘包和拆包问题,但是,代码臃肿。因为,可以向pipeline中添加多个handler,因此,我们可以将TimeClientHandler分割成2个handler:
1)、TimeDecoder
2)、上节里的TimeClientHandler版本。

TimeDecoder.java

  1. import java.util.List;
  2.  
  3. import io.netty.buffer.ByteBuf;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.handler.codec.ByteToMessageDecoder;
  6.  
  7. public class TimeDecoder extends ByteToMessageDecoder {//(1)
  8. @Override
  9. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
  10. if (in.readableBytes() < 4) {
  11. return ;//(3)
  12. }
  13. out.add(in.readBytes(4));//(4)
  14. }
  15. }

1、ByteToMessageDecoder 实现了ChannelInboundHandler

2、ByteToMessageDecoder,当调用decode()时,其内部实现了“累积”功能的buffer,即不用自己在写全局buffer了,当接收到新数据时,会向该buffer中写入。
3、decode():比如,一个包分2次传的,则会调用2次decode()。第一次即使不够4个字节,也会存入其内部“累积”buffer。我们的decode()方法中,return即可。
4、decode()中,一旦添加了一个obj到“out”,意味着该decoder已经成功将消息解码了,即解决了粘包拆包问题。此时,ByteToMessageDecoder将会丢弃掉“累积”buffer中已读的消息。ByteToMessageDecoder将会不断调用decode(),直到添加“空”到out为止。

TimeClientHandler.java(上节time中的TimeClientHandler)

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import lombok.extern.slf4j.Slf4j;
  5. import cn.hutool.core.date.DateUtil;
  6. @Slf4j
  7. public class TimeClientHandler extends ChannelInboundHandlerAdapter {
  8.  
  9. @Override
  10. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  11. ByteBuf m = (ByteBuf) msg;
  12. try {
  13. long currentTimeMillis = (m.readUnsignedInt() - 2208988800L)*1000L;
  14. log.info("{}",DateUtil.date(currentTimeMillis));
  15. ctx.close();
  16. } finally {
  17. m.release();
  18. }
  19. }
  20.  
  21. @Override
  22. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  23. cause.printStackTrace();
  24. ctx.close();
  25. }
  26.  
  27. }

然后,

  1. @Override
  2. protected void initChannel(SocketChannel ch) throws Exception {
  3. ch.pipeline().addLast(new TimeDecoder(),new TimeClientHandler());
  4. }

执行顺序:TimeDecoder.decode(ctx, ByteBuf in, List<Object> out)--->TimeClientHandler.channelRead(ctx, Object msg)。

上例中,从TimeDecoder传给TimeClientHandler的依然是ByteBuf,既然是int,那我们可以直接传递int吗?可以,如下:

TimeDecoder.java,修改成

  1. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
  2. if (in.readableBytes() < 4) {
  3. return ;//(3)
  4. }
  5. //out.add(in.readBytes(4));//(4)
  6. out.add(in.readUnsignedInt());//(4)
  7. }

TimeClientHandler.java,修改成

  1. @Override
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  3. long m = (long) msg;
  4. long currentTimeMillis = (m - 2208988800L)*1000L;
  5. log.info("{}",DateUtil.date(currentTimeMillis));
  6. ctx.close();
  7. }

ReplayingDecoder是一个更加简单的decoder。可以代替ByteToMessageDecoder,只需要修改TimeDecoder,如下:

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.handler.codec.ReplayingDecoder;
  4.  
  5. import java.util.List;
  6.  
  7. public class TimeDecoder extends ReplayingDecoder<Void> {//(1)
  8. @Override
  9. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
  10. out.add(in.readUnsignedInt());
  11. }
  12. }

其他代码都不用动。

最终输出:

服务端:18:43:45.824 [nioEventLoopGroup-3-4] -549056671

客户端:

18:43:45.854 [nioEventLoopGroup-2-1] 2018-09-14 18:43:45
18:43:45.929 [main] client channel is closed.

Netty(4)Stream by codec(粘包与拆包)的更多相关文章

  1. 如何基于Netty处理粘包、拆包问题?

    涉及到相关重要组件: ByteToMessageDecoder MessageToMessageDecoder 这两个组件都实现了ChannelInboundHandler接口,这说明这两个组件都是用 ...

  2. netty 解决TCP粘包与拆包问题(二)

    TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...

  3. netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...

  4. 【Netty】TCP粘包和拆包

    一.前言 前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包. 二.粘包和拆包 对于TCP协议而言,当底层发送消息和接受消息时,都需要考虑TCP ...

  5. Netty(二)——TCP粘包/拆包

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...

  6. Netty使用LineBasedFrameDecoder解决TCP粘包/拆包

    TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...

  7. Netty 中的粘包和拆包

    Netty 底层是基于 TCP 协议来处理网络数据传输.我们知道 TCP 协议是面向字节流的协议,数据像流水一样在网络中传输那何来 "包" 的概念呢? TCP是四层协议不负责数据逻 ...

  8. Netty(三)TCP粘包拆包处理

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...

  9. Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

随机推荐

  1. hdu1085Holding Bin-Laden Captive!组合问题

    题目连接 题目意思:有单位价值为1 2 5的三种硬币,分别给出他们的数量,求用这些硬币不能组成的最小的价值 解题思路:普通的母函数 普通的母函数: 利用母函数的思想可以解决很多组合问题,下面举例说明: ...

  2. OpenCV——PS滤镜算法之 球面化 (凹陷效果)

    // define head function #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGORITHM_H_INCLUDED #include < ...

  3. Shell读取文件内容【转】

    while read wOne wTwo wThreedo    [ -z $wOne ] && continue           #测试此行内容是否为空    xxx=$wOne ...

  4. ZJOI2012题解

    t1灾难 给一个食物网 如果一个生物吃的所有东西都灭绝了 它也跟着灭绝 求每个生物灭绝时跟着灭绝的生物数量 支配树裸题,我们先拓扑排序,然后建立一棵树满足一个点灭绝时,有且仅有它的子树跟着灭绝 考虑如 ...

  5. BZOJ1251 序列终结者(Splay平衡树)(占位)

    网上有许多题,就是给定一个序列,要你支持几种操作:A.B.C.D.一看另一道题,又是一个序列 要支持几种操作:D.C.B.A.尤其是我们这里的某人,出模拟试题,居然还出了一道这样的,真是没技术含量…… ...

  6. Cloudera Manager 5 和 CDH5 本地(离线)安装指南

    http://archive.cloudera.com/cm5/redhat/6/x86_64/cm/5.0.0/RPMS/x86_64/ http://archive-primary.clouder ...

  7. P2766 [网络流24题]最长不下降子序列问题

    ha~ «问题描述: 给定正整数序列$x_1,...,x_n$ .$n<=500$ 求(1)计算其最长不下降子序列的长度$s$. (2)计算从给定的序列中最多可取出多少个长度为$s$的不下降子序 ...

  8. Spring boot 学习六 spring 继承 mybatis (基于注解)

    MyBatis提供了多个注解如:@InsertProvider,@UpdateProvider,@DeleteProvider和@SelectProvider,这些都是建立动态语言和让MyBatis执 ...

  9. Lagom学习 五 Hello world工程

    用Maven创建一个Hello world的Lagom工程: 1: 在想创建工程的目下下,打开CMD 2:  mvn archetype:generate -Dfilter=com.lightbend ...

  10. Docker入门(七):部署app

    这个<Docker入门系列>文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成.主要是作为个人学习记录.有错误的地方,Robin欢迎大家指 ...