TCP是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
连接建立与终止
连接建立协议
为建立一条TCP连接的步骤:
- 客户端发送一个 SYN 段(SYN 标志置 1)指明客户端打算连接的服务器的端口,以及其初始序号 ISN。
- 服务器发回包含服务器的初始序号的 SYN-ACK 报文段(SYN、ACK标志都置 1)作为应答,确认序号设置为客户端的 ISN 加 1 以对客户端的 SYN 报文段进行确认(一个 SYN 占用一个序号)。
- 客户端必须将确认序号设置为服务器的 ISN 加 1 以对服务器的 SYN-ACK 报文段进行确认。
这三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake)。
连接终止协议
由于TCP的半关闭(half-close),终止一个连接要经过 4 次握手。TCP的连接是全双工,因此每个方向必须单独进行关闭。当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接。当一端收到一个 FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。
收到一个 FIN 只意味着在这一方向上没有数据流动,一个TCP连接在收到一个 FIN 后仍能发送数据。
当服务器收到客户端的 FIN,它发回一个 ACK,确认序号为收到的序号加 1,和 SYN 一样,一个 FIN 将占用一个序号。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,也可以双方执行主动关闭。
最大报文段长度
最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS(MSS选项只能出现在 SYN 报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值 536 字节(这个默认值允许 20 字节的IP首部和 20 字节的TCP首部以适合 576 字节的IP数据报)。
一般来说,如果没有分段发生,MSS还是越大越好,报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率。连接发起端或接收端一般将MSS值设置为外出接口上的MTU(最大传输单元)长度减去固定的IP首部和TCP首部长度。
TCP 的半关闭
TCP 的半关闭是指连接的一端在结束它的发送后还能接收来自另一端数据的能力。
半关闭使一端可以通知另一端数据传输结束,但仍然在等待对端的返回数据。
TCP 状态变迁图
2MSL等待时间
从TCP状态图可以看到,执行主动关闭后,Socket会进入一个 TIME_WAIT 状态,这个 TIME_WAIT 状态也称为 2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在 TIME_WAIT 状态停留的时间为 2倍的MSL,这样可以让TCP再次发送最后的 ACK 以防止这个 ACK 丢失(另一端超时并重发最后的 FIN)。
这种 2MSL等待的一个结果是这个TCP连接在 2MSL等待期间,定义这个连接的接口不能再被使用。大多数TCP实现强加了更为严格的限制,在 2MSL等待期间,socket中使用的本地端口在默认情况下不能再被使用。可以通过 SO_REUSEADDR
选项来允许或禁止使用处于 2MSL等待的Socket。这也是为什么需要由客户端来执行主动关闭,服务器执行被动关闭,这样服务器端不会进入 2MSL等待,因为服务器的端口是熟知的,比如 80,如果该端口处于 2MSL状态,那么在关闭后就需要等待一定时间才能重新启动应用。
FIN_WAIT_2 状态
在 FIN_WAIT_2 状态我们已经发出了 FIN,并且另一端也已对它进行确认。除非我们在实现半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一个 FIN 来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从 FIN_WAIT_2 状态进入 TIME_WAIT 状态。
这意味着我们这端可能永远保持这个状态,另一端也将处于 CLOSE_WAIT 状态,并一直保持这个状态直到应用层决定进行关闭。
复位报文段
RST 标志置 1 表示复位报文段。
到不存在的端口的连接请求
产生复位的一种常见情况是当连接请求到达时,目的端口没有进程在监听。
在复位报文段中,序号被置为 0,确认序号被置为进入的ISN加上数据字节数(由于接收到的是 SYN 报文段,占 1 个字节,所以确认序号是ISN加 1)。RST 标志置 1,ACK 标志置 0。
异常终止一个连接
终止一个连接的正常方式是一方发送 FIN,这也称为有序释放(orderly release),因为在所有排队数据都发送之后才发送 FIN,正常情况下没有任何数据丢失。
异常释放(abortive release):发送一个复位报文段而不是 FIN 来中途释放一个连接的方式。
异常终止一个连接对应用程序来说有两个有点:
- 丢弃任何待发送数据并立即发送复位报文段;
- RST的接收方会区分另一端执行的是异常关闭还是正常关闭。
Socket API 通过 “linge on close” 选项(SO_LINGER)提供了这种异常关闭的能力,这将导致连接关闭时进行复位而不是正常的 FIN。
检测半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,这样的TCP连接成为半打开(half-open)的。
同时打开
一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。
同时关闭
双方都执行主动关闭即为同时关闭(simultaneous close)。
当应用层发出关闭命令时,两端均从 ESTABLISHED 变为 FIN_WAIT_1,发送一个 FIN;收到对端的 FIN 后,状态由 FIN_WAIT_1 变迁到 CLOSING,并发送最后的 ACK,收到对端的最后 ACK 后,状态变为 TIME_WAIT。
TCP 选项
每个选项的开始是1字节的kind字段,说明选项的类型。kind字段为0和1的选项仅占1个字节。其他选项在kind字节后还有len字节,它说明的长度是指该选项的总长度,包括kind字节和len字节。
TCP 服务器设计
呼入连接请求队列
TCP接受一个连接是完成三次握手并放入连接队列,应用层接受连接是将其从该队列中移出。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。