我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Netty如何解决TCP 粘包拆包

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Netty如何解决TCP 粘包拆包

小编给大家分享一下Netty如何解决TCP 粘包拆包,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

什么是粘包/拆包

       一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。

我们都知道TCP是基于字节流的传输协议。

那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句号什么地方有分号这个对于TCP底层来说并不清楚。应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段,之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。

所以对于这个数据拆分成大包小包的问题就是我们今天要讲的粘包和拆包的问题。

1、TCP粘包拆包问题说明

粘包和拆包这两个概念估计大家还不清楚,通过下面这张图我们来分析一下:

Netty如何解决TCP 粘包拆包

假设客户端分别发送两个数据包D1,D2个服务端,但是发送过程中数据是何种形式进行传播这个并不清楚,分别有下列4种情况:

  • 服务端一次接受到了D1和D2两个数据包,两个包粘在一起,称为粘包;

  • 服务端分两次读取到数据包D1和D2,没有发生粘包和拆包;

  • 服务端分两次读到了数据包,第一次读到了D1和D2的部分内容,第二次读到了D2的剩下部分,这个称为拆包;

  • 服务器分三次读到了数据部分,第一次读到了D1包,第二次读到了D2包的部分内容,第三次读到了D2包的剩下内容。

2、TCP粘包产生原因

我们知道在TCP协议中,应用数据分割成TCP认为最适合发送的数据块,这部分是通过“MSS”(最大数据包长度)选项来控制的,通常这种机制也被称为一种协商机制,MSS规定了TCP传往另一端的最大数据块的长度。这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。

tcp为提高性能,发送端会将需要发送的数据发送到缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制,来接收数据。

发生粘包拆包的原因主要有以下这些:

  • 应用程序写入数据的字节大小大于套接字发送缓冲区的大小将发生拆包;

  • 进行MSS大小的TCP分段。MSS是TCP报文段中的数据字段的最大长度,当TCP报文长度-TCP头部长度>mss的时候将发生拆包;

  • 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,将发生粘包;

  • 数据包大于MTU的时候将会进行切片。MTU即(Maxitum Transmission Unit) 最大传输单元,由于以太网传输电气方面的限制,每个以太网帧都有最小的大小64bytes最大不能超过1518bytes,刨去以太网帧的帧头14Bytes和帧尾CRC校验部分4Bytes,那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值我们就把它称之为MTU。这个就是网络层协议非常关心的地方,因为网络层协议比如IP协议会根据这个值来决定是否把上层传下来的数据进行分片。

3、如何解决TCP粘包拆包

我们知道tcp是无界的数据流,且协议本身无法避免粘包,拆包的发生,那我们只能在应用层数据协议上,加以控制。通常在制定传输数据时,可以使用如下方法:

  1. 设置定长消息,服务端每次读取既定长度的内容作为一条完整消息;

  2. 使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容;

  3. 设置消息边界,服务端从网络流中按消息边界分离出消息内容。比如在消息末尾加上换行符用以区分消息结束。

当然应用层还有更多复杂的方式可以解决这个问题,这个就属于网络层的问题了,我们还是用java提供的方式来解决这个问题。我们先看一个例子看看粘包是如何发生的。

服务端:

public class HelloWordServer {    private int port;    public HelloWordServer(int port) {        this.port = port;    }    public void start(){        EventLoopGroup bossGroup = new NioEventLoopGroup();        EventLoopGroup workGroup = new NioEventLoopGroup();        ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)                                    .channel(NioServerSocketChannel.class)                                    .childHandler(new ServerChannelInitializer());        try {            ChannelFuture future = server.bind(port).sync();            future.channel().closeFuture().sync();        } catch (InterruptedException e) {            e.printStackTrace();        }finally {            bossGroup.shutdownGracefully();            workGroup.shutdownGracefully();        }    }    public static void main(String[] args) {        HelloWordServer server = new HelloWordServer(7788);        server.start();    }}

服务端Initializer:

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {        ChannelPipeline pipeline = socketChannel.pipeline();        // 字符串解码 和 编码        pipeline.addLast("decoder", new StringDecoder());        pipeline.addLast("encoder", new StringEncoder());        // 自己的逻辑Handler        pipeline.addLast("handler", new HelloWordServerHandler());    }}

服务端handler:

public class HelloWordServerHandler extends ChannelInboundHandlerAdapter {    private int counter;    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        String body = (String)msg;        System.out.println("server receive order : " + body + ";the counter is: " + ++counter);    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {        super.exceptionCaught(ctx, cause);    }}

客户端:

public class HelloWorldClient {    private  int port;    private  String address;    public HelloWorldClient(int port,String address) {        this.port = port;        this.address = address;    }    public void start(){        EventLoopGroup group = new NioEventLoopGroup();        Bootstrap bootstrap = new Bootstrap();        bootstrap.group(group)                .channel(NioSocketChannel.class)                .handler(new ClientChannelInitializer());        try {            ChannelFuture future = bootstrap.connect(address,port).sync();                     future.channel().closeFuture().sync();        } catch (Exception e) {            e.printStackTrace();        }finally {            group.shutdownGracefully();        }    }    public static void main(String[] args) {        HelloWorldClient client = new HelloWorldClient(7788,"127.0.0.1");        client.start();    }}

客户端Initializer:

public class ClientChannelInitializer extends  ChannelInitializer<SocketChannel> {    protected void initChannel(SocketChannel socketChannel) throws Exception {        ChannelPipeline pipeline = socketChannel.pipeline();        pipeline.addLast("decoder", new StringDecoder());        pipeline.addLast("encoder", new StringEncoder());        // 客户端的逻辑        pipeline.addLast("handler", new HelloWorldClientHandler());    }}

客户端handler:

public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {    private byte[] req;    private int counter;    public BaseClientHandler() {        req = ("Unless required by applicable law or agreed to in writing, software\n" +                "  distributed under the License is distributed on an \"AS IS\" BASIS,\n" +                "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +                "  See the License for the specific language governing permissions and\n" +                "  limitations under the License.This connector uses the BIO implementation that requires the JSSE\n" +                "  style configuration. When using the APR/native implementation, the\n" +                "  penSSL style configuration is required as described in the APR/native\n" +                "  documentation.An Engine represents the entry point (within Catalina) that processes\n" +                "  every request.  The Engine implementation for Tomcat stand alone\n" +                "  analyzes the HTTP headers included with the request, and passes them\n" +                "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software\n" +                "# distributed under the License is distributed on an \"AS IS\" BASIS,\n" +                "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +                "# See the License for the specific language governing permissions and\n" +                "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log\n" +                "# each component that extends LifecycleBase changing state:\n" +                "#org.apache.catalina.util.LifecycleBase.level = FINE"                ).getBytes();    }    @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        ByteBuf message;        //将上面的所有字符串作为一个消息体发送出去        message = Unpooled.buffer(req.length);        message.writeBytes(req);        ctx.writeAndFlush(message);    }    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        String buf = (String)msg;        System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter));    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {        ctx.close();    }}

运行客户端和服务端我们能看到:

Netty如何解决TCP 粘包拆包

我们看到这个长长的字符串被截成了2段发送,这就是发生了拆包的现象。同样粘包我们也很容易去模拟,我们把BaseClientHandler中的channelActive方法里面的:

message = Unpooled.buffer(req.length);message.writeBytes(req);ctx.writeAndFlush(message);

这几行代码是把我们上面的一长串字符转成的byte数组写进流里发送出去,那么我们可以在这里把上面发送消息的这几行循环几遍这样发送的内容增多了就有可能在拆包的时候把上一条消息的一部分分配到下一条消息里面了,修改如下:

for (int i = 0; i < 3; i++) {    message = Unpooled.buffer(req.length);    message.writeBytes(req);    ctx.writeAndFlush(message);}

改完之后我们再运行一下,输出太长不好截图,我们在输出结果中能看到循环3次之后的消息服务端收到的就不是之前的完整的一条了,而是被拆分了4次发送。

对于上面出现的粘包和拆包的问题,Netty已有考虑,并且有实施的方案:LineBasedFrameDecoder。
我们重新改写一下ServerChannelInitializer:

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {        ChannelPipeline pipeline = socketChannel.pipeline();        pipeline.addLast(new LineBasedFrameDecoder(2048));               // 字符串解码 和 编码        pipeline.addLast("decoder", new StringDecoder());        pipeline.addLast("encoder", new StringEncoder());        // 自己的逻辑Handler        pipeline.addLast("handler", new BaseServerHandler());    }}

新增:pipeline.addLast(new LineBasedFrameDecoder(2048))。同时,我们还得对上面发送的消息进行改造BaseClientHandler:

public class BaseClientHandler extends ChannelInboundHandlerAdapter {    private byte[] req;    private int counter;    req = ("Unless required by applicable dfslaw or agreed to in writing, software" +                "  distributed under the License is distributed on an \"AS IS\" BASIS," +                "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +                "  See the License for the specific language governing permissions and" +                "  limitations under the License.This connector uses the BIO implementation that requires the JSSE" +                "  style configuration. When using the APR/native implementation, the" +                "  penSSL style configuration is required as described in the APR/native" +                "  documentation.An Engine represents the entry point (within Catalina) that processes" +                "  every request.  The Engine implementation for Tomcat stand alone" +                "  analyzes the HTTP headers included with the request, and passes them" +                "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software" +                "# distributed under the License is distributed on an \"AS IS\" BASIS," +                "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +                "# See the License for the specific language governing permissions and" +                "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log" +                "# each component that extends LifecycleBase changing state:" +                "#org.apache.catalina.util.LifecycleBase.level = FINE\n"                ).getBytes();      @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        ByteBuf message;        message = Unpooled.buffer(req.length);        message.writeBytes(req);        ctx.writeAndFlush(message);    }    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        String buf = (String)msg;        System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter));    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {        ctx.close();    }}

去掉所有的”\n”,只保留字符串末尾的这一个。原因稍后再说。channelActive方法中我们不必再用循环多次发送消息了,只发送一次就好(第一个例子中发送一次的时候是发生了拆包的),然后我们再次运行,大家会看到这么长一串字符只发送了一串就发送完毕。程序输出我就不截图了。下面来解释一下LineBasedFrameDecoder。

LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf 中的可读字节,判断看是否有”\n” 或者” \r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器。支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。这个对于我们确定消息最大长度的应用场景还是很有帮助。

对于上面的判断看是否有”\n” 或者” \r\n”以此作为结束的标志我们可能回想,要是没有”\n” 或者” \r\n”那还有什么别的方式可以判断消息是否结束呢。别担心,Netty对于此已经有考虑,还有别的解码器可以帮助我们解决问题,

以上是“Netty如何解决TCP 粘包拆包”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Netty如何解决TCP 粘包拆包

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Netty如何解决TCP 粘包拆包

小编给大家分享一下Netty如何解决TCP 粘包拆包,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数
2023-06-20

go语言如何处理TCP拆包/粘包

这篇文章主要讲解了“go语言如何处理TCP拆包/粘包”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言如何处理TCP拆包/粘包”吧!part 1最近在学习go自带的rpc,看完了一遍想着
2023-06-22

Netty粘包拆包及使用原理详解

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序,这篇文章主要介绍了Netty粘包拆包及使用原理
2022-11-13

workerman怎么自定义协议解决粘包拆包问题

这篇“workerman怎么自定义协议解决粘包拆包问题”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“workerman怎么自
2023-07-04

利用TCP进行通信出现丢包如何解决

这篇文章给大家介绍利用TCP进行通信出现丢包如何解决,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。如果通信中发现缺少数据或者丢包,那么,最大的可能在于程序发送的过程或者接收的过程出现问题。例如服务器给客户端发大量数据,
2023-05-31

electron打包的坑如何解决

这篇文章主要介绍“electron打包的坑如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“electron打包的坑如何解决”文章能帮助大家解决问题。两种方式,electron-builder打
2023-07-05

windows丢包率高如何解决

这篇文章主要讲解了“windows丢包率高如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“windows丢包率高如何解决”吧!解决方法:1、有些情况是因为线路故障导致的,这种情况需要和
2023-07-02

win11无法安装中文包语言包如何解决

这篇文章主要介绍“win11无法安装中文包语言包如何解决”,在日常操作中,相信很多人在win11无法安装中文包语言包如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”win11无法安装中文包语言包如何解决
2023-07-01

electron打包中的坑如何解决

这篇文章主要讲解了“electron打包中的坑如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“electron打包中的坑如何解决”吧!问题一:css文件中图片加载失败问题描述问题是这样
2023-07-05

go中import包的坑如何解决

这篇文章主要介绍“go中import包的坑如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“go中import包的坑如何解决”文章能帮助大家解决问题。方案一:使用GOROOT和GOPATH(以我
2023-07-02

java包导入错误如何解决

要解决Java包导入错误,可以尝试以下方法:1. 检查包名和文件路径:确保包名与文件路径一致,包名应与文件所在的文件夹路径一致。2. 检查类路径和环境变量:确定类路径已正确设置,包含了所需的类和包。可以使用命令行中的`java -cp`命令
2023-09-15

matlab缺少工具包如何解决

如果在MATLAB中缺少工具包,可以尝试以下几种解决方法:1. 更新MATLAB版本:在MATLAB的新版本中,可能已经包含了你所需要的工具包。因此,尝试升级到最新版本的MATLAB,查看是否已经包含了你需要的工具包。2. 安装缺失的工具包
2023-09-14

idea找不到jar包如何解决

要解决找不到jar包的问题,可以尝试以下几个步骤:确保你已经正确地设置了项目的依赖关系。这意味着你已经将所需的jar包添加到了项目的构建路径中。你可以通过在IDE中的项目设置或pom.xml文件中添加所需的依赖项来实现。确保你正在使用正确的
idea找不到jar包如何解决
2024-02-29

java声明的包与期望的包不匹配如何解决

当Java声明的包与期望的包不匹配时,可以采取以下几种解决方法:1. 修改Java文件的package声明:将Java文件的package声明与期望的包保持一致。例如,如果期望的包名是com.example,那么在Java文件的开头应该声明
2023-09-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录