Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3934236
  • 博文数量: 93
  • 博客积分: 3189
  • 博客等级: 中校
  • 技术积分: 4229
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-02 13:29
个人简介

出没于杭州和青岛的程序猿一枚,对内核略懂一二

文章分类

全部博文(93)

文章存档

2016年(2)

2015年(3)

2014年(11)

2013年(29)

2012年(16)

2011年(5)

2010年(5)

2009年(22)

分类: LINUX

2014-03-19 11:30:06

环境:

       Client 通过tcp 连接serverserver端只是listen,但是不调用accept。通过netstat –ant查看两端的连接情况。

server端listen,不调用accept。

client一直去connect server。

问题:

运行一段时间后,为什么server端的ESTABLISHED连接的个数基本是固定的129个,但是client端的ESTABLISHED连接的个数却在不断增加?

分析

Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是accpetd队列(用来保存处于established状态,但是应用层没有调用accept取走的请求)。

第一个队列的长度是/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是1024。如果开启了syncookies,那么基本上没有限制。

第二个队列的长度是/proc/sys/net/core/somaxconn,默认是128,表示最多有129个established链接等待accept。(为什么是129?详见下面的附录1)。

现在假设acceptd队列已经达到129的情况:

client发送syn到server。client(SYN_SENT),server(SYN_RECV)

server端处理流程:tcp_v4_do_rcv--->tcp_rcv_state_process--->tcp_v4_conn_request

      if(sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_yong(sk)>1)

              goto drop;

inet_csk_reqsk_queue_yong(sk)的含义是请求队列中有多少个握手过程中没有重传过的段。

在第一次的时候,之前的握手过程都没有重传过,所以这个syn包server端会直接drop掉,之后client会重传syn,当inet_csk_reqsk_queue_yong(sk) < 1,那么这个syn被server端接受。server会回复synack给client。这样一来两边的状态就变为client(ESTABLISHED), server(SYN_SENT)

Client收到synack后回复ack给server。

server端处理流程: tcp_check_req--->syn_recv_sock-->tcp_v4_syn_recv_sock

        if(sk_acceptq_is_full(sk)

                 goto exit_overflow;

如果server端设置了sysctl_tcp_abort_on_overflow,那么server会发送rst给client,并删除掉这个链接;否则server端只是记录一下LINUX_MIB_LISTENOVERFLOWS(详见附录2),然后返回。默认情况下是不会设置的,server端只是标记连接请求块的acked标志,之后连接建立定时器,会遍历半连接表,重新发送synack,重复上面的过程(具体的函数是inet_csk_reqsk_queue_prune),如果重传次数超过synack重传的阀值(/proc/sys/net/ipv4/tcp_synack_retries),会把该连接从半连接链表中删除。

一次异常问题分析

Nginx通过FASTCGI协议连接cgi程序,出现cgi程序read读取socket内容的时候永远block。通过netstat查看,cgi程序所在的服务器上显示连接存在,但是nginx所在的服务器上显示不存在该连接。

下面是原始数据图:

我们从上面的数据流来分析一下:

出现问题的时候,cgi程序(tcp server端)处理非常慢,导致大量的连接请求放到accept队列,把accept队列阻塞。

148021 nginx(tcp client) 连接cgi程序,发送syn

       此时serveraccpet队列已满,并且inet_csk_reqsk_queue_yong(sk) > 1server端直接丢弃该数据包

148840 client端等待3秒后,重传SYN

       此时server端状态与之前送变化,仍然丢弃该数据包

150163 client端又等待6秒后,重传SYN

                   此时serveraccept队列仍然是满的,但是存在了重传握手的连接请求,server端接受连接请求,并发送synackclient端(150164

150166 client端收到synack,标记本地连接为ESTABLISHED状态,给server端应答ackconnect系统调用完成。

                   Server收到ack后,尝试将连接放到accept队列,但是因为accept队列已满,所以只是标记连接为acked,并不会将连接移动到accept队列中,也不会为连接分配sendbufrecvbuf等资源。

150167 client端的应用程序,检测到connect系统调用完成,开始向该连接发送数据。

                   Server端收到数据包,由于acept队列仍然是满的,所以server端处理也只是标记acked,然后返回。

150225 client端由于没有收到刚才发送数据的ack,所以会重传刚才的数据包

150296 同上

150496 同上

150920 同上

151112 server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,重新发送synackclient端。

151113 client端收到synack后,根据ack值,使用SACK算法,只重传最后一个ack内容。

                   Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回。

151896 client端等待3秒后,没有收到对应的ack,认为之前的数据包也丢失,所以重传之前的内容数据包。

152579 server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,synack重传次数在阀值以内,重新发送synackclient端。

152581 cient端收到synack后,根据ack值,使用SACK算法,只重传最后一个ack内容。

                   Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回

153455 client端等待3秒后,没有收到对应的ack,认为之前的数据包也丢失,所以重传之前的内容数据包。

155399  server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,synack重传次数在阀值以内,重新发送synackclient端。

155400 cient端收到synack后,根据ack值,使用SACK算法,只重传最后一个ack内容。

                   Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回。

156468 client端等待几秒后,没有收到对应的ack,认为之前的数据包也丢失,所以重传之前的内容数据包。

161309  server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,synack重传次数在阀值以内,重新发送synackclient端。

161310 cient端收到synack后,根据ack值,使用SACK算法,只重传最后一个ack内容。

                   Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回。

162884 client端等待几秒后,没有收到对应的ack,认为之前的数据包也丢失,所以重传之前的内容数据包。

                   Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回。

164828 client端等待一段时间后,认为连接不可用,于是发送FINACKserver端。Client端的状态变为FIN_WAIT1,等待一段时间后,client端将看不到该链接。

164829 server端收到ACK后,此时cgi程序处理完一个请求,从accept队列中取走一个连接,此时accept队列中有了空闲,server端将请求的连接放到accept队列中。

这样cgi所在的服务器上显示该链接是established的,但是nginx(client)所在的服务器上已经没有该链接了。

之后,当cgi程序从accept队列中取到该连接后,调用read去读取sock中的内容,但是由于client端早就退出了,所以read就会block那里了。

问题解决

         或许你会认为在164829中,server端不应该建立连接,这是内核的bug。但是内核是按照RFC来实现的,在3次握手的过程中,是不会判断FIN标志位的,只会处理SYNACKRST这三种标志位。

从应用层的角度来考虑解决问题的方法,那就是使用非阻塞的方式read,或者使用select超时方式read;亦或者nginx中关闭连接的时候使用RST方式,而不是FIN方式。

附录1

when I use linux TCP socket, and find there is a bug in function sk_acceptq_is_full()

When a new SYN comes, TCP module first checks its validation. If valid,send SYN,ACK to the client and add the sock 

to the syn hash table. 

Next time if received the valid ACK for SYN,ACK from the client. server will accept this connection and increase the

sk->sk_ack_backlog -- which is done in function tcp_check_req().

We check wether acceptq is full in function tcp_v4_syn_recv_sock().

Consider an example:

 After listen(sockfd, 1) system call, sk->sk_max_ack_backlog is set to

 As we know, sk->sk_ack_backlog is initialized to 0. Assuming accept() system call is not invoked now

1. 1st connection comes. invoke sk_acceptq_is_full(). sk->sk_ack_backlog=0 sk->sk_max_ack_backlog=1, function return 0 accept this connection. Increase the sk->sk_ack_backlog

2. 2nd connection comes. invoke sk_acceptq_is_full(). sk->sk_ack_backlog=1 sk->sk_max_ack_backlog=1, function return 0 accept this connection. Increase the sk->sk_ack_backlog
3. 3rd connection comes. invoke sk_acceptq_is_full(). sk->sk_ack_backlog=2 sk->sk_max_ack_backlog=1, function return 1. Refuse this connection.
I think it has bugs. after listen system call. sk->sk_max_ack_backlog=1
but now it can accept 2 connections.

附录2

netstat -s

 cat /proc/net/netstat

最后感谢Tiger提供的测试数据

阅读(22598) | 评论(6) | 转发(8) |
给主人留下些什么吧!~~

瀚海书香2015-07-02 15:59:03

ktryrg:重现出来了

客户端的FIN+ACK发送几秒后,再Accept是获取不到的这个连接的

我刚走查了centos7.1的3.10内核代码,还是存在这个问题的。

回复 | 举报

ktryrg2015-07-02 14:10:44

ktryrg:我用同样的环境试了下,发现客户端发送FIN后,服务器再去调用accept,即使完成队列空出来了,也不会把未完成队列的连接移到完成队列里,但已经在完成队列里的连接,accept是可以获取出来的。


164829 server端收到ACK后,此时cgi程序处理完一个请求,从accept队列中取走一个连接,此时accept队列中有了空闲,server端将请求的连接放到accept队列中。
这样cgi所在的服务器上显示该链接是established


centos环境,内核:2.6.32


难道这个问题,tcp协议栈已经修复了吗?

重现出来了

客户端的FIN+ACK发送几秒后,再Accept是获取不到的这个连接的

回复 | 举报

ktryrg2015-07-02 11:52:39

我用同样的环境试了下,发现客户端发送FIN后,服务器再去调用accept,即使完成队列空出来了,也不会把未完成队列的连接移到完成队列里,但已经在完成队列里的连接,accept是可以获取出来的。


164829 server端收到ACK后,此时cgi程序处理完一个请求,从accept队列中取走一个连接,此时accept队列中有了空闲,server端将请求的连接放到accept队列中。
这样cgi所在的服务器上显示该链接是established


centos环境,内核:2.6.32


难道这个问题,tcp协议栈已经修复了吗?

shaohui9732014-03-26 08:31:46

瀚海书香:不在帝都,不在魔都,不在性都

看来都去过了啊

回复 | 举报

瀚海书香2014-03-25 09:49:42

shaohui973:文明上网,理性发言...博主在哪个城市高就啊?

不在帝都,不在魔都,不在性都

回复 | 举报