三次握手过程
1 客户端的协议栈向服务器端发送了SYN包,并告诉服务器端当前发送序列号j,客户端进入SYNC_SENT状态;
2 服务器端的协议栈收到这个包之后,和客户端进行ACK应答,应答的值为j+1,表示对SYN包j的确认,同时服务器也发送一个SYN包,告诉客户端当前我的发送序列号
为k,服务器端进入SYNC_RCVD状态;
3 客户端协议栈收到ACK之后,使得应用程序从connect调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为ESTABLISHED,同时客户端协议栈也
会对服务器端的SYN包进行应答,应答数据为k+1;
应答包到达服务器端后,服务器端协议栈使得accept阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入ESTABLISHED状态。
形象一点的比喻是这样的,有A和B想进行通话:
A先对B说:“喂,你在么?我在的,我的口令是j。”
B收到之后大声回答:“我收到你的口令j并准备好了,你准备好了吗?我的口令是k。”
A收到之后也大声回答:“我收到你的口令k并准备好了,我们开始吧。”
可以看到,这样的应答过程总共进行了三次,这就是TCP连接建立之所以被叫为“三次握手”的原因了。
UDP
我们可以给 UDP 找一个类似的例子,这个例子就是邮寄明信片。在这个例子中,发信方在明信片中填上了接收方的地址和邮编,投递到邮局的邮筒之后,就可以不管
了。发信方也可以给这个接收方再邮寄第二张、第三张,甚至是第四张明信片,但是这几张明信片之间是没有任何关系的,他们的到达顺序也是不保证的,有可能最后寄出的
第四张明信片最先到达接收者的手中,因为没有序号,接收者也不知道这是第四张寄出的明信片;而且,即使接收方没有收到明信片,也没有办法重新邮寄一遍该明信片。
TCP 是一个面向连接的协议,TCP 在 IP 报文的基础上,增加了诸如重传、确认、有序传输、拥塞控制等能力,通信的双方是在一个确定的上下文中工作的。
而 UDP 则不同,UDP 没有这样一个确定的上下文,它是一个不可靠的通信协议,没有重传和确认,没有有序控制,也没有拥塞控制。我们可以简单地理解为,在 IP 报
文的基础上,UDP 增加的能力有限。
UDP 不保证报文的有效传递,不保证报文的有序,也就是说使用 UDP 的时候,我们需要做好丢包、重传、报文组装等工作。
四次挥手
![](https://i-blog.csdnimg.cn/blog_migrate/209ae24caf7a36e66322eb5ee7b72dcd.png)
首先,一方应用程序调用 close,我们称该方为主动关闭方,该端的 TCP 发送一个 FIN 包,表示需要关闭连接。之后主动关闭方进入 FIN_WAIT_1 状态。
接着,接收到这个 FIN 包的对端执行被动关闭。这个 FIN 由 TCP 协议栈处理,我们知道,TCP 协议栈为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可
以通过 read 调用来感知这个 FIN 包。一定要注意,这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着接收端应用程序需要处理这种异常情况,因为 EOF
表示在该连接上再无额外数据到达。此时,被动关闭方进入 CLOSE_WAIT 状态。
接下来,被动关闭方将读到这个 EOF,于是,应用程序也调用 close 关闭它的套接字,这导致它的 TCP 也发送一个 FIN 包。这样,被动关闭方将进入 LAST_ACK 状态。
最终,主动关闭方接收到对方的 FIN 包,并确认这个 FIN 包。主动关闭方进入 TIME_WAIT 状态,而接收到 ACK 的被动关闭方则进入 CLOSED 状态。进过 2MSL 时间
之后,主动关闭方也进入 CLOSED 状态。
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
当然,这中间使用 shutdown,执行一端到另一端的半关闭也是可以的。
TIME_WAIT
TCP 连接终止时,主机 1 先发送 FIN 报文,主机 2 进入 CLOSE_WAIT 状态,并发送一个 ACK 应答,同时,主机 2 通过 read 调用获得 EOF,并将此结果通知应用程
序进行主动关闭操作,发送 FIN 报文。主机 1 在接收到 FIN 报文后发送 ACK 应答,此时主机 1 进入 TIME_WAIT 状态。
主机 1 在 TIME_WAIT 停留持续时间是固定的,是最长分节生命期 MSL(maximum segment lifetime)的两倍,一般称之为 2MSL。和大多数 BSD 派生的系统一样,
Linux 系统里有一个硬编码的字段,名称为TCP_TIMEWAIT_LEN,其值为 60 秒。也就是说,Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
过了这个时间之后,主机 1 就进入 CLOSED 状态。只有发起连接终止的一方会进入 TIME_WAIT 状态。
time_wait 的作用
为什么不直接进入 CLOSED 状态,而要停留在 TIME_WAIT 这个状态
首先,这样做是为了确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
TCP 在设计的时候,做了充分的容错性设计,比如,TCP 假设报文会出错,需要重传。在这里,如果图中主机 1 的 ACK 报文没有传输成功,那么主机 2 就会重新发送 FIN 报文。
如果主机 1 没有维护 TIME_WAIT 状态,而直接进入 CLOSED 状态,它就失去了当前状态的上下文,只能回复一个 RST 操作,从而导致被动关闭方出现错误。
现在主机 1 知道自己处于 TIME_WAIT 的状态,就可以在接收到 FIN 报文之后,重新发出一个 ACK 报文,使得主机 2 可以进入正常的 CLOSED 状态。
time_wait 的危害
过多的 TIME_WAIT 的主要危害有两种。
第一是内存资源占用,这个目前看来不是太严重,基本可以忽略。
第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过
net.ipv4.ip_local_port_range指定,如果 TIME_WAIT 状态过多,会导致无法创建新连接。这个也是我们在一开始讲到的那个例子。