应用层处理 tcp 粘/拆包问题

背景

很多TCP相关的八股文中都有粘包和拆包的概念,但理解TCP基本原理之后就明白了 TCP 是个字节流协议,根本没有粘/拆包这个问题,所以通常指的其实是应用层要如何从一堆数据[也就是字节数组]中,反序列化出自己的数据结构。

底层原理

应用层读取 TCP 流数据,归根结底由 read() 系统调用[无论是阻塞的还是非阻塞的], 把数据从 os 的 socket 缓冲区中以字节数组的形式读出来。

应用层,也就是我们写的代码或者类库,面对拿到的字节数组,需要思考如何处理,而这不属于tcp的范畴。

想要把字节数组转为自己定义的数据结构[也就是反序列化], 那么需要考虑:如何确定一个应用层协议数据体的大小?
例如收到的字节数组是 200Byte, 而应用层对应的数据[或者说对象,结构体等]大小不确定,就没办法反序列化。

解决方案

如何界定应用层的数据体大小,进一步反序列化,通常有3种方式:

  1. 固定大小的数据体。 例如规定是 128Byte, 那么在收到字节数组之后,每次取128Byte,进行解析就搞定了。
    优点:实现起来最简单 缺点:规定太大了需要填充,太小了要切分,不符合现实情况。

  2. 特殊字符分割。 例如规定,数据体的末尾是 # 。 那么在收到字节数组之后,每次把数据都取出来[放到自己开辟的一个缓冲区里],然后扫描找 # , 找到后进行解析 0 ~ index 的 Byte 数据就可以了,然后要记录缓冲区里面的位置,等待下一次读取寻找 #。 redis 协议和 http 协议的请求头部分就是这么干的,分别用 # 和 换行进行分割。

  3. 指定数据体的长度。 一般需要结合前两种方式。例如结根据二种方式,可以解析出http 请求头部分,然后请求头中有个字段叫 content-length , 用来告诉服务器紧跟在请求头后面的请求体的具体字节大小,那么服务端应用层就能根据这个大小读取对应的字节数进行解析了。

一些 tips

  1. 现实应用中,一般是第三种方案比较多。
  2. 应用层通过 read() 从tcp流中读数据之后,需要自己找个地方缓冲起来。这个缓冲区要能自己扩容,例如采用方案2时,第一次读了100Byte没读到分割,那么要读第二次,第三次,直到读取到。但也要防止客户端恶意攻击,一直填充非分割字符数据,导致缓冲区OOM,所以要有个最大大小限制,超过了就说明客户端是恶意的或者数据量过大,进行异常处理。例如golang自带的http包中规定的这个最大值是 256MB, 超过之后就主动断开链接了。
    请添加图片描述

https://www.zhihu.com/question/20210025/answer/1982654161

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值