我的编程空间,编程开发者的网络收藏夹
学习永远不晚

探析如何使用SystemTap观测TCP Backlog

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

探析如何使用SystemTap观测TCP Backlog

什么是TCP Backlog

本文所使用的Linux内核版本信息

5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

backlog的中文含义是 积压 的意思,在Linux网络中,意味着网络数据包的积压,在Linux表现为半连接队列和全连接队列存储这些积压的数据包。backlog参数的大小,则会影响半连接队列和全连接队列缓存数据包的多少。

其中,半连接队列和全连接队列的含义如图所示(此处引用张师傅博客中的图)

  • 半连接队列(Incomplete connection queue),又称 SYN 队列
  • 全连接队列(Completed connection queue),又称 Accept 队列

从服务端角度看待TCP三次握手的过程,有以下几步:

  • 调用 listen 函数时,TCP 的状态被从 CLOSE 状态变为 LISTEN,此时内核就创建了半连接队列和全连接队列。backlog参数就是在listen的时候指定的。
int listen(int sockfd, int backlog);
  • 在TCP进行三次握手的时候,收到SYN报文会先将数据包放到半连接队列,然后发出SYN+ACK
  • 接着当收到对端的SYN+ACK的时候,再将这个连接请求的数据包移动到全连接队列,等待应用程序通过accept() 函数读取。

我们可以通过listen函数传入backlog参数值,且backlog参数值会影响到半连接队列和全连接队列的大小,但是我们该怎么观测到最终操作系统使用的backlog的大小呢?又怎么观测到半连接队列、全连接队列中的缓存的包数量呢?backlog参数和半连接队列、全连接队列的大小之间又有什么关系呢?

实验环境搭建

先在本地电脑上启动了两个虚拟机,Linux虚拟机1(命名为L1,ip: 10.211.55.6)和Linux虚拟机2(命名为L2,ip: 10.211.55.8),以 L1 作为服务器,L2作为客户端。

观测Linux最终采用的backlog大小

为确定backlog值通过listen函数设置进去之后,操作系统最终采用的数值,可以通过systemtap工具来确定。安装好systemtap工具之后,编写探测脚本如下:

probe kernel.function("tcp_v4_conn_request") {  
    tcphdr = __get_skb_tcphdr($skb);  
    dport = __tcp_skb_dport(tcphdr);  
    if (dport == 9090)  
    {  
        printf("reach here\n");  
        printf("socket struct: %s \n", $sk$);  
        syn_qlen = @cast($sk, "struct inet_connection_sock")->icsk_accept_queue->qlen;  
        max_backlog=$sk->sk_max_ack_backlog;  
        printf("qlen: %d, max_backlog: %d  \n", syn_len, max_backlog);  
    }  
}

这个脚本做的事情,就是对linux中 tcp_v4_conn_request 这个内核函数做了探针,只要调用到这个内核函数,且端口号为9090,就会执行一系列的打印操作。其中,会将socket对象打印出来,也会将socket对象中的 sk_max_ack_backlog 变量打印出来,这个变量正是linux最终采用的backlog值。

将这个脚本放到机器L1中的任一用户目录下,脚本命名为 tcp_backlog.stp,然后用命令执行:

sudo stap -v tcp_backlog.stp

如果运行成功,则会看到在终端上显示正在运行的提示:

此时,为避免编程语言的干扰,用C语言准备一段服务器的启动代码,backlog值可以通过修改常量来更改,这里使用backlog值为20


// main.c
#include <sys/socket.h>  
#include <stdio.h>  
#include <netinet/in.h>  
#include <unistd.h>  
#include <string.h>  
#include <stdlib.h>  
#include <sys/shm.h>  
#define MYPORT  9090  
#define BACKLOG 20  
#define BUFFER_SIZE 1024  
int main()  
{  
    ///定义sockfd  
    int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);  
    ///定义sockaddr_in  
    struct sockaddr_in server_sockaddr;  
    server_sockaddr.sin_family = AF_INET;  
    server_sockaddr.sin_port = htons(MYPORT);  
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
    ///bind,成功返回0,出错返回-1  
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)  
    {  
        perror("bind");  
        exit(1);  
    }  
    ///listen,成功返回0,出错返回-1  
    if(listen(server_sockfd, BACKLOG) == -1)  
    {  
        perror("listen");  
        exit(1);  
    }  
    ///客户端套接字  
    char buffer[BUFFER_SIZE];  
    char message[100] = "已成功接收!";  
    struct sockaddr_in client_addr;  
    socklen_t length = sizeof(client_addr);  
    ///成功返回非负描述字,出错返回-1  
    int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);  
    if(conn<0)  
    {  
        perror("connect");  
        exit(1);  
    }  
    while(1)  
    {  
        memset(buffer,0,sizeof(buffer));  
        int size = read(conn, buffer, 1024);  
        if(strcmp(buffer,"exit\n")==0)  
            break;  
        strncat(buffer, message, 100);  
        fputs(buffer, stdout);  
        write(conn,buffer,strlen(buffer)+1);  
    }  
    close(conn);  
    close(server_sockfd);  
    return 0;  
}

在L1上通过命令编译sk.c 并启动:

gcc main.c -o sk.o && ./sk.o 

启动后,在L2上通过nc命令连接L1的9090端口:

nc 10.211.55.6 9090

接着观察 tcp_backlog.stp 探针脚本的输出:

可见此时使用的backlog值为20,通过这个方法,我们可以观测到linux最终采用的 backlog值的大小是多少了。

<>系统变量对backlog大小的影响

backlog虽然可以通过listen设置进去,但是按照张师傅的博客所说,最终的大小会受到操作系统的配置影响。可通过sysctl命令查看这两个系统变量:

sysctl net.ipv4.tcp_max_syn_backlog
# net.ipv4.tcp_max_syn_backlog = 128
sysctl net.core.somaxconn
# net.core.somaxconn = 4096

按照上述观测的方法,函数传入的backlog值分别在 小于128,大于128但小于4096,大于4096这三个区间取一个值。设置backlog大小为 20、200、6000,分别观测操作系统最终采用的backlog值如下:

listen backlog值为200时,操作系统采用的backlog值为200

listen backlog值为6000时,操作系统采用的backlog值为4096,和系统变量 net.core.somaxconn 保持一样。

将上述测试数据总结如下:

listen backlog值操作系统实际采用的backlog值
2020
200200
60004096

在张师傅的博客中提到, Linux内核版本在3.10.0的时候,会受到 net.ipv4.tcp_max_syn_backlognet.core.somaxconn 的影响,且受这两个变量影响的逻辑还比较复杂。但是在 5.15.0版本中,已经做了简化,代码如下:

// net/socket.c
int __sys_listen(int fd, int backlog)
{
  struct socket *sock;
  int err, fput_needed;
  int somaxconn;
  sock = sockfd_lookup_light(fd, &err, &fput_needed);
  if (sock) {
    # sysctl_somaxconn对应系统变量net.core.somaxconn的值
    somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
    if ((unsigned int)backlog > somaxconn)
      backlog = somaxconn;
    err = security_socket_listen(sock, backlog);
    if (!err)
      err = sock->ops->listen(sock, backlog);
    fput_light(sock->file, fput_needed);
  }
  return err;
}

再简化一下核心逻辑,核心逻辑的伪代码如下:

backlog = listen_backlog;
somaxconn = valuOf(`net.core.somaxconn`);
if(backlog > somaxconn) {
	backlog = somaxconn;
}

按张师傅的博客所说,在内核版本为3.10.0中, backlog 值会在这个时候依次传递给 __sys_listen() -> inet_listen()->inet_csk_listen_start()->reqsk_queue_alloc(),最终在 reqsk_queue_alloc函数中根据这两个系统变量经历一系列复杂的计算,最终得到操作系统使用的backlog值。但是这些操作,在5.x版本的内核都去掉了,reqsk_queue_alloc函数中不再对backlog做过任何处理:

// net/ipv4/inet_connection_sock.c
// 在这个函数中,虽然传入了backlog,但是在后续的处理中完全没有用上,由此证明backlog的赋值,在 __sys_listen 函数中已经完成
int inet_csk_listen_start(struct sock *sk, int backlog)
{
  struct inet_connection_sock *icsk = inet_csk(sk);
  struct inet_sock *inet = inet_sk(sk);
  int err = -EADDRINUSE;
  reqsk_queue_alloc(&icsk->icsk_accept_queue);
  sk->sk_ack_backlog = 0;
  inet_csk_delack_init(sk);
  
  inet_sk_state_store(sk, TCP_LISTEN);
  if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
    inet->inet_sport = htons(inet->inet_num);
    sk_dst_reset(sk);
    err = sk->sk_prot->hash(sk);
    if (likely(!err))
      return 0;
  }
  inet_sk_set_state(sk, TCP_CLOSE);
  return err;
}
// net/core/request_sock.c
void reqsk_queue_alloc(struct request_sock_queue *queue)
{
  spin_lock_init(&queue->rskq_lock);
  spin_lock_init(&queue->fastopenq.lock);
  queue->fastopenq.rskq_rst_head = NULL;
  queue->fastopenq.rskq_rst_tail = NULL;
  queue->fastopenq.qlen = 0;
  queue->rskq_accept_head = NULL;
}

观测半连接队列大小

在三次握手的过程中,服务端收到握手请求包之后,会先把它放到半连接队列中,然后回复SYN+ACK。接着接收到客户端返回的ACK报文时,再把这个数据包从半连接队列移动到全连接队列中。在正常情况下,SYN报文在半连接队列逗留的时间会很快,观测半连接队列大小要做点处理。

按照张师傅博客提供的方法,可以在客户端设置防火墙,把服务端返回的ACK包都扔掉,这样在服务端就不会收到ACK报文了。

// 在L2机器上设置这条防火墙规则
sudo iptables --append INPUT --match tcp --protocol tcp --class="lazy" data-src 10.211.55.6 --sport 9090 --tcp-flags SYN SYN --jump DROP
// 查看防火墙规则是否设置成功
sudo iptables -L

接着用上述的服务端代码启动服务后,在L2上通过nc命令连接上:

nc 10.211.55.6 9090

接着可以通过以下命令观察到,当前有多少个连接处于SYN_RECV状态:

sudo netstat -lnpa | grep :9090  | awk '{print $6}' | sort | uniq -c | sort -rn

处于SYN_RECV状态的连接,意味着接收到了客户端的SYN报文但未接收到ACK报文。此时连接就处于SYN_RECV状态。通过这个点可以观测到半连接队列此时的大小是多少。你也可以在L2上通过程序发起多次连接,看看SYN_RECV状态的连接数是否有变化,此处就不再叙述了。

观测全连接队列大小

当请求收到ACK之后,就会从半连接队列挪到全连接队列,此时连接已经完全建立,连接状态就会从LISTEN变成ESTABLISHED状态,等待应用程序调用accept函数从全连接队列中取走数据。所以,要观察全连接队列的大小,只要观察ESTABLISHED状态的连接数即可。同样可以采用netstat命令:

netstat -lnpa | grep :9090 | awk '{print $6}' | sort | uniq -c | sort -rn

也可以使用ss命令来进行观测。使用命令如下:

ss -lnt | grep :9090
  • 处于 LISTEN 状态的 socket,Recv-Q 表示 accept 队列排队的连接个数,Send-Q 表示全连接队列(也就是 accept 队列)的总大小
  • 对于非 LISTEN 状态的 socket,Recv-Q 表示 receive queue 的字节大小,Send-Q 表示 send queue 的字节大小

总结

SystemTap是一个很有力的工具,用好这个工具,可以实实在在地观测到Linux内部的状态,让自己对操作系统有个更深刻的认识。

以上就是使用SystemTap观测TCP Backlog过程解析的详细内容,更多关于SystemTap观测TCP Backlog的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

探析如何使用SystemTap观测TCP Backlog

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

探析如何使用SystemTap观测TCP Backlog

这篇文章主要为大家介绍了使用SystemTap观测TCP Backlog过程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-17

探析如何使用SystemTap观测TCP Backlog

目录什么是TCP Backlog实验环境搭建观测linux最终采用的backlog大小观测半连接队列大小观测全连接队列大小总结什么是TCP Backlog 本文所使用的Linux内核版本信息 5.15.0-56-generic #62-Ub
2023-05-06

如何使用watch实时观察TCP和UDP端口

这篇文章将为大家详细讲解有关如何使用watch实时观察TCP和UDP端口,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。在软件方面,尤其是在操作系统级别,端口是一种逻辑结构,用于标识特定的进程
2023-06-05

技术分享 | 如何使用 bcc 工具观测 MySQL 延迟

作者:刘安爱可生测试团队成员,主要负责 TXLE 开源项目相关测试任务,擅长 Python 自动化测试开发,最近醉心于 Linux 性能分析优化的相关知识。本文来源:原创投稿*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。最近在极
技术分享 | 如何使用 bcc 工具观测 MySQL 延迟
2015-02-01

如何使用Go语言测量和分析API性能

使用 go 语言测量和分析 api 性能的方法:使用 net/http/pprof 测量 http 性能。使用 pprof 工具分析性能剖析。禁用生产环境中的性能剖析。使用适当的采样率。定期分析性能剖析并解决问题。如何使用 Go 语言测量和
如何使用Go语言测量和分析API性能
2024-05-08

python如何使用cProfile针对回测进行性能分析

这篇文章主要讲解了“python如何使用cProfile针对回测进行性能分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python如何使用cProfile针对回测进行性能分析”吧!安装不
2023-06-02

如何使用C++进行时间序列分析和预测?

使用 c++++ 进行时间序列分析和预测涉及以下步骤:安装必需的库预处理数据提取特征 (acf、ccf、sdf)拟合模型 (arima、sarima、指数平滑)预测未来值使用 C++ 进行时间序列分析和预测时间序列分析是一项用于预测未来值
如何使用C++进行时间序列分析和预测?
2024-05-15

如何使用入侵探测系统(IDS)保护CentOS服务器免受未经授权访问

以下是使用入侵探测系统(IDS)保护CentOS服务器免受未经授权访问的步骤:1. 安装入侵探测系统(IDS):在CentOS服务器上安装适合的IDS软件。一些常见的IDS软件包括Snort、Suricata和OSSEC等。可以使用包管理器
2023-10-12

阿里云服务器租赁市场探析如何将服务器租给其他人使用

阿里云作为国内领先的云计算服务提供商,除了为企业和开发者提供稳定可靠的云服务器外,还提供了将服务器租给其他用户使用的服务。本文将从阿里云服务器租赁市场的角度出发,介绍如何将阿里云服务器租给其他人使用,并探讨其中的机会和挑战。1.为什么要将阿里云服务器租给其他人?在云计算时代,随着云计算技术的普及和发展,越来越多的
阿里云服务器租赁市场探析如何将服务器租给其他人使用
2024-01-01

如何使用Python中的数据分析库处理和预测时间序列数据

如何使用Python中的数据分析库处理和预测时间序列数据时间序列数据是指按时间顺序排列的数据,其特点是具有时间上的相关性和趋势性。在许多领域中,时间序列数据分析起着重要的作用,如股市预测、天气预报、销售预测等。Python中有许多强大的数据
2023-10-22

如何使用python对泰坦尼克号幸存者进行数据分析与预测

本篇内容主要讲解“如何使用python对泰坦尼克号幸存者进行数据分析与预测”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用python对泰坦尼克号幸存者进行数据分析与预测”吧!数据获取当我
2023-07-05

如何实现MySQL底层优化:性能测试和调优工具的高级使用与分析

如何实现MySQL底层优化:性能测试和调优工具的高级使用与分析引言MySQL是一种常用的关系型数据库管理系统,广泛应用于各种Web应用和大型软件系统中。为了确保系统的运行效率和性能,我们需要进行MySQL的底层优化。本文将介绍如何使用性能测
如何实现MySQL底层优化:性能测试和调优工具的高级使用与分析
2023-11-08

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录