深入理解TCP三次握手与四次挥手

一、TCP首部结构详解

TCP数据封装在一个IP数据报中,下图是TCP报文数据格式。TCP首部如果不计选项和填充字段,它通常是20个字节。

其中比较重要的字段:

(0)源端口和目的端口:各自占用2字节,16位,可以表示的最大端口号是2^16=65535,这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。有时一个IP地址和一个端口号也称为socket(插口) (1)序列号seq:占4字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

(2)确认号ack:占4字节,表示期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

(3)标志位,共6个:ACK、SYN、FIN、URG、PSH、RST。重点了解前3个标志

  • ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
  • SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
  • FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放连接
  • URG:当URG=1时,注解此报文应尽快传送,而不要按本来的列队次序来传送。与“紧急指针”字段共同应用,紧急指针指出在本报文段中的紧急数据的最后一个字节的序号,使接管方可以知道紧急数据共有多长;
  • PSH:当PSH=1时,接收方应该尽快将本报文段立即传送给其应用层。
  • RST:当RST=1时,表示出现连接错误,必须释放连接,然后再重建传输连接。复位比特还用来拒绝一个不法的报文段或拒绝打开一个连接;

(4)窗口:TCP通过滑动窗口的概念来进行流量控制,可以理解成接收端所能提供的缓冲区大小。TCP利用一个滑动的窗口来告诉发送端对它所发送的数据能提供多大的缓冲区。窗口大小为字节数,起始于确认序号字段指明的值(这个值是接收端正期望接收的字节)。窗口大小是一个16bit字段,因而窗口大小最大为65535字节。

(5)校验和:检验和覆盖了整个TCP报文段:TCP首部和数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。

(6)紧急指针:只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。

二、TCP建立socket通道的过程(TCP三次握手的过程)

TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake),过程如下图所示。

注:握手之前,主动打开的客户端结束CLOSED状态,被动打开的服务器进入到监听(LISTEN)状态

  • 第一次握手:建立连接,客户端向服务器发送一个SYN数据包(syn=x,随机值),之后客户端进入SYN_SEND(同步已发送)状态,等待服务器确认;

  • 第二次握手:服务器收到客户端的SYN数据包,读取发现SYN=1,表示客户端要求建立连接,于是服务器向客户端发送SYN+ACK数据包:SYN=1,ACK=1,ack=x+1,seq=y(随机值) 表示"确认客户端的序列号有效,服务器能正常接收客户端的消息,并同意创建连接"。之后服务器进入SYN_RECVD(同步收到)状态。

  • 第三次握手:客户端收到服务器的SYN+ACK数据包之后,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器就进入到EATABLISHED(连接建立)状态,TCP三次握手成功,之后就可以传输数据了。

三次握手动态过程演示:

三、TCP连接终止(TCP四次挥手,Four-Way Wavehand)

建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的,如图:

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN标志来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  • 客户端发送一个FIN,用来关闭和客户端到服务器的数据传输。此过程发送的报文是:FIN=1,seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),发送之后客户端进入FIN-WAIT-1(终止等待1)状态。

  • 服务器收到连接释放的报文之后,返回确认报文,ACK=1,ack=u+1,并带上自己的序列号seq=v(随机值),此后服务器就进入到CLOSE-WAIT(关闭等待)状态。(此时客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。)

  • 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文FIN,不过在这之前服务器发来的细小客户端还是可以正常接收的。

  • 终于,服务器发送完数据之后,它向客户端发送一个连接释放报文:FIN=1,ACK=1,ack=u+1,发送完之后服务器处于LAST-WAIT(最后确认)状态。

  • 客户端收到服务器的连接释放报文后,发出确认:ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

  • 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。一般服务器结束TCP连接的时间要比客户端早一些。

四、关于TCP三次握手以及四次挥手过程的一些思考

1、为什么建立连接是三次握手,但是关闭连接需要四次挥手?

因为在建立连接的时候,当客户端向服务器发送SYN数据包,请求建立连接被服务器接收到之后,服务器可以立即发送SYN+ACK报文同意并证明自己可以正常收发数据了(SYN用于同步,ACK用于确认应答,表示同意建立连接)。但是当关闭连接的时候,当客户端向服务器发送FIN报文之后,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,"你发的FIN报文我收到了"。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

说的通俗点就是:一方发送FIN只表示自己发完了数据,但是对方有可能还需要发送数据,此时另一方就不能立即回应FIN,而是需要等到把数据发完了再回应FIN。建立连接的过程不存在这些问题,只要可以正常收到数据包并作出正确回应,就可以立即建立连接。

2、建立连接为什么需要三次握手?两次或四次不行吗?

先说明为什么不能两次?

TCP有一个超时重传机制,如果客户端发出SYN包之后,由于网络原因,服务器没能立即响应SYN+ACK包,那么client会再次发起syn包, 这一点, 已经有过多次实验。

如果第二次syn包正常达到且与server端建立了tcp连接, server端维护了一个连接, 一次貌似OK, 但别忘了, 第一次那个syn包可能就在此时达到server端了, 于是server端又要维护一个连接, 而这个连接是无效的, 可以认为是死连接。 而一个进程打开的socket是有限度的, 维护这些死连接非常耗费资源。

所以, 二次握手, 服务端有较大隐患, 容易因为资源耗尽而崩溃

而三次握手既可以解决这个问题,经过三次握手客户端和服务器双方都可以确认对方具有收发数据的能力,从而不会存在无效连接。

为什么不采用四次、五次或更多次?

这个问题就更简单了,三次握手就可以确认客户端和服务器双方都可以确认对方具有收发数据的能力了,再增加握手, 并不能显著提高可靠性, 而且也没有必要。

3、在关闭阶段,为什么客户端要经历一个2MSL的TIME-WAIT阶段?

虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是当网络不可靠的时候,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

具体的工作原理就是:

客户端会在发送出ACK之后进入到TIME_WAIT状态。客户端会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么客户端会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个报文片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。

4、如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP设值有一个保活计时器(显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源)。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75s发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

留言区

还能输入500个字符