TCP socket SYN队列和Accept队列区别原理解析

首先我们必须明白,处于“LISTENING”状态的TCP socket,有两个独立的队列:

  • SYN队列(SYN Queue)
  • Accept队列(Accept Queue)

这两个术语有时也被称为“reqsk_queue”,“ACK backlog”,“listen backlog”,甚至“TCP backlog”,但是这篇文章中我们使用上面两个术语以免造成混淆。

SYN队列

SYN队列存储了收到SYN包的连接(对应内核代码的结构体:struct inet_request_sock)。它的职责是回复SYN+ACK包,并且在没有收到ACK包时重传,直到超时。在Linux下,重传的次数为:

$ sysctl net.ipv4.tcp_synack_retries

net.ipv4.tcp_synack_retries = 5

文档中对tcp_synack_retries的描述如下:

tcp_synack_retries - int整型
 对于一个被动TCP连接,重传SYNACKs的次数。该值不能超过255。
 默认值为5,如果初始RTO是1秒,那么对应的最后一次重传是31秒。
 对应的最后一次超时是63秒之后。

发送完SYN+ACK之后,SYN队列等待从客户端发出的ACK包(也即三次握手的最后一个包)。当收到ACK包时,首先找到对应的SYN队列,再在对应的SYN队列中检查相关的数据看是否匹配,如果匹配,内核将该连接相关的数据从SYN队列中移除,创建一个完整的连接(对应内核代码的结构体:struct inet_sock),并将这个连接加入Accept队列。

Accept队列

Accept队列中存放的是已建立好的连接,也即等待被上层应用程序取走的连接。当进程调用accept(),这个socket从队列中取出,传递给上层应用程序。

这就是Linux处理SYN包的一个简单描述。顺便一提,当socket开启了TCP_DEFER_ACCEPT和TCP_FASTOPEN时,工作方式将会有细微不同,本文不做介绍。

队列大小限制

应用程序通过调用系统调用listen(2),传入backlog参数,来设置SYN队列和Accept队列的最大大小。比如下面这样,将SYN队列和Accept队列的最大大小同时设置为1024:

listen(sfd, 1024)

注意,在4.3版本之前的内核,SYN队列的大小是用另一种方式计算。

SYN队列的最大大小以前是用net.ipv4.tcp_max_syn_backlog来配置,但是现在已经不再使用了。现在用net.core.somaxconn来同时表示SYN队列和Accept队列的最大大小。在我们的服务器上,我们将它设置为16k:

$ sysctl net.core.somaxconn

net.core.somaxconn = 16384

知道了上面这些信息后,你可能会问,队列设置为多大合适?队列设置为多大合适

答案是:看情况。对于大多数的TCP服务来说,这并不太重要。比如,Go语言1.11版本之前,并没有提供设置队列大小的方法。

尽管如此,也存在一些合理的原因,需要增大队列的大小:

  • 当建立连接的请求速度确实很大时,即使是对于一个高性能的服务来说,SYN队列也可能需要设置的大一些。
  • SYN队列的大小,换言之就是等待ACK包的连接数。也即与客户端的平均往返时间越大,堆积在SYN队列中的连接就越多。对于那些大部分客户端都距离服务器很远的场景,比如说往返时间几百毫秒以上,可以将队列大小设置的大一些。
  • TCP_DEFER_ACCEPT选项如果打开了,会导致socket在SYN-RECV状态下维持更长的时间,也即增大了处于SYN队列中的时间。

但是,将backlog设置的过大也会带来不好的影响:SYN队列中的每一个槽位都需要占用一些内存。当遇到SYN Flood攻击时,我们没有必要为这些发起攻击的包浪费资源。SYN队列中的inet_request_sock结构体,在4.14内核下,每个将占用256字节的内存。

linux下,如果想查看SYN队列的当前状态,我们可以使用ss命令来查询SYN-RECV状态的socket。比如如下执行结果,表示80端口的SYN队列中当前有119个元素,443端口则为78。

$ ss -n state syn-recv sport = :80 | wc -l
 119
 $ ss -n state syn-recv sport = :443 | wc -l
 78

假如程序调用accept()不够快?还可以通过我们的SystemTap脚本来观察这个数据:resq.stp

如果程序调用accept()不够快会发生什么呢?

  • 后续收到的SYN包,不会被SYN队列处理
  • 后续收到的(用于建立连接的)ACK包,不会被SYN队列处理
  • TcpExtListenOverflows / LINUX_MIB_LISTENOVERFLOWS计数增加
  • TcpExtListenDrops / LINUX_MIB_LISTENDROPS计数增加

发生这种情况时,我们只能寄希望于程序的处理性能稍后能恢复正常,客户端重新发送被服务端丢弃的包。

内核的这种表现对于大部分服务来说是可接受的。顺便一提,可以通过调整net.ipv4.tcp_abort_on_overflow这个全局参数来修改这种表现,但是最好还是不要改这个参数。

可以通过查看nstat的计数来观察Accept队列溢出的状态:

$ nstat -az TcpExtListenDrops
 TcpExtListenDrops 49199 0.0

但是这是一个全局的计数。观察起来不够直观,比如有时我们观察到它在增长,但是所有的服务程序看起来都是正常的。此时我们可以使用ss命令来观察单个监听端口的Accept队列大小:

$ ss -plnt sport = :6443|cat
 State Recv-Q Send-Q Local Address:Port Peer Address:Port
 LISTEN 0 1024 *:6443 *:*

Recv-Q这一列显示的是处于Accept队列中的socket数量,Send-Q显示的是队列的最大大小。在上面的例子中,我们发现并没有未被程序accept()的socket,但是我们依然发现ListenDrops计数在增长。

这是因为我们的程序只是周期性的短暂卡住不处理新的连接,而非永久性的不处理,过段时间程序又恢复了正常。这种情况下,用ss命令比较难观察这种现象,因此我们写了一个SystemTap脚本,它会hook进内核,把被丢弃的SYN包打印出来:

$ sudo stap -v acceptq.stp
time (us)    acceptq qmax local addr  remote_addr
1495634198449075 1025  1024 0.0.0.0:6443 10.0.1.92:28585
1495634198449253 1025  1024 0.0.0.0:6443 10.0.1.92:50500
1495634198450062 1025  1024 0.0.0.0:6443 10.0.1.92:65434
...

通过上面的操作,可以观察到哪些SYN包被ListenDrops影响了。从而我们也就可以知道哪些程序在丢失连接。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Wireshark基本介绍和学习TCP三次握手

    这篇文章介绍另一个好用的抓包工具wireshark, 用来获取网络数据封包,包括http,TCP,UDP,等网络协议包. 记得大学的时候就学习过TCP的三次握手协议,那时候只是知道,虽然在书上看过很多TCP和UDP的资料,但是从来没有真正见过这些数据包, 老是感觉在云上飘一样,学得不踏实.有了wireshark就能截获这些网络数据包,可以清晰的看到数据包中的每一个字段.更能加深我们对网络协议的理解. 对我而言, wireshark 是学习网络协议最好的工具. 阅读目录 wireshark介绍 w

  • TCP第三次握手传数据过程图解

    RFC793文档里带有SYN标志的过程包是不可以携带数据的,也就是说三次握手的前两次是不可以携带数据的(逻辑上看,连接还没建立,携带数据好像也有点说不过去).重点就是第三次握手可不可以携带数据. 先说结论:TCP协议建立连接的三次握手过程中的第三次握手允许携带数据. 对照着上边的TCP状态变化图的连接建立部分,我们看下RFC793文档的说法.RFC793文档给出的说法如下(省略不重要的部分): 重点是这句 "Data or controls which were queued for trans

  • TCP性能调优实现原理及过程解析

    三次握手阶段 客户端SYN包的重试次数 sysctl -w net.ipv4.tcp_syn_retries=6 相关介绍 第 1 次重试发生在 1 秒钟后,接着会以翻倍的方式在第 2.4.8.16.32 秒共做 6 次重试,最后一次重试会等待 64 秒,如果仍然没有返回 ACK,才会终止三次握手.所以,总耗时是 1+2+4+8+16+32+64=127 秒,超过 2 分钟. 服务端半连接池大小 sysctl -w net.ipv4.tcp_max_syn_backlog=16384 服务端半连

  • 基于python模拟TCP3次握手连接及发送数据

    源码如下 from scapy.all import * import logging logging.getLogger('scapy.runtime').setLevel(logging.ERROR) target_ip = '192.168.1.1' target_port = 80 data = 'GET / HTTP/1.0 \r\n\r\n' def start_tcp(target_ip,target_port): global sport,s_seq,d_seq #主要是用于TC

  • TCP三次握手及原理

    TCP/IP是很多的不同的协议组成,实际上是一个协议组,TCP用户数据报表协议(也称作TCP传输控制协议,Transport Control Protocol.可靠的主机到主机层协议.这里要先强调一下,传输控制协议是OSI网络的第四层的叫法,TCP传输控制协议是TCP/IP传输的6个基本协议的一种.两个TCP意思非相同. ).TCP是一种可靠的面向连接的传送服务.它在传送数据时是分段进行的,主机交换数据必须建立一个会话.它用比特流通信,即数据被作为无结构的字节流. 通过每个TCP传输的字段指定顺

  • Java基于TCP协议socket网络编程的文件传送的实现

    先了解一下socket基本概念 socket也叫套接字: 是指在网路中不同主机上的应用进程之间,进行双向通信的端点的抽象. 简单理解就是: 两个主机之间要通信,就需要知道彼此的ip,端口号等信息,而一台主机这些信息的集合: 就可以理解为一个端点,即为套接字 双方通过套接字作为一种坐标,建立信息通道,形成连接(两点连接一条直线) 简单理解了套接字的概念后,来看看如何通过java socket编程来实现 两台主机文件的接收与发送: 代码如下: 发送方: import java.io.*; impor

  • TCP/IP协议中三次握手四次挥手的原理及流程分析

    当初学的是通信专业,毕业以后,同学们各奔东西,去追逐自己的梦想,奔波于大大小小的工地之间.哈哈,开个玩笑,也有厉害的,进了某某研究所,嗯?他爸不是所长,内心不要太阴暗.记得有一门十分高大上的课程,名字叫做计算机网络(大概是这个名字吧).里面有一个关于握手的概念,现在温习一下. 先来看看原理图: TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接.在TCP/IP协议中,TCP 协议提供可靠的连接服务,连接是通过三次握手进行初始化的.三次握手的目的是同步连接双方的序列

  • TCP的三次握手与四次挥手详细介绍

    TCP的三次握手与四次挥手详细介绍 为什么是三次握手? 目的:防止已失效的连接请求又传到了服务器端. 场景(A为客户,B为服务器):A向B发送一个请求连接报文,但是这个报文在网络中阻塞了,并没有传到B.所以B也无法向A发送确认报文,在A的重传计时器到达之后,A再次向B发送请求连接报文,这个报文B收到了,并且向A做出应答,建立连接,传输数据.数据传输完后,关闭连接.问题来了,就在B关闭连接之后,A第一次发送的请求连接报文到了(这个报文是已经失效的),B以为A要再次创建一个新连接,于是向A发送确认报

  • Java 基于tcp协议实现文件上传

    服务端 package lesson02; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 服务端接收文件 */ public class TcpServerDemo2 { public static void main(String[] args) throws IOException { //1.创建服务 ServerSocket serverSocket = new ServerSo

  • TCP socket SYN队列和Accept队列区别原理解析

    首先我们必须明白,处于"LISTENING"状态的TCP socket,有两个独立的队列: SYN队列(SYN Queue) Accept队列(Accept Queue) 这两个术语有时也被称为"reqsk_queue","ACK backlog","listen backlog",甚至"TCP backlog",但是这篇文章中我们使用上面两个术语以免造成混淆. SYN队列 SYN队列存储了收到SYN包的连

  • Java重写(Override)与重载(Overload)区别原理解析

    这篇文章主要介绍了Java重写(Override)与重载(Overload)区别原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为. 也就是说子类能够根据需要实现父类的方法. 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常.例如: 父类的一个

  • Java特性队列和栈的堵塞原理解析

    做消息通信,消息会不断从网络流中取得,而后台也有线程不断消费.本来我一直是使用一些线程安全标识或方法来控制,后来在网上找到一些java新特性,里面包含了可以用到的堆栈使用,而且是堵塞的,这样至少可以保证一些安全性. 对于堆: BlockingQueue 不接受 null 元素.试图 add.put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException.null 被用作指示 poll 操作失败的警戒值. BlockingQueue 可以是限定容量的.它在

  • Mysql InnoDB和MyISAM区别原理解析

    mysql支持很多表类型的表(即存储引擎),如myisam.innodb.memory.archive.example等.每种存储引擎都有自己的优点和缺点,充分的理解每种存储引擎,有助于合理的使用它们.有人认为在同一个数据库中使用多种存储引擎很影响性能,其实这是一种十分错误的想法.实际上,除非是非常简单的数据库,否则的话,只使用一种存储引擎,对应用程序的性能来说是一个十分糟糕的行为.对数据库了解的人会根据每张表的作用不同来选择适当的存储引擎,这才是正确的做法. 前面说过mysql的存储引擎很多,

  • Python局部变量与全局变量区别原理解析

    1.局部变量 name = "Yang Li" def change_name(name): print("before change:",name) name = "你好" print("after change", name) change_name(name) print("在外面看看name改了么?",name) 输出: before change: Yang Li after change 你好

  • python TCP Socket的粘包和分包的处理详解

    概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包的情况.本文详细讲解解决该问题的步骤.使用的语言是Python.实际上解决该问题很简单,在应用层下,定义一个协议:消息头部+消息长度+消息正文即可. 那什么是粘包和分包呢? 关于分包和粘包 粘包:发送方发送两个字符串"hello"+"world",接收方却一次性接收到了"helloworld". 分包:发送方发送字符串"helloworld",接收方却接收到了两

  • Java消息队列JMS实现原理解析

    一.什么是JMS JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信.Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持(百度百科给出的概述).我们可以简单的理解:两个应用程序之间需要进行通信,我们使用一个JMS服务,进行中间的转发,通过JMS 的使用,我们可以解除两个程序之间的耦合. 二.为什么需要JMS 在JA

  • 如何解决TCP socket的阻塞问题

    目录 解决TCP socket的阻塞问题 在异常处理程序当中退出socket连接 TCP连接阻塞的监控和处理 我们整理出符合该类异常的特征如下 如何查看一个连接的创建时间 解决TCP socket的阻塞问题 大家知道,tcp的读和写是阻塞的,即读的时候不知道什么时候读完,写的时候不知道什么时候写完,因此线程就一直暂停在哪里,一般tcp程序用在上位机下位机之间对吧! 下位机一些设备一般会发心跳报文给我们机器,假设为10s发一次吧,当机器超过10s没接收到数据,那么我们就要考虑把socket断开,因

  • 如何解决TCP socket的阻塞问题

    目录 解决TCP socket的阻塞问题 在异常处理程序当中退出socket连接 TCP连接阻塞的监控和处理 我们整理出符合该类异常的特征如下 如何查看一个连接的创建时间 解决TCP socket的阻塞问题 大家知道,tcp的读和写是阻塞的,即读的时候不知道什么时候读完,写的时候不知道什么时候写完,因此线程就一直暂停在哪里,一般tcp程序用在上位机下位机之间对吧! 下位机一些设备一般会发心跳报文给我们机器,假设为10s发一次吧,当机器超过10s没接收到数据,那么我们就要考虑把socket断开,因

  • Python基础教程之tcp socket编程详解及简单实例

    Python tcp socket编程详解 初学脚本语言Python,测试可用的tcp通讯程序: 服务器: #!/usr/bin/env python # -*- coding: utf-8 -*- import socket import threading import time def tcplink(sock, addr): print('Accept new connection from %s:%s...' % addr); sock.send(b'Welcome!!!'); whi

随机推荐