1. TCP 之11种状态变迁
TCP 为一个连接定义了 11 种状态,并且 TCP 规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态。如,当某个应用进程在 CLOSED 状态下执行主动打开时,TCP 将发送一个 SYN,且新的状态是 SYN_SENT。如果这个 TCP 接着接收到一个带 ACK 的 SYN,它将发送一个 ACK,且新的状态是 ESTABLISHED。这个最终状态是绝大多数数据传送发送的状态。
自 ESTABLISHED 状态引出的两个箭头处理连接的终止。如果某个应用进程在接收到一个 FIN 之前调用 close(主动关闭),那就转换到 FIN_WAIT_1 状态。但如果某个应用进程在 ESTABLISHED 状态期间接收到一个 FIN(被动关闭),那就转换到 CLOSE_WAIT 状态。
TCP 的状态变迁图 1
1.1 观察分组
如下图示例中的客户通告一个值为 536 的 MSS(表示该客户只实现了最小重组缓冲区大小),服务通告一个值为 1460 的 MSS(以太网上 IPv4 的典型值)。不同方向上 MSS 值不相同不成问题。
TCP 连接的分组交换图 2
如上图,一旦建立一个连接,客户就构造一个请求并发送给服务器。这里假设该请求适合于单个 TCP 分节(即请求大小小于服务器通告的值为 1460 字节的 MSS)。服务器处理该请求并发送一个应答,同样假设该应答也适合于单个分节(本例即小于 536 字节)。如上图的粗箭头所示。注意,服务器对客户请求的确认是伴随其应答发送的。这种做法称为捎带(piggybacking),它通常在服务器处理请求并产生应答的时间少于 200ms 时发生。如果服务器耗用更长时间,譬如说 1s,那么我们将看到先是确认后是应答。
1.2 TIME_WAIT 状态
TIME_WAIT 状态也称为 2MSL 等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL 字段。
注:RFC 793[Postel 1981c] 指出 MSL 为 2 分钟。然而,实现中的常用值是 30 秒,1 分钟,或 2 分钟。
对一个具体实现所给定的 MSL 值,处理的原则是:当 TCP 执行一个主动关闭,并发回最后一个 ACK,该连接必须在 TIME_WAIT 状态停留的时间为 2 倍的 MSL。这样可让 TCP 再次发送最后的 ACK 以防这个 ACK 丢失(另一端超时并重发最后的 FIN)。
这种 2MSL 等待的另一个结果是这个 TCP 连接在 2MSL 等待期间,定义这个连接的 socket(客户的 IP 地址和端口号,服务器的 IP 地址和端口号)不能再被使用。这个连接只能在 2MSL 结束后才能再被使用。
大多数 TCP 实现(如伯克利版)强加了更加严格的限制。在 2MSL 等待期间,socket 中使用的本地端口在默认情况下不能再被使用。
某些实现和 API 提供了一种避开这个限制的方法。可使用 setsockopt API 对该套接字设置 SO_REUSEADDR 属性,该属性可让处于 2MSL 等待的本地端口再次被使用。
TIME_WAIT 状态有两个存在的理由:
- 可靠地实现 TCP 全双工连接的终止;
- 允许老的重复分节在网络中消逝。
第一个理由的解释:
如上图 2,假设最终的 ACK 丢失了,服务器将重新发送它的最终的那个 FIN,因此客户必须维护状态信息,以允许它重新发送最终那个 ACK。要是客户不维护状态信息,它将响应一个 RST(另外一种类型的 TCP 分节),该分节将被服务器解释成一个错误。如果 TCP 打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须正确处理连接终止序列 4 个分节中任何一个分节丢失的情况。本例也说明为什么执行主动关闭的那一端是处于 TIME_WAIT 状态的那一端:因为可能不得不重传最终那个 ACK 的就是那一端。
第二个理由的解释:
假设在 12.106.32.254 的 1500 端口和 206.168.112.219 的 21 端口之间有一个 TCP 连接。我们关闭这个连接,过一段时间后在相同的 IP 地址和端口之间建立另一个连接。后一个连接称为前一个连接的化身,因为它们的 IP 地址和端口号都相同。TCP 必须防止来自某个连接的老的重复分组在该连接已终止后再现,从而被误解成属于同一个连接的某个新的化身。为做到这一点,TCP 将不给处于 TIME_WAIT 状态的连接发起新的化身。既然 TIME_WAIT 状态的持续时间是 MSL 的 2 倍,这就足以让某个方向上的分组最多存活 MSL 秒即被丢弃,另一个方向上的应答最多存活 MSL 秒也被丢弃。通过实施这个规则,我们就能保证每成功建立一个 TCP 连接时,来自该连接先前化身的重复分组都已在网络中消逝。
1.3 半连接状态: FIN_WAIT_2
若服务器发送了一个 FIN 分节给客户端,并接收到客户端回复的 ACK,但是客户端没有发送 FIN 该服务器,此时导致服务器停留在半连接状态(FIN_WAIT_2),客户端也将处于 CLOSE_WAIT 状态。
1.3.1 close 和 shutdown 的区别
- close 把描述符的引用计数减 1,仅在该计数变为 0 时才关闭套接字;
- close 终止了数据传输的两个方向;
- shutdown 可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向;
- shutdown how = 1 就可以保证对方接收到一个 EOF 字符,而不管其他进程是否打开了套接字。而 close 不能保证,直到套接字引用计数值减为 0 时才会发送 EOF。也就是说直到所有的进程都关闭套接字。
1.3.2 shutdown 函数
int shutdown(int sockfd, int how);
how 的有如下取值:
- SHUT_RD:关闭连接的读这一半。套接字中不再有数据可接收,而且套接字接收缓冲区的现有数据都被丢弃。进程不能再对这样的套接字调用任何的读函数。
- SHUT_WR:关闭连接的写这一半。对于 TCP 套接字,这称为半关闭。但以前留在套接字发送缓冲区中的数据将被发送掉,后跟 TCP 的正常终止序列。
- SHUT_RDWR:连接的读半部和写半部都关闭。这与调用 shutdown 两次等效:第一次调用指定 SHUT_RD,第二次调用指定 SHUT_WR。
原文地址:https://www.cnblogs.com/jimodetiantang/p/9093808.html