一、黏包和半包现象例子

服务器端代码:

package com.test.netty.c6;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class HelloServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(boss, worker);serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 连接建立时会执行该方法log.debug("connected {}", ctx.channel());super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// 连接断开时会执行该方法log.debug("disconnect {}", ctx.channel());super.channelInactive(ctx);}});}});ChannelFuture channelFuture = serverBootstrap.bind(8080);log.debug("服务器端启动...");channelFuture.sync();log.debug("服务器端启动成功...");//channelFuture.channel().close().sync();}catch (Exception e){e.printStackTrace();}finally {//boss.shutdownGracefully();//worker.shutdownGracefully();}}
}

客户端代码:

package com.test.netty.c6;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class HelloClinet {public static void main(String[] args) {NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {log.debug("发送数据...");ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {for (int i = 0; i < 10; i++) {ByteBuf byteBuf = ch.alloc().buffer();byteBuf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});ctx.writeAndFlush(byteBuf);}}});}});ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();channelFuture.channel().closeFuture().sync();}catch (Exception e){e.printStackTrace();}finally {worker.shutdownGracefully();}}
}

黏包

上面的代码可以看到,客户端期望发送10次,每次发送16个字节。但是运行结果如下:

客户端一次性接受到了160字节,这就是黏包现象。

半包

当设置了服务器端缓存小于16个字节: serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);

运行结果:

服务器端会多次接收,但是每次接收的大小都不是16个字节,这就是半包现象

二、出现的原理分析

黏包

发送abc def ,接收到abcdef

  • 应用层设置的ByteBuf太大了(Netty默认1024)
  • 滑动窗口,假设发送256字节数据,由于滑动窗口足够大,返回的不及时,一次发送的数据(256字节)会缓存到滑动窗口中,多次发送就会形成黏包
  • Nagle算法,太小的数据发送一次影响效率,所以保存到缓冲区,缓冲区达到一定大小后在一起发送

半包

发送abcdef,接收到abc def

  • 应用层的ByteBuf太小了
  • 滑动窗口,滑动窗口的缓冲区小于发送的数据,导致发送的数据被分割了
  • MSS限制(网卡),当发送数据超过MSS限制后,会将数据分割

发送黏包和半包的本质就是因为TCP是流式协议,消息无边界

三、解决方案

1、短连接

每次客户端发送数据后,都断开连接,这样服务器端就知道这次发送已经完成,这样就不会出现黏包现象,但是一次性发送数据量过大的话,就会出现半包现象

代码改进:

每次发送直接关闭channel即可。

2、定长解码器

其实就是客户端和服务器端约定一个数据大小长度,服务器端每次接收到固定长度的大小就知道是一次发送,但是这样会消耗带宽,例如发送一个字节的数据也需要占位符去占位到约定大小。

这种方式服务器端使用FixedLengthFrameDecoder对接收到的数据进行固定长度的解码。

服务器端:

客户端:

运行结果:

3、行解码器

第三种解决方案就是,客户端和服务器端约定一个关键字,遇到这个关键字就知道是一条数据。但是这种方式效率比较低,因为需要检查每个字符是不是关键字。

解码器:

  • LineBasedFrameDecoder(int maxLength)  使用"\n",分隔数据
  • DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) 可以自定义分隔符

服务器改造:

客户端改造:

运行结果:

4、长度字段解码器

这种方式就是把消息体的长度,也当做数据进行传递,这样就告诉了服务器端消息体的长度,服务器可以根据长度去解析消息体,但是因为整个消息中包含了数据的长度内容,所以需要约定数据长度站多少字节数,同时在数据长度前后可能存在其他信息(例如版本号等等),所以就算不像前几个那样约定,也要约定怎么解析整个数据体,但是相对前几个效率已经有大大的提升了,并且消息整体携带的信息也有很大的提升。

LengthFieldBasedFrameDecoder的参数,一共5个:

  • maxFrameLength 整个数据的最大长度,包含了全部信息
  • lengthFieldOffset “长度”的偏移量(用于确定“长度”的起始位置)
  • lengthFieldLength  “长度”所占用的字节数
  • lengthAdjustment  “长度”标识和真正消息体的偏移量(用于确定“消息体”的起始位置)
  • initialBytesToStrip  读取数据的起始位置

图解:

集中情况的解析:

1、

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 0 (= do not strip header)BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+

没有其它数据,只有消息体和长度,其中长度占2个字节。

2、

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 2 (= the length of the Length field)BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
+--------+----------------+      +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
+--------+----------------+      +----------------+

没有其它数据,只有消息体和长度,其中长度占2个字节,但是读取从消息体开始读取,也就是2个字节之后开始读取(initialBytesToStrip =2)

3、

lengthFieldOffset   = 2 (= the length of Header 1)
lengthFieldLength   = 3
lengthAdjustment    = 0
initialBytesToStrip = 0BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
| Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
|  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+      +----------+----------+----------------+

在“长度”前面,存在其它内容,长度是2字节(lengthFieldOffset =2),“长度”字段是3个字节,所以一共是17个字节

4、

lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
lengthAdjustment    = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
+------+--------+------+----------------+      +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+      +------+----------------+

在“长度”前面和后面都有一个字节的其他数据(lengthFieldOffset =1,lengthAdjustment =1),从第三个字节开始读取(initialBytesToStrip=3),所以读到了“长度”后面的1个字节的其他消息

实例:

package com.test.netty.c7;import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;import java.nio.charset.StandardCharsets;public class LengthFieldBasedFrameDecoderTest {public static void main(String[] args) {EmbeddedChannel channel = new EmbeddedChannel(new LengthFieldBasedFrameDecoder(1024, 1, 4, 1, 0),new LoggingHandler(LogLevel.DEBUG));ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();send(buf, "hello");channel.writeInbound(buf);buf.clear();send(buf, "world");channel.writeInbound(buf);}private static void send(ByteBuf buf, String msg){int length = msg.length();byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);//长度前面的标识 1个字节buf.writeByte(0xCA);//长度buf.writeInt(length);//长度后面的标识 1个字节buf.writeByte(0xFE);//写消息体buf.writeBytes(bytes);}}

运行结果:

总结,在实际使用过程中,一定是最后一种 LengthFieldBasedFrameDecoder 运用更加广泛,不仅能动态的读取到消息体,同时也能携带更多的信息,但是还是需要约定相关内容,这个是必然的,我们平时运用的各种协议也都是基于协议的约定进行使用的。

Netty关于黏包和半包的处理相关推荐

  1. Netty进阶 黏包与半包问题的处理,数据解码器详解

    概述 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下情况. (1)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP黏包: (2 ...

  2. Netty——ByteBuffer(ByteBuffer消息粘包、半包示例)

    目录 一. ByteBuffer消息粘包.消息半包的概述 二.示例需求 三.示例代码 一. ByteBuffer消息粘包.消息半包的概述 NIO是面向缓冲区进行通信的,不是面向流的.既然是缓冲区,那它 ...

  3. TCP 粘包、半包 Netty 全搞定

    什么是粘包和半包? 粘包的主要原因: • 发送方每次写入数据 < 套接字缓冲区大小 • 接收方读取套接字缓冲区数据不够及时 半包的主要原因: • 发送方写入数据 > 套接字缓冲区大小 • ...

  4. Java NIO 框架 Netty 之美:粘包与半包问题

    Netty 是一个可以快速开发网络应用程序的 NIO 框架,它大大简化了 TCP 或者 UDP 服务器的网络编程.Netty 的简易和快速开发并不意味着由它开发的程序将失去可维护性或者存在性能问题,它 ...

  5. 透过现象看本质,我找到了Netty粘包与半包的这几种解决方案

    1.粘包与半包 啥也不说了,直接上代码是不是有点不太友好,我所谓了,都快过年了,还要啥自行车 我上来就是一段代码猛如虎 1.1 服务器代码 public class StudyServer {stat ...

  6. 透过现象看本质,我找到了Netty粘包与半包的这几种解决方案。

    1.粘包与半包 啥也不说了,直接上代码是不是有点不太友好,我所谓了,都快过年了,还要啥自行车 我上来就是一段代码猛如虎 1.1 服务器代码 public class StudyServer {stat ...

  7. netty半包粘包 处理_Java NIO 框架 Netty 之美:粘包与半包问题

    一.前言 Netty 是一个可以快速开发网络应用程序的 NIO 框架,它大大简化了 TCP 或者 UDP 服务器的网络编程.Netty 的简易和快速开发并不意味着由它开发的程序将失去可维护性或者存在性 ...

  8. Netty - 粘包和半包(上)

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 定义 TCP 传输中,客户端发送数据,实际是把数据写 ...

  9. netty中的粘包和半包

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 一.定义 TCP 传输中,客户端发送数据,实际是把数 ...

最新文章

  1. corosync+pacemaker+crm简单配置
  2. 2. JSF---托管Bean
  3. 论文笔记 Traffic Data Reconstruction via Adaptive Spatial-Temporal Correlations
  4. 8.图片组件和动画效果--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...
  5. 1075 PAT Judge (25 分)【难度: 一般 / 知识点: 多关键字排序】
  6. 三路合并 —— Git 学习笔记 17
  7. Boost:与容器相关的BOOST_TEST_EQ测试
  8. vscode markdown_使用vscode开始Markdown写作之旅
  9. Audacity Mac版(音频录制编辑合成工具)中文版
  10. 深度| 百度副总裁王海峰:百度在NLP领域都做了什么?
  11. 阿里云服务器续费坑啊早知道不买了
  12. 实时调度论文中经常出现的术语 ties broken arbitrary的意思
  13. 【渝粤教育】21秋期末考试建筑设备10327k1
  14. 江南情节——紫砂壶品茶
  15. java综合知识点总结基础篇
  16. 水果店圈子:水果店怎么入门,水果店工作注意什么
  17. 动态规划(DP)小结
  18. win10 redis集群搭建 ruby
  19. 【转】canvas save restore详解(包你懂,绝对不是百度里千篇一律的教程)
  20. linux修改证书通用名,linux – 证书通用名称`* .c.ssl.fastly.net’与请求的主机名不匹配...

热门文章

  1. 马上创业网日常有话说——我与外国友人Say Hi
  2. oca考试及ocp考试指南
  3. js报错- cannot set property xxx of undefined
  4. 带头双向循环链表的模拟实现
  5. 就是这35个人,率先入选了《共识算法高阶课》第一期!
  6. 循序渐进,探寻Excel二次开发.NET源码(3)-ExcelBase类
  7. 刷题记录:牛客NC24083Greedy Gift Takers
  8. solidworks正版多少钱_SOLIDWORKS 2020、2021正版软件价格是多少钱?SW官方报价_专业版和白金版...
  9. Spark并行度的设定
  10. 1267 老鼠的旅行 2012年CCC加拿大高中生信息学奥赛