关于backlog参数的疑惑

致敬 W.Richard Stevens

零. 背景知识

如果读者对以下知识点有基本的了解,读起来会很轻松,就像是在大学做物理实验。

  • Linux
  • C/Python/Java
  • TCP/IP

一. 系统调用listen

大多数同学第一次接触到backlog参数,是从listen函数开始的。
下面是关于listen函数的man手册摘要

int listen(int sockfd, int backlog);
...

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. 
 
If a connection request arrives  when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

这段文档中有两个地方需要思考:

  1. the queue of pending connections
    可以有两种解释

    a. 已经完成三次握手的连接(ESTABLISHED)
    b. 已经完成三次握手的连接(ESTABLISHED) + 未完成三次握手的连接(SYN-RECEIVED)

    这两种解释可能都是对的,因为不同的操作系统有不同的实现。
    对现代Linux来说,这里的队列特指已经完成三次握手的连接

  2. 当队列已满的时候,直接返回reset是否合适(ECONNREFUSED)
    这样处理有个坏处,就是客户端无法区别是未处理队列满了,还是访问了系统未监听的端口,
    所以还是直接忽略这个SYN比较好。

本文档聚焦在第一个问题。

二. 内核参数

有两个内核参数,涉及到TCP三次握手的队列长度配置

  • net.core.somaxconn
    [ESTABLISHED] 已完成连接队列
    web 应用中 listen 函数的 backlog 默认会被我们内核参数net.core.somaxconn 限制到128

  • tcp_max_syn_backlog
    [SYN-RECEIVED] 未完成连接队列
    记录的那些尚未收到客户端确认信息的连接请求的最大值。

我们用两个实验来验证一下这两个参数。

三. 实验环境

两台虚拟机

  • 172.28.32.101 [服务端]
  • 172.28.32.102 [客户端]

服务端代码 serv.py

#!/usr/bin/python

import socket
import sys
import time

HOST = '0.0.0.0'   # Symbolic name meaning all available interfaces
PORT = 1234 # Arbitrary non-privileged port
BACKLOG = 2

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(BACKLOG)
print 'Socket now listening'

time.sleep(3600)

不调用accept函数,已完成三次握手的连接可以保持在队列中

客户端代码 cli.py

#!/usr/bin/python

import socket   #for sockets
import sys  #for exit
import time

def conn(host, port):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error, msg:
        print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
        sys.exit();

    s.connect((host , port))
    print "%s" %(time.time()) + ' Socket Connected to ' + host + ' on ip ' + host

    return s

def main():
    host = '172.28.32.101'
    port = 1234
    socks = []

    while True:
        sock = conn(host, port)
        socks.append(sock)
        time.sleep(1)

if __name__ == '__main__':
   main()

客户端程序,每隔10s钟,创建一个socket,并连接到服务端。

四. 实验一

1. 运行服务端程序

2. @服务端 查看网络连接

State      Recv-Q Send-Q                                  Local Address:Port                                                 Peer Address:Port
LISTEN     0      2                                                   *:1234                                                            *:*

backlog正确设置为2

3. @服务端 修改服务端代码

BACKLOG = 512

4. 运行服务端程序

5. @服务端 查看网络连接

State      Recv-Q Send-Q                                  Local Address:Port                                                 Peer Address:Port
LISTEN     0      128                                                 *:1234                                                            *:*

backlog设置为128,这并不是我们期望的

再看另外一个内核参数

[root@vagrant-172-28-32-101 ~]# sysctl net.core.somaxconn
net.core.somaxconn = 128

是的,backlog 默认会被我们内核参数net.core.somaxconn 限制

6. @服务端 修改内核参数

echo 1024 > /proc/sys/net/core/somaxconn

7. 重新运行服务端程序

8. @服务端 查看网络连接

State      Recv-Q Send-Q                                  Local Address:Port                                                 Peer Address:Port
LISTEN     0      512                                                 *:1234                                                            *:*

这次,backlog被正确设置为512啦

背景知识

  • Recv-Q
    • Established: The count of bytes not copied by the user program connected to this socket.
    • Listening: Since Kernel 2.6.18 this column contains the current syn backlog.
  • Send-Q
    • Established: The count of bytes not acknowledged by the remote host.
    • Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.

五. 实验二

1. @服务端 运行服务端程序

[root@vagrant-172-28-32-101 ~]# ./serv.py
Socket created
Socket bind complete
Socket now listening

再打开另一个终端查看

[root@vagrant-172-28-32-101 ~]# netstat -an|grep 1234
tcp        0      0 0.0.0.0:1234            0.0.0.0:*               LISTEN

很明显,端口1234已经被监听

2. @客户端 运行客户端程序

[root@vagrant-172-28-32-102 ~]# ./cli.py
Socket Created
Socket Connected to 172.28.32.101 on ip 172.28.32.101
Socket Created
Socket Connected to 172.28.32.101 on ip 172.28.32.101
Socket Created

3. @服务端 查看连接状态

[root@vagrant-172-28-32-101 ~]# netstat -ant |grep 1234
tcp        3      0 0.0.0.0:1234            0.0.0.0:*               LISTEN
tcp        0      0 172.28.32.101:1234      172.28.32.102:38254     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:38252     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:38246     ESTABLISHED
tcp        0      0 172.28.32.101:1234      172.28.32.102:38250     ESTABLISHED
tcp        0      0 172.28.32.101:1234      172.28.32.102:38248     ESTABLISHED

很明显,端口1234只有3个已完成连接,后端连接都停留在SYN_RECV。

Q:为什么是3个ESTABLISHED连接,而不是两个呢?
A:因为Linux实现的是队列长度=backlog+1
参见 《UNIX网络编程》第四章第五节 listen函数

6. backlog队列满了之后,系统怎么处理后续连接请求

服务端运行抓包命令(抓包结果删除部分无用信息)

[root@vagrant-172-28-32-101 ~]# tcpdump -i any port 1234 -nnn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes

18:30:46 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [S], seq 3272162761
18:30:46 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166
18:30:46 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1, win 229 
18:30:47 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166 
18:30:47 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1
18:30:49 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166
18:30:49 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1
18:30:54 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166
18:30:54 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1
18:31:02 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166
18:31:02 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1
18:31:18 IP 172.28.32.101.1234 > 172.28.32.102.42804: Flags [S.], seq 1934414166
18:31:18 IP 172.28.32.102.42804 > 172.28.32.101.1234: Flags [.], ack 1

服务端忽略了客户端返回的ack信息,然后重新发送了6次syn+ack,
连接处于SYN_RECV,一段时间后,自动删除

7. @服务端 查看连接状态

[root@vagrant-172-28-32-102 ~]# netstat -ant|grep 42804
tcp        0      0 172.28.32.102:42804     172.28.32.101:1234      ESTABLISHED

可见,在客户端看来,连接已经建立完成。这中情况下,就可能造成服务端和客户端连接状态不一致的情况。

4. @客户端 中断客户端程序,然后再重启

5. @服务端 查看连接状态

[root@vagrant-172-28-32-101 ~]# netstat -ant |grep 1234
tcp        3      0 0.0.0.0:1234            0.0.0.0:*               LISTEN
tcp        0      0 172.28.32.101:1234      172.28.32.102:42172     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42178     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42164     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42182     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42176     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42174     SYN_RECV
tcp        0      0 172.28.32.101:1234      172.28.32.102:42180     SYN_RECV
tcp        1      0 172.28.32.101:1234      172.28.32.102:42158     CLOSE_WAIT
tcp        1      0 172.28.32.101:1234      172.28.32.102:42160     CLOSE_WAIT
tcp        1      0 172.28.32.101:1234      172.28.32.102:42162     CLOSE_WAIT

有3个连接处于CLOSE_WAIT,新连接已经无法建立,说明已完成连接包含状态CLOSE_WAIT。
这个CLOSE_WAIT的产生条件,建立连接后,没有被accept关联到文件描述符后,客户端异常关闭

六. 实验三

tcp_max_syn_backlog, 这个参数很容易和listen函数的backlog混淆。看一下官方解释

Maximal number of remembered connection requests, which have not
received an acknowledgment from connecting client.

详细解释请查看 src/linux-3.10.105/Documentation/networking/ip-sysctl.txt

我们继续实验。

1. @服务端 修改两个内核参数值

  • 关闭SYN Cookie
    echo 0 > /proc/sys/net/ipv4/tcp_syncookies
  • 调整tcp_max_syn_backlog
    echo 4 > /proc/sys/net/ipv4/tcp_max_syn_backlog

Q: 为什么关闭SYN Cookie?
A: When syncookies are enabled there is no logical maximum length and this sysctl(tcp_max_syn_backlog) setting is ignored.

2. @服务端 运行serv.py

3. @客户端 添加iptables规则

禁止客户端发送RST和ACK

iptables -I OUTPUT -p tcp --dport 1234 --tcp-flags RST ACK -j DROP

4. @客户端 运行cli.py

修改其中一条打印语句

 s.connect((host , port))
 print "%s" %(time.time()) + ' local port: ' + "%s" % (s.getsockname()[1])

5. @服务端 查看网络连接

[root@vagrant-172-28-32-101 ~]# netstat -ant|grep 1234|sort
tcp        0      0 172.28.32.101:1234      192.168.1.233:51475     ESTABLISHED
tcp        0      0 172.28.32.101:1234      192.168.1.233:51476     ESTABLISHED
tcp        0      0 172.28.32.101:1234      192.168.1.233:51477     ESTABLISHED
tcp        0      0 172.28.32.101:1234      192.168.1.233:51482     SYN_RECV
tcp        0      0 172.28.32.101:1234      192.168.1.233:51483     SYN_RECV
tcp        0      0 172.28.32.101:1234      192.168.1.233:51484     SYN_RECV
tcp        0      0 172.28.32.101:1234      192.168.1.233:51485     SYN_RECV
tcp        3      0 0.0.0.0:1234            0.0.0.0:*               LISTEN

有3个sock已经建立连接,有4个sock处于半连接,符合我们的预期

5. @客户端 查看程序打印的日志

1490059497.78 local port: 51475
1490059498.79 local port: 51476
1490059499.81 local port: 51477
1490059500.82 local port: 51478
1490059501.83 local port: 51479
1490059502.85 local port: 51480
1490059503.87 local port: 51481
1490059568.0 local port: 51482

前面7个socket都是每秒建立一个连接,第8个连接经过大约64秒后才建立,说明服务端处于SYN_RECV状态的连接超时被删除,第8个连接才得以完成三次握手。

6. @客户端 查看网络连接状态

]# netstat -ant|grep 1234|sort
tcp        0      0 172.28.32.102:51475     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51476     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51477     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51478     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51479     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51480     192.168.1.193:1234      ESTABLISHED
tcp        0      0 172.28.32.102:51481     192.168.1.193:1234      ESTABLISHED
tcp        0      1 172.28.32.102:51482     192.168.1.193:1234      SYN_SENT

第8个连接处于SYN_SENT状态,服务端没有返回SYN/ACK, 可以确认服务器丢弃了客户端发出的SYN包

7. @服务端 查看系统统计信息和日志

]# netstat -s|grep dropped
181924245 SYNs to LISTEN sockets dropped

可见,系统已经丢弃了很多SYN包

# dmesg|grep 51482
[13766.635742] TCP: drop open request from 172.28.32.101/51482

从系统日志中可以看出,系统确实把第8个连接的SYN丢弃啦。

七. 总结

一般不要直接使用网上找到的一些优化参数,而应该深入了解各个参数的细节,知其然,并知其所以然,这样才能把系统优化的工作做好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容