`

TCP全连接队列和半连接队列

阅读更多

文章转载自:https://www.cnblogs.com/sidesky/p/6844228.html

摘要: # 关于TCP 半连接队列和全连接队列 > 最近碰到一个client端连接异常问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解 > > 查资料过程中发现没有文章把这两个队列以及怎么观察他们的指标说清楚,希望通过这篇文章能把他们说清楚一点 ### 问题描述 JAVA的client和server,使用socket通信。server使用NIO。

关于TCP 半连接队列和全连接队列

最近碰到一个client端连接异常问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解

查资料过程中发现没有文章把这两个队列以及怎么观察他们的指标说清楚,希望通过这篇文章能把他们说清楚一点

问题描述

JAVA的client和server,使用socket通信。server使用NIO。
1.间歇性的出现client向server建立连接三次握手已经完成,但server的selector没有响应到这连接。
2.出问题的时间点,会同时有很多连接出现这个问题。
3.selector没有销毁重建,一直用的都是一个。
4.程序刚启动的时候必会出现一些,之后会间歇性出现。

分析问题

正常TCP建连接三次握手过程:

image.png

  • 第一步:client 发送 syn 到server 发起握手;
  • 第二步:server 收到 syn后回复syn+ack给client;
  • 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)

从问题的描述来看,有点像TCP建连接的时候全连接队列(accept队列)满了,尤其是症状2、4. 为了证明是这个原因,马上通过 ss -s 去看队列的溢出统计数据:

667399 times the listen queue of a socket overflowed

反复看了几次之后发现这个overflowed 一直在增加,那么可以明确的是server上全连接队列一定溢出了

接着查看溢出后,OS怎么处理:

# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0

tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接队列满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)

为了证明客户端应用代码的异常跟全连接队列满有关系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的时候如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来)。

接着测试然后在客户端异常中可以看到很多connection reset by peer的错误,到此证明客户端错误是这个原因导致的。

于是开发同学翻看java 源代码发现socket 默认的backlog(这个值控制全连接队列的大小,后面再详述)是50,于是改大重新跑,经过12个小时以上的压测,这个错误一次都没出现过,同时 overflowed 也不再增加了。

到此问题解决,简单来说TCP三次握手后有个accept队列,进到这个队列才能从Listen变成accept,默认backlog 值是50,很容易就满了。满了之后握手第三步的时候server就忽略了client发过来的ack包(隔一段时间server重发握手第二步的syn+ack包给client),如果这个连接一直排不上队就异常了。

深入理解TCP握手过程中建连接的流程和队列


(图片来源:http://www.cnxct.com/something-about-phpfpm-s-backlog/)

如上图所示,这里有两个队列:syns queue(半连接队列);accept queue(全连接队列)

三次握手中,在第一步server收到client的syn后,把相关信息放到半连接队列中,同时回复syn+ack给client(第二步);

比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,导致server上这个队列满其它正常请求无法进来

第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。

这时如果全连接队列满了并且tcp_abort_on_overflow是0的话,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,就很容易异常了。

在我们的os中retry 第二步的默认次数是2(centos默认是5次):

net.ipv4.tcp_synack_retries = 2

如果TCP连接队列溢出,有哪些指标可以看呢?

上述解决过程有点绕,那么下次再出现类似问题有什么更快更明确的手段来确认这个问题呢?

netstat -s

[root@server ~]#  netstat -s | egrep "listen|LISTEN" 
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

ss 命令

[root@server ~]# ss -lnt
Recv-Q Send-Q Local Address:Port  Peer Address:Port 
0        50               *:3306             *:* 

上面看到的第二列Send-Q 表示第三列的listen端口上的全连接队列最大为50,第一列Recv-Q为全连接队列当前使用了多少

全连接队列的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数

半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。 不同版本的os会有些差异

实践验证下上面的理解

把java中backlog改成10(越小越容易溢出),继续跑压力,这个时候client又开始报异常了,然后在server上通过 ss 命令观察到:

Fri May  5 13:50:23 CST 2017
Recv-Q Send-QLocal Address:Port  Peer Address:Port
11         10         *:3306               *:*

按照前面的理解,这个时候我们能看到3306这个端口上的服务全连接队列最大是10,但是现在有11个在队列中和等待进队列的,肯定有一个连接进不去队列要overflow掉

进一步思考

如果client走完第三步在client看来连接已经建立好了,但是server上的对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,还是实践看看)

先来看一个例子:

image.png
(图片来自:http://blog.chinaunix.net/uid-20662820-id-4154399.html)

如上图,150166号包是三次握手中的第三步client发送ack给server,然后150167号包中client发送了一个长度为816的包给server,因为在这个时候client认为连接建立成功,但是server上这个连接实际没有ready,所以server没有回复,一段时间后client认为丢包了然后重传这816个字节的包,一直到超时,client主动发fin包断开该连接。

这个问题也叫client fooling,可以看这里:https://github.com/torvalds/linux/commit/5ea8ea2cb7f1d0db15762c9b0bb9e7330425a071 (感谢 @刘欢(浅奕(16:00后答疑) 的提示)

**从上面的实际抓包来看不是reset,而是server忽略这些包,然后client重传,一定次数后client认为异常,然后断开连接。
**

过程中发现的一个奇怪问题

[root@server ~]# date; netstat -s | egrep "listen|LISTEN" 
Fri May  5 15:39:58 CST 2017
1641685 times the listen queue of a socket overflowed
1641685 SYNs to LISTEN sockets ignored

[root@server ~]# date; netstat -s | egrep "listen|LISTEN" 
Fri May  5 15:39:59 CST 2017
1641906 times the listen queue of a socket overflowed
1641906 SYNs to LISTEN sockets ignored

如上所示:
overflowed和ignored居然总是一样多,并且都是同步增加,overflowed表示全连接队列溢出次数,socket ignored表示半连接队列溢出次数,没这么巧吧。

翻看内核源代码(http://elixir.free-electrons.com/linux/v3.18/source/net/ipv4/tcp_ipv4.c):

image.png

可以看到overflow的时候一定会drop++(socket ignored),也就是drop一定大于等于overflow。

同时我也查看了另外几台server的这两个值来证明drop一定大于等于overflow:

server1
150 SYNs to LISTEN sockets dropped

server2
193 SYNs to LISTEN sockets dropped

server3
16329 times the listen queue of a socket overflowed
16422 SYNs to LISTEN sockets dropped

server4
20 times the listen queue of a socket overflowed
51 SYNs to LISTEN sockets dropped

server5
984932 times the listen queue of a socket overflowed
988003 SYNs to LISTEN sockets dropped

那么全连接队列满了会影响半连接队列吗?

来看三次握手第一步的源代码(http://elixir.free-electrons.com/linux/v2.6.33/source/net/ipv4/tcp_ipv4.c#L1249):

image.png

TCP三次握手第一步的时候如果全连接队列满了会影响第一步drop 半连接的发生。大概流程的如下:

tcp_v4_do_rcv->tcp_rcv_state_process->tcp_v4_conn_request
//如果accept backlog队列已满,且未超时的request socket的数量大于1,则丢弃当前请求  
  if(sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_yong(sk)>1)
      goto drop;

总结

全连接队列、半连接队列溢出这种问题很容易被忽视,但是又很关键,特别是对于一些短连接应用(比如Nginx、PHP,当然他们也是支持长连接的)更容易爆发。 一旦溢出,从cpu、线程状态看起来都比较正常,但是压力上不去,在client看来rt也比较高(rt=网络+排队+真正服务时间),但是从server日志记录的真正服务时间来看rt又很短。

另外就是jdk、netty等一些框架默认backlog比较小,可能有些情况下导致性能上不去,比如 @毕玄 碰到的这个 《netty新建连接并发数很小的case》 
都是类似原因

希望通过本文能够帮大家理解TCP连接过程中的半连接队列和全连接队列的概念、原理和作用,更关键的是有哪些指标可以明确看到这些问题。

另外每个具体问题都是最好学习的机会,光看书理解肯定是不够深刻的,请珍惜每个具体问题,碰到后能够把来龙去脉弄清楚。

分享到:
评论

相关推荐

    p242- p270 TCP半连接队列和长连接队列

    增大TCP全连接队列的大小可以通过调整`somaxconn`和`backlog`参数。`somaxconn`是内核级别的参数,可以通过`/proc/sys/net/core/somaxconn`进行修改,而`backlog`是在`listen()`函数中设定的,表示服务器可接受的...

    TCP 半连接队列和全连接队列 420 -452

    在实战部分,我们可以通过`ss`命令查看TCP全连接队列的状态。在LISTEN状态下,Recv-Q表示当前全连接队列的大小,Send-Q表示最大全连接队列长度。而在非LISTEN状态,Recv-Q和Send-Q分别表示未被应用进程读取的字节数...

    tcp全连接和半连接问题查询步骤

    在 TCP 的三次握手过程中,有两个队列:syns queue(半连接队列)和 accept queue(全连接队列)。半连接队列用于存储客户端的 SYN 请求,服务器收到 SYN 请求后,将其放入半连接队列中,并回复 SYN+ACK 给客户端。...

    我发现 Linux 文档写错了.doc

    Linux netstat 命令中 Recv-Q 和 Send-Q 的正确描述 在 Linux 中,netstat 命令是查看网络状态很常见的...我们需要了解 netstat 命令和 TCP 半连接队列、全连接队列的概念,以便更好地理解 Recv-Q 和 Send-Q 的描述。

    没有accept可以建立TCP链接吗 668 - 684

    这是因为TCP协议栈本身会处理连接请求,将连接放入半连接队列,然后在完成三次握手后将其移动到全连接队列。即使服务端后续没有调用`accept()`,连接已经在系统层面建立了。 然而,服务端如果没有调用`accept()`,...

    TCP.zip_TCP连接_tcp vc_tcp vc_vc通信程序_以太网通信

    5. 服务器端监听:`listen()` 函数设置最大连接队列长度,等待客户端连接。 6. 客户端连接:`connect()` 函数发起连接请求,连接到服务器的指定地址和端口。 7. 服务器接受连接:`accept()` 函数接收客户端的连接...

    基于Visual Studio开发的简单Socket聊天小工具C++源码(服务端+客户端).zip

    基于Visual Studio开发的...当TCP全连接队列不为空后,服务端的accept()函数,就会从内核中的TCP全连接队列拿出一个已完成连接的Socket返回程序,后续数据传输都用这个Socket。此时,这个Socket用作传数据,与监听的

    TCP客户端和服务端代码

    接着调用`listen()`函数,设置最大连接队列长度,等待客户端的连接请求。 - **接受(Accept)**:当有客户端请求连接时,`accept()`函数会返回一个新的socket,专门用于处理这个客户端的连接。服务端可以继续监听新...

    C++网络编程实例(面向连接TCP)

    这个实例对于学习和理解C++中的TCP网络编程是非常有价值的,它涵盖了从创建套接字到建立连接、数据交换及关闭连接的全过程。通过实际操作,开发者可以更好地掌握网络编程的核心原理和实践技巧。

    用了TCP协议 数据一定不会丢失吗684 - 708

    在服务器端,半连接队列用于存储等待第二次握手的连接请求,全连接队列则存储已完成三次握手的连接。如果这两个队列长度有限,且达到上限,新到来的连接请求会被丢弃,表现为连接建立失败。可以通过`netstat -s`命令...

    TCP协议 计算机见通信

    TCP连接是全双工的,即数据可以在两个方向上同时传输。 在C#中实现两台计算机间的TCP通信,首先需要创建Socket对象,并指定使用TCP协议。然后,服务器端会监听特定的端口,等待客户端的连接请求。客户端则需要连接...

    基于TCP协议的网络客户端及服务端的编写

    这通常通过调用socket()函数创建套接字,bind()函数绑定地址,listen()函数设置最大连接队列长度,最后accept()函数接受客户端的连接。当有新的连接请求时,服务端会创建一个新的套接字与客户端进行通信。 客户端则...

    TCP_Socket.rar

    对于服务器端,使用`listen()`函数设置Socket为监听状态,指定最大连接队列长度。 4. **接受连接**: `accept()`函数用于等待并接受客户端的连接请求,返回一个新的Socket描述符,用于与已连接的客户端通信。 5....

    iocp_tcp服务端.rar

    - TCP(Transmission Control Protocol)是一种面向连接、可靠的传输协议,用于在互联网上建立全双工的数据通信。 - TCP服务器需要监听指定的端口,当接收到客户端的连接请求时,服务器会创建一个新的套接字进行...

    tcp序号混乱分析与解决

    而断开连接则需要四次挥手(FIN-ACK-FIN-ACK),因为TCP是全双工的,所以每个方向上的关闭都需要单独的FIN和ACK。 在TCP的状态机中,有多种状态表示连接的不同阶段,例如LISTEN、SYN_SENT、ESTABLISHED、CLOSE_WAIT...

    面试必备TCP&UDP;区别

    它在源和目的之间建立一个全双工的连接,并保证数据传输的顺序性和可靠性。TCP会进行流量控制和拥塞控制。流量控制可以避免接收方来不及处理接收到的数据,拥塞控制则可以防止网络拥塞。TCP连接的建立需要经过三次...

    TCP.Socket.rar_socket tcp_tcp socket 协议

    - 监听连接:调用`listen()`函数,设置最大连接队列长度。 - 接受连接:当有客户端连接时,使用`accept()`函数接收新的套接字并返回。 2. **客户端**: - 创建套接字:同样使用`socket()`函数创建套接字。 - ...

    C# 面向对象网络编程 SOCKET TCP 全套

    TcpClient提供了网络流,可以直接读写,而TcpListener则负责监听和接受连接。 此外,还需要了解异常处理,因为网络通信过程中可能出现各种异常,如SocketException、IOException等,应当通过try-catch块捕获并适当...

    TCP三次握手与四次挥手

    * 半连接存活时间:是指半连接队列的条目存活的最长时间,也即服务从收到 SYN 包到确认这个报文无效的最长时间,该时间值是所有重传请求包的最长等待时间总和。 四次挥手 TCP 连接是全双工的,因此每个方向都必须...

Global site tag (gtag.js) - Google Analytics