引言
TCP是TCP/IP体系中非常复杂的一个协议。TCP是面向字节流的,但是传送的数据单元是报文段,一个TCP报文段分为首部和数据两部分。TCP首部的某些字段是我们必须要掌握的,在说明三次握手和四次挥手之前,有必要先了解TCP首部的字段。
TCP报文段的首部格式
TCP报文段的首部前20个字节是固定的,后面有4n个字节是可变的,其中n是整数。因此TCP报文段的最小长度是20个字节。
各字段的含义如下:
- 源端口
占两个字节,写入源端口号。 - 目标端口
占两个字节,写入目标端口。 - 序号
占4个字节,众所周知,一个字节是8个bit,四个字节32个bit,可以表示的序号范围是[0, 232-1],序号使用mod 232运算,即当序号为232-1时,下一个序号又回到0。首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号。 - 确认号
占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号。如果确认号为N,则表明到序号N-1为止的所有数据都已正确收到。 - 数据偏移
占4个bit,指出TCP报文段的首部长度。因为只有4位,所以能表示的最大的数是15,它的单位是4字节,所以TCP报文段的首部长度最长就是60字节,又因为有20字节已经固定,所以可变的长度最长就是40个字节。 - 保留
占6位,设计的时候保留的,没啥用。 - URG(urgent,紧急)
占一位,当为1时,紧急指针字段有效。它告诉系统此报文段中有紧急数据,应该紧急传送(相当于高优先级的数据),而不是按照原来的排队顺序来传送。 - ACK(acknowledgment,确认)
占一位,当为1时,确认号字段才有效。当为0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置为1。 - PSH(push,推送)
占一位,当为1时有效。当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立刻就能够接收到对方的响应。在这种情况下,TCP就可以使用推送操作。但其实很少使用。 - RST(reset,复位)
当RST为1时,表明TCP连接出现严重差错(如由于主机崩溃或者其他原因)。必须释放连接,然后再重新建立运输连接。RST置1还用来拒绝一个非法的报文段或者拒绝打开一个连接。 - SYN(synchronization,同步)
在连接建立时用来同步序号。当SYN=1并且ACK=0时,表明这是一个连接请求报文段。如果对方同意建立连接,则应在响应的报文段中使用SYN=1和ACK=1。因此SYN为1表示这是一个连接请求或者连接接受报文。 - FIN(finish,终止)
用来释放一个连接,当FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。 - 窗口
占两个字节,所以能表示的数为[0, 15],窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口)。窗口值作为接收方让发送方设置其发送窗口的依据。 - 检验和
占两个字节。检验和字段检验的范围包括首部和数据这两个部分,和UDP用户数据报一样,在计算检验和时,要在TCP报文段的前面加上12个字节的伪首部。 - 紧急字段
占两个字节,如上所述,只有在URG=1时,这个字段才有效。它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。 - 选项
长度可变,最长可达40个字节。
TCP的连接建立
TCP建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP报文段,所以也叫三次握手。其实更准确的叫三报文握手。
假定主机A运行的是TCP客户程序,B运行的是TCP服务器程序。最初两端都处于CLOSED状态。
- A主动打开连接,B被动打开连接。
- B的TCP服务器先创建TCB(传输控制块),准备接受客户进程的连接请求。然后服务器进程就处于LISTEN状态。
- A的TCP客户进程也创建TCB,向B发送请求报文段,此时SYN=1,同时选择一个初始序号seq=x。此时TCP客户进程进入SYN-SENT状态。(这部分是重点,之前面试官问过我三次握手建立连接的过程中,客户和服务器的各个阶段的状态是什么。)
- B收到请求报文段后,如果同意建立连接,会向A发送确认。SYN=1,ACK=1,seq=y,ack=x+1。同样消耗掉一个序号。这是TCP服务器进程进入SYN-RCVD状态。
- TCP客户进程收到B的确认后,还要向B给出确认。这时,TCP连接已经建立,A进入ESTABLISHED状态。
- 当B收到A的确认后,也进入ESTABLISHED状态。
为什么A最后还要发送一次确认?
或者说为什么是三次握手而不是两次握手?这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。
假定出现了这样一种异常情况,A向B发送了第一个连接请求,但是在某些网络节点长时间滞留了,以致延误到A向B发送了第二次连接请求,数据传输完毕后,连接释放以后的某个时间才到达B,本来这是一个早已时效的报文段。如果是两次握手,B认为连接已经建立,一直等待A发来数据,B的许多资源就这样白白浪费了。
TCP的连接释放
数据传输结束后,通信的双方都可以释放连接。
- A和B一开始都处于ESTABLISHED状态。
- A的应用进程先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。A把连接释放报文段首部的FIN置为1,seq=u,它等于前面已传送过的数据的最后一个字节的序号加一,这时A进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
- B收到连接释放的报文段后即发出确认,确认号是ack=u+1,而这个报文段自己的序号是v,等于B前面已传送过的数据的最后一个字节的序号加1.然后B就进入CLOSE-WAIT状态,这时从A到B这个方向的连接就释放了,这时TCP连接处于半关闭状态。
- A收到B的确认后,就进入FIN-WAIT-2状态,等待B发出的连接释放报文。
- 若B没有要向A发送的数据,其应用进程就通知TCP释放连接。这时B发出的连接释放报文段必须使用FIN=1。假定B的序号为w(在半关闭状态B可能又发送了一些数据)。B还必须重复上次已发送过的确认号ack=u+1。这时B就进入LAST-ACK(最后确认状态),等待A的确认。
- A在收到B的连接释放报文段后,必须对此发出确认。在确认报文段中把ACK置为1,确认号ack=w+1,自己的序号seq=u+1,然后进入到TIME-WAIT状态,此时TCP连接还没有释放掉,必须经过时间等待计时器设置的2MSL后,A才进入到CLOSED状态。MSL叫做最长报文段寿命(Maximum Segment Lifetime)。当A撤销相应的TCB后,就结束了这次的TCP连接
为什么A要等待2MSL?
- 保证A发送的最后一个ACK报文段能够到达B。、
因为这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到这个确认。B会超时重传FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的报文段,接着A重传一次确认,重新启动2MSL计时器。最后A和B都正常进入CLOSED状态。否则,B无法正常进入CLOSED状态。 - 防止已失效的连接请求报文段出现在本连接中。
A在发送完最后一个ACK报文段后,再经过2MSL,就可以使本链接持续的时间内所产生的的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。