TCP协议原理 滑动窗口 流量控制 应答 阻塞控制 粘包问题
由TCP协议引出的各种网络技术
TCP概念
- TCP协议是一个面向连接的、可靠的、基于字节流的传输层协议。当通信双方通过
tcp
协议来进行通信的时候,双方发送的都是完整的tcp
报文。
TCP 报文段结构
源/目的端口号:
- 表示数据的来源进程和目标进程。
32 位序号/32 位确认号:
用于保证数据的顺序和确认。
当我们的数据加载到发送缓冲区的时候,每一个字节天然有自己的编号。(本质就是数组下标)序号保存的就是数据块最后一个字符的下标。
确认序号:填充的是 ——
报文序号
+1
。这样的意义是可以辨别是哪一个报文序号
被正确传递了,允许了一定程度上的丢包。并且确认序号对应的数字之前的数据包都是被保证传递到的。
4 位 TCP 报头长度:
表示 TCP 头部的长度,以 32 位(4 字节)为单位。因此,TCP 头部最大长度为
15 * 4 = 60
字节,此时选项将占据40
字节。15:4位
1111
。(8+4+2+1)根据这个可以完成TCP报文和报头的分离。
6 位标志位:区分TCP报文的类型。
URG: 表示紧急指针是否有效。要求对紧急指针对应的偏移量的数据优先进行处理。并且一般只能携带一个字节的紧急数据(out-of-band)。
ACK: 表示确认号是否有效。—— 如果有效则代表是一个
确认报文
。PSH: 提示接收端应用程序立刻从 TCP 缓冲区读取数据,腾出接受缓冲区。
RST: TCP是允许连接建立失败的。对方要求重新建立连接;携带 RST 标识的报文称为
复位报文
。
SYN: 请求建立连接(三次握手);携带 SYN 标识的报文称为
同步报文
。FIN: 通知对方本端要关闭连接;携带 FIN 标识的报文为
结束报文
。16 位校验和:
- 由发送端填充,用于 CRC 校验。接收端如果校验不通过,则认为数据有问题。校验和包括 TCP 首部和数据部分。
16 位紧急指针:
- 标识哪部分数据是紧急数据。
40 字节头部选项:
- 可选部分。
1. 为什么叫传输控制协议
- 用户级缓冲区和内核缓冲区:
在网络编程中,应用程序的数据首先会存储在用户级缓冲区中。这是应用程序管理的内存空间。
当应用程序调用
read
、write
、recv
等系统调用时,数据会在用户级缓冲区和内核缓冲区之间进行传输。内核缓冲区是操作系统管理的内存空间。
write
和send
等系统调用:
当你调用
write
或send
等系统调用时,数据从用户级缓冲区被复制到内核缓冲区。一旦数据被成功复制到内核缓冲区,应用程序可以继续执行,而实际的数据发送由内核和底层的网络栈负责——故也叫传输控制协议。
- 对于 TCP 连接,内核会将收到的数据按照顺序重新组装并放入内核缓冲区,然后应用程序可以通过
read
或recv
系统调用来读取这些数据。
- 对于 TCP 连接,内核会将收到的数据按照顺序重新组装并放入内核缓冲区,然后应用程序可以通过
应用通过网络协议将数据传输到网络的过程,类似于应用程序通过操作系统将数据传输给磁盘的过程。
2. TCP通信是全双工的。
全双工的原因:其套接字在操作系统中都有独立的发送缓冲区和接收缓冲区。
read/wirte类似一种拷贝函数,负责用户数据与系统缓冲区之间的数据传递。然后由传输控制协议决定数据的收发。
- 之所以叫传输控制协议,因为数据什么时候发送,发送多少,出错了怎么办都是由TCP协议自主决定的。
3. TCP
的特点有些什么?
1. 面向连接
三次握手:在发送数据之前,TCP 需要通过三次握手与接收方建立一个连接。这确保了双方的通信是可靠的,并且为接下来的数据传输做了准备。
四次挥手:在数据传输完成后,TCP 通过四次挥手来关闭连接,确保双方都能正确地终止通信。
2. 可靠性
数据确认:每当一方发送数据后,接收方都会返回一个确认(ACK)—— 即确认应答,表明数据已成功接收。
- 如果接收方在发送报文之后一段时间没有收到应答,那么我们就假定它发送失败了。
重传机制:如果发送的数据包在一定时间内没有收到确认,TCP 会自动重传数据,直到确认数据被成功接收。
顺序保证:TCP 将数据分成多个数据包传输,并通过32位的序号保证接收方能够按照正确的顺序组装数据,即使数据包乱序到达,也能按顺序排列。
3. 流量控制
流量控制的依据之一:由对方接收缓冲区的剩余空间的大小来决定。在
TCP
报头中,16位窗口大小就是发送方的接收缓冲区剩余空间的大小。窗口机制:TCP 使用窗口机制来控制发送方的发送速率,防止发送方发送的数据超过接收方的处理能力。接收方会通过窗口大小字段告知发送方它能够接收的最大数据量。
拥塞控制:TCP 采用各种算法(如慢启动、拥塞避免、快重传、快恢复)来检测和应对网络拥塞情况,调整发送速率以减少网络拥塞。
4. 数据完整性
- 校验和:TCP 在每个数据包中包含校验和,用于检测数据在传输过程中是否被篡改。如果接收方发现校验和不匹配,数据包会被丢弃并要求重传。
5. 字节流传输
- 数据流管理:TCP 将应用层的数据看作一个字节流,数据被划分为多个数据段进行传输,而不是独立的数据包。TCP 确保这些数据段能够按顺序、无差错地传输到接收方。
6. 全双工通信
- 双向数据传输:TCP 支持全双工通信,允许双方同时发送和接收数据。每一方都有独立的发送和接收缓冲区,确保数据可以双向独立传输。
7. 拥塞控制与避免
- 慢启动和拥塞避免:TCP 使用慢启动和拥塞避免算法,逐步增加数据发送速率,避免突然增加网络负担。
- 快速重传与快速恢复:在检测到网络拥塞时,TCP 能够快速减少发送速率,避免网络进一步拥塞,并通过快速重传尽快恢复数据传输。
捎带应答
为了提高传输效率,将应答和数据放到一起的一种方案。
在捎带应答的情境中,序号和确认序号在被接收方接收后。不仅要将序号+1填充到确认序号,因为自己要传递数据,还要重新写入一个序号,因此序号和确认序号都要存在。
连接之前服务器的准备工作与listen的第二个参数
服务器首先需要通过 socket 函数创建一个套接字。
使用 bind 函数将该套接字绑定到指定的 IP 地址和端口号。
调用 listen 函数后,服务器的套接字进入监听状态,等待客户端的连接请求。
- listen 函数的第二个参数是待处理连接的队列长度(backlog),表示服务器最多可以在队列中保留多少个未处理的连接请求。如果队列已满,新请求将被拒绝或丢弃。在 Linux 中,backlog 实际上决定了全连接队列的长度,而半连接队列长度则通常由系统自动调整。
接受连接请求:
当客户端发起连接请求时,服务器在 listen 状态下会检测到这个请求,并将请求放入待处理队列中,等待进一步处理。(通常是通过
accept
函数来接受请求,是否accpet
不影响实际tcp的三次握手)。如果
backlog
队列已满,那么服务器将不接受最后的ACK
,因此三次握手并不建立成功。对于建立不成功的连接,服务器会进入到syn_recv
状态。Server端不会长时间维持
syn_recv
。如果服务器在规定时间内收到了客户端的 ACK 报文段,那么这个连接请求会从半连接队列中移除,并转移到已连接队列(全连接队列)中,表示该连接已经建立成功,可以开始传输数据。- 半连接队列长度有限。如果半连接队列已满,服务器将无法处理新的 SYN 请求,可能会直接丢弃新的连接请求或延迟处理。
建立连接的过程
客户端调用
socket
创建文件描述符。客户端调用
connect
向服务器发起连接请求。connect
会发出 SYN 段并阻塞等待服务器应答。(第一次)服务器收到客户端的 SYN,会应答一个 SYN-ACK 段,表示”同意建立连接”。(第二次)
客户端收到 SYN-ACK 后会从
connect()
返回,同时应答一个 ACK 段。(第三次)
这个建立连接的过程通常称为 三次握手。
断开连接的过程(以下FIN,ACK均是TCP报文)
如果客户端没有更多的请求了,就调用
close()
关闭连接,客户端会向服务器发送FIN
。(第一次)服务器收到
FIN
后,会回应一个ACK
,同时read
会返回 0。(第二次)read
返回之后,服务器就知道客户端关闭了连接,也调用close
关闭连接,这个时候服务器会向客户端发送一个FIN
。(第三次)客户端收到
FIN
,再返回一个ACK
给服务器。(第四次)
三次握手的原因
如果只有一次握手,那么就很容易导致
SYN
洪水攻击。如果只有两次握手,服务器无法确认客户端是否准备好接受数据,因此可能会造成数据传输的不可靠性。
如果只有两次握手,当
TCP
在最后一次ACK
失败时,对于服务器来说,TCP仍是建立成功的,只有等长时间无响应,服务器才会关闭这个异常的TCP连接。会增加服务器的成本。
- 防止历史连接请求的干扰:
- 三次握手可以防止过时的连接请求报文段被服务器误认为是新的连接请求。举个例子,假设某个 SYN 报文段因网络原因被延迟了很久才到达服务器,如果只有两次握手,服务器可能会错误地接受这个过时的请求。三次握手可以避免这种情况,因为服务器不会在没有收到客户端的最终确认(第三次握手)之前进入连接状态。
四次挥手的原因(因为可能存在捎带应答,也可以只有三次挥手)
TCP 使用“四次挥手”来断开连接,这是为了确保双方都能完全、可靠地释放资源,具体原因如下:
半关闭状态的处理:
第一次挥手:当客户端或服务器一方想要断开连接时,会发送一个 FIN 报文段,表示它不再发送数据,但仍能接收数据。发送方进入 FIN-WAIT-1 状态。
第二次挥手:接收方收到 FIN 报文段后,发送一个 ACK 报文段,确认收到了 FIN。这时接收方进入 CLOSE-WAIT 状态,发送方进入 FIN-WAIT-2 状态。
第三次挥手:接收方如果也不再需要发送数据了,就会发送一个 FIN 报文段,进入 LAST-ACK 状态,等待最后的确认。
第四次挥手:发送方收到这个 FIN 报文段后,发送一个 ACK 报文段,表示确认断开连接,进入 TIME-WAIT 状态,一段时间后彻底关闭连接。接收方收到 ACK 后直接关闭连接。
这里的四次挥手分为两对 FIN/ACK 是因为 TCP 是全双工的通信协议,连接的双方都需要独立地关闭自己的数据流。在任何一方发送 FIN 之后,另一方还可能有未发送完的数据,因此需要等待对方确认可以关闭。
确保可靠的连接关闭:
- 四次挥手确保了在连接关闭的过程中,双方都能完全释放资源,并且任何一方的未发送数据都能发送完毕。这种设计避免了潜在的数据丢失和连接资源泄漏问题。
主动断开的一方,需要进入time wait
状态
为什么?
- 等待历史发送的数据,在网络中被传递完毕。
- 如果在四次挥手中,最后一次
ACK
丢失了,服务器可能重新发送FIN
和ACK
,此时如果关闭了连接,就会导致服务器的无效等待。
- 如果在四次挥手中,最后一次
time_wait
状态
- 确保旧连接的报文不会影响新连接:
- 在
TIME_WAIT
状态期间,TCP 保持连接的相关信息一段时间(通常是 2 倍的最大报文生存时间,约 2 分钟),以防止旧连接中的延迟数据包被误认为是新连接的一部分。
- 确保被动关闭方收到 ACK:
TIME_WAIT
状态允许主动关闭方重新发送最后一个 ACK 以防止被动关闭方没有收到(由于网络问题),从而导致其错误地认为连接未成功关闭。
- 状态持续时间:
TIME_WAIT
状态通常持续 2 倍的最大报文生存时间(2MSL)。在此期间,该端口号不能被重新分配给新的连接,这在高并发环境下可能会导致端口耗尽问题。
- **如何避免
TIME_WAIT
**:
- 一些场景中可以通过
SO_REUSEADDR
选项允许端口立即重用,避免等待时间过长。
滑动窗口——大小最大
是接受窗口的大小。其本质是利用双指针移动过程。
发送窗口(Send Window):
- 发送窗口表示发送方在不需要等待确认的情况下可以发送的最大数据量。发送窗口由 TCP 的发送窗口大小(
SND.WND
)和未确认数据的数量决定。发送方可以在窗口范围内连续发送数据,而不需要等待每个数据包的确认。
- 发送窗口表示发送方在不需要等待确认的情况下可以发送的最大数据量。发送窗口由 TCP 的发送窗口大小(
接收窗口(Receive Window):
- 接收窗口表示接收方可以接收并缓存的最大数据量,通常由接收方通过 TCP 报文中的
Window
字段告知发送方。接收窗口的大小可以根据接收方的处理能力和缓存空间动态调整。
- 接收窗口表示接收方可以接收并缓存的最大数据量,通常由接收方通过 TCP 报文中的
窗口大小:
- 窗口大小是 TCP 通信的一个重要参数,它由接收方在每个 TCP 报文中通知发送方。窗口大小越大,发送方可以在不等待确认的情况下发送更多数据,从而提高传输效率。
窗口滑动:
- 当接收方
确认
已经收到某些数据时,发送窗口会向前滑动,即窗口的起始位置(SND.UNA
)会更新为确认序号+1的位置—— 这样的目的是为了防止丢包。然后继续发送数据。
- 当接收方
快重传
- 如果一个数据段一直没有被接收到,那么确认应答将一直重复。当发送发收到
3
个相同的确认应答时,则进行重发。这个过程就叫快重传。
窗口探测
窗口探测(Window Probe)是 TCP 协议中的一种机制,用于解决发送方和接收方之间因窗口大小为零而导致的死锁问题。它确保即使接收窗口为零,发送方仍能通过发送小数据包探测接收方的窗口,及时了解接收方的窗口大小变化,从而继续数据传输。
窗口探测的工作原理
当发送方收到接收窗口
为零的通知后,它会进入一种特殊的状态,定期发送窗口探测包(Window Probe)。目的通过这些探测包触发接收方返回当前窗口大小的确认(ACK)。
窗口探测的步骤如下:
接收方通知窗口为零:接收方通知发送方它的接收窗口为零,发送方暂停发送数据。
发送方进入窗口探测状态:发送方进入等待状态,并定期发送小数据包(窗口探测包),目的是触发接收方返回确认消息。
接收方回复确认消息:接收方在收到窗口探测包后,即使其窗口仍然为零,通常也会发送一个 ACK 包,告知发送方当前的窗口大小。
发送方继续探测:如果窗口大小仍为零,发送方继续定期发送探测包。
恢复正常数据传输:如果,接收窗口不再为零,发送方根据接收方发送的新的窗口大小恢复正常的数据传输。
滑动窗口的优点
高效数据传输:通过允许发送方在不等待每个数据段的确认的情况下连续发送数据,滑动窗口提高了网络的吞吐量。
流量控制:接收方可以动态调整窗口大小,避免发送方发送过多的数据,防止接收方缓冲区溢出。
拥塞控制:滑动窗口机制与 TCP 的拥塞控制机制结合,可以有效管理网络拥塞,确保可靠传输。
延迟应答
延迟应答是 TCP 协议中的一种优化策略,用于减少网络中的 ACK(确认)数据包的数量,从而提高网络效率和降低带宽占用。
工作原理
延迟应答的基本思路是,接收方在接收到数据段后,不会立即发送 ACK,而是等待一小段时间(通常是 200 毫秒以内),以期望在这段时间内接收到更多的数据段 —— 提高IO效率。
- 如果在这个延迟时间内接收到了新的数据段,接收方会对所有接收到的数据段发送一个累计 ACK。如果在延迟时间内没有接收到新的数据段,接收方才会发送 ACK 以确认已经接收到的数据。
主要优点
- 减少 ACK 数据包的数量:延迟应答可以减少网络中的 ACK 数据包,从而降低网络带宽的占用,从而能够一次性确认多个数据段,尤其是在高流量环境下,这种优化尤为明显
潜在的缺点
引入额外延迟:由于接收方会等待一段时间才发送 ACK,这可能会引入一些额外的延迟,尤其是在需要快速确认的场景下。
可能导致不必要的重传:在某些网络环境下,如果延迟时间过长而导致发送方误认为数据段丢失,从而触发不必要的重传,会降低传输效率。
阻塞控制
如果通信的时候,出现了少量的丢包,那么就按照常规情况进行处理,如:超时重发。
但如果出现了大量的丢包,那么我们就认为是网络出现了问题。此时作为发送方,不能立即对报文进行超时重发,因为会加重网络的阻塞。并且不仅仅是对一台主机的传输数据能力进行约束,而是根据网络拥塞的情况,对整个网络上的主机都进行一定程度的约束。
当识别到网络拥塞的情况时,会采用拥塞控制的策略
此时:滑动窗口大小 = min(窗口大小,拥塞窗口大小)
- 此时不仅考虑了对方主机的接受能力,也考虑了整个网络的接受能力。
拥塞控制的四个阶段
慢启动:
- 初始阶段:当一个 TCP 连接刚刚建立时,慢启动控制发送窗口的增长。初始的拥塞窗口(Congestion Window,
cwnd
)通常设置为一个或几个最大报文段(Maximum Segment Size,MSS
),表示可以发送的最大数据量。 - 指数增长:每次收到一个 ACK 后,
cwnd
会增大一个MSS
的值。这样,发送窗口会呈指数增长,直到达到一个阈值(慢启动门限,ssthresh
)为止。 - 作用:慢启动的目的是逐步探测网络的可用带宽,而不是一开始就发送大量数据,从而防止对网络造成过大的负载。
- 初始阶段:当一个 TCP 连接刚刚建立时,慢启动控制发送窗口的增长。初始的拥塞窗口(Congestion Window,
拥塞避免:
- 当
cwnd
达到ssthresh
后,TCP 进入拥塞避免阶段,此时cwnd
不再指数增长,而是以线性增长的方式逐渐增加。具体来说,每经过一个往返时间(Round Trip Time,RTT
),cwnd
增加MSS
的值。 - 作用:通过线性增长,避免网络过载,同时继续探测网络的承载能力。
- 当
网络拥塞
- 立即将慢启动阈值会变成原来的一半, 同时拥塞窗口置回1。
重新发送
- 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1。
慢启动的作用
安全地增加发送速率:通过慢启动机制,TCP 在初始阶段以指数增长的方式逐渐增加发送速率,这可以安全地探测网络的最大可用带宽,避免因一开始发送过多数据导致网络瞬间拥塞。
避免网络崩溃:如果没有慢启动,TCP 可能会一开始就发送大量数据包,这可能会导致网络拥塞和崩溃。而慢启动则通过逐步增加发送窗口的方式,确保网络能够逐渐适应增加的数据流量。
粘包问题
- 粘包问题发生在 TCP 协议的应用中,指的是报文和报文之间,边界不明确的问题,从而难以正确解析消息。
解决方法:
定长报文。
使用特殊字符。
使用自描述符 + 当长报头或(特殊字符)
- 自描述符 —— 包含了有效载荷的长度信息。
TCP中断的异常情况
进程终止:正常的自动连接断开。(四次挥手)
机器重启:正常的自动连接断开。(四次挥手)
机器掉电/网线断开:不会正常的断开连接。