Linux TCP/IP 性能调优

再谈三次握手

  Linux 提供了listen()系统调用,它的作用是在 server socket 上监听新的连接请求:

1
int listen(int sockfd, int backlog);

  那么listen()的第二个参数backlog的作用是什么呢?让我们先回顾一下 TCP 的三次握手:


  对于 server 来说,一个新的连接首先会经过 SYN_RECV 状态,然后才会进入 ESTABLISHED 状态。那么对于每个 server socket 来说,内核需要两个队列来保存所有连接的信息:

  • 一个是半连接队列 (incompletely queue),用来保存所有处在 SYN_RECV 状态的连接。
  • 另一个是连接队列 (completely queue),用来保存所有处在 ESTABLISHED 状态的连接。

  也就是说,当一个新的连接进入 SYNC_RECV 状态时,内核会将它放到半连接队列中。而当这个连接从 SYNC_RECV 状态进入到 ESTABLISHED 状态时,内核会将它从半连接队列中移动到连接队列中。最后当accept()返回时,这个连接也会从连接队列中移走。
  listen()backlog参数其实是用来指定连接队列的长度。而如果要改变半连接队列的长度,则需要改变内核参数:

1
2
3
$ sudo vi /etc/sysctl.conf
net.ipv4.tcp_max_syn_backlog = 1024 # 半连接队列的长度
$ sudo sysctl -p # 使改变生效

  在实际情况中,如果 server 的负载比较高,无法迅速地accept()新的连接,就可能导致连接队列满了,这时候就需要调高listen()backlog参数。然而注意到,backlog参数有一个上限,这个上限由内核参数net.core.somaxconn决定,这个参数的默认值是128。也就是说,如果我们要让backlog的上限大于 128,就需要调高net.core.somaxconn这个内核参数的值:

1
2
3
$ sudo vi /etc/sysctl.conf
net.core.somaxconn = 1024 # listen backlog 的上限,默认值是 128
$ sudo sysctl -p # 使改变生效


  让我们思考一个问题,对于 server 的一个连接来说,当它接收到 client 发送的 ACK segment 之后,就会从 SYNC_RECV 进入到 ESTABLISHED 状态,但如果这时连接队列满了,那么会发生什么事情呢?
  默认情况下,server的 TCP 协议栈会丢弃这个 ACK segment。但此时这个连接仍然处在 SYNC_RECV 状态,所以 server 的 TCP 协议栈会重新发送 SYN/ACK 给 client。下图显示了进行一次重试的过程:


  最大的重传次数由内核参数net.ipv4.tcp_synack_retries规定,默认情况下,这个值是 5,重传 5 次的总耗时约为 180 秒。在某些情况下,我们可以调小这个值,以减小重传的超时时间。

参考资料