三次握手异常分析

175 阅读3分钟

一、客户端 connect 异常

  • 出现的原因:端口号不足

  • 客户端端口选取的方案:

    • 首先选定一个随机数,然后从随机的位置开始向后遍历,直到找到一个可用的端口,就进行返回
    • 如果端口很充足,那么循环只需要执行少数几次就可以退出。但假设说端口消耗掉很多已经不充足,或者干脆就没有可用的了。那么这个循环就得执行很多遍。

这是线上截取到的正常时的 connect 系统调用耗时,是 22 us(微秒)。 图片

这个是我们一台服务器在端口不足情况下 connect 开销,是 2581 us(微秒)。\

图片

从上两张图中可以看出,异常情况下的 connect 耗时是正常情况下的 100 多倍。虽然换算成毫秒只有 2 ms 多一点,但是要知道这消耗的全是 CPU 时间。

二、第一次握手丢包

2.1 半连接队列满了

  • 半连接队列底层是一个 HashMap
  • 如果半连接队列满了,可用通过设定 ipv4.tcp_syncookies 的参数进行处理
    • 1:通过 SYN Cookie 进行处理
    • 0:直接丢弃

SYN Cookie :在TCP服务器接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区(半连接队列),而是根据这个SYN包计算出一个cookie值。这个cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接。

2.2 全连接队列满了

  • 当第一次握手的收获,如果半连接队列没有慢,当时全连接队列已经满了,也会自动将包进行对其

2.3 客户端重试

  • 当全连接队列或者半连接队列满了,服务器如果选择将包自动丢弃,那么当超过时间限制的收获,客户端将会发起重试
  • 需要注意的是,重试 下一次的超时时间是上一次超时时间的两倍

图片

  • 客户端在 1 s 以后进行了第一次握手重试。重试仍然没有响应,那么接下来依次又分别在 3 s、7 s 15 s,31 s,63 s 等时间共重试了 6 次(我的 tcp_syn_retries 当时设置是 6)。

第三次握手丢包

  • 第三次握手时,如果服务器全连接队列满了,来自客户端的 ack 握手包又被直接丢弃了

  • 服务器等到半连接定时器到时后,向客户端重新发起 synack ,客户端收到后再重新回复第三次握手 ack。如果这期间服务器端全连接队列一直都是满的,那么服务器重试 5 次(受内核参数 net.ipv4.tcp_synack_retries 控制)后就放弃了。