TCP 三次握手与四次挥手
TCP 如何保证有序性
TCP 与 UDP 的一个基本区别, TCP 是可靠通信协议, 而 UDP 是不可靠通信协议。
TCP 的可靠性含义: 接收方收到的数据是完整, 有序, 无差错的。
UDP 不可靠性含义: 接收方接收到的数据可能存在部分丢失, 顺序也不一定能保证。
UDP 和 TCP 协议都是基于同样的互联网基础设施, 且都基于 IP 协议实现, 互联网基础设施中对于数据包的发送过程是会发生丢包现象的, 为什么 TCP 就可以实现可靠传输, 而 UDP 不行?
TCP 协议为了实现可靠传输, 通信双方需要判断自己已经发送的数据包是否都被接收方收到, 如果没收到, 就需要重发。 为了实现这个需求, 很自然地就会引出序号(sequence number) 和 确认号(acknowledgement number) 的使用。
发送方在发送数据包(假设大小为 10 byte)时, 同时送上一个序号( 假设为 500),那么接收方收到这个数据包以后, 就可以回复一个确认号(510 = 500 + 10) 告诉发送方 “我已经收到了你的数据包, 你可以发送下一个数据包, 序号从 510 开始” 。
这样发送方就可以知道哪些数据被接收到,哪些数据没被接收到, 需要重发。
包的序号为什么是随机的
三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是 TCP 包的序号的问题。A 要告诉 B,我这面发起的包的序号起始是从哪个号开始的,B 同样也要告诉 A,B 发起的包的序号起始是从哪个号开始的。
为什么序号不能都从 1 开始呢?因为这样往往会出现冲突。例如,A 连上 B 之后,发送了 1、2、3 三个包,但是发送 3 的时候,中间丢了,或者绕路了,于是重新发送,后来 A 掉线了,重新连上 B 后,序号又从 1 开始,然后发送 2,但是压根没想发送 3,但是上次绕路的那个 3 又回来了,发给了 B,B 自然认为,这就是下一个包,于是发生了错误。
因而,每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的,可以看成一个 32 位的计数器,每 4 微秒加一,如果计算一下,如果到重复,需要 4 个多小时,那个绕路的包早就死翘翘了,因为我们都知道 IP 包头里面有个 TTL,也即生存时间。
三次握手
为什么要三次握手
为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
过程如下:
客户端发起链接,负载为 syn+序列号,给到服务器端
服务器端接到后返回确认信息,负载为:syn+服务器的序列号+确认号(客户端的序号+1)
客户端收到后,返回确认信息给服务端,负载为 ack +客户端序号+确认号
如果砍成二次握手会怎样?
如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。
四次挥手
因为 TCP 是全双工通道,有可能一方要关闭的时候,另一方还在传输资源,因此如果要关闭链接,需要相互进行确认。
过程如下:
主动发起方:发送关闭请求
被动关闭方:收到关闭请求,回复确认。被动关闭一方进入 CLOSE_WAIT 状态
被动关闭方:资源传输完毕后,发起关闭请求。
主动发起方:收到关闭请求,回复确认,连接彻底关闭。关闭之后,主动发起方就会处于 TIME_WAIT 状态。
服务端大量 time-wait 如何解决
背景
Linux 系统下,TCP/IP 连接断开后,会以 TIME_WAIT 状态保留 2 个 MSL 的时间,然后才会释放端口。在高并发短连接的时候,就会产生大量的 TIME_WAIT 状态的连接,无法及时断开的话,会占用大量的端口资源和文件描述符。这样会导致新的客户端无法建立连接。
为什么要设计 TIME_WAIT
防止上一次连接中的包,迷路后重新出现,影响新连接(经过 2MSL,上一次连接中所有的重复包都会消失)
可靠的关闭 TCP 连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发 fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计 TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
大量 TIME_WAIT 解决方案
解决思路很简单,就是让服务器能够快速回收和重用那些 TIME_WAIT 的资源。
应用层面
尽量避免频繁关闭连接,如业务优化,或者使用长连接等;
系统层面
缩短 MSL 时间。
增加可用端口数量。可用端口数量=单进程可打开的连接数量*机器数量。
服务端大量 CLOSE_WAIT 如何解决
被动关闭一方才会出现 CLOSE_WAIT 状态,因此如果出现大量 CLOSE_WAIT 说明是由被动关闭方未调用 close
关闭 socket 导致,问题肯定是由服务器代码引起。
评论