我们都知道listen参数有个参数backlog。如果服务器不能及时调用accept,把连接从listen queue里面取走,那么UNP告诉我们,服务器的listen queue满掉后,服务器不会对再对建立新连接的syn进行应答,所以客户端的connect就会返回ETIMEDOUT。但实际上Linux的行为不是这样的!
让一个程序在8000端口上listen,backlog值是n,不调用accept;再让一个客户端程序不停的调用connect。客户端的前n个connect调用立刻就成功返回,这是意料之中的。按照UNP的说法,以后再调用connect应该统统超时失败,但实际上我看到的是:有的connect超时失败了,有的立刻成功返回了,有的经过明显延迟后成功返回了。用这个命令观看:
watch “netstat -t -n | grep 8000 | grep -oP ‘\w+\s*$’ | sort | uniq -c”
发现建立的连接数是可以无限增加的!再用tcpdump看,发现当客户的第一个syn发出后,服务器会随机的选择是否发送syn/ack,也就是tcp握手的第二步。所以对connect行为的解释就是:如果这次调用connect的第一个syn就被syn/ack了,那么connect就会立刻成功;如果第一个syn被忽略了,而地二个syn被服务器应答了,那么connect就会延迟成功;如果不幸三个syn都被服务器忽略了,connect就会返回超时失败。我google了半天,并没有发现任何文档描述这种行为,如果要确证只能看源码了。
结论:tcp的listen queue满后,linux不会如书中所说的拒绝连接,而是会随机的忽略发起连接的syn,建立起来的连接数可以无限增加,但是客户端会随机的遇到延迟和超时。
不管是拒绝连接还是随机的不理会连接请求,都是我们不愿意看到的,所以作为服务器工程师我们要监控这种现象。怎么在服务端看到呢?有两种方法,一是用netstat:
$ netstat -s | grep listen
21391 times the listen queue of a socket overflowed
如果这个计数不停的增加就是有麻烦了。
令一种方法是针对单个listen socket的。调用getsockopt,传入TCP_INFO,返回一个tcp_info结构,这个结构中的成员tcpi_sacked是listen queue的大小,即传人listen的backlog值;成员tcpi_unacked是listen queue里连接的数量,如果tcp_unacked > tcpi_sacked,那就应该注意啦。具体调用方法用一个python程序说明:
import time import socket import struct HOST = 'localhost' PORT = 8000 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(88) #/usr/include/linux/tcp.h struct_tcp_info = struct.Struct('7B 24I') while True: buf = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 1024) tcp_info = struct_tcp_info.unpack(buf) tcpi_unacked, tcpi_sacked = tcp_info[11:13] print 'tcpi_unacked:', tcpi_unacked,\ 'tcpi_sacked:', tcpi_sacked time.sleep(1)
tcpi_unacked: 0 tcpi_sacked: 88
不断的往8000端口建立连接,到后来输出就成了:
tcpi_unacked: 89 tcpi_sacked: 88
这就表示listen queue已经满了。此时上面netstat报告的overflow计数也开始不停增加了。
另外据我了解,listen queue满后的行为其实还可以通过
/proc/sys/net/ipv4/tcp_abort_on_overflow
来改变。如果把它的值设置成1,那么connect就会被rerest。
转载请注明:爱开源 » 关于tcp listen queue的一点事