浅谈TCP粘包及其解决方法
本文最后更新于21 天前,其中的信息可能已经过时,如有错误请发送邮件到everfades1218@gmail.com

——by Everfades

​ 鉴于笔者水平有限,部分讲解可能存在不规范,于此还望见谅,如果您有宝贵的意见或建议,请发邮件至:everfades1218@gmail.com或everfades@foxmail.com

希望大家能从这篇笔记中取得一些收获!

(⚠在使用Typora阅读前,请将文件→偏好设置→Markdown→内联公式 勾选上,以免显示出错!)

 

一.什么是TCP?

​ 在研究TCP粘包之前,我们先来看看什么是TCP:TCP全称Transmission Control Protocol,也叫传输控制协议,一般和IP组合成TCP/IP网络核心,其在网络通信中起到了不可或缺的作用。一般来说TCP的流程如下:

1.三次握手(建立连接)

  1. 客户端 -> 服务端:SYN(请求连接)
  2. 服务端 -> 客户端:SYN + ACK(同意+确认)
  3. 客户端 -> 服务端:ACK(准备就绪)

2.四次挥手(断开连接)

  1. 客户端发出FIN,表示不再发出数据
  2. 服务端回复ACK,表示已经收到关闭请求
  3. 服务端发出FIN,表示我方的数据已经发完,可以关闭
  4. 客户端回复ACK,表示确认断开

通过三次握手四次挥手,TCP保证了数据不丢包,不错乱,不重复。

二.什么是TCP粘包、拆包?

​ TCP的数据收发采取的是字节流协议,既然是流协议,为什么还会有包的概念呢?其实这个所谓的TCP粘包,是体现在服务端的”粘包”。而粘包又分为发送粘包接收粘包,前者是指发送方缓冲区数据攒多帧一次性发出,导致多个逻辑包拼成一块;而后者是指接收缓冲区积攒多条数据,一次读取拿到多个数据包。

​ 无论是哪种粘包,最后都会导致在recv的时候一次性拿到多条数据,无法分清边界。

​ 拆包和粘包恰恰相反,拆包是把一份完整的单个业务数据包,拆成多段分次收到,一次 recv 只拿到部分片段。

三.为什么会出现粘包、拆包的情况?

​ 出现粘包的核心原因就是TCP 是面向字节流的协议,协议层本身不携带应用消息边界,操作系统只负责收发连续字节流,无法区分应用层一条消息的起止位置

​ 举个例子,发送方发送了两条消息:

send("Hello")
send("World")

​ 但是TCP只看到了连续字节流:

HelloWorld

​ 显然,接收方一次 recv() 到多少字节,并不等于发送方一次 send() 发了多少字节。

实际触发粘包分为两类场景:

  1. 发送侧合并粘包

    ①开启 Nagle 算法时系统主动缓存小包、等待合并发送;

    ②短时间多次发送少量数据,发送缓冲区尚有富余,内核将多段应用数据缓存后封装为单个 TCP 报文发送;

  2. 接收侧合并粘包:

    对端一个 TCP 报文携带多条应用数据存入本机接收缓冲区,应用单次recv读取缓冲区全部数据,一次性拿到多条业务消息,形成粘包。

    Nagle 算法、发送缓冲区攒包、接收缓冲区批量读数据,都只是粘包的常见诱因,而非本质原因。

拆包的原因也类似:其本质原因是 TCP 是字节流协议,加上 MSS/MTU、缓冲区、窗口控制、拥塞控制和接收方读取大小等因素,导致一条应用层消息可能被分成多次接收。其中,一般MTU是路由器的最大传输单元,来定义一个数据包的大小,当数据包大小超过MTU限制的时候,会按MTU分成几个包,对于家庭和办公路由器,MTU的默认大小一般为1500字节。

四.如何解决TCP粘包拆包问题?

​ 一般有三种解决办法:定长分割法,分隔符法,长度字段法。

1.定长分割法:

​ 顾名思义,定长分割法就是在接收消息的时候按照固定的长度接收消息,不足的部分会补空格或其他填充字符,这样无论数据包被如何分割,接受放都能够按照固定的长度来解析,我们常见的FTP(File Transfer Protocol)就是采用这种方案(FTP的底层传输一般也是基于TCP的)。

​ 优点:解析稳定,不容易出问题。

​ 缺点:浪费空间,效率较低。

定长分割法

2.分隔符法

​ 第二种就是采用分隔符,我们会定义一个特殊的分隔符,每发出一条消息,就在消息末尾添加这个分隔符,接收方通过寻找这个分隔符来确定消息的开始和结束。

​ 在电子邮件传输协议SMTP(Simple Mail Transfer Protocol)中就是采用的这个方案,它采用连续的两个换行+回车表示邮件的结束。

​ 优点:泛用性强。

​ 缺点:分隔符选择不当,在消息中出现会导致意外分隔。

分割符法

3.长度字段法

​ 这种是目前业界最主流,最标准的做法,其在消息头加一个字段来存储消息的长度,接收方在收到消息时,会首先读取这个长度,再根据这个长度读取对应长度的数据,这种方式最流行的实现方式是TLV(Type-Length-Value)。

​ 当接收方读取到Type时,说明一个新的协议数据包开始了(类似于Java class中的开始字符JAVA BABE)。Length字段存储了数据包的长度,接收方只要按照这个长度读取后续的字符即可,读取完就会获得一个完整的包,还没接受够那就继续等,直到接受足够长度的数据为止。Value就是数据内容。

长度字段法

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇