首先为什么要握手,其实主要就是为了初始化Seq Numer,SYN 的全称是 Synchronize Sequence Numbers,这个序号是用来保证之后传输数据的顺序性。
你要说是为了测试保证双方发送接收功能都正常,我觉得也没毛病,不过我认为重点在于同步序号。
那为什么要三次,就拿我和你这两个角色来说,首先我告诉你我的初始化序号,你听到了和我说你收到了。
然后你告诉我你的初始序号,然后我对你说我收到了。
这好像四次了?如果真的按一来一回就是四次,但是中间一步可以合在一起,就是你和我说你知道了我的初始序号的时候同时将你的初始序号告诉我。
因此四次握手就可以减到三次了。
不过你没有想过这么一种情形,我和你同时开口,一起告诉对方各自的初始序号,然后分别回应收到了,这不就是四次握手了?
我来画个图,清晰一点。
看看是不是四次握手了? 不过具体还是得看实现,有些实现可能不允许这种情况出现,但是这不影响我们思考,因为握手的重点就是同步初始序列号,这种情况也完成了同步的目标。
初始序列号 ISN 的取值不知道大家有没有想过 ISN 的值要设成什么?代码写死从零开始?
想象一下如果写死一个值,比如 0 ,那么假设已经建立好连接了,client 也发了很多包比如已经第 20 个包了,然后网络断了之后 client 重新,端口号还是之前那个,然后序列号又从 0 开始,此时服务端返回第 20 个包的ack,客户端是不是傻了?
所以 RFC793 中认为 ISN 要和一个假的时钟绑定在一起
ISN 每四微秒加一,当超过 2 的 32 次方之后又从 0 开始,要四个半小时左右发生 ISN 回绕。
所以 ISN 变成一个递增值,真实的实现还需要加一些随机值在里面,防止被不法份子猜到 ISN。
SYN 超时了怎么处理?也就是 client 发送 SYN 至 server 然后就挂了,此时 server 发送 SYN+ACK 就一直得不到回复,怎么办?
我脑海中一想到的就是重试,但是不能连续快速重试多次,你想一下,假设 client 掉线了,你总得给它点时间恢复吧,所以呢需要慢慢重试,阶梯性重试。
在 Linux 中就是默认重试 5 次,并且就是阶梯性的重试,间隔就是1s、2s、4s、8s、16s,再第五次发出之后还得等 32s 才能知道这次重试的结果,所以说总共等63s 才能断开连接。
SYN Flood 攻击你看到没 SYN 超时需要耗费服务端 63s 的时间断开连接,也就说 63s 内服务端需要保持这个资源,所以不法分子就可以构造出大量的 client 向 server 发 SYN 但就是不回 server。
使得 server 的 SYN 队列耗尽,无法处理正常的建连请求。
所以怎么办?
可以开启 tcp_syncookies,那就用不到 SYN 队列了。
SYN 队列满了之后 TCP 根据自己的 ip、端口、然后对方的 ip、端口,对方 SYN 的序号,时间戳等一波操作生成一个特殊的序号(即 cookie)发回去,如果对方是正常的 client 会把这个序号发回来,然后 server 根据这个序号建连。
或者调整 tcp_synack_retries 减少重试的次数,设置 tcp_max_syn_backlog 增加 SYN 队列数,设置 tcp_abort_on_overflow SYN 队列满了直接拒绝连接。
为什么要四次挥手?四次挥手和三次握手成双成对,同样也是 TCP 中的一线明星,让我们重温一下熟悉的图。
为什么挥手需要四次?因为 TCP 是全双工协议,也就是说双方都要关闭,每一方都向对方发送 FIN 和回应 ACK。
就像我对你说我数据发完了,然后你回复好的你收到了。然后你对我说你数据发完了,然后我向你回复我收到了。
所以看起来就是四次。
从图中可以看到主动关闭方的状态是 FIN_WAIT_1 到 FIN_WAIT_2 然后再到 TIME_WAIT,而被动关闭方是 CLOSE_WAIT 到 LAST_ACK。
四次挥手状态一定是这样变迁的吗状态一定是这样变迁的吗?让我们再来看个图。