Linux高性能服务器编程 学习笔记 第十六章 服务器调制、调试和测试

news2025/7/15 5:23:47

Linux平台的一个优秀特性是内核微调,即我们可以通过修改文件的方式来调整内核参数。

服务器开发过程中,可能会碰到意想不到的错误,一种调试方法是用tcpdump抓包,但这种方法主要用于分析程序的输入和输出,对于服务器的逻辑错误,更方便的调试方法是使用gdb调试器。

系统分配给应用进程的文件描述符数量是有限制的,所以我们必须关闭那些已经不再使用的文件描述符,以释放它们占用的资源,比如作为守护进程运行的服务器就应该总是关闭标准输入、标准输出、标准错误这3个文件描述符。

Linux对应用进程能打开的最大文件描述符数量有两个层次的限制:用户级限制和系统级限制。用户级限制指目标用户运行的所有进程总共能打开的文件描述符数;系统级限制指所有用户总共能打开的文件描述符数。

以下命令可查看用户级文件描述符限制:

ulimit -n

我们可通过一下方式将用户级文件描述符限制设为2048:

ulimit -SHn 2048

但这种设置是临时的,只在当前session中有效,为永久修改用户级文件描述符数限制,可在/etc/security/limits.conf文件中加入以下两项:

* hard nofile 2048
* soft nofile 2048

第一行是硬限制,第二行是软限制。*是通配符,表示所有用户。

如果要修改系统级文件描述符数限制,可用以下命令:

sysctl -w fs.file-max=2048

但该命令也是临时更改系统限制,要永久更改系统级文件描述符数限制,需要在/etc/sysctl.conf文件中添加以下项:

fs.file-max=2048

然后执行sysctl -p命令,该Linux系统上的命令用于重新加载系统内核参数设置。

几乎所有内核模块,包括内核核心模块和驱动程序,都在/proc/sys目录下提供了某些配置文件以供用户调整模块的属性和行为。通常一个配置文件对应一个内核参数,文件名就是参数的名字,文件内容就是参数的值。我们可通过命令sysctl -a查看所有这些内核参数,我们只讨论其中与网络编程关系较为紧密的内核参数。

/proc/sys/fd目录下的内核参数都与文件系统相关,对服务器程序来说,有以下重要参数:
1./proc/sys/fd/file-max:系统级文件描述符数限制,修改它是临时性修改,与以上所述临时修改系统级文件描述符数的限制效果相同。一般修改完该文件后,需要把/proc/sys/fd/inode-max设置为新/proc/sys/fd/file-man值的3~4倍,否则可能导致i节点数不够用。

2./proc/sys/fd/epoll/max-user_watches:一个用户能够往epoll内核事件表中注册的事件总量。它是某用户打开的所有epoll实例总共能监听的事件数目,而不是单个epoll实例能监听的事件数目。往epoll内核事件表中注册一个事件,在32位系统上大概消耗90字节的内核空间,在64位系统上则消耗160字节的内核空间。这个内核参数限制了epoll使用的内核内存总量。

内核中网络模块的相关参数都位于/proc/sys/net目录下,其中和TCP/IP协议相关的参数主要位于以下3个子目录中:core、ipv4、ipv6,以下是和服务器性能相关的部分参数:
1./proc/sys/net/core/somaxconn:指定listen函数监听队列里,能建立完整连接从而进入ESTABLISHED状态的socket的最大数目。

2./proc/sys/net/ipv4/tcp_max_syn_backlog:指定listen函数监听队列里,能够转移至ESTABLISHED或SYN_RCVD状态的socket的最大数目。

3./proc/sys/net/ipv4/tcp_wmem:它包含3个值,分别指定一个socket的TCP写缓冲区的最小值、默认值、最大值。

4./proc/sys/net/ipv4/tcp_rmem:它包含3个值,分别指定一个socket的TCP读缓冲区的最小值、默认值、最大值。

5./proc/sys/net/ipv4/tcp_syncookies:指定是否打开TCP同步标签(syncookie),同步标签通过启动cookie来防止一个监听socket因不停地重复接收来自同一个地址的连接请求(同步报文段),而导致listen函数监听队列溢出(所谓的SYN风暴)。

除了通过直接修改文件的方式来修改这些系统参数外,我们也可使用sysctl命令来修改它们,这两种修改方式都是临时的,永久的修改方法是在/etc/sysctl.conf文件中加入相应网络参数及其数值,并执行sysctl -p使之生效。

以下讨论如何使用gdb来调试多进程和多线程程序,我们假设读者懂得基本的gdb调试方法,如设置断点、查看变量等。

如果一个进程通过fork系统调用创建了子进程,gdb会继续调试原来的进程,子进程则正常运行,以下方式可调试子进程:
1.单独调试子进程。子进程本质上来说也是一个进程,因此我们可通过通用的gdb调试方法来调试它,例如,我们可先运行服务器,然后找到子进程的PID,再将其附加(attach)到gdb调试器上,具体操作如下:
在这里插入图片描述
在这里插入图片描述
上图中,b命令表示设置断点,格式为b filename:linenumber;c命令的作用是继续程序的执行,直到遇到下一个断点或程序正常结束;bt命令的作用是打印当前程序的函数调用堆栈(backtrace),显示当前执行路径中各个函数的调用关系和调用帧信息。

2.使用调试器选项follow-fork-mode。gdb调试器的选项follow-fork-mode允许我们选择程序在执行fork系统调用后是继续调试父进程还是调试子进程,其用法如下:
在这里插入图片描述
上图中,mode的可选值是parent和child,分别表示调试父进程和子进程,使用前面的例子,这次使用follow-fork-mode选项来调试子进程,具体过程如下:
在这里插入图片描述
在这里插入图片描述
上图中,gdb ./cgisrv命令以gdb启动名为cgisrv的可执行文件,当我们设置完调试器选项follow-fork-mode和断点后,使用r命令启动被调试的程序。

gdb有一组命令可辅助多线程程序的调试,以下是其中一些常用的命令:
1.info threads:显示当前可调式的所有线程。gdb会为每个线程分配一个ID,我们可使用这个ID来操作对应的线程,ID前面有*的线程是当前被调试的线程。

2.thread ID:调试目标ID指定的线程。

3.set scheduler-locking [off|on|stop]:调试多线程程序时,默认除了被调试的线程在执行外,其他线程也在继续执行,但有时我们仅希望被调试的线程运行,这可通过该命令来实现。该命令设置scheduler-locking的值:off表示不锁定任何线程,即所有线程都可继续执行,这是默认值;on表示只有当前被调试的线程会继续执行;stop表示在单步执行的时候,只有当前线程会执行。

以下过程独立调试每个线程:
在这里插入图片描述
在这里插入图片描述
上图中,gdb的n命令的作用是单步执行程序,一次性执行一行源代码,然后将控制权交还给调试器,以便检查程序状态、变量值和执行路径。

一个关于调试进程池和线程池程序的不错的方法是,将池中的进程或线程个数减少至1,以观察程序的逻辑是否正确,然后逐步增加进程或线程数量,以调试进程或线程的同步是否正确。

压力测试程序有很多种实现方式,如IO复用方式、多线程(进程)并发编程方式,以及这些方式的结合使用,但单纯的IO复用方式的施压程度是最高的,因为线程和进程的调度本身也要占用一定CPU时间(作者此处认为单线程、单进程的压力测试程序施压程度最高,但单线程、单进程只能同时使用CPU的一个核心,而多线程或多进程情况下可以使用CPU的多个核心,为什么多核心同时施压比单核心施压程度高呢?),因此我们使用epoll来实现一个通用的服务器压力测试程序:

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

// 每个客户连接不停向服务器发送这个请求
static const char *request = "GET http://localhost/index.html HTTP/1.1\r\n"
    "Connection: keep-alive\r\n\r\nxxxxxxxxxxxx";

int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epoll_fd, int fd) {
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLOUT | EPOLLET | EPOLLERR;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
}

// 向服务器写入len字节的数据
bool write_nbytes(int sockfd, const char *buffer, int len) {
    int bytes_write = 0;
    printf("write out %d bytes to socket %d\n", len, sockfd);
    while (1) {
        bytes_write = send(sockfd, buffer, len, 0);
        if (bytes_write == -1) {
            return false;
        } else if (bytes_write == 0) {
            return false;
        }
        
        len -= bytes_write;
        buffer = buffer + bytes_write;
        if (len <= 0) {
            return true;
        }
    }
}

// 从服务器读取数据
bool read_once(int sockfd, char *buffer, int len) {
    int bytes_read = 0;
    memset(buffer, '\0', len);
    bytes_read = recv(sockfd, buffer, len, 0);
    if (bytes_read == -1) {
        return false;
    } else if (bytes_read == 0) {
        return false;
    }
    printf("read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer);
    
    return true;
}

// 向服务器发起num参数个TCP连接,我们可以通过改变num参数来调整测试压力
void start_conn(int epoll_fd, int num, const char *ip, int port) {
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    
    for (int i = 0; i < num, ++i) {
        sleep(1);
        int sockfd = socket(PF_INET, SOCK_STREAM, 0);
        printf("create 1 sock\n");
        if (sockfd < 0) {
            continue;
        }
        
        if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) == 0) {
            printf("build connection %d\n", i);
            addfd(epoll_fd, sockfd);
        }
    }
}

void close_conn(int epoll_fd, int sockfd) {
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sockfd, 0);
    close(sockfd);
}

int main(int argc, char *argv[]) {
    assert(argc == 4);
    int epoll_fd = epoll_create(100);
    start_conn(epoll_fd, atoi(argv[3]), argv[1], atoi(argv[2]));
    epoll_event events[10000];
    char buffer[2048];
    while (1) {
        // 第3个参数是第2个参数数组的大小
        // 第4个参数是等待的毫秒数
        int fds = epoll_wait(epoll_fd, events, 10000, 2000);
        for (int i = 0; i < fds; ++i) {
            int sockfd = events[i].data.fd;
            if (events[i].events & EPOLLIN) {
                // 此处代码有问题,当读取失败后(对端关闭连接或读函数失败)
                // 先关闭了连接,然后接着又监听被关闭连接的可写状态,此处关闭连接后,应该continue
                if (!read_once(sockfd, buffer, 2048)) {
                    close_conn(epoll_fd, sockfd);
                }
                struct epoll_event event;
                event.events = EPOLLOUT | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event);
            } else if (events[i].events & EPOLLOUT) {
                // 此处也是同样的问题
                if (!write_nbytes(sockfd, request, strlen(request))) {
                    close_conn(epoll_fd, sockfd);
                }
                struct epoll_event event;
                event.events = EPOLLIN | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event);
            } else if (events[i].events & EPOLLERR) {
                close_conn(epoll_fd, sockfd);
            }
        }
    }
}

使用以上压力测试程序(名为stress_test)来测试第十五章中用线程池实现的Web服务器,我们现在测试机器ernest-laptop上运行websrv,然后从Kongming20上执行stress_test,向websrv服务器发起1000个连接,具体操作如下:
在这里插入图片描述
如果websrv服务器程序足够稳定,那么websrv和stress_test这两个进程将一直运行下去,并不断交换数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1103209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Atlassian Confluence OGNL表达式注入RCE CVE-2021-26084

影响版本 All 4.x.x versions All 5.x.x versions All 6.0.x versions All 6.1.x versions All 6.2.x versions All 6.3.x versions All 6.4.x versions All 6.5.x versions All 6.6.x versions All 6.7.x versions All 6.8.x versions All 6.9.x versions All 6.1…

uniapp使用uQRCode绘制二维码,下载到本地,调起微信扫一扫二维码核销

1.效果 2.在utils文件夹下创建uqrcode.js // uqrcode.js //--------------------------------------------------------------------- // github https://github.com/Sansnn/uQRCode //---------------------------------------------------------------------let uQRCode {…

小学数学题AI自动出题系统源码,支持在线打印及导出PDF!

今天给大家开发了个好东西&#xff0c;小学数学作业练习册AI自动出题网站源码&#xff0c;全面支持打印机打印机转成PDF文件&#xff0c;快给你家娃娃整一套吧&#xff0c;AI自动出题&#xff0c;让娃练习算数&#xff0c;解放双手&#xff0c;让您的孩子成绩蒸蒸日上&#xff…

【微服务 SpringCloudAlibaba】实用篇 · Nacos注册中心

微服务&#xff08;5&#xff09; 文章目录 微服务&#xff08;5&#xff09;1. 认识和安装Nacos2. 服务注册到nacos和拉取服务1&#xff09;引入依赖2&#xff09;配置nacos地址3&#xff09;重启 3. 服务分级存储模型3.1 给user-service配置集群3.2 同集群优先的负载均衡 4. …

高程DEM-等高线生成-AutoCAD等高线

高程DEM-等高线生成-AutoCAD等高线 发布时间&#xff1a;2018-01-17 版权&#xff1a; 同步视频教程&#xff1a;卫星地图_高清卫星地图_卫星地图视频_下载高程等高线使用视频教程 专题地图制作视频教程&#xff1a;卫星地图_高清卫星地图_卫星地图视频_地图数据应用&#xf…

【C++】--遇到抛异常没有及时释放的空间该怎么办??---智能指针来帮你解决(以及定制删除器)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

04、RocketMQ -- 核心基础使用

目录 核心基础使用1、入门案例生产者消费者 2、消息发送方式方式1&#xff1a;同步消息方式2&#xff1a;异步消息方式3&#xff1a;一次性消息管控台使用过程中可能出现的问题 3、消息消费方式集群模式&#xff08;默认&#xff09;广播模式 4、顺序消息分析图&#xff1a;代码…

[uni-app] canvas绘制圆环进度条

文章目录 需求参考链接基本问题的处理1:画布旋转的问题2:注意arc()的起始位置是3点钟方向3: 如果绘制1.9*Matn.PI的圆环, 要保证其实位置在0点方向?4:小线段怎么画, 角度怎么处理? 源码 需求 要绘制一个如此的进度条 参考链接 uni-app使用canvas绘制时间刻度以及不显示问…

线段树【java实现】

一、解决问题 区间最值和区间求和问题 力扣相关题目&#xff1a; ​​​​​​303. 区域和检索 - 数组不可变 729. 我的日程安排表 I 二、线段树定义 平衡二叉树&#xff0c;数组中的元素都存储在叶子结点中&#xff0c;如图是一个求区间最大值的线段树。 已知数组arr[11…

电源特性测试之电源模块负载调整率测试方法及测试条件

负载调整率是衡量电源好坏的重要指标&#xff0c;它反映的是当负载电流变化时&#xff0c;稳压电源输出电压相应的变化情况。好的电源负载变化时引起的输出变化较小&#xff0c;通常是在3%-5%。负载调整率是电源模块测试的一个重要步骤&#xff0c;今天纳米软件将为大家介绍负载…

Yakit工具篇:综合目录扫描与爆破的使用

简介&#xff08;来自官方文档&#xff09; 目录扫描是一种常用的Web应用程序安全测试技术&#xff0c;用于发现Web应用程序中存在的可能存在的漏洞和弱点。其原理是通过对Web应用程序中的目录和文件进行遍历&#xff0c;来发现可能存在的安全漏洞和风险。 具体来说&#xff…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

《进化优化》第5章 进化规划

文章目录 5.1 连续进化规划5.2 有限状态机优化5.3 离散进化规划5.4 囚徒困境5.5 人工蚂蚁问题 5.1 连续进化规划 目的&#xff1a;最小化f(x)&#xff0c; 这里的x是一个n维向量&#xff0c;假定对所有的x, f(x)>0。 进化规划从随机生成的一个个体种群{xi}开始, 按如下方式…

Umi3实战教程

一、框架介绍 umi是蚂蚁金服的前端开发框架&#xff0c;它内置了路由、web/移动端UI库、数据流、权限控制、常用hooks库、构建、部署、测试、等等一些工具&#xff0c;几乎涵盖了正常前端开发要用到的所有工具。 二、环境准备 pnpm 相比npm、yarn&#xff0c;pnpm更小更快扁平…

为大模型而生!顶流大佬发起成立学术会议 COLM,或成为未来 NLP 最强顶会?!

夕小瑶科技说 原创 作者 | 智商掉了一地、ZenMoore 前段时间&#xff0c;ACL 2024 的主席公开抨击称“ arXiv是科研的毒瘤”&#xff0c;这引发了大范围的争论。 一时间&#xff0c;大家对 *CL 的抵触情绪愈发高涨&#xff0c;绝大多数学界都在这场辩论中站在了支持 arXivTwit…

PreparedStatement

使用参数化查询&#xff1a;使用预编译的语句和参数化查询来执行SQL语句&#xff0c;而不是将用户输入直接嵌入到SQL语句中。这将帮助防止恶意输入注入SQL语句。

Zoho WorkDrive荣获专业研究机构评定的“Leader”称号

近年&#xff0c;在云计算、大数据、移动互联网、社交所引领的数字化转型变革中&#xff0c;企业对于数字资产的保护和利用愈加重视。相较于结构化数据&#xff0c;企业对于非结构化数据&#xff08;文档、图片、音视频等&#xff09;管理的需求更强、难度更大。 同时&#xf…

NodeJS 菜鸟教程目录

NodeJS 七天入门教程 谁适合阅读本教程? 前端开发者和希望构建后端的开发者:如果你是一名前端开发者,或者是一名希望构建后端的开发者,那么本教程将为你提供一个很好的学习Node.js的机会。通过学习本教程,你可以更好地了解后端开发的技术和Node.js在后端开发中的应用。初学…

日常学习记录随笔-redis实战

redis的持久化&#xff08;rdb,aof,混合持久化&#xff09; redis的主从架构以及redis的哨兵架构 redis的clusterredis 是要做持久化的&#xff0c;一般用redis会把数据放到缓存中为了提升系统的性能 如果redis没有持久化&#xff0c;重启的化数据就会丢失&#xff0c;所有的请…