本文摘录自《UNIX网络编程 卷1》。
1. TCP特性
相对于不可靠、无连接的用户数据报协议(User Datagram Protocol, UDP),传输控制协议(Transmission Control Protocol, TCP)是可靠的、面向连接的协议。除此之外,TCP还提供了以下特性:
1)TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time, RTT),以便它知道等待一个确认需要多长时间。
2)TCP通过给其中每个字节关联一个序列号对所发送的数据进行排序(sequencing)。
3)TCP提供流量控制(flow control)。TCP总是告知对端在任何时刻它一次能够从对端接受多少字节的数据,这称为通知窗口(advertised window)。在任何时刻,该窗口指出接受缓冲区中当前可用的空间量,从而确保发送端发送的数据不会使接收缓冲区溢出。该窗口时刻动态变化:当接收到来自发送端的数据时,窗口大小就减小,但是当接收端应用从缓冲区中读取时,窗口大小就增大。通知窗口大小减小到0是有可能的:当TCP对应某个套接字的接收缓冲区已满,导致它必须等待应用从该缓冲区读取数据时,方能从对端再接收数据。
4)TCP连接是全双工的(full-duplex)。
2. TCP连接的建立和终止
2.1 三路握手
建立一个TCP连接时会发生下述情形:
1)服务器必须准备好接受外来的链接。这通常通过调用socket、bind和listen这3个函数来完成,我们称之为被动打开(passive open)。
2)客户端通过调用connect发起主动打开(active open)。这导致客户端TCP发送一个SYN(synchronization,同步)分节,它告诉服务器,客户端将在(待建立的)连接中发送的数据的初始序列号。通常SYN字节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。
3)服务器必须确认(acknowlegement, ACK)客户端的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送SYN和对客户端SYN的ACK(确认)。
4)客户端必须确认服务器的SYN。
下图展示了TCP三路握手:
图2-2给出的客户端的初始序列号为J,服务器的初始序列号为K。ACK中的确认号是发送这个ACK的一端所期待的下一个序列号。因为SYN占据一个字节的序列号空间,所以每一个SYN的ACK中的确认号就是SYN的初始序列号加1。类似地,每一个FIN(finish, 表示结束)的ACK中的确认号为该FIN的序列号加1。
2.2 TCP连接终止
TCP建立一个连接需要3个分节,终止一个连接则需要4个分节。
1)某个应用进程首先调用close,我们称该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
2)接收到这个FIN的对端执行被动关闭(passive clsose)。这个FIN有TCP确认。它的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
3)一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
4)接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为:某些情形下步骤1的FIN随数据一起发送;另外,步骤2和步骤3发送的分节都出自执行被动关闭的那一端,有可能被合并成一个分节。
下图展示了TCP连接终止的4个分节:
类似SYN,一个FIN也占据1个字节的序列号空间。因此,每个FIN的ACK确认号就是这个FIN的序列号加1。
在步骤2和步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的。这称为半关闭(half-close)。
当套接字被关闭时,其所在端TCP各自发送了一个FIN。我们在图中指出,这是由应用进程调用close而发生的,不过需要认识到,当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接端也发出一个FIN。
图2-3展示了客户端执行主动关闭的情形,不过我们指出,无论是客户端还是服务器,任何一端都可以执行主动关闭。通常情况是客户端执行主动关闭,但是某些协议(如HTTP/1.0)却由服务器执行主动关闭。
2.3 观察分组
图2-5展示了一个完整的TCP连接所发生的实际分组交换情况,包括连接建立、数据传送和连接终止3个阶段。