返回顶部
首页 > 资讯 > 精选 >Netty如何解决TCP 粘包拆包
  • 941
分享到

Netty如何解决TCP 粘包拆包

2023-06-20 14:06:20 941人浏览 薄情痞子
摘要

小编给大家分享一下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 粘包拆包”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网精选频道!

--结束END--

本文标题: Netty如何解决TCP 粘包拆包

本文链接: https://lsjlt.com/news/297336.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Netty如何解决TCP 粘包拆包
    小编给大家分享一下Netty如何解决TCP 粘包拆包,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!什么是粘包/拆包    ...
    99+
    2023-06-20
  • Netty解决 TCP 粘包拆包的方法
    什么是粘包/拆包        一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘...
    99+
    2024-04-02
  • 说说 TCP的粘包、拆包
    分析&回答 拆包和粘包是在socket编程中经常出现的情况, 在socket通讯过程中,如果通讯的一端一次性连续发送多条数据包,tcp协议会将多个数据包打包成一个tcp报文发送出去,这就是所谓的粘包。如果通讯的一端发送的数据包超过一次tcp...
    99+
    2023-09-05
    tcp/ip 网络 网络协议
  • go语言如何处理TCP拆包/粘包
    这篇文章主要讲解了“go语言如何处理TCP拆包/粘包”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言如何处理TCP拆包/粘包”吧!part 1最近在学习go自带的rpc,看完了一遍想着...
    99+
    2023-06-22
  • Netty粘包拆包及使用原理详解
    目录为什么使用Netty框架Netty框架介绍Netty实战Netty编写服务器端Netty客户端粘包与拆包为什么使用Netty框架 NIO的类库和API繁杂,使用麻烦,你需要熟练掌...
    99+
    2022-11-13
    Netty 粘包拆包 Netty 原理
  • 解决TCP粘包/拆包问题的方法及示例
    TCP粘包和拆包是网络编程中常见的问题,特别是在数据传输的过程中,可能会发生将多个数据包粘在一起或将一个数据包拆成多个数据包的情况,这可能会导致应用程序无法正确解析数据,从而造成数据错误或系统故障。本文将介绍TCP粘包和拆包的原因、解决方案...
    99+
    2023-09-25
    网络 服务器 tcp/ip c# 网络协议
  • php的tcp粘包和拆包怎么实现
    今天小编给大家分享一下php的tcp粘包和拆包怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。tcp 长链接模式下,使...
    99+
    2023-07-04
  • 分享一个php的tcp粘包/拆包例子
    本文给大家介绍一个例子,有关PHP的tcp 粘包/拆包,希望对需要的朋友有所帮助~tcp 长链接模式下,使用固定消息头长度的方式进行消息 拆包 ,解决 粘包 问题。 固定消息头协议将消息头的前 N 个字节固定为 消息长度位 ,结合业务场景,...
    99+
    2023-05-14
    php
  • workerman 自定义的协议如何解决粘包拆包
    前言:         由于最近在使用 workerman 实现 Unity3D 联机游戏的服务端,虽然也可以通过 TCP 协议直接通信,但是在实际测试的过程中发现了一些小问题。         比如双方的数据包都是字符串的方式吗,还有就因...
    99+
    2023-09-07
    TCP粘包拆包 workerman unity3d PHP网游服务
  • go语言处理TCP拆包/粘包的具体实现
    目录part 1part 2part 3part 1 最近在学习go自带的rpc,看完了一遍想着自己实现一个codec,也就是自定义消息的序列化和反序列化。消息的序列化和反序...
    99+
    2022-06-07
    GO go语言
  • workerman怎么自定义协议解决粘包拆包问题
    这篇“workerman怎么自定义协议解决粘包拆包问题”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“workerman怎么自...
    99+
    2023-07-04
  • Golang通过包长协议处理TCP粘包的问题解决
    目录tcp粘包现象代码重现tcp粘包问题处理方法tcp粘包产生的原因这里就不说了,因为大家能搜索TCP粘包的处理方法,想必大概对TCP粘包有了一定了解,所以我们直接从处理思路开始讲起...
    99+
    2024-04-02
  • 如何解决Socket粘包问题
    本篇内容介绍了“如何解决Socket粘包问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!问题一:TCP存...
    99+
    2024-04-02
  • python socket粘包问题怎么解决
    今天小编给大家分享一下python socket粘包问题怎么解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我...
    99+
    2024-04-02
  • 利用TCP进行通信出现丢包如何解决
    这篇文章给大家介绍利用TCP进行通信出现丢包如何解决,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。如果通信中发现缺少数据或者丢包,那么,最大的可能在于程序发送的过程或者接收的过程出现问题。例如服务器给客户端发大量数据,...
    99+
    2023-05-31
    tcp tc
  • python如何实现智能拆包
    这篇文章主要介绍了python如何实现智能拆包,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。智能拆包迭代地解压值可能会非常耗费时力,Python中有几种不错的方法可以用来解压...
    99+
    2023-06-27
  • Socket粘包问题的解决方法有哪些
    这篇文章主要讲解了“Socket粘包问题的解决方法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Socket粘包问题的解决方法有哪些”吧!什么是 TC...
    99+
    2024-04-02
  • webpack如何实现拆分、打包、压缩
    这篇文章主要介绍webpack如何实现拆分、打包、压缩,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!步骤1.传统项目中的问题在不依赖任何自动化、模块化工具的项目中,通常我们的代码是这...
    99+
    2024-04-02
  • 如何在python使用元组进行拆包
    这篇文章主要为大家详细介绍了如何在python使用元组进行拆包,文中示例代码介绍的非常详细,具有一定的参考价值,发现的小伙伴们可以参考一下:Python主要用来做什么Python主要应用于:1、Web开发;2、数据科学研究;3、网络爬虫;4...
    99+
    2023-06-06
  • electron打包的坑如何解决
    这篇文章主要介绍“electron打包的坑如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“electron打包的坑如何解决”文章能帮助大家解决问题。两种方式,electron-builder打...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作