目录
TCP与UDP的区别
TCP 三次握手
为什么需要三次握手?而不是两次
TCP 四次挥手
为什么需要等待 2MSL
为什么需要四次挥手?而不是三次
1. TCP 与 UDP 的区别
TCP(Transmission Control Protocol,传输控制协议)是面向连接的,可靠的通信协议,而 UDP(User Datagram Protocol,用户数据报协议)是无连接的,不可靠的通信协议。
它们两者都是构成网络运输层的关键协议,接下来我们通过一个表格对比二者的区别:
从表格中我们可以看出,TCP 与 UDP 最大的区别就在于可靠传输。那 TCP 的可靠传输是由什么保证的呢?一个很重要的性质就是,TCP 是面向连接的通信协议。
TCP 每次在通信之前,客户端和服务端都需要通过发送数据消息,先建立一个稳定的传输"通道",然后再进行数据传输。这个过程我们称之为"TCP 连接的三次握手"。
2. TCP 三次握手
TCP 三次握手建立连接,是 TCP 数据传输的必要过程。流程大致分为以下几步:
刚开始,客户端(Client)和服务器(Server)都处于 CLOSED 状态;
服务端创建传输控制块(TCB),时刻准备客户进程的连接请求,处于 LISTEN 监听状态;
第一次握手:客户端将 TCP 报文的标志位 SYN 置为1,随机产生一个序号值 seq=x 保存在 TCP 首部的序列号字段里,然后指明客户端打算连接的服务器端口,并将数据包发送给服务器端。发送完毕后,客户端进入 SYN-SEND 状态;
第二次握手:服务端收到数据包后,由标志位 SYN=1 确认了客户端要请求建立连接。于是,服务端将 TCP 报文的标志位 SYN 和确认应答号 ACK 都置为 1,请求号 ack = x+1(表示序列号为 x 的消息已经接收了,下一次传输的序列号为 x+1),再随机产生一个序号值 seq=y,然后将该数据包发送给客户端以确认连接请求。这时,服务端进入 SYN-RCVD 状态;
第三次握手:客户端收到服务端的确认后,检查 ack 是否为 x+1,ACK 是否为1,如果正确则将确认应答 ACK 置为 1,请求号 ack=y+1(表示序列号为 y 的消息已经接收了,下一次传输的序列号为 y+1),并将数据包发送给服务器。服务器端检查 ack 是否为 y+1,ACK 是否为 1,如果正确则成功建立连接。客服端和服务器都进入 ESTABLISHED 状态,三次握手结束,客户端和服务器可以开始传输数据了。
在上述过程中,还有一些重要的概念:
半连接:收到 SYN 包而还未收到 ACK 包时的连接状态称为半连接,即尚未完全完成三次握手的 TCP 连接,处于第一次握手之后,第三次握手之前。
半连接队列:在三次握手协议中,服务器维护一个半连接队列,该队列为每个客户端的 SYN 包开设一个条目,该条目表明服务器已收到 SYN 包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 SYN_ RECV 状态,当服务器收到客户的确认包时,删除该条目,服务器进入 ESTABLISHED 状态。
3. 为什么三次握手
如上可见,TCP 建立通信连接时,需要进行三次握手。那么,为什么两次不行呢?举一个 A-B 拨打电话的场景:
A:你好,我想和你建立通话连接,连接号为 x;
B:好的,可以建立通话连接,连接号为 x;
A:收到,连接号为 x 的连接已经建立,我们可以开始通话了。
我们可以发现,如果只有两次握手,会发生比如以下场景:
当客户端发出第一次请求连接时,由于网络节点拥堵导致服务端未收到请求的报文,这时,由于服务端没有响应,客户端可能会发送多次连接请求;
如果没有三次握手的确认,当之前传了很久的请求到达服务端时,服务器会认为这是一个新的请求,于是建立连接,但是这个 TCP 连接一直不会通信,这样,服务端的很多资源就被白白浪费掉了。
结合上面的电话拨号例子,两次连接可能出现的问题:
A:你好,我想和你建立通话连接,连接号为 x;
由于信号不稳定,这条消息发了很久 B 都没收到,于是,A 又重新发了一条,连接号为 y;
B:好的,可以建立通话连接,连接号为 y;
A:收到,连接号为 y 的连接已经建立(自然之前连接号为 x 的连接就不用了),于是开始通话。
这时,之前发送的 x 连接也发到了 B 端,如果没有三次握手,那么 B 可能会误以为这时 A 又发起了一个连接。于是,分配好资源,等待连接号为 x 的无效连接传输消息。
因此,采用三次握手建立连接可以防止上述问题的出现,当客户端收到一个已失效的建立连接确认报文时,不会向服务端进行第三次握手确认。而服务端由于收不到确认,就不会和客户端建立连接了。
总结:三次握手可以防止已失效的连接请求又传送到服务器端,导致无效连接的出现,浪费服务端资源。
4. TCP 四次挥手
当数据传输完成后,为了节省服务器的资源和网络开销,需要进行断连。可以理解为,电话打完了,需要我们手动进行挂电话的操作。
当客户端(Client,以下简称C端)和服务器(Server,以下简称S端)都是连接状态时:
第一次挥手:C 端不想再进行数据传输了,就发起一条挥手请求,将 TCP 报文的标志位 FIN=1,设置序列号 seq 为随机数 x。此时,C 端进入 FIN_WAIT_1 状态;
第二次挥手:S 端收到 C 端的 FIN 数据报,知道 C 端不再发送数据了。于是返回一条 ACK 确认消息,表示同意 C 端的关闭请求,然后 S 端进入 CLOSE_WAIT 状态。当 C 端收到 S 端的确认消息后,进入 FIN_WAIT_2 状态,等待 S 端的连接结束;
第三次挥手:S 端发送数据完毕后,给 C 端发送标志位为 FIN 的报文段,请求关闭连接,并进入 LAST_ACK 状态;
第四次挥手:当 C 端收到 FIN 报文段之后,再向 S 端回复标志位为 ACK 的应答消息,然后进入 TIME_WAIT 状态,当等待 2MSL(报文的最大存活时间,后面会详细讲解) 后还没收到回复,证明 S 端已经正常关闭,于是 C 端进入CLOSED 状态。而 S 端在收到 C 端的 ACK 报文段以后,就关闭连接,直接进入 CLOSED 状态。
和我们的电话通话不同,TCP 连接是全双工的,也就是在通信的时候允许数据在两个方向上同时传输。可以理解为是电话+电话留言,两边都需要挂电话才会结束通讯过程,整个流程可以看成是如下场景:
A:我不想说话了,挂电话了哈;
B:好的,你先挂电话,但是我还有话要说;
A:收到了挂电话的请求应答,于是把电话挂了。这时 A 除了做应答以外不能再说话(传输真实数据),但是可以听到声音(接收数据);
B:我也不想说话了,挂电话了哈;
A:好的,已收到,等待一会儿就把电话挂了;
B:收到挂电话的请求应答后,把手机关掉了。 A 过了一会儿,发现 B 确实把电话挂了,于是也关了手机,通讯结束。
5. 为什么要等待 2MSL
Max Segment Lifetime(简称 MSL),指报文的最大存活时间,它是任何报文段被丢弃前在网络内的最长时间。
从上面断连的第四次挥手阶段,我们发现客户端在收到服务端的断连请求后,还等待了 2MSL 才变为 CLOSED 状态。这是为什么呢?
1)可以保证 TCP 的全双工连接能够可靠关闭
由于 IP 协议的不可靠性或者其它网络原因,导致 S 端没有收到 C 端的 ACK 报文,那么 S 端就会在超时后重新发送 FIN,如果此时 C 端的连接已经关闭处于 CLOSED 状态,那么重发的 FIN 就找不到对应的连接了,从而导致连接错乱。
因此,C 端发送完最后的 ACK 不能直接进入 CLOSED 状态,而要保持 TIME_WAIT,等待可能重传的 FIN 报文,保证对方能收到 ACK。
2)保证此处连接的重复数据段从网络中消失
如果 C 端发送最后的 ACK 后直接进入 CLOSED 状态,然后再向 S 端发起一个新连接,这时无法保证新连接与刚关闭连接的端口号是不同的,就可能出现问题:如果前一次连接的某些数据滞留在网络中,这些延迟数据在建立新连接后到达 C 端,由于新老接口的端口号和 IP 都一样,TCP 协议就认为延迟数据是属于新连接的,新连接就会收到脏数据,导致数据包混乱。
所以,TCP 连接需要在 TIME_WAIT 状态等待 2 倍 MSL,保证本次连接的所有数据在网络中消失。
6. 为什么四次挥手
建立连接时当 Server 收到 Client 端的 SYN 连接请求时,可以直接发送带有同步标志位 SYN 和确认应答号 ACK 的报文,所以建立连接只需要三次握手。
由于 TCP 是全双工模式,这就意味着关闭连接时,当 C 端发出 FIN 报文段时,只是表示 C 端的数据已经发送完毕了,但 S 端还是可以发送数据到 C 端的。
因此,S 端很可能不会立即关闭 SOCKET,故 S 端数据发送完毕后就需要 "第四次挥手" 另外发送报文段通知 C 端去断开连接。