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)。
TCP头格式
下图展示了TCP的头格式:
TCP头格式(图片来源)
需要注意有这么几点:
- TCP的包是没有IP地址的,那是IP层上的事。但是有源端口和目标端口。
- 一个TCP连接需要四个元组来表示是同一个连接(src_ip, src_port, dst_ip, dst_port)准确说是五元组,还有一个是协议。但因为这里只是说TCP协议,所以,这里我们只说四元组。
- 注意上图中的四个非常重要的东西:
- Sequence Number是包的序号,用来解决网络包乱序(reordering)问题。
- Acknowledgement Number就是确认号——用于确认收到,用来解决不丢包的问题。需要注意这个跟TCP Flags中的ACK(只有一位)是不一样的。只有TCP Flags中的ACK有效,这个确认号才有效。
- Window又叫Advertised-Window,也就是著名的滑动窗口(Sliding Window),用于解决流控的。
- TCP Flag ,也就是包的类型,主要是用于操控TCP的状态机的。其中常有的有ACK、SYN、FIN、PUSH、RESET。
对于TCP Flag中的标志PUSH,它的用途在于发送方用于通知接收方将所接收到的数据全部提交给接收进程。这里的数据包括与PUSH一起传送的数据以及接收方TCP已经为接收进程收到的其他数据。现在许多程序认为PUSH标志已经过时,一个好的TCP实现能够自行实现何时设置这个标志。
关于其它的东西,可以参看下面的图示
(图片来源)
TCP连接的建立和终止
三路握手
建立一个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三路握手:
上图给出的客户端的初始序列号为X,服务器的初始序列号为Y。ACK中的确认号是发送这个ACK的一端所期待的下一个序列号。因为SYN占据一个字节的序列号空间,所以每一个SYN的ACK中的确认号就是SYN的初始序列号加1。类似地,每一个FIN(finish, 表示结束)的ACK中的确认号为该FIN的序列号加1。
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个分节:
注:上图中的服务器端对客户端发送的是FIN而不是SYN。之前画错。
类似SYN,一个FIN也占据1个字节的序列号空间。因此,每个FIN的ACK确认号就是这个FIN的序列号加1。
在步骤2和步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的。这称为半关闭(half-close)。
当套接字被关闭时,其所在端TCP各自发送了一个FIN。我们在图中指出,这是由应用进程调用close而发生的,不过需要认识到,当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接端也发出一个FIN。
上图展示了客户端执行主动关闭的情形,不过,无论是客户端还是服务器,任何一端都可以执行主动关闭。通常情况是客户端执行主动关闭,但是某些协议(如HTTP/1.0)却由服务器执行主动关闭。
观察分组
下图展示了一个完整的TCP连接所发生的实际分组交换情况,包括连接建立、数据传送和连接终止3个阶段。
(图片来源)
实验
具体实验工具采用的是Wireshark,实验对象是cnBeta官网。
Wireshark与对应的OSI七层模型
Wireshark抓到的包与对应的协议层如下图所示:
1)Frame: 物理层的数据帧概况
2)Ethernet II: 数据链路层以太网帧头部信息
3)Internet Protocol Version 4: 互联网层IP包头部信息
4)Transmission Control Protocol: 传输层的数据段头部信息,此处是TCP
5)Hypertext Transfer Protocol: 应用层的信息,此处是HTTP协议
TCP包的具体内容
从下图可以看到wireshark捕获到的TCP包中的每个字段。
Wireshark使用
详细使用方法可参考博文Wireshark的使用教程--用实践的方式帮助我们理解TCP/IP中的各个协议是如何工作的。
在这里我们主要是设定:
1)捕捉过滤器
2)显示过滤器
在这里我们只想显示端口54753的数据交互:
3)显示Flow Graph
最终得到的结果:
由上图可以看出一个比较完整的TCP连接建立、数据传输以及连接终止的过程。
参考资料
《UNIX网络编程 卷1》