环境:

Client通过tcp连接server,server端只是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

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

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

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

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

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

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

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

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

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

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

150296同上

150496同上

150920同上

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

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

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

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

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

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

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

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

155399

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

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

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

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

161309

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

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

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

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

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

164828 client端等待一段时间后,认为连接不可用,于是发送FIN、ACK给server端。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标志位的,只会处理SYN、ACK、RST这三种标志位。

从应用层的角度来考虑解决问题的方法,那就是使用非阻塞的方式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提供的测试数据

linux accept 修改数据包,Linux协议栈accept和syn队列问题相关推荐

  1. linux 监听数据包,linux下网络监听与发送数据包的方法(即libpcap、libnet两种类库的使用方法)...

    linux下可以用libpcap函数库实现监听数据包,使用libnet 函数库发送数据包 安装: 在命令行下apt-get install 就可以了 libpcap的使用: /*author hjj ...

  2. linux netfilter路由表,Linux netfilter 学习笔记 之十四 netfilter模块会修改数据包关联的路由缓存吗...

    起因: 最近在完成网关的一个相关功能时,对于网关本身通过socket发送的数据包,没有从正确的wan接口发送出去,但是在udp_sendmsg 函数里打印出来的 信息显示路由查找是正确的,但是当数据包 ...

  3. Linux内核网络数据包发送(四)——Linux netdevice 子系统

    Linux内核网络数据包发送(四)--Linux netdevice 子系统 1. 前言 2. `dev_queue_xmit` and `__dev_queue_xmit` 2.1 `netdev_ ...

  4. Linux内核网络数据包发送(三)——IP协议层分析

    Linux内核网络数据包发送(三)--IP协议层分析 1. 前言 2. `ip_send_skb` 3. `ip_local_out` and `__ip_local_out` 3.1 netfilt ...

  5. Linux内核网络数据包发送(二)——UDP协议层分析

    Linux内核网络数据包发送(二)--UDP协议层分析 1. 前言 2. `udp_sendmsg` 2.1 UDP corking 2.2 获取目的 IP 地址和端口 2.3 Socket 发送:b ...

  6. Linux内核网络数据包发送(一)

    Linux内核网络数据包发送(一) 1. 前言 2. 数据包发送宏观视角 3. 协议层注册 4. 通过 socket 发送网络数据 4.1 `sock_sendmsg`, `__sock_sendms ...

  7. linux数据包注释,关于 linux中TCP数据包(SKB)序列号的小笔记

    关于  SKB序列号的小笔记 为了修改TCP协议,现在遇到了要改动tcp分组的序列号,但是只是在tcp_sendmsg函数中找到了SKB的end_seq  一直没有找到seq 不清楚在那里初始化了,就 ...

  8. Linux内核网络数据包处理流程

    Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...

  9. linux如何查看丢弃数据包,导致Linux服务器丢弃数据包?

    我们使用Linux作为服务器操作系统时,为了达到高并发处理能力,充分利用机器性能,经常会进行一些内核参数的调整优化,但不合理的调整常常也会引起意想不到的其他问题,本文就一次Linux服务器丢包故障的处 ...

  10. linux下udp数据包接收工具,linux环境下数据包回放工具--pplayer分享

    pplayer(packet player)是我写的一款小工具,支持主流协议,专门用来测试IPS和防火墙设备,经长时间验证,简单可靠,故发布. 程序的原理很简单,首先加载pcap包中的数据包,保存在内 ...

最新文章

  1. let 和 var 区别
  2. hibernate详解
  3. 已解决:Unable to register authentication agent: GDBus.Error:org.freedesktop.PolicyKit1.Error.Failed:
  4. python库学习笔记——分组计算利器:pandas中的groupby技术
  5. 你会因为什么原因而离职
  6. 一个命名管道可以被多个客户端访问吗_Redis 的事务机制和管道技术Pipelining
  7. 该功能仅支持Android5.0,Android 5.0 android:elevation适用于View,但不适用于Button?
  8. 【Linux】GCC程序开发工具(上)
  9. 主流微型计算机,主流微型计算机硬件系统维护
  10. Docker系列一 ~docker的安装
  11. POJ-1149(网络流)
  12. Java:面向对象编程
  13. 《Java核心技术 卷1 基础知识 原书第10版》
  14. 计算机网络技术练习,计算机网络技术基础各章节综合练习题及答案
  15. 扩增子测序引入的假阳性稀有类群干扰对微生物群落多样性、构建机制及相互作用的研究...
  16. A Jupyter widget could not be displayed because the widget state could not be found.
  17. Error:Undefined symbol DMA_Cmd (referred from dac.o)
  18. 用代理服务器加速爬虫速率
  19. 【算法导论】 二叉搜索树、AVL树、和红黑树
  20. vue出生日期转年龄

热门文章

  1. 输入密码后默认回车提交登录事件
  2. 22.Linux/Unix 系统编程手册(上) -- 信号:高级特性
  3. 7.jenkins 发布邮件
  4. 53.NFS 服务器
  5. Spring MVC JSON自己定义类型转换
  6. [Unity3D]脚本中Start()和Awake()的区别
  7. MapReduce Example
  8. [原创] 测试策略是什么?
  9. 微信小程序——风水罗盘
  10. Cmd替代者 Cmder