接上文:
文章目录
前言

(6)流量控制

-
如果客户端发送很多数据,导致服务端的缓冲区被打满,这个时候如果服务端继续发送, 就会造成丢包, 继而引起丢包重传等等⼀系列连锁反应。这时应减少客户端的发送量,调整至服务端能接收的速度。
-
当服务端接收缓冲区剩余空间很大时,客户端也会增加发送量,最大限度的提高效率。
-
所以流量控制不只是减少网络吞吐量,也会增加网络吞吐量。
-
TCP支持根据接收端的处理能力, 来决定发送端的发送速度 . 这个机制就叫做流量控制
-
接收方的接收能力是由其接收缓冲区的剩余空间大小决定的。即服务端的接收能力取决于他的接收缓冲区剩余空间大小。
-
报头中16位窗口大小 表示的是其接收缓冲区剩余空间的大小,并通过确认应答返回给发送端。
-
窗⼝大小字段越大, 说明网络的吞吐量越高;
-
接收端⼀旦发现自己的缓冲区快满了, 就会将窗口大小设置成⼀个更小的值通知给发送端。发送端接受到这个窗口之后, 就会减慢自己的发送速度;
-
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 而是会定期发送⼀个窗口探测 数据段, 使接收端把窗口大小告诉发送端.

-
16位窗口大小中: 16位数字最大表示65535, 那么TCP窗口最大就是65535字节吗?实际上, TCP首部40字节选项中还包含了⼀个窗口扩⼤因子M, 实际窗口大小是 16位窗口大小的值左移 M 位;
(7)连接管理机制
TCP建立连接时要进行三次握手, 断开连接时要进行四次挥手

在实际通信中,一个服务端会同多个客户端进行通信,所以TCP报文就有各种各样的类型,这就要求服务端能够区分不同的TCP报文类型。而报文中标志位的存在就是为了区分报文类型。
-
ACK
该标志位置1表明自己是一个确认报文,指示确认序号字段有效,确认已收到数据。因为有捎带应答机制,所以大部分TCP报文,ACK都是1。
-
SYN
同步标志位,表明建立连接的请求,是三次握手的核心标志。
-
FIN
连接断开标志位,通信结束时,进行挥手协商。

服务端状态转化:
-
[CLOSED -> LISTEN]
服务器端调用listen后进入LISTEN状态,等待客户端连接;
-
[LISTEN -> SYN_RCVD]
当收到SYN时,即进入SYN_RCVD状态。一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文.
-
[SYN_RCVD -> ESTABLISHED]
服务器端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了.
-
[ESTABLISHED -> CLOSE_WAIT]
当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT;
-
[CLOSE_WAIT -> LAST_ACK]
进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
-
[LAST_ACK -> CLOSED]
服务器收到了对FIN的ACK,彻底关闭连接.
客户端状态变化:
-
[CLOSED -> SYN_SENT]
客户端调用connect,发送同步报文段。
-
[SYN_SENT -> ESTABLISHED]
当发送ACK时,即进入ESTABLISHED状态,开始读写数据。
-
[ESTABLISHED -> FIN_WAIT_1]
客户端主动调用close时,向服务器发送结束报文段,同时进入FIN_WAIT_1。
-
[FIN_WAIT_1 -> FIN_WAIT_2]
客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,开始等待服务器的结束报文段。
-
[FIN_WAIT_2 -> TIME_WAIT]
客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK。
-
[TIME_WAIT -> CLOSED]
客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态。等待历史游离的报文在网络中消散。
全双工TCP连接 = 两个独立的单向通道
客户端 → 服务器通道: 用于客户端发送数据 (client发送缓冲区→ server接收缓冲区)
客户端 ← 服务器通道: 用于服务器发送数据 (client接收缓冲区← server发送缓冲区)
注意:
三次握手是双方操作系统自动完成的。
- accept并不参与三次握手,accept只是等待三次握手完成。
connect触发三次握手,等连接成功后再返回 - 三次握手的最后一个ACK是否被对方收到我并不确定
- 客户端往往先认为建立好连接,最后一个ACK还需要再网络中跑一会儿服务端才能收到
- 为什么要进行三次握手?三次握手的本质是四次握手,因为做了捎带应答,所以变成了三次握手,并且三次握手可以以最小次数验证通信的全双工信道时通畅的。
- 三次握手完成后,一条连接就被建立起来,一条连接就和一个文件(fd)对应。服务端操作系统肯定维护多条连接(多个客户端访问),客户端操作系统也要维护多条连接(要访问不同的服务端,微信、QQ等)
四次挥手要征得双方同意,因为TCP是全双工的,要关闭两个朝向上的连接
-
close是用来关闭两个方向上的连接,即发送和接收都会被关闭。还有一个系统调用shutdown,可以只关闭读,只关闭写。调用 shutdown()(尤其是 shutdown(SHUT_WR) 或 shutdown(SHUT_RDWR))通常会使"主动关闭的一方"进入 FIN_WAIT_1 状态,随后可能进入 FIN_WAIT_2。 是否进入 FIN_WAIT,关键在于:有没有发送 FIN 。当调用shutdown(SHUT_RD),内核不会发送FIN。
举例说明shutdown的使用场景:客户端向服务器发送一个请求,然后调用shutdown(SHUT_WR)关闭写方向,表示客户端已经发送完请求,然后等待服务器响应。服务器在收到FIN后,知道客户端已经发送完请求,但还可以继续向客户端发送响应。客户端在收到服务器的响应后,再调用close完全关闭连接。
-
close和shutdown都会影响TCP连接的状态,但close会释放套接字描述符,而shutdown不会。因此,在调用shutdown后,仍然需要调用close来释放描述符。
-
客户端调用close(),那么它会发送FIN,然后进入FIN_WAIT_1,接着收到ACK后进入FIN_WAIT_2。在FIN_WAIT_2状态,内核仍然会接收数据并保存在接收缓冲区,这些数据会被内核接收并确认,然后丢弃。但是应用程序已经不能通过已经close的文件描述符来读取数据了,因为close之后文件描述符已经无效。
-
为什么四次挥手不进行捎带应答变成三次挥手呢?因为服务器收到客户端的FIN后,可能还有数据要发送,所以先确认,等数据发送完再发送自己的FIN。因此,ACK和FIN分开发送,导致需要四次 。如果服务器收到FIN后没有数据要发送,那么可以将ACK和FIN合并发送,这样就变成三次挥手了。TCP协议允许这种情况,不过一般情况下,ACK和FIN是分开发的,因为应用程序可能还需要处理数据。
-
因此,四次挥手是为了保证双方都完成数据发送,并且确保对方收到关闭请求的确认。
为什么要设计TIME_WAIT状态?主要有两个原因:
-
可靠地实现TCP全双工连接的终止。
如果主动关闭方发送的最后一个ACK丢失,被动关闭方会重传FIN。这样,主动关闭方在TIME_WAIT状态下可以再次发送ACK。
如果没有TIME_WAIT状态,那么主动关闭方在发送ACK后立即关闭连接,若该ACK丢失,被动关闭方将永远收到不到ACK,导致连接无法正常关闭。
-
允许旧的重复数据包在网络中消逝,避免被新的连接误接收。
考虑一个连接关闭后,又立即在相同的IP地址和端口之间建立新的连接。旧连接的数据包可能由于网络延迟而在新连接建立后才到达。
如果没有TIME_WAIT状态,这些旧数据包可能会被新连接接收,造成数据混乱。而TIME_WAIT状态确保在连接关闭后,至少等待2MSL的时间,
使得旧连接的所有数据包都在网络中消失,这样新连接就不会收到旧数据。
为什么要设计 CLOSE_WAIT 状态?主要有两个原因:
-
保证被动关闭方能够完成剩余数据的发送,正确支持 TCP 半关闭语义。
当一端收到对方发送的 FIN 时,表示对方已经关闭了发送方向,但并不意味着本端也已经完成了数据发送。本端可能仍然有尚未发送完的业务数据需要发送给对方。如果在收到 FIN 后立即关闭连接并发送 FIN,将会导致这些尚未发送的数据被中断 ,从而破坏 TCP 全双工通信中"半关闭"的语义。CLOSE_WAIT 状态的存在,使被动关闭方在确认对端关闭之后,仍然可以继续向对端发送数据,直到应用层明确调用 close() 或 shutdown(SHUT_WR),再由内核发送 FIN 完成本端的关闭。
-
将连接最终关闭的控制权交给应用层,避免协议层替应用层做决定。
TCP 协议栈只能感知对端是否发送了 FIN,却无法判断本地应用层的业务逻辑是否已经处理完成。如果在收到 FIN 后由内核自动完成连接关闭,那么应用层将失去对连接生命周期的控制权,可能导致业务处理尚未结束就被强制终止。CLOSE_WAIT 状态的设计,使协议层在收到 FIN 后仅负责确认对端的关闭,而将"何时真正关闭连接"的决定权交由应用层,从而实现协议层与应用层的解耦,保证连接关闭过程符合实际业务需求。