博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

TCP Queue第二波

Posted on 2015-07-31 14:16  bw_0927  阅读(638)  评论(0)    收藏  举报

http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/

 

先来回顾下三次握手里面涉及到的问题: 【以下有些是错误的理解,看正确的就是了】

1. 当 client 通过 connect 向 server 发出 SYN 包时,client 会维护一个 socket 等待队列,而 server 会维护一个 SYN 队列

2. 此时进入半链接的状态,如果 socket 等待队列满了,server 则会丢弃,而 client 也会由此返回 connection time out;只要是 client 没有收到 SYN+ACK,3s 之后,client 会再次发送,如果依然没有收到,9s 之后会继续发送

3. 半连接 syn 队列的长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 决定

4. 当 server 收到 client 的 SYN 包后,会返回 SYN, ACK 的包加以确认,client 的 TCP 协议栈会唤醒 socket 等待队列,发出 connect 调用

5. client 返回 ACK 的包后,server 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 的含义请看这里。需要注意的是,一些 Linux 的发型版本可能存在对 somaxcon 错误 truncating 方式

6. 当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被相应,此时,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回,0 表示直接丢丢弃该 ACK,1 表示发送 RST 通知 client;相应的,client 则会分别返回 read timeout 或者 connection reset by peer。上面说的只是些理论,如果服务器不及时的调用 accept(),当 queue 满了之后,服务器并不会按照理论所述,不再对 SYN 进行应答,返回 ETIMEDOUT。根据这篇文档的描述,实际情况并非如此,服务器会随机的忽略收到的 SYN,建立起来的连接数可以无限的增加,只不过客户端会遇到延时以及超时的情况。

可以看到,整个 TCP stack 有如下的两个 queue:

1. 一个是 half open(syn queue) queue(max(tcp_max_syn_backlog, 64)),用来保存 SYN_SENT 以及 SYN_RECV 的信息。

2. 另外一个是 accept queue(min(somaxconn, backlog)),保存 ESTAB 的状态,等待调用 accept()。

 

=============================================

http://www.douban.com/note/178129553/

今天编程序的偶然遇到了一点问题,然后小研究了一下,发现一些以前不知道的事情,还有点小吃惊 ,记录一下。有些观点可能有问题,求指正。

我们都知道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。

 

=================================

http://stackoverflow.com/questions/114874/socket-listen-backlog-parameter-how-to-determine-this-value

 

Keep in mind that if there is no room in the queue for a new connection, no RST will be sent, allowing the client to automatically continue trying to connect by retransmitting SYN.

Also, the backlog argument can have different meanings in different socket implementations.

In most it means the size of the half-open connection queue, in some it means the size of the completed connection queue.

In many implementations, the backlog argument will multiplied to yield a different queue length.

If a value is specified that is too large, all implementations will silently truncate the value to maximum queue length anyways.