zoukankan      html  css  js  c++  java
  • Netty 粘包/拆包应用案例及解决方案分析

    熟悉TCP变成的可以知道,无论是客户端还是服务端,但我们读取或者发送消息的时候,都需要考虑TCP底层粘包/拆包机制,下面我们先看一下TCP 粘包/拆包和基础知识,然后模拟一个没有考虑TCP粘包/拆包导致功能异常的案例,最后,通过正确的例程来谈谈Netty是如何实现的。

    主要内容:

    • TCP粘包/拆包的基础知识

    • 没考虑TCP粘包/拆包的问题案例

    • 使用Netty解决读半包问题

    1、TCP粘包/拆包

        TCP是个“流“协议,所谓流,就是没有界限的一串数据。TCP底层并不知道上层业务逻辑,它会根据TCP缓冲区的实际情况进行包的拆分,所以在业务上认为,一个完整的包可能会被拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包/拆包的问题。

    2、TCP粘包/拆包发生的原因

        问题产生的原因有三个:如下

    • 应用程序write写入的字节大小大于套接口发送缓冲区大小;

    • 进行MSS大小的分段;

    • 以太网帧的payload大于MTU进行IP分片; 

    备注:mtu是网络传输最大报文包。mss是网络传输数据最大值。 

    https://img3.mukewang.com/5b7ba6030001819a08230455.jpg

    3、粘包问题的解决策略

       由于底层TCP无法理解上层业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下:

    • 消息定长,例如每个报文的大小长度200字节,如果不够,不空格;

    • 在包尾增加回车换行符,例如FTP协议;

    • 将消息分为消息头和消息体,消息头包含表示消息总长度的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;

    • 更复杂的设计协议;

    介绍完了TCP粘包/拆包的基础知识后,我们看一下Netty是如何解决半包问题的,是如何使用Netty的半包解码器来解决TCP粘包/拆包问题。

    4、未考虑TCP粘包/拆包问题出现的功能异常

    TimeServer的改造(可以查看上一篇文章中的netty客户端-服务端的实现):

    https://img1.mukewang.com/5b7badcc0001a03413320886.jpg

    每读到一条消息后,就计数一次,然后发送应答消息给服务端。

    TimeClient端的改造:

    https://img2.mukewang.com/5b7bb0c20001cdb211660848.jpg

    https://img2.mukewang.com/5b7bb0df0001caaf10360573.jpg

    运行结果(服务端接收指令):

    The time server receive order : QUERY TIME ORDER

    此处省略57行。。。。。。。

    QUERY TIME ORD ; the counter is :1

    The time server receive order : 

    此处省略43行。。。。。。。

    QUERY TIME ORDER ; the counter is :2

    运行结果(客户端接收响应):

    Now is : BAD ORDER

    BAD ORDER

     ; the counter is : 1

    原因分析:服务端运行结果表明它只接收到两条消息,第一条包含57条“QUERY TIME ORDER”指令,第二天包含了43条指令,总数100条,我们期望的也是100条,但是计数只有两条,所有发生TCP粘包,按照设计初衷,客户端应该收到100响应,但实际上只收到了1条,不难理解,客户端也发生了粘包,一条应答消息中包含两条“BAD ORDER”指令的消息。

    5、通过LineBasedFrameDecoder解决TCP粘包问题

       为了解决TCP粘包/拆包导致的半包读写问题,Netty默认提供了多种编解码器用于处理半包,这是其他NIO框架和JDK原生的NIO API不能匹敌的。

    直接上代码

    TimeServer:

    https://img3.mukewang.com/5b7bb71000018a2610730414.jpg

        在原来的TimeServerHandler之前增加了两个解码器:LineBasedFrameDecoder、StringDecoder

    TimeServerHandler:

    https://img2.mukewang.com/5b7bbafd0001168013390526.jpg

    TimeClient:

    https://img3.mukewang.com/5b7bb9b90001e18310300693.jpg

    TimeClientHandler:

    https://img2.mukewang.com/5b7bbbb500017c0311540389.jpg

    支持TCP粘包的运行结果:

    服务端:

    The time server receive order : QUERY TIME ORDER ; the counter is :1

    此处省略92条。。。。。。

    The time server receive order : QUERY TIME ORDER ; the counter is :100

    客户端:

    Now is : Tue Aug 21 15:15:21 CST 2018 ; the counter is : 1

    此处省略92条。。。。。。

    Now is : Tue Aug 21 15:15:21 CST 2018 ; the counter is : 100

    6、LineBasedFrameDecoder、StringDecoder原来分析

       LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断是否有“ “或者“ ”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标记的解码器,

        StringDecoder非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的Handler,

    总结:LineBasedFrameDecoder + StringDecoder组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包、拆包。

    疑问:

    1、如果发送的消息不是以换行符结束的怎么办?

    2、靠消息头中的长度字段来分包的怎么办?

    这样的话是否需要自己写半包解码器,答案是否定的,Netty 提供了多种支持 TCP粘包、拆包的解码器,用来满足需求,下面的文章中会详细介绍《分隔符解码器》《定长解码器》,因为它在项目中使用非常广泛,所以单独去分享这一知识点。

  • 相关阅读:
    模拟展示动态按钮
    模拟界面请求到web服务器
    bean的生命周期
    structs2的action实现方式
    annotation中的Autowired
    华为笔试题练习
    开发工具
    [转]Linux双向链表的知识
    【转】 嵌入式C语言编程中Inline函数的应用
    打印格式化printf
  • 原文地址:https://www.cnblogs.com/wenhongyu/p/9511887.html
Copyright © 2011-2022 走看看