Python 网络编程(进阶):TCP协议、TCP编程
- 1. 预备知识
- 分包
- 粘包
- 2. TCP协议
- 2.1 TCP协议的作用
- 分包:TCP传输的“特性”
- 2.2 TCP 数据包的大小
- 2.3 TCP数据包的编号(SEQ)
- 2.4 TCP数据包的组装
- 2.5 慢启动和 ACK
- 2.6 数据包的遗失处理
- TCP粘包与分包的解决
- 分包与粘包
- 简单的解决方案
- 代码
1. 预备知识
分包
(1)what is 分包
TCP是 以段(Segment)
为单位 发送数据的,建立TCP链接后,有一个 最大报文长度(Maximum Segment Size,MSS) ,就是 TCP数据包每次能够传输的最大数据分段。
(2)why use 分包
路由器有一个最大传输单元( Maximum Transmission Unit,MTU),一般是1500字节
,除去IP头部20字节,留给TCP的就只有MTU-20字节。所以一般TCP的最大报文长度(MSS)为1460字节(MTU-20)。
最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
粘包
**(1) what **
什么是“粘包”?
发送方发送两个字符串”hello”
+”world”
,接收方却一次性接收到了”helloworld”
。
(2) why
为什么出现粘包?
因为TCP socket通信
是面向流的传输协议,而UDP
是面向消息的传输。
所以在TCP socket中,当发送速度特别快,而接收端接收速度跟不上时,就会出现粘包的问题,即接收端一次读出多包数据或者读出两包数据的个一部分。而UDP不会出现这种情况。
所以,TCP socket需要解决的最大问题可能是粘包问题。
有时候,TCP为了提高网络的利用率,会使用一个叫做Nagle的算法。该算法是指,发送端即使有要发送的数据,如果很少的话,会延迟发送。如果应用层给TCP传送数据很快的话,就会把两个应用层数据包**“粘”在一起**,TCP最后只发一个TCP数据包给接收端。
2. TCP协议
2.1 TCP协议的作用
TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议。
最底层的以太网协议(Ethernet)
规定了电子信号如何组成数据包(packet),解决了子网内部的点对点通信。
但是,以太网协议不能解决多个局域网如何互通,这由 IP 协议
解决。
IP 协议定义了一套自己的地址规则,称为 IP 地址。它实现了路由功能,允许某个局域网的 A 主机,向另一个局域网的 B 主机发送消息。
(路由器就是基于 IP 协议。局域网之间要靠路由器连接。)
IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议。
分包:TCP传输的“特性”
2.2 TCP 数据包的大小
一图胜千言:
概述:
- 以太网数据包(packet)大小 = 1522字节(最新)= 负载(payload)1500字节 + 头信息(head)22字节
- 以太网报头至少 22 字节
- IP报头和TCP报头至少 20字节
- TCP 数据包的最大负载是 1480 - 20 = 1460 字节
- IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为
1400字节
左右。
因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议
的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。
2.3 TCP数据包的编号(SEQ)
一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。
发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。
第一个包的编号是一个随机数。
为了便于理解,这里就把它称为1号包
。假定这个包的负载长度是100字节
,那么可以推算出下一个包的编号应该是101
。
这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。
2.4 TCP数据包的组装
收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。
简述这个过程中各个部分所起的作用:
- TCP协议:给数据包编号(①便于文件还原:按编号顺序 ②便于确定丢失的包进行重发)
- 操作系统:组装 TCP数据包里的数据(不包括处理)
- 应用程序:接收操作系统组装好的TCP数据包
(TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。) - 应用层协议(比如 HTTP协议):表示原始文件的大小,并在应用程序接收组装好的数据后根据协议指定的
Content-Length
(HTTP协议)正确地读出一段段数据。
(这也意味着,一次 TCP 通信可以包括多个 HTTP 通信)
##------- 丢包:TCP传输的问题---------##
2.5 慢启动和 ACK
TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动(slow start)机制
。
开始的时候,发送得较慢,然后根据丢包的情况,调整速率:如果不丢包,就加快发送速度;如果丢包,就降低发送速度。
默认情况下,接收方每收到两个 TCP 数据包,就要发送一个确认消息。"确认"的英语是 acknowledgement,所以这个确认消息就简称 ACK
。
ACK 携带两个信息。
- 期待要收到下一个数据包的编号
- 接收方的接收窗口的剩余容量
发送方有了这两个信息,再加上自己已经发出的数据包的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。这被称为 “发送窗口”,这个窗口的大小是可变的。
2.6 数据包的遗失处理
TCP 协议可以保证数据通信的完整性,这是怎么做到的?
每一个数据包都带有下一个数据包的编号。
如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。
如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。
TCP粘包与分包的解决
分包与粘包
(1)分包
一个以太网包只能传输1500字节长度的数据,而在这其中,IP头、TCP头又各占了20个字节,因此有效载荷为1460字节。
如果要发送的数据的长度超过了1460,假设为2000,那么必然被分成多个以太网包发送过来。
对于接收方来说,如果每次接收1024个字节,则需要多次recv才能把这整段数据接收,而TCP是不维护数据边界的,因此对于接收方来说,完全不知道这一段数据什么时候结束。
(2)粘包
粘包和分包相反,要发送的数据长度很短,比如只有30个字节左右,如果以非常快的速度发送,那么有可能一个以太网包里包含了好几段数据,他们是被一起发送过来的,这时接收方recv得到的数据是好几段数据连在一起,无法分开。
简单的解决方案
(1)方案一
如何解决呢,一种方法就是约定好命令的长度,这样一来,接收方就可以根据提前约定好的数据长度来解析数据了。但这样会产生许多不必要的麻烦了,比如实际发送数据小于约定长度时需要填充,这样也造成了传输上的浪费。
(2)方案二
另一种方法就是对要传输的数据进行封装,比如在数据的最前面加上一个长度标识,指明本次要发送的数据长度是多少,这样一来,接收方先获得数据的长度,然后根据数据的长度来获取实际数据。
也有人这样做:
因此如果要使用socket通信,就一定要自己定义一份协议(在应用层下)。目前最常用的协议标准是:消息头部(包头)+ 消息长度 + 消息正文。