目录
- 概述
- TCP报文段结构
- TCP连接的建立
- TCP连接的释放
- 问题解答
一、概述
TCP被称为是面向连接的,这是因为,当在一个应用进程开始向另一个应用进程发送数据之前,这两个进程必须先相互“握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。
不用赘述,大家肯定都听过TCP的三次握手、四次挥手。那么,我们现在来思考几个问题:
- TCP握手的目的是什么?
- TCP握手的详细过程是什么样的?
- 为什么TCP要进行三次握手?两次行不行?
- 为什么连接的时候是三次握手,而关闭的时候却需要四次握手?
- 在四次挥手时,为什么主动方还要等待2MSL才断开连接?
带着这几个问题,我们来详细的探讨一下TCP连接的建立和释放。
二、TCP报文段结构
在讨论连接之前,我想清楚的了解TCP报文段的结构是有必要的。
TCP报文段结构图:
TCP报文段由首部字段和一个数据字段组成。数字字段包含一块应用数据。我们重点看看首部字段。和UDP一样,TCP的首部字段也包括了源端口号和目的端口号,它被用于多路复用/分解来自或送到上层应用的数据。另外它也包含检验和字段。除了这些还有:
- 32bit 的 序号字段(Seq,sequence number field)和 32bit 的 确认号字段(acknowledgment number field)。这两个字段在传输过程中,被TCP发送方和接收方用来实现可靠数据传输服务。下一章我们将会详细讨论。
- 16bit 的 接收窗口字段(receive window field),该字段用于指示接收方愿意接收的字节数量,它用于流量控制。后面也会有相应的文章来介绍。
- 4bit 的 首部长度字段(header length field),该字段指示了以 32bit 的字为单位的TCP首部长度。图中可以看到,TCP首部还有一个 选项字段,这说明了TCP的首部长度是可变的。不过通常 选项字段为空,所以典型的TCP首部长度为20字节。
- 可选与变长 选项字段(options field),该字段用于发送方与接收方协商最大报文段长度(MSS)或在高速网络环境下用作窗口调节因子。
- 6bit的 标志字段(flag field)
- 确认ACK,仅当ACK被置为1时,确认字段才有效。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。且在连接建立后,所有传输的报文都必须把ACK置为1。
- 复位RST、同步SYN和终止FIN,用于连接建立和拆除。当RST = 1,表明TCP连接中出现了严重的差错,必须释放连接然后重新建立。同步SYN在连接的建立时用来同步序号。当SYN = 1,ACK = 0,表明是连接请求报文。若同意连接,则响应报文中应该使SYN = 1,ACK = 1。对于终止FIN,用来释放连接。当FIN = 1,表明此报文的发送方已经发送完毕,并且要求释放连接。
- CWR和ECE比特 用于拥塞控制,后面文章会讲到。
- PSH比特 被置位时,就指示接收方应立即将数据交给上层。比如,当两个应用进程进行交互式通信时,在一端的应用进程希望在键入一个命令后立即能收到响应,这时就将PSH置为1。
- URG比特 用来指示报文段里存在着被发送端的上层实体置为“紧急”的数据。
- 紧急数据的最后一个字节由16bit的 紧急数据指针字段 指出。当紧急数据存在并给出指向紧急数据尾指针时,TCP必须通知接收端的上层实体。(在实践中,PSH、URG和紧急指针并没有使用)
关于 序号字段 和 确认号字段,它们是TCP报文段中两个非常重要的字段,是实现可靠传输服务的关键部分。我们来详细的解释一下TCP的这两个字段究竟放置了什么。
TCP把数据看成是一个无结构的、有序的字节流。我们从TCP对序号的使用上可以看出这一点。因此,一个报文段的序号,是该报文段首字节的字节流编号。假设主机A上的一个进程想通过一条TCP连接向主机B上的一个进程发送一个数据流。主机A中的TCP将隐式的对数据流中的每一个字节编号。假定数据流由一个包含500,000字节的文件组成,其MSS[^MSS]为1000字节,数据流的首字节编号是0。那么如下图所示,该TCP将为该数据流构建500个报文段。给第一个报文段分配序号0,第二个分配序号1000,以此类推。每一个序号被填入到相应的TCP报文段首部的序号字段中。
我们再来考虑一下确认号。确认号是干嘛的呢?它用来表示发送方期望收到的下一个字节的序号。假如主机A和主机B通信,那么A填充进报文段的确认号是主机A希望从主机B收到的下一个字节的序号。
三、TCP连接的建立(TCP的三次握手)
了解了TCP报文段的结构之后,我们再来看看TCP是如何建立连接的。
我们都知道,TCP在建立连接时需要“三次握手”。那么这三次握手的目的是什么呢?—— 连接服务器指定的端口,同步连接双方的序号和确认号并交换TCP窗口大小信息。
前文中提到,在socket编程中,客户端执行clientSocket.connect((serverName, serverPort))
时就将触发TCP连接完成三次握手。
下面详细看看三次握手的过程:
第一次握手:客户端向服务器发送一个特殊的TCP报文段,连接请求报文段(也叫SYN包)。在这个报文段的首部中,目标服务器端口号被填充进目标端口字段,同时序号字段被初始化(假设序号为X)且标志位SYN被置为1。也就是SYN = 1,ACK = 0,Seq = X。客户端发送请求报文后进入SYN_SENT状态。
第二次握手:服务器返回确认包进行应答(也叫ACK确认包)。返回包的首部中,序号字段也被初始化(假设为Y),确认号字段被填充为希望客户端发送的下一个字节的序号,即 X + 1,同时标志位SYN和ACK都被置为1。也就是SYN = 1,ACK = 1,Seq = Y,确认号ack = X + 1。此时服务器端进入SYN_RECV状态。
第三次握手:客户端再次发送确认包。同样的,除了设置发送序号字段,还将服务器发送过来的序号Y进行加1然后填充到字段确认号中,同时标志位ACK为1,SYN重新被置为0。即ACK = 1,SYN = 0,Seq = Z,ack = Y + 1。此时,客户端进入ESTABLISHED状态。
注意,TCP规定,前两个报文段不承载“有效载荷”,也就是不包含应用层数据,但需要消耗掉一个序号;而第三个报文段可以承载有效载荷,但是如果不承载有效载荷则不消耗序号。此时,进行了三次握手后,客户端和服务器就建立起了连接。
解释一下,有些人可能会奇怪,为啥这样就说“建立起连接了”?其实这里的“连接”是一条逻辑上的连接,表现为客户端和服务器端这两个端系统的TCP程序中保存了共同的状态。
四、TCP连接的释放(TCP的四次挥手)
既然TCP是面向连接的,那么在结束通信之前,释放连接是必要的。
TCP连接的关闭需要发送四个报文段,因此被称为四次挥手。客户端和服务器端都能主动的发出挥手动作,在socket编程中,任何一方执行close()操作都能触发。
四次挥手的详细过程:
第一次:主动方发送连接关闭报文(FIN报文),报文的标志位FIN被置为1,确认号字段被填充(假设为Z,前面传过来报文段的序号加1),且初始化序号字段为X。即FIN = 1,ack = Z,Seq = X。此时,主动方进入FIN_WAIT_1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据也要消耗一个序号。
第二次:被动方收到释放报文后发送确认报文,序号字段填充为V,且确认号为填充为X + 1。即ACK = 1,ack = Z + 1,Seq = V。此时被动方进入CLOSE_WAIT(关闭等待)状态,TCP通知应用层进程,主动方的发送数据方向已经关闭了。这种状态被称为半关闭状态,表现为,主动方虽然不再发送带有有效载荷的数据,但是仍然接收数据。这个状态会持续一段时间,也就是整个CLOSE_WAIT状态持续的时间。
主动方收到被动方的确认报文后进入FIN_WAIT_2(终止等待2)状态,等待被动方发送连接释放报文。(在这之前还需要接收来自被动方的数据)
第三次:被动方将最后的数据发送完毕后,向主动方发送连接释放报文(FIN报文),标志位FIN被置为1,确认号字段、序号字段分别被填充为X、Y。即FIN = 1,ack = Z + 1,Seq = W。此时被动方进入LAST_ACK(最后确认)状态,等待主动方确认。
第四次:主动方收到被动方发出的确认报文后必须发出确认,ACK = 1,ack = W + 1,Seq = Z + 1。此时主动方进入TIME_WAIT(时间等待)状态。注意,此时TCP连接还没有释放,必须经过2 * MSL(最长报文寿命)的时间,当主动方撤销响应的进程后才进入CLOSED状态。
被动方只要收到了主动方发出的确认,立即进入CLOSED状态。若没有发生丢包,我们会发现,被动方进入CLOSED状态比主动方要早一些。
五、问题解答
- 通过同步请求发送端和请求接收端的状态信息来建立逻辑连接。
- 如上所述
- 主要是防止已经失效的连接请求报文又被传送到了服务器,导致连接重新建立,引起资源的浪费。假设,我们使用两次握手就建立连接,主机A向主机B发送连接请求报文,而由于网络原因主机A迟迟未接收到确认报文,认为服务器没有收到报文,重新向服务器发送了请求报文。经过握手,完成连接的建立,传输数据然后关闭连接。这时,如果主机B又收到了之前A发送的连接请求报文又会认为连接已经建立,等待接收数据,造成不必要的错误和资源的浪费。
- 文中已经有提到。当被动方接收到释放连接报文时,它还需要将还没有传完的数据继续传完(如果有的话),然后再发送FIN报文告知主动方,数据发完了,我也要准备释放连接了。
- MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。它的作用,第一,保证主动发送端发送的最后一个ACK报文能够到达被动端。当被动方发送FIN + ACK报文请求后,一段时间没有接收到确认报文时又会重新发送一次报文。这个时候主动方就会知道,刚刚我发送出去的确认报文对方没有收到,于是又发送一次确认报文,并且重启2MSL计时器。
[^MSS]:MSS(Maximum Segment Size)最大报文段长度。它通常根据最初确定的本地发送主机发送的最大链路层帧长度(最大传输单元,MTU)来设置。设置MSS要保证一个TCP报文段(当封装在一个IP数据报中)加上TCP/IP首部长度(20 + 20,通常40个字节)适合单个链路层帧。以太网和PPP链路层协议都具有1500字节的MTU,因此MSS的经典值为1460字节。
参考文章:
1.TCP的三次握手与四次挥手(详解+动图)
2.TCP三次握手四次挥手详解
以上内容整理自《计算机网络自顶向下方法》第七版。有问题?发送 issues 给我~