参考资料:
计算机网络 PDF.pdf
https://www.yuque.com/u41716106/ni1clp/irinqgaq7vs31rwl?singleDoc# 《【学习】4. 传输层协议(TCP、UDP)》
前言:
总结:
【TCP/IP 协议族】传输层协议(TCP、UDP)
参考资料:
- 协议森林07 傀儡 (UDP协议) - Vamei - 博客园
- https://segmentfault.com/a/1190000038287599#item-2-3
- TCP和UDP详解(非常详细)_tcp udp-CSDN博客
- bestJavaer/computer-network/computer-translayer.md at master · crisxuan/bestJavaer
我们已经讲解了物理层、连接层和网络层。最开始的连接层协议种类繁多(Ethernet、Wifi、ARP等等)。到了网络层,我们只剩下一个IP协议(IPv4和IPv6是替代关系)。进入到传输层(transport layer),协议的种类又开始繁多起来(比如TCP、UDP、SCTP等)。这就好像下面的大树,根部(连接层)分叉很多,然后统一到一个树干(网络层),到了树冠(传输层)部分又开始开始分叉,而每个树枝上长出更多的树叶(应用层)。我们在网络层已经看到,通过树干的统一,我们实现了一个覆盖全球的互联网络(Internet)。然而,我们可能出于不同的目的利用这张“网”,随之使用的方式也有所区分。不同的传输层协议(以及更多的应用层协议)正是我们使用“网”的不同方式的体现。
传输层最重要的协议为TCP协议和UDP协议。这两者使用“网”的方式走了两个极端。两个协议的对比非常有趣。TCP协议复杂,但传输可靠。UDP协议简单,但传输不可靠。其他的各个传输层协议在某种程度上都是这两个协议的折中。
传输层简介:
在 TCP/IP 协议中能够实现传输层功能的,最具代表性的就是 TCP 和 UDP。提起 TCP 和 UDP ,就得先从这两个协议的定义说起。
- TCP 叫做传输控制协议(TCP,Transmission Control Protocol),通过名称可以大致知道 TCP 协议有控制传输的功能,主要体现在其可控,可控就表示着可靠,确实是这样的,TCP 为应用层提供了一种可靠的、面向连接的服务,它能够将分组可靠的传输到服务端。
- UDP 叫做用户数据报协议(UDP,User Datagram Protocol),通过名称可以知道 UDP 把重点放在了数据报上,它为应用层提供了一种无需建立连接就可以直接发送数据报的方法。
传输层基本作用:
- 格式化信息流
- 分段及封装应用层送来的数据
- 提供可靠传输
TCP 协议规定接收端必须发回确认,并且假如分组丢失,必须重新发送
- 提供端到端的传输服务
- 在发送主机与接收主机之间构建逻辑通信
- 包括两个协议
-
- TCP:打电话
- UDP:发短信
传输层
中数据包(帧头/首部+应用数据)
叫做:数据段(segment)
【报文段】
怎么计算机网络中的术语对一个数据的描述这么多啊?
在计算机网络中,在不同层之间会有不同的描述。我们上面提到会将运输层的分组称为报文段,除此之外,还会将 TCP 中的分组也称为报文段,然而将 UDP 的分组称为数据报,同时也将网络层的分组称为数据报
但是为了统一,一般在计算机网络中我们统一称 TCP 和 UDP 的报文为 报文段,这个就相当于是约定,到底如何称呼不用过多纠结啦。
【传输层】伪首部/伪包头
伪首部,又称为伪包头(Pseudo Header):是指在TCP的分段或UDP的数据报格式中,在数据报首部前面增加源IP地址、目的IP地址、IP分组和协议字段、TCP或UDP数据报的总长度等共12字节,所构成的扩展首部结构。此伪首部是一个临时的结构,它既不向上也不向下传递,仅仅只是为了保证可以校验套接字的正确性。
【传输层】UDP 协议(用户数据报协议)
端口是传输层带来的最重要的概念。我们进一步了解了UDP协议。如果已经掌握了IP协议,那么UDP协议就没有任何困难可言,它只是IP协议暴露在传输层上的接口
。
UDP 协议简介
UDP(User Datagram Protocol)
传输与IP传输非常类似。你可以将UDP协议看作IP协议暴露在传输层的一个接口
。UDP协议同样以数据包(datagram)的方式传输,它的传输方式也是"Best Effort"的,所以UDP协议也是不可靠的(unreliable)
。
那么,我们为什么不直接使用IP协议而要额外增加一个UDP协议呢? 一个重要的原因是IP协议中并没有端口(port)
的概念。IP协议进行的是IP地址到IP地址的传输,这意味者两台计算机之间的对话。但每台计算机中需要有多个通信通道,并将多个通信通道分配给不同的进程使用(关于进程,可以参考Linux进程基础)。一个端口就代表了这样的一个通信通道。正如我们在邮局和邮差中提到的收信人的概念一样。UDP协议实现了端口,从而让数据包可以在送到IP地址的基础上,进一步可以送到某个端口。
UDP是一种全双工
通信协议。
UDP能传输的报文长度是64K
(包含UDP首部)。
- 如果我们需要传输的数据超过64K, 就需要在应用层手动的
分包
, 多次发送, 并在接收端手动拼装。
常见的基于UDP的应用层协议:
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
- 程序员在写UDP程序时自己定义的协议
UDP 协议的特点
- 面向
无连接
,尽最大努力交付:只知道对端的IP和端口号就可以发送,不需要实现建立连接。
(例如:发短信,不管能不能收到,都可以发送)
不可靠
的传输:没有确认机制, 没有重传机制。如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
UDP 不可靠的原因是它虽然提供差错检测的功能,但是对于差错没有恢复能力更不会有重传机制。
- 面向
数据报
: 应用层交给UDP多长的报文, UDP原样发送既不会拆分,也不会合并。如果发送端调用一次sendto
, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个 字节,而不能循环调用10次recvfrom, 每次接收10个字节。所以UDP不能够灵活的控制读写数据的次数和数量。
UDP 协议报文格式
字段(按顺序介绍) | 存储大小 (比特/Bit) | 解释 |
源端口号 (Source Port) | 占 16 位 | 通常包含发送数据报的应用程序所使用的 UDP 端口。接收端的应用程序利用这个字段的值作为发送响应的目的地址。这个字段是可选项,有时不会设置源端口号。没有源端口号就默认为 0 ,通常用于不需要返回消息的通信中。 |
目标端口号 (Destination Port) | 占 16 位 | 表示接收端端口 |
长度 (Length) | 占 16 位 | 表示 UDP 数据报长度,包含 UDP 报文头和 UDP 数据长度。因为 UDP 报文头长度是 8 个字节,所以这个值最小为 8,最大长度为 65535 字节。 |
校验和 (Checksum) | 占 16 位 | UDP 使用校验和来保证数据安全性,UDP 的校验和也提供了差错检测功能,差错检测用于校验报文段从源到目标主机的过程中,数据的完整性是否发生了改变。 |
- 16位UDP长度表示整个数据报
(UDP首部+UDP数据)
的长度 - 如果校验和出错,就会直接丢弃(UDP校验首部和数据部分),没有重发机制
校验和案例
发送方的 UDP 对报文段中的 16 比特字的和进行反码运算,求和时遇到的位溢出都会被忽略,比如下面这个例子,三个 16 比特的数字进行相加
这些 16 比特的前两个和是
然后再将上面的结果和第三个 16 比特的数进行相加
最后一次相加的位会进行溢出,溢出位 1 要被舍弃,然后进行反码运算,反码运算就是将所有的 1 变为 0 ,0 变为 1。因此 1000 0100 1001 0101 的反码就是 0111 1011 0110 1010,这就是校验和,如果在接收方,数据没有出现差错,那么全部的 4 个 16 比特的数值进行运算,同时也包括校验和,如果最后结果的值不是 1111 1111 1111 1111 的话,那么就表示传输过程中的数据出现了差错。
UDP 协议的数据封装
UDP 的缓冲区:UDP存在接收缓冲区,但不存在发送缓冲区。
UDP没有发送缓冲区,在调用sendto时会直接将数据交给内核,由内核将数据传给网络层协议进行后续的传输动作。为什么UDP不需要发送缓冲区?
因为UDP不保证可靠性,它没有重传机制,当报文丢失时,UDP不需要重新发送,而TCP不同,他必须具备发送缓冲区,当报文丢失时,TCP必须保证重新发送,用户不会管,所以必须要具备发送缓冲区。
UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报文的顺序和发送UDP报的顺序一致,如果缓冲区满了再到达的UDP数据报就会被丢弃。
UDP 接收缓冲区和丢包问题UDP接收端缓冲区和丢包问题_udp huanchongqushifouyoushuju-CSDN博客
UDP 协议的数据传输
发送时:先把数据放到报文,写到缓冲区字节数组再传送。
接收时:从缓冲器数组读取,打包到报文。
UDP 的单播、广播、组播
- 单播:用于两个主机之间端对端的通信。即一对一(客户端与服务器端点到点连接)。
- 广播:用于一个主机对整个局域网上所有主机通信。即一对所有。广播禁止在Internet宽带网上传输(广播风暴)。
- 组播(多播):对一组特定的主机进行通信,而不是整个局域网上的所有主机。即一对一组
将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。
单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是组播的用途。
注意:只有UDP才有广播、组播的传递方式。而TCP是一对一连接通信。组播的重点是高效的把同一个包尽可能多的发送到不同的,甚至可能未知的设备。但是TCP连接是一对一明确的。
组播的优点
具有同种业务的主机加入同一数据流,共享同一通道,节省了带宽和服务器的优点,具有广播的优点而又没有广播所需要的带宽。
服务器的总带宽不受客户端带宽的限制。由于组播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。
与单播一样,多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行。
IP地址的不同
广播:主机发送广播消息时,需要指定目的IP地址255.255.255.255和接受者的端口号。
多播:在多播系统中,有一个源点一组终点。这是一对多的关系。在这种类型的通信中,源地址是一个单播地址,而目的地址则是一个组地址。
IP多播通信必须依赖于IP多播地址,在IPv4中它是 一个D类IP地址,范围从224.0.0.0到239.255.255.255,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址三类。
局部多播地址 | 在224.0.0.0~224.0.0.244之间,是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。 |
预留多播地址 | 在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。 |
本地管理组播地址 | 在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。 |
【补充】为什么说 UDP 协议相当于是 IP 协议的开放接口
那么,我们为什么不直接使用IP协议而要额外增加一个UDP协议呢?
一个重要的原因是IP协议中并没有端口(port)
的概念。IP协议进行的是IP地址到IP地址的传输,这意味者两台计算机之间的对话。但每台计算机中需要有多个通信通道,并将多个通信通道分配给不同的进程使用(关于进程,可以参考Linux进程基础)。一个端口就代表了这样的一个通信通道。正如我们在邮局和邮差中提到的收信人的概念一样。UDP协议实现了端口,从而让数据包可以在送到IP地址的基础上,进一步可以送到某个端口。
UDP协议作为传输层协议,运行在IP协议之上,提供了一个简单且直接的接口,允许应用程序直接访问和利用IP层的功能。以下是UDP作为IP开放接口的几个关键点:
- 无连接特性:UDP不建立连接,直接发送数据报,使应用程序能够迅速利用IP的路由和传输功能,无需额外的连接管理。
- 简单报文头:UDP的报文头简单,包含端口号、长度和校验和,减少了传输层的处理开销,使数据能够快速传输。
- 直接封装:UDP数据报直接封装在IP数据包中,依赖IP进行路由和寻址,无需额外的传输层功能。
- 实时应用支持:UDP的高效传输特性使其适用于实时应用,如视频会议和游戏,这些应用直接利用IP的快速路由能力。
- 应用程序控制:UDP允许应用程序直接控制数据传输,包括多播和广播,利用IP层的高级功能。
综上所述,UDP通过其简单性和无连接特性,成为IP协议的一个开放接口,允许上层应用灵活、高效地利用网络资源。
【补充】UDP 协议为什么会提供差错检测(没有重发机制)的功能?
这其实是一种 端到端
的设计原则,这个原则说的是要让传输中各种错误发生的概率降低到一个可以接受的水平。
文件从主机A传到主机B,也就是说AB主机要通信,需要经过三个环节:首先是主机A从磁盘上读取文件并将数据分组成一个个数据包packet,,然后数据包通过连接主机A和主机B的网络传输到主机B,最后是主机B收到数据包并将数据包写入磁盘。在这个看似简单其实很复杂的过程中可能会由于某些原因而影响正常通信。比如:磁盘上文件读写错误、缓冲溢出、内存出错、网络拥挤等等这些因素都有可能导致数据包的出错或者丢失,由此可见用于通信的网络是不可靠的。
由于实现通信只要经过上述三个环节,那么我们就想是否在其中某个环节上增加一个检错纠错机制来用于对信息进行把关呢?
网络层肯定不能做这件事,因为网络层的最主要目的是增大数据传输的速率,网络层不需要考虑数据的完整性,数据的完整性和正确性交给端系统去检测就行了,因此在数据传输中,对于网络层只能要求其提供尽可能好的数据传输服务,而不可能寄希望于网络层提供数据完整性的服务。
UDP 不可靠的原因是它虽然提供差错检测的功能,但是对于差错没有恢复能力更不会有重传机制。
【补充】UDP 协议的应用场景
在选择使用协议的时候,选择UDP必须要谨慎。由于缺乏可靠性且属于非连接导向协议,UDP一般必须允许一定量的数据包丢失、出错和复制粘贴。但有些应用,比如TFTP,需要可靠性保证,则必须在应用层增加根本的可靠机制。但是绝大多数UDP应用都不需要可靠机制,甚至可能因为引入可靠机制而降低性能。流媒体、即时多媒体游戏和IP电话(VoIP)就是典型的UDP应用。如果某个应用需要很高的可靠性,那么可以用传输控制协议(及TCP协议)来代替UDP。
由图一我们可以看到,使用UDP协议的应用有:域名系统(DNS)、简单网络管理协议(SNMP)、路由信息协议(RIP)等等。因为UDP不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以音频、视频和普通数据在传送时使用UDP比较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用到的QQ就是使用UDP协议。
【传输层】TCP 协议(传输控制协议)
简单理解TCP三次握手四次挥手(看一遍你就懂)-CSDN博客
TCP和UDP详解(非常详细)_tcp udp-CSDN博客
TCP 协议简介
TCP( Transmission control protocol )即传输控制协议,是一种面向连接、可靠的数据传输协议,它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。
- 面向连接:数据传输之前客户端和服务器端必须建立连接
- 可靠的:数据传输是有序的 要对数据进行校验
TCP 协议的特点
TCP 协议报文格式
TCP 协议的报文格式
TCP 报⽂段结构相⽐ UDP 报⽂结构多了很多内容。但是前两个 32 ⽐特的字段是⼀样的。它们是 源端⼝号 和 ⽬标端⼝号 ,我们知道,这两个字段是⽤于多路复⽤和多路分解的。另外,和 UDP ⼀样, TCP 也包含 校验和(checksum field) ,除此之外, TCP 报⽂段⾸部还有下⾯这些
字段 (按顺序介绍) | 存储大小 (比特/Bit) | 解释 | 作用 |
源端口号 (Source Port) | 占 16 位 | 表示数据从哪个进程来 | 这两个字段是⽤于多路复⽤和多路分解的。 |
目标端口号 (Destination Port) | 占 16 位 | 表示数据要到哪个进程去 | |
序号字段 (sequence number field) | 占 32 位 | 序号是 比如:一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为401。 | 这些字段被 TCP 发送⽅和接收⽅⽤来实现可靠的数据传输。 |
确认号字段 (acknowledgment number field) | 占 32 位 | 每一个 | |
首部字段长度字段(数据偏移) (header length field) | 占 4 位 | 表示该TCP头部有多少个 所以TCP头部大长度是 根据该部分可以将TCP报头和有效载荷分离。TCP报文默认大小为20个字节。 | 这个字段指示了以 32 ⽐特的字为单位的 TCP ⾸部⻓度。 TCP ⾸部的⻓度是可变的,但是通常情况下,选项字段为空,所以 TCP ⾸部字段的⻓度是 20 字节。 |
保留 | - | - | - |
| ⽤于拥塞控制 | - | |
标志字段 (flag field) | 占 6 位 |
我们把携带
我们把携带
我们称携带 |
⼀般情况下, PSH 和 URG 并没有使⽤。
|
接收窗口字段 (receive window field) | 占 16 位 | 如果发送方发送大量数据,接收方接收不过来,会导致大量数据丢失。然后接收方可以发送给发送发消息让发送方发慢一点,这是流量控制。
| 这个字段⽤于流量控制。 ⽤于指示接收⽅能够/愿意接受的字节数量。 TCP 报文段都包含 ACK+窗口通告,TCP 接收方会根据这两个参数调整窗口结构 |
校验和字段 | 占 16 位 | 发送端填充, | |
紧急数据指针字段 | 占 16 位 | 按序到达是 | 紧急数据最后⼀个字节由 16 ⽐特的 紧急数据指针字段 指出。 |
选项字段 (options field) | 最多占 40 位 | - | - |
填充 | - | - | - |
TCP 协议的常见报文段类型
一定要熟记序号、确认号的含义
- TCP 标志字段可以同时设置多个标志
- TCP 通过设置不同的标志位来区分和管理连接的建立、数据传输和终止。每个标志位代表特定的操作
如SYN请求连接,ACK确认数据,FIN终止连接等。这些报文类型协同工作,确保数据可靠传输和连接的有效管理
- TCP 标志字段没使用的标志位为 0(与开关类似)
TCP标志字段占6位,常见的6个标志位是:
+-----+-----+-----+-----+-----+-----+
| URG | ACK | PSH | RST | SYN | FIN |
+-----+-----+-----+-----+-----+-----+5 4 3 2 1 0例如:SYN报文: 000010 (只有SYN=1)SYN-ACK报文: 010010 (ACK=1,SYN=1)ACK报文: 010000 (只有ACK=1)FIN报文: 000001 (只有FIN=1)FIN-ACK报文: 010001 (ACK=1,FIN=1)RST报文: 000100 (只有RST=1)PSH-ACK报文: 011000 (ACK=1,PSH=1)URG-ACK报文: 110000 (URG=1,ACK=1)
未使用的标志位都是0。就像开关,1表示打开,0表示关闭。
——————————————————————————————————————————————————————————————————————————————————————
+-----+-----+-----+-----+-----+-----+-----+-----+
| CWR | ECE | URG | ACK | PSH | RST | SYN | FIN |
+-----+-----+-----+-----+-----+-----+-----+-----+7 6 5 4 3 2 1 0
CWR和ECE标志位是后来为了支持拥塞控制而添加的,它们在TCP首部的其他位置。CWR(Congestion Window Reduced)和ECE(ECN-Echo)是TCP拥塞控制的标志位:
用途:ECE(ECN-Echo):接收方告诉发送方"网络拥塞了"路由器标记了拥塞(ECN)CWR(Congestion Window Reduced):发送方回应"我已经降低发送速率了"对ECE的响应
工作流程:路由器: 检测到拥塞↓接收方: 设置ECE=1通知发送方↓发送方: 收到ECE,降低速率↓发送方: 设置CWR=1回应接收方
1. SYN报文段:同步报文/连接请求报文;连接建立第一步,客户端发送SYN报文请求连接
- 标志字段:只有SYN=1标志
- 用途:连接建立第一步,客户端发送SYN报文请求连接。
示例报文格式:
源端口:12345
目的端口:80
序列号(Seq):1000
确认号(Ack):0
标志位:SYN=1,ACK=0
窗口大小:8192
校验和:XXXX
2. SYN-ACK报文段:同步确认报文;用于连接建立第二步,服务器响应SYN,发送SYN-ACK报文确认连接请求
- 同时设置SYN=1和ACK=1
- 用途:用于连接建立第二步,服务器响应SYN,发送SYN-ACK报文确认连接请求。
示例报文格式:
源端口:80
目的端口:12345
序列号(Seq):2000
确认号(Ack):1001
标志位:SYN=1,ACK=1
窗口大小:8192
校验和:XXXX
3. ACK报文段:确认报文;确认接收,连接建立完成
用途:确认接收,连接建立完成。
示例报文格式:
源端口:12345
目的端口:80
序列号(Seq):1001
确认号(Ack):2001
标志位:ACK=1
窗口大小:8192
校验和:XXXX
4. FIN 报文段/FIN+ACK 报文段:终止连接请求
用途:终止连接请求。
示例报文格式:
源端口:12345
目的端口:80
序列号(Seq):1001
确认号(Ack):2001
标志位:FIN=1,ACK=1
窗口大小:0
校验和:XXXX
FIN报文和FIN+ACK报文的区别:
纯结束请求 | 结束请求+确认
标志位: FIN=1 | 标志位: FIN=1, ACK=1
作用: 请求断开连接 | 作用: 请求断开 + 确认之前的数据
称呼: 结束报文 | 称呼: 结束确认报文通常用FIN+ACK:
- 因为要确认之前的数据
- 所以一般都带ACK标志纯FIN很少用:
- 因为TCP是全双工的
- 基本都需要确认机制
5. RST报文段:异常终止连接
用途:异常终止连接。
示例报文格式:
源端口:80
目的端口:12345
序列号(Seq):0
确认号(Ack):0
标志位:RST=1
窗口大小:0
校验和:XXXX
6. PSH报文段:推送数据,要求立即传递
用途:推送数据,要求立即传递。
示例报文格式:
源端口:12345
目的端口:80
序列号(Seq):1002
确认号(Ack):2001
标志位:PSH=1,ACK=1
窗口大小:8192
校验和:XXXX
7. URG报文段:紧急数据传输
用途:紧急数据传输。
示例报文格式:
源端口:12345
目的端口:80
序列号(Seq):1002
确认号(Ack):2001
标志位:URG=1,ACK=1
窗口大小:8192
校验和:XXXX
8. 数据报文段:正常数据传输
用途:正常数据传输。
示例报文格式:
源端口:80
目的端口:12345
序列号(Seq):2001
确认号(Ack):1002
标志位:ACK=1 / ACK=0
窗口大小:8192
校验和:XXXX
TCP数据报文段通常都有ACK=1,原因是:
- 全双工
A发数据给B时,同时也要确认收到B之前发的数据
所以数据报文顺便捎带ACK确认
- 累积确认
第一次发数据:
A → B: 数据包1 (ACK=0,因为还没收到任何数据需要确认)后续发数据:
B → A: 数据包2 + ACK1 (ACK=1,确认收到数据包1)
A → B: 数据包3 + ACK2 (ACK=1,确认收到数据包2)
B → A: 数据包4 + ACK3 (ACK=1,确认收到数据包3)
- 提高效率
- 不用单独发ACK报文
- 数据和确认一起发送(捎带确认)
TCP 协议的通信机制
【TCP 通信】确认应答机制——序号、确认号实现传输可靠性
TCP 报文段 = 序号 + 确认号 + ……
TCP 报⽂段⾸部中两个最重要的字段就是 序号
和 确认号
,这两个字段是 TCP 实现可靠性的基础,那么你肯定好奇如何实现可靠性呢?要了解这⼀点,⾸先我们得先知道这两个字段⾥⾯存了哪些内容吧?
序号字段存储的内容:【由发送端发出】一个报文段的序号就是数据流的字节编号
作用:告知当前数据流的字节编号
⼀个报⽂段的序号就是数据流的字节编号。因为 TCP 会把数据流分割成为⼀段⼀段的字节流,因为字节流本身是有序的,所以每⼀段的字节编号就是标示是哪⼀段的字节流。
⽐如,主机 A 要给主机 B 发送⼀条数据:
- 数据经过应⽤层产⽣后会有⼀串数据流,数据流会经过 TCP 分割,分割的依据就是 MSS,假设数据是 10000 字节, MSS是 2000 字节,那么 TCP 就会把数据拆分成 0 - 1999 , 2000 - 3999 的段,依次类推。
- 所以,第⼀个数据 0 - 1999 的
⾸字节编号就是 0
, 2000 - 3999 的⾸字节编号就是 2000
…… - 然后,每个序号都会被填⼊ TCP 报⽂段⾸部的序号字段中。
确认号字段存储的内容:【由接收方发出】该确认号(序号)之前的数据接收到了,期望从发送方收到的下⼀字节的序号
作用:确认序号之前的字节数据都接收成功,并告知下一次期望收到的字节序号
⾄于确认号的话,会⽐序号要稍微麻烦⼀些。这⾥我们先拓展下⼏种通信模型。
- 单⼯通信:单⼯数据传输只⽀持数据在⼀个⽅向上传输;在同⼀时间只有⼀⽅能接受或发送信息,不能实现双向通信,⽐如⼴播、电视等。
- 双⼯通信是⼀种点对点系统,由两个或者多个在两个⽅向上相互通信的连接⽅或者设备组成。双⼯通信模型有两种: 全双⼯(FDX)和半双⼯(HDX)
-
- 全双⼯:在全双⼯系统中,连接双⽅可以相互通信,⼀个最常⻅的例⼦就是电话通信。全双⼯通信是两个单⼯通信⽅式的结合,它要求发送设备和接收设备都有ᇿ⽴的接收和发送能⼒。
- 半双⼯:在半双⼯系统中,连接双⽅可以彼此通信,但不能同时通信,⽐如对讲机,只有把按钮按住的⼈才能够讲话,只有⼀个⼈讲完话后另外⼀个⼈才能讲话。
单⼯、半双⼯、全双⼯通信如下图所示
TCP 是⼀种全双⼯的通信协议,因此主机 A 在向主机 B 发送消息的过程中,也在接受来⾃主机 B 的数据。
主机 A 填充进报⽂段的确认号是期望从主机 B 收到的下⼀字节的序号。
稍微有点绕,我们来举个例⼦看⼀下。
- ⽐如主机 A 收到了来⾃主机 B 发送的编号为
0 - 999 字节的报⽂段
,这个报⽂段会写⼊序号中; - 随后主机 A 期望能够从主机 B 收到
1000 - 剩下的报⽂段
; - 因此,主机 A 发送到主机 B 的报⽂段中,它的
确认号就是 1000
。
累积确认
这⾥再举出⼀个例⼦,⽐如主机 B 在发送 0 - 999 报⽂段后,期望能够接受到 1000 之后的报⽂段,但是主机 A 却给主机 B 发送了⼀个 1500 之后的报⽂段,那么主机 B 是否还会继续进⾏等待呢?
- 答案显然是会的,因为 TCP 只会确认流中⾄第⼀个丢失字节为⽌的字节,因为 1500 虽然属于 1000 之后的字节,但是主机 A 没有给主机 B 发送 1000 - 1499 之间的字节,所以主机 B 会继续等待。
这里其实就差不多累积确认的含义了:确认号的序号,代表该序号之前的都接收到了
正常的发送过程:
在了解完序号和确认号之后,我们下⾯来聊⼀下 TCP 的发送过程。下⾯是⼀个正常的发送过程
TCP 通过肯定的 确认应答(ACK) 来实现可靠的数据传输,当主机 A 将数据发出之后会等待主机 B 的响应。如果有确认应答(ACK),说明数据已经成功到达对端。反之,则数据很可能会丢失。
发生方发送数据包丢失:发送方重发
如下图所示,如果在⼀定时间内主机 A 没有等到确认应答,则认为主机 B 发送的报⽂段已经丢失,并进⾏重发。
主机 A 给主机 B 的响应可能由于⽹络抖动等原因⽆法到达,那么在经过特定的时间间隔后,主机 A 将重新发送报⽂段。
主机 A 没有收到主机 B 的响应还可能是因为主机 B 在发送给主机 A 的过程中丢失。
接收方确认应答丢失:发送方重发,接收方重新应答
如上图所示,由主机 B 返回的确认应答,由于⽹络拥堵等原因在传送的过程中丢失,并没有到达主机 A。主机 A 会等待⼀段时间,如果在这段时间内主机 A 仍没有等到主机 B 的响应,那么主机 A 会重新发送报⽂段。
确认应答与报文段失序:接收方正常接收,并继续等待期望报文段
那么现在就存在⼀个问题,如果主机 A 给主机 B 发送了⼀个报⽂段后,主机 B 接受到报⽂段发送响应,此刻由于⽹络原因,这个报⽂段并未到达,等到⼀段时间后主机 A 重新发送报⽂段,然后此时主机 B 发送的响应在主机 A 第⼆次发送后失序到达主机 A,那么主机 A 应该如何处理呢?
TCP RFC 并未为此做任何规定,也就是说,我们可以⾃⼰决定如何处理失序到达的报⽂段。⼀般处理⽅式有两种
- 接收⽅⽴刻丢弃失序的报⽂段。
- 接收⽅接受失序到达的报⽂段,并等待后续的报⽂段。
⼀般来说通常采取的做法是第⼆种
【TCP 通信】超时重传机制(没写完)
没有永远不出错误的通信,这句话表明着不管外部条件多么完备,永远都会有出错的可能。所以,在 TCP 的正常通信过程中,也会出现错误,这种错误可能是由于数据包丢失引起的,也可能是由于数据包重复引起的,甚⾄可能是由于数据包 失序
引起的。
TCP 的通信过程中,会由 TCP 的接收端返回⼀系列的确认信息来判断是否出现错误,⼀旦出现丢包等情况, TCP 就会启动 重传
操作,重传尚未确认的数据。
TCP 的重传有两种⽅式,⼀种是基于 时间
,⼀种是基于 确认信息
,⼀般通过确认信息要⽐通过时间更加⾼效。
所以从这点就可以看出, TCP 的确认和重传,都是基于数据包是否被确认为前提的。
TCP 在发送数据时会设置⼀个定时器,如果在定时器指定的时间内未收到确认信息,那么就会触发相应的超时或者基于计时器的重传操作,计时器超时通常被称为重传超时(RTO)。
但是有另外⼀种不会引起延迟的⽅式,这就是快速重传。
TCP 在每次重传⼀次报⽂后,其重传时间都会 加倍
,这种"间隔时间加倍"被称为⼆进制指数补偿(binary exponential backoff) 。等到间隔时间加倍到 15.5 min 后,客户端会显示
Connection closed by foreign host.
TCP 拥有两个阈值来决定如何重传⼀个报⽂段,这两个阈值被定义在 RFC[RCF1122] 中
- 第⼀个阈值是
R1
,它表示愿意尝试重传的次数 - 阈值
R2
表示 TCP 应该放弃连接的时间。 - R1 和 R2 应⾄少设为三次重传和 100 秒放弃 TCP 连接。
这⾥需要注意下,对连接建⽴报⽂ SYN 来说,它的 R2 ⾄少应该设置为 3 分钟,但是在不同的系统中, R1 和 R2 值的设置⽅式也不同。
- 在 Linux 系统中, R1 和 R2 的值可以通过应⽤程序来设置,或者是修改 net.ipv4.tcp_retries1 和net.ipv4.tcp_retries2 的值来设置。变量值就是重传次数。
tcp_retries2 的默认值是 15,这个充实次数的耗时⼤约是 13 - 30 分钟,这只是⼀个⼤概值,最终耗时时间还要取
决于 RTO ,也就是重传超时时间。 tcp_retries1 的默认值是 3 。
对于 SYN 段来说, net.ipv4.tcp_syn_retries 和 net.ipv4.tcp_synack_retries 这两个值限制了 SYN 的重传次数,默认是 5,⼤约是 180 秒。
- Windows 操作系统下也有 R1 和 R2 变量,它们的值被定义在下⽅的注册表中
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
HKLM\System\CurrentControlSet\Services\Tcpip6\Parameters
其中有⼀个⾮常重要的变量就是 TcpMaxDataRetransmissions
,这个 TcpMaxDataRetransmissions 对应Linux 中的 tcp_retries2 变量,默认值是 5。这个值的意思表示的是 TCP 在现有连接上未确认数据段的次数。
TCP 协议的数据传输控制
【TCP 传输】传输控制——滑动窗口协议
利⽤窗⼝控制提⾼速度
前⾯我们介绍了 TCP 是以数据段的形式进⾏发送,如果经过⼀段时间内主机 A 等不到主机 B 的响应,主机 A 就会重新发送报⽂段,接受到主机 B 的响应,再会继续发送后⾯的报⽂段,我们现在看到,这⼀问⼀答的形式还存在许多条件,⽐如响应未收到、等待响应等,那么对崇尚性能的互联⽹来说,这种形式的性能应该不会很⾼。
那么如何提升性能呢?
为了解决这个问题, TCP 引⼊了 窗⼝ 这个概念,即使在往返时间较⻓、频次很多的情况下,它也能控制⽹络性能的下降,听起来很⽜批,那它是如何实现的呢?
如下图所示
我们之前每次请求发送都是以报⽂段的形式进⾏的,引⼊窗⼝后,每次请求都可以发送多个报⽂段,也就是说⼀个窗⼝可以发送多个报⽂段。窗⼝⼤⼩就是指⽆需等待确认应答就可以继续发送报⽂段的最⼤值。
在这个窗⼝机制中,⼤量使⽤了 缓冲区
,从⽽达到对多个段同时进⾏确认应答的效果。
如下图所示,发送报⽂段中⾼亮部分即是我们提到的窗⼝,在窗⼝内,即是没有收到确认应答也可以把请求发送出去。不过,在整个窗⼝的确认应答没有到达之前,如果部分报⽂段丢失,那么主机 A 将仍会重传。为此,主机 A 需要设置缓存来保留这些需要重传的报⽂段,直到收到他们的确认应答。
- 在滑动窗⼝以外的部分是尚未发送的报⽂段和已经接受到的报⽂段,如果报⽂段已经收到确认则不可进⾏重发,此时报⽂段就可以从缓冲区中清除。
在收到确认的情况下,会将窗⼝滑动到确认应答中确认号的位置,如上图所示,这样可以顺序的将多个段同时发送,⽤以提⾼通信性能,这种窗⼝也叫做 滑动窗⼝(Sliding window) 。
窗口的控制与重发
没有/有丢包的情况
报⽂段的发送和接收,必然伴随着报⽂段的丢失和重发,窗⼝也是同样如此,如果在窗⼝中报⽂段发送过程中出现丢失怎么办?
⾸先我们先考虑确认应答没有返回的情况。在这种情况下,主机 A 发送的报⽂段到达主机 B,是不需要⽴即进⾏重发的。这和单个报⽂段的发送不⼀样,如果发送单个报⽂段, 即使确认应答没有返回,也要进⾏重发。
窗⼝在⼀定程度上⽐较⼤时,即使有少部分确认应答的丢失,也不会⽴刻重新发送报⽂段。
我们知道,如果在某个情况下由于发送的报⽂段丢失,导致接收主机未收到请求,或者主机返回的响应未到达客户端的话,会经过⼀段时间重传报⽂。那么在使⽤窗⼝的情况下,报⽂段丢失会怎么样呢?
如下图所示,报⽂段 0 - 999 丢失后,但是主机 A 并不会等待,主机 A 会继续发送余下的报⽂段,主机 B 发送的确认应答却⼀直是 1000,同⼀个确认号的应答报⽂会被持续不断的返回,如果发送端主机在连续 3 次收到同⼀个确认应答后,就会将其所对应的数据重发,这种机制要⽐之前提到的超时重发更加⾼效,这种机制也被称为 高速重发控制
。这种重发的确认应答也被称为 冗余 ACK(响应)
。
- 主机 B 在没有接收到⾃⼰期望序列号的报⽂段时,会对之前收到的数据进⾏确认应答。
- 发送端则⼀旦收到某个确认应答后,⼜连续三次收到同样的确认应答,那么就会认为报⽂段已经丢失。需要进⾏重发。
使⽤这种机制可以提供更为快速的重发服务。
【TCP 传输】流量控制——接收窗口+窗口探测包
前⾯聊的是传输控制,下⾯我再和你聊⼀下 流量控制 。
我们知道,在每个 TCP 连接的⼀侧主机都会有⼀个 socket 缓冲区,缓冲区会为每个连接设置接收缓存和发送缓存,当 TCP 建⽴连接后,从应⽤程序产⽣的数据就会到达接收⽅的接收缓冲区中,接收⽅的应⽤程序并不⼀定会⻢上读取缓冲区的数据,它需要等待操作系统分配时间⽚。如果此时发送⽅的应⽤程序产⽣数据过快,⽽接收⽅读取接收缓冲区的数据相对较慢的话,那么接收⽅中缓冲区的数据将会 溢出
。
但是还好, TCP 有 流量控制服务(flow-control service)
⽤于消除缓冲区溢出的情况。流量控制是⼀个速度匹配服务,即发送⽅的发送速率与接受⽅应⽤程序的读取速率相匹配。
TCP 通过使⽤⼀个 接收窗⼝(receive window)
的变量来提供流量控制。接收窗⼝会给发送⽅⼀个指示到底还有多少可⽤的缓存空间。发送端会根据接收端的实际接受能⼒来控制发送的数据量。
接收端主机向发送端主机通知⾃⼰可以接收数据的⼤⼩,发送端会发送不超过这个限度的数据,这个⼤⼩限度就是窗⼝⼤⼩,还记得 TCP 的⾸部么,有⼀个接收窗⼝,我们上⾯聊的时候说这个字段⽤于流量控制。它⽤于指示接收⽅能够/愿意接收的字节数量。
现在只知道这个字段⽤于流量控制,那么如何控制呢?
发送端主机会定期发送⼀个 窗⼝探测包
,这个包⽤于探测接收端主机是否还能够接受数据,当接收端的缓冲区⼀旦⾯临数据溢出的⻛险时,窗⼝⼤⼩的值也随之被设置为⼀个更⼩的值通知发送端,从⽽控制数据发送量。
下⾯是⼀个流量控制示意图
发送端主机根据接收端主机的窗⼝⼤⼩进⾏流量控制。由此也可以防⽌发送端主机⼀次发送过⼤数据导致接收端主机⽆法处理。
如上图所示,当主机 B 收到报⽂段 2000 - 2999 之后缓冲区已满,不得不暂时停⽌接收数据。然后主机 A 发送窗⼝探测包,窗⼝探测包⾮常⼩仅仅⼀个字节。然后主机 B 更新缓冲区接收窗⼝⼤⼩并发送窗⼝更新通知给主机 A,然后主机 A 再继续发送报⽂段。
在上⾯的发送过程中,窗⼝更新通知可能会丢失,⼀旦丢失发送端就不会发送数据,所以窗⼝探测包会随机发送,以避免这种情况发⽣。
【TCP 传输】数据流与窗口管理——滑动窗口的具体管理(发送、接收窗口)
我们在上⾯的讲述中知道了可以使⽤滑动窗⼝来实现流量控制,也就是说,客户端和服务器可以相互提供数据流信息的交换,数据流的相关信息主要包括报⽂段序列号、 ACK 号和窗⼝⼤⼩。
图中的两个箭头表示数据流⽅向,数据流⽅向也就是 TCP 报⽂段的传输⽅向。可以看到,每个 TCP 报⽂段中都包括了序列号、 ACK 和窗⼝信息,可能还会有⽤户数据。
TCP 报⽂段中的窗⼝⼤⼩表示接收端还能够接收的缓存空间的⼤⼩,以字节为单位。
A → B: 窗口=3000 (告诉B:"我能接收3000字节")
B → A: 数据包(1000字节)
A → B: ACK + 窗口=2000 (已收到1000,还能接收2000)
B → A: 数据包(1500字节)
A → B: ACK + 窗口=500 (还能接收500字节)
—————————————————————————————————————————————
关键点:接收方在每个ACK中告知自己的接收窗口发送方根据窗口大小控制发送速率窗口大小是动态调整的
这个窗⼝⼤⼩是⼀种动态的,因为⽆时⽆刻都会有报⽂段的接收和消失,这种动态调整的窗⼝⼤⼩我们称之为 滑动窗⼝
,下⾯我们就来具体认识⼀下滑动窗⼝。
滑动窗⼝
TCP 连接的每⼀端都可以发送数据,但是数据的发送不是没有限制的,实际上, TCP 连接的两端都各⾃维护了⼀个发送窗⼝结构 (send window structure) 和 接收窗⼝结构 (receive window structure),这两个窗⼝结构就是数据发送的限制。
发送⽅窗⼝
发送窗口的主要逻辑:
窗口的变化/概念都是基于发送接收的底层逻辑
- 受双方限制:发送上限 = min(接收方窗口, 拥塞窗口)
- 滑动规则:发送且收到ACK → 窗口右移
- 超时重传
下图是⼀个发送⽅窗⼝的示例。
在这幅图中,涉及滑动窗⼝的四种概念:
已经发送并确认的报⽂段
:发送给接收⽅后,接收⽅回回复 ACK 来对报⽂段进⾏响应,图中标注绿⾊的报⽂段就是已经经过接收⽅确认的报⽂段。已经发送但是还没确认的报⽂段
:图中绿⾊区域是经过接收⽅确认的报⽂段,⽽浅蓝⾊这段区域指的是已经发送但是还未经过接收⽅确认的报⽂段。等待发送的报⽂段
:图中深蓝⾊区域是等待发送的报⽂段,它属于发送窗⼝结构的⼀部分,也就是说,发送窗⼝结构其实是由已发送未确认 + 等待发送的报⽂段构成。窗⼝滑动时才能发送的报⽂段
:如果图中的 [4,9] 这个集合内的报⽂段发送完毕后,整个滑动窗⼝会向右移动,图中橙⾊区域就是窗⼝右移时才能发送的报⽂段。
滑动窗⼝也是有边界的,这个边界是 Left edge
和 Right edge
, Left edge 是窗⼝的左边界, Right edge是窗⼝的右边界。
- 当 Left edge 向右移动⽽ Right edge 不变时,这个窗⼝可能处于
close
关闭状态。随着已发送的数据逐渐被确认从⽽导致窗⼝变⼩时,就会发⽣这种情况。
- 当 Right edge 向右移动时,窗⼝会处于
open
打开状态,允许发送更多的数据。当接收端进程读取缓冲区数据,从⽽使缓冲区接收更多数据时,就会处于这种状态。
- 还可能会发⽣ Right edge 向左移动的情况,会导致发送并确认的报⽂段变⼩,这种情况被称为糊涂窗⼝综合症,这种情况是我们不愿意看到的。出现糊涂窗⼝综合症时,通信双⽅⽤于交换的数据段⼤⼩会变⼩,⽽⽹络固定的开销却没有变化,每个报⽂段中有⽤数据相对于头部信息的⽐例较⼩,导致传输效率⾮常低。
-
- 这就相当于之前你明明有能⼒花⼀天时间写完⼀个复杂的⻚⾯,现在你花了⼀天的时间却改了⼀个标题的 bug,⼤材⼩⽤。
-
- TCP 滑动窗⼝的 Left edge 永远不可能向左移动,因为发送并确认的报⽂段永远不可能被取消,就像这世界上没有后悔药⼀样。这条边缘是由另⼀段发送的 ACK 号控制的。当 ACK 标号使窗⼝向右移动但是窗⼝⼤⼩没有改变时,则称该窗⼝向前滑动。
- 每个 TCP 报⽂段都包含 ACK 号和窗⼝通告信息,所以每当收到响应时,TCP 接收⽅都会根据这两个参数调整窗⼝结构(发送窗口)。
ACK号:对方期望下一次接收的字节序号
窗口通告:对方当前能接收的最大字节数
- 如果 ACK 的编号增加但是窗⼝通告信息随着其他 ACK 的到达却变⼩了,此时 Left edge 会接近 Right edge。当 Left edge 和 Right edge 重合时,此时发送⽅不会再传输任何数据,这种情况被称为
零窗⼝
。此时 TCP 发送⽅会发起窗⼝探测
,等待合适的时机再发送数据。
接收方窗口
接收窗口的主要逻辑:
窗口的变化/概念都是基于发送接收的底层逻辑
- 只增不减:窗口序号只能向前移动,不能后退
其实对应了发送窗口不可能左边界左移!
- 按序接收:
期待序号1000:
- 收到1000: 接收并移动窗口(✓)
- 收到2000: 先缓存,等1000(⏳)
- 收到800: 直接丢弃(✗)
- 缓存管理:
乱序数据→缓存
缺口补齐→合并处理
重复数据→丢弃
接收⽅也维护了⼀个窗⼝结构,这个窗⼝要⽐发送⽅的简单很多。这个窗⼝记录了已经接收并确认的数据,以及它能够接收的最⼤序列号。接收⽅的窗⼝结构不会存储重复的报⽂段和 ACK,同时接收⽅的窗⼝也不会记录不应该收到的报⽂段和 ACK。下⾯是 TCP 接收⽅的窗⼝结构。
与发送端的窗⼝⼀样,接收⽅窗⼝结构也维护了⼀个 Left edge 和 Right edge。
- 位于 Left edge 左边的被称为已经接收并确认的报⽂段
- 位于 Right edge 右边的被称为不能接收的报⽂段
- 对于接收端来说,到达序列号⼩于 Left efge 的被认为是已经重复的数据,需要丢弃。
- 超过 Right edge 的被认为超出处理范围。只有当到达的报⽂段等于 Left edge 时,数据才不会被丢弃,窗⼝才能够向前滑动。
- 接收⽅窗⼝结构也会存在零窗⼝的情况,如果某个应⽤进程消耗数据很慢,⽽ TCP 发送⽅却发送了⼤量的数据给接收⽅,会造成 TCP 缓冲区溢出,通告发送⽅不要再发送数据了,但是应⽤进程却以⾮常慢的速度消耗缓冲区的数据(⽐如 1 字节),就会告诉接收端只能发送⼀个字节的数据,这个过程慢慢持续,造成⽹络开销⼤,效率很低。
【补充】这里根据接收窗口的功能,重新回顾一下数据传输的各种情况:
TCP接收方窗口处理数据的几种情况:
- 按序到达
接收窗口: 1000-4000
收到数据: 序号=1000(500字节)
处理过程:
- 接收数据
- 移动窗口到1500-4500
- 发送ACK=1500
- 乱序到达:
接收窗口: 1000-4000
收到数据: 序号=1500(500字节)
处理过程:
- 缓存1500的数据
- 窗口不动
- 继续发ACK=1000(等1000的数据)
- 重复到达:
接收窗口: 2000-5000
收到数据: 序号=1000(旧数据)
处理过程:
- 直接丢弃
- 窗口不动
- 发送当前ACK
- 缺口填补:
窗口:1000-4000
已收到:1500-2000(缓存)
收到:1000-1500(填补空缺)
处理过程:
- 接收1000-1500
- 合并1500-2000
- 移动窗口到2000-5000
- 发送ACK=2000
零窗口
我们上⾯提到了窗⼝存在 Left edge = Right edge 的情况,此时被称为零窗⼝,下⾯我们就来具体研究⼀下零窗⼝。
TCP 是通过接收端的窗⼝通告信息来实现流量控制的。通告窗⼝告诉了 TCP ,接收端能够接收的数据量。当接收⽅的窗⼝变为 0 时,可以有效的阻⽌发送端继续发送数据。当接收端重新获得可⽤空间时,它会给发送端传输⼀个窗⼝更新
告知⾃⼰能够接收数据了。窗⼝更新⼀般是纯 ACK ,即不带任何数据。但是纯 ACK 不能保证⼀定会到达发送端,于是需要有相关的措施能够处理这种丢包。
如果纯 ACK 丢失的话,通信双⽅就会⼀直处于等待状态,发送⽅⼼想拉垮的接收端怎么还让我发送数据!接收端⼼想天杀的发送⽅怎么还不发数据!为了防⽌这种情况,发送⽅会采⽤⼀个持续计时器来间歇性的查询接收⽅,看看其窗⼝是否已经增⻓。持续计时器会触发 窗⼝探测
,强制要求接收⽅返回带有更新窗⼝的 ACK。
可以看一下 TCP 数据——流量控制,有讲窗口探测包
窗⼝探测包含⼀个字节的数据,采⽤的是 TCP 丢失重传的⽅式。当 TCP 持续计时器超时后,就会触发窗⼝探测的发送。⼀个字节的数据能否被接收端接收,还要取决于其缓冲区的⼤⼩。
探测包格式:
- 1字节数据
- 序号为接收方期望的下一个字节
- ACK标志位置1
- 窗口大小字段为0示例:
+------------+
| 标志=ACK |
| 序号=2000 | → 上次ACK是2000
| 窗口=0 | → 接收方窗口满了
| 数据=1字节| → 强制接收方响应
+------------+
【TCP 传输】拥塞控制(没写完)
有了 TCP 的窗⼝控制后,使计算机⽹络中两个主机之间不再是以单个数据段的形式发送了,⽽是能够连续发送⼤量的数据包。然⽽,⼤量数据包同时也伴随着其他问题,⽐如⽹络负载、⽹络拥堵等问题。 TCP 为了防⽌这类问题的出现,使⽤了 拥塞控制 机制,拥塞控制机制会在⾯临⽹络拥塞时遏制发送⽅的数据发送。
拥塞控制主要有两种⽅法
- 端到端的拥塞控制 : 因为⽹络层没有为运输层拥塞控制提供显示⽀持。所以即使⽹络中存在拥塞情况,端系统也要通过对⽹络⾏为的观察来推断。 TCP 就是使⽤了端到端的拥塞控制⽅式。 IP 层不会向端系统提供有关⽹络拥塞的反馈信息。那么 TCP 如何推断⽹络拥塞呢? 如果超时或者三次冗余确认就被认为是⽹络拥塞, TCP会减⼩窗⼝的⼤⼩,或者增加往返时延来避免。
- ⽹络辅助的拥塞控制 : 在⽹络辅助的拥塞控制中,路由器会向发送⽅提供关于⽹络中拥塞状态的反馈。这种反馈信息就是⼀个⽐特信息,它指示链路中的拥塞情况。
下图描述了这两种拥塞控制⽅式
TCP 拥塞控制
如果你看到这⾥,那我就暂定认为你了解了 TCP 实现可靠性的基础了,那就是使⽤序号和确认号。除此之外,另外⼀个实现 TCP 可靠性基础的就是 TCP 的拥塞控制。
如果说 TCP 所采⽤的⽅法是让每⼀个发送⽅根据所感知到的⽹络的拥塞程度来限制发出报⽂段的速率,如果 TCP 发送⽅感知到没有什么拥塞,则 TCP 发送⽅会增加发送速率;如果发送⽅感知沿着路径有阻塞,那么发送⽅就会降低发送速率。
但是这种⽅法有三个问题
- TCP 发送⽅如何限制它向其他连接发送报⽂段的速率呢?
- ⼀个 TCP 发送⽅是如何感知到⽹络拥塞的呢?
- 当发送⽅感知到端到端的拥塞时,采⽤何种算法来改变其发送速率呢?
我们先来探讨⼀下第⼀个问题, TCP 发送⽅如何限制它向其他连接发送报⽂段的速率呢?
我们知道 TCP 是由接收缓存、发送缓存和 变量(LastByteRead, rwnd,等)
组成。发送⽅的 TCP 拥塞控制机制会跟踪⼀个变量,即 拥塞窗⼝(congestion window)
的变量,拥塞窗⼝表示为 cwnd ,⽤于限制 TCP 在接收到 ACK 之前可以发送到⽹络的数据量。⽽ 接收窗⼝(rwnd)
是⼀个⽤于告诉接收⽅能够接受的数据量。
⼀般来说,发送⽅未确认的数据量不得超过 cwnd 和 rwnd 的最⼩值,也就是
LastByteSent - LastByteAcked <= min(cwnd,rwnd)
由于每个数据包的往返时间是 RTT,我们假设接收端有⾜够的缓存空间⽤于接收数据,我们就不⽤考虑 rwnd 了,只专注于 cwnd,那么,该发送⽅的发送速率⼤概是 cwnd/RTT 字节/秒 。通过调节 cwnd,发送⽅因此能调整它向连接发送数据的速率。
⼀个 TCP 发送⽅是如何感知到⽹络拥塞的呢?
这个我们上⾯讨论过,是 TCP 根据超时或者 3 个冗余 ACK 来感知的。
当发送⽅感知到端到端的拥塞时,采⽤何种算法来改变其发送速率呢 ?
这个问题⽐较复杂,且容我娓娓道来,⼀般来说, TCP 会遵循下⾯这⼏种指导性原则
- 如果在报⽂段发送过程中丢失,那就意味着⽹络拥堵,此时需要适当降低 TCP 发送⽅的速率。
- ⼀个确认报⽂段指示发送⽅正在向接收⽅传递报⽂段,因此,当对先前未确认报⽂段的确认到达时,能够增加发送⽅的速率。为啥呢?因为未确认的报⽂段到达接收⽅也就表示着⽹络不拥堵,能够顺利到达,因此发送⽅拥塞窗⼝⻓度会变⼤,所以发送速率会变快
带宽探测
,带宽探测说的是 TCP 可以通过调节传输速率来增加/减⼩ ACK 到达的次数,如果出现丢包事件,就会减⼩传输速率。因此,为了探测拥塞开始出现的频率, TCP 发送⽅应该增加它的传输速率。然后慢慢使传输速率降低,进⽽再次开始探测,看看拥塞开始速率是否发⽣了变化。
在了解完 TCP 拥塞控制后,下⾯我们就该聊⼀下 TCP 的 拥塞控制算法(TCP congestion control algorithm)
了。 TCP 拥塞控制算法主要包含三个部分: 慢启动、拥塞避免、快速恢复
,下⾯我们依次来看下
慢启动
拥塞避免
快速恢复
TCP 状态转换
TCP 协议连接管理机制(三次握手、四次挥手)
在继续介绍下⾯有意思的特性之前,我们先来把关注点放在 TCP 的 连接管理 上,因为没有 TCP 连接,也就没有后续的⼀系列 TCP 特性什么事⼉了。
所谓的 3/4 次握手/挥手的次数其实指的是那个流程中的报文段个数
(补充向)【报文格式】
补充:
- 第一次通信没有 ack,因为没有接收不用确认
- 之后都有活用确认应答机制(seq ack)
-
- seq:当前数据字节序号
- ack:这个序号之前的数据接收完毕,下一次期望接收到的字节序号
- 一定要时刻记住序号的概念
-
- 只增不减
- 有无带数据的报文,对序号的影响不同
-
-
- SYN 报文不带数据但是会消耗一个序号
-
建立连接&&报文类型
第一次:同步报文(SYN)——消耗一个序号
客户端 → 服务器
SYN报文:
- SYN=1
- seq=x(随机数)
第二次:同步确认报文(SYN-ACK)
服务器 → 客户端
SYN-ACK报文:
- SYN=1, ACK=1
- seq=y(随机数)
- ack=x+1
第三次:确认报文(ACK)
客户端 → 服务器
ACK报文:
- ACK=1
- seq=x+1
- ack=y+1
断开连接&&报文类型
客户端主动断开:
第一次挥手:客户端 → 服务器FIN+ACK报文:- FIN=1, ACK=1- seq=x表示:"我要断开连接"
第二次挥手:服务器 → 客户端ACK报文:- ACK=1- ack=x+1表示:"好的,我知道了"
第三次挥手:服务器 → 客户端FIN+ACK报文:- FIN=1, ACK=1- seq=y表示:"我也要断开了"
第四次挥手:客户端 → 服务器ACK报文:- ACK=1- ack=y+1表示:"好的,再见"
服务器主动断开:
服务器 → FIN报文
客户端 → ACK报文
客户端 → FIN报文
服务器 → ACK报文
(补充向)【TCP 状态】
在⼀个 TCP 连接的⽣命周期内,运⾏在每台主机中的 TCP 协议都会在各种 TCP 状态(TCP State)
之间进⾏变化。
TCP 的状态主要有 LISTEN、 SYN-SEND、 SYN-RECEIVED、 ESTABLISHED、 FIN-WAIT-1、 FIN-WAIT-2、 CLOSE-WAIT、 CLOSING、 LAST-ACK、 TIME-WAIT 和 CLOSED 。
这些状态的解释如下:
LISTEN
: 表示等待任何来⾃远程 TCP 和端⼝的连接请求。SYN-SEND
: 表示发送连接请求后等待匹配的连接请求。SYN-RECEIVED
: 表示已接收并发送连接请求后等待连接确认,也就是 TCP 三次握⼿中第⼆步后服务端的状态ESTABLISHED
: 表示已经连接已经建⽴,可以将应⽤数据发送给其他主机
上⾯这四种状态是 TCP 三次握⼿所涉及的。
FIN-WAIT-1
: 表示等待来⾃远程 TCP 的连接终⽌请求,或者等待先前发送的连接终⽌请求的确认。FIN-WAIT-2
: 表示等待来⾃远程 TCP 的连接终⽌请求。CLOSE-WAIT
: 表示等待本地⽤户的连接终⽌请求。CLOSING
: 表示等待来⾃远程 TCP 的连接终⽌请求确认。LAST-ACK
: 表示等待先前发送给远程 TCP 的连接终⽌请求的确认(包括对它的连接终⽌请求的确认)。TIME-WAIT
: 表示等待⾜够的时间以确保远程 TCP 收到其连接终⽌请求的确认。CLOSED
: 表示连接已经关闭,⽆连接状态。
上⾯ 7 种状态是 TCP 四次挥⼿,也就是断开连接所设计的。
TCP 的连接状态会进⾏各种切换,这些 TCP 连接的切换是根据事件进⾏的,这些事件由⽤户调⽤:OPEN、SEND、RECEIVE、CLOSE、ABORT 和 STATUS。涉及到 TCP 报⽂段的标志有 SYN、 ACK、 RST 和 FIN,当然,还有超时。
我们下⾯加上 TCP 连接状态后,再来看⼀下三次握⼿和四次挥⼿的过程。
【TCP 连接管理】建立连接(三次握手)
"你好"(SYN)
"你好,我也认识你"(SYN-ACK)
"好的"(ACK)
为了保证客户端和服务器端的可靠连接,TCP建立连接时必须要进行三次会话,也叫TCP三次握手,进行三次握手的目的是为了确认双方的接收能力和发送能力是否正常。
在TCP中,只能是客户端主动发起连接,服务器不能主动连接客户端。
不存在服务器主动连接的情况
必须是客户端发起SYN
这是TCP协议的基本设计
服务器特点:
- 被动等待连接
- 监听固定端口
- 可接受多个客户端
客户端特点:
- 主动发起连接
- 使用随机端口
- 连接特定服务器
设计原理:
服务器:像店铺固定位置等客人
客户端:像顾客主动去店铺
【三次握手】TCP 客户端主动建立连接
下图画出了 TCP 连接建⽴的过程。假设图中左端是客户端主机,右端是服务端主机。
- ⼀开始,两端都处于
CLOSED(关闭)
状态。主动打开连接的为客户端,被动打开连接的是服务器 - 服务端进程准备好接收来⾃外部的 TCP 连接,⼀般情况下是调⽤ bind、 listen、 socket 三个函数完成。这种打开⽅式被认为是
被动打开(passive open)
。然后服务端进程处于LISTEN(监听TCP连接请求)
状态,等待客户端连接请求
- 第一次握手:TCP 客户端通过
connect
发起主动打开(active open)
/ TCP 客户端进程先创建传输控制块TCB,向 TCP 服务器发出连接请求报文(SYN 报文)。
-
- 在连接请求报文中(SYN 报文):
SYN 报⽂段不允许携带数据,只消耗⼀个序号
-
-
- 标志字段:
SYN = 1
- 序号:
seq = x(随机数)
- 不包含数据,但消耗一个序号
- 标志字段:
-
-
- 此时,TCP 客户端进⼊
SYN-SEND(同步已发送)
状态
- 此时,TCP 客户端进⼊
- 第二次握手:TCP 服务器收到 TCP 客户端连接请求报文后,(如同意连接)需向 TCP 客户端发出确认报⽂(SYN+ACK 报文)。
-
- 在确认报⽂中(SYN-ACK 报文):
请注意,这个报⽂段也不能携带数据,但同样要消耗掉⼀个序号
-
-
- 标志字段:
SYN = 1, ACK = 1
- 序号:
seq = y(随机数)
- 确认号:
ack = x + 1
- 标志字段:
-
-
- 此时, TCP 服务器进⼊
SYN-RECEIVED(同步收到)
状态
- 此时, TCP 服务器进⼊
- 第三次握手:TCP 客户端收到 TCP 服务器的确认报文后,需向 TCP 服务器发出确认连接报文(ACK 报文)。
-
- 在确认连接报文中(ACK 报文):
TCP 规定,这个报⽂段可以携带数据也可以不携带数据
- 如果不携带数据,那么下⼀个数据报⽂段的序号仍是 seq = x + 1——不消耗序号
-
-
- 标志字段:
ACK = 1
- 序号:
seq = x + 1
- 确认号:
ack = y + 1
- 标志字段:
-
-
- 这时,客户端进⼊
ESTABLISHED(已建立连接)
状态 - 服务器收到客户的确认后,也进⼊
ESTABLISHED(已建立连接)
状态
- 这时,客户端进⼊
【重要】有人可能会很疑惑为什么要进行第三次握手?
主要原因:防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误
第一次握手: 客户端向服务器端发送连接请求报文
- 证明客户端的发送能力正常
第二次握手:服务器端接收到报文并向客户端发送报文
- 证明服务器端的接收能力、发送能力正常
第三次握手:客户端向服务器发送报文
- 证明客户端的接收能力正常
【补充】如果采用两次握手会出现以下情况:
客户端向服务器端发送的请求报文由于网络等原因滞留,未能发送到服务器端,此时连接请求报文失效,客户端会再次向服务器端发送请求报文,之后与服务器端建立连接,当连接释放后,由于网络通畅了,第一次客户端发送的请求报文又突然到达了服务器端,这条请求报文本该失效了,但此时服务器端误认为客户端又发送了一次连接请求,两次握手建立好连接,此时客户端忽略服务器端发来的确认,也不发送数据,造成不必要的错误和网络资源的浪费。
如果采用三次握手的话,就算那条失效的报文发送到服务器端,服务器端确认并向客户端发送报文,但此时客户端不会发出确认,由于客户端没有确认,由于服务器端没有接收到确认,就会知道客户端没有请求连接。
【TCP 连接管理】断开连接(四次挥手)
任何人都可以说"我要走了"()
对方回应"好的"(ACK)
然后也说"我也走了"(FIN-ACK)
最后确认"再见"(ACK)
数据传输结束后,通信的双⽅可以释放连接。数据传输结束后的客户端主机和服务端主机都处于 ESTABLISHED
状态,然后进⼊释放连接的过程。
TCP中断开连接(四次挥手)可以由任意一方发起:
客户端主动断开:
客户端 → FIN报文 任何人都可以说"我要走了"
服务器 → ACK报文 对方回应"好的"
服务器 → FIN报文 然后也说"我也走了"
客户端 → ACK报文 最后确认"再见"
服务器主动断开:
服务器 → FIN报文
客户端 → ACK报文
客户端 → FIN报文
服务器 → ACK报文
关键点:
- 双方都可以主动断开
- 过程都是四次挥手
- 只是发起方角色互换
1. 【四次挥手】客户端:主动断开连接
- 第一次挥手:TCP 客户端发出连接释放报文(FIN 报文),并且停止发送数据,主动关闭 TCP 连接。
-
- 在连接释放报文中(FIN 报文)
-
-
- 标志字段:FIN = 1
- 序号:seq = u(等于前面已经传送过来的数据的最后一个字节的序号加1)
- 不包含数据
-
-
- 此时,TCP 客户端进入
FIN-WAIT-1(终止等待1)
状态
- 此时,TCP 客户端进入
- 第二次挥手:TCP 服务器接收到连接释放报文后,发出确认报文(ACK 报文)
-
- 在确认报文中(ACK 报文)
-
-
- 标志字段:
ACK = 1
- 序号:
seq = v(自己的序号)
- 确认号:
ack = u + 1
- 标志字段:
-
-
- 此时,TCP 服务端就进入了
CLOSE-WAIT(关闭等待)
状态
- 此时,TCP 服务端就进入了
这个时候客户端主机 ->服务器主机这条⽅向的连接就释放了,客户端主机没有数据需要发送,此时服务器主机是⼀种半连接的状态,但是服务器主机仍然可以发送数据。
- 第三次挥手:
-
- TCP 客户端接收到 TCP 服务器的确认请求后,就进入
FIN-WAIT-2(终止等待2)
状态,等待 TCP 服务器发送连接释放报文; - TCP 服务器将最后的数据发送完毕后,应⽤进程就会通知 TCP 释放连接。向客户端发送连接释放报文(FIN 报文);
- TCP 客户端接收到 TCP 服务器的确认请求后,就进入
-
-
- 在连接释放报文中(FIN 报文)
-
-
-
-
- 标志字段:
FIN = 1, ACK = 1
- 序号:
seq = w
- 标志字段:
-
-
因为在这之间可能已经发送了⼀些数据,所以 seq 不⼀定等于 v + 1
-
-
-
- 确认号:
ack = u + 1
- 确认号:
-
-
-
-
- 在发送完断开请求的报⽂后,TCP 服务器就进入了
LAST-ACK(最后确认)
状态,等待 TCP 客户端的确认报文
- 在发送完断开请求的报⽂后,TCP 服务器就进入了
-
- 第四次挥手:TCP 客户端收到 TCP 服务器的连接释放报文后,发出确认报文(ACK 报文)
-
- 在确认报文中:
-
-
- 标志字段:
ACK = 1
- 序号:
seq = u + 1
- 确认号:
ack = w + 1
- 标志字段:
-
-
- 此时,TCP 客户端就进入了
TIME-WAIT(时间等待)
状态,但此时 TCP 连接还未终止,必须要经过2MSL后(最长报文寿命)
,(当客户端撤销相应的TCB后)TCP 客户端才会进入CLOSED(关闭)
状态
- 此时,TCP 客户端就进入了
时间 MSL 叫做 最⻓报⽂段寿命(Maximum Segment Lifetime)
-
- TCP 服务器接收到确认报文后,会立即进入
CLOSED(关闭)
状态,到这里TCP连接就断开了,四次挥手完成
- TCP 服务器接收到确认报文后,会立即进入
2. 【四次挥手】服务器:主动断开连接
【重要】为什么客户端要等待2MSL?
主要原因是为了保证客户端发送那个的第一个ACK报文能到到服务器,因为这个ACK报文可能丢失,并且2MSL是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃,这样新的连接中不会出现旧连接的请求报文。
【补充】TCP 断开连接&四次挥手
TCP 连接的任意⼀⽅都可以发起关闭操作,只不过通常情况下发起关闭连接操作⼀般都是客户端。然⽽,⼀些服务器⽐如 Web 服务器在对请求作出相应后也会发起关闭连接的操作。 TCP 协议规定通过发送⼀个 FIN 报⽂来发起关闭操作。
所以综上所述,建⽴⼀个 TCP 连接需要三个报⽂段,⽽关闭⼀个 TCP 连接需要四个报⽂段。 TCP 协议还⽀持⼀种 半开启(half-open) 状态,虽然这种情况并不多⻅。
TCP 半开启(没写完)
TCP 半关闭
同时打开和同时关闭
聊一聊初始序列号
【补充】快速理解 TCP 协议
- 报文格式(优先记住核心部分)
- 序号、确认号含义
- 标志字段
- 了解 TCP 协议的各种机制(TCP 协议的核心)
- 传输控制
-
- 窗口……
- 通信机制
-
- ……
- TCP 的状态转换
- TCP 的连接管理机制
【传输层】TCP 和 UDP 区别
特征点 | TCP | UDP |
传输可靠性 | 面向连接 | 面向非连接 |
应用场景 | 传输数据量大 | 传输量小 |
速度 | 慢 | 快 |
TCP(传输控制协议)提供的是面向连接、可靠的字节流服务。当客户端和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP(用户数据协议)是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP咋传输数据前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
由于UDP缺乏拥塞控制(congestion control),需要基于网络的机制来减少因失控和高速UDP流量负荷而导致的拥塞崩溃效应。换句话说,因为UDP发送者不能够检测拥塞,所以像使用包队列和丢弃技术的路由器这样的网络基本设备往往就成为降低UDP过大通信量的有效工具。数据报拥塞控制协议(DCCP)设计成通过在诸如流媒体类型的高速率UDP流中,增加主机拥塞控制,来减小这个潜在的问题。