当前位置: 首页 > news >正文

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

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) {// 此处代码有问题,当读取失败后(对端关闭连接或读函数失败)// 先关闭了连接,然后接着又监听被关闭连接的可写状态,此处关闭连接后,应该continueif (!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这两个进程将一直运行下去,并不断交换数据。

相关文章:

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

Linux平台的一个优秀特性是内核微调&#xff0c;即我们可以通过修改文件的方式来调整内核参数。 服务器开发过程中&#xff0c;可能会碰到意想不到的错误&#xff0c;一种调试方法是用tcpdump抓包&#xff0c;但这种方法主要用于分析程序的输入和输出&#xff0c;对于服务器的…...

第三期:云函数入门指南答案

1.云函数需要用户自行考虑租用/购买多少资源以达到最少成本最高效运行自己的函数。 答案&#xff1a;错误(False) 2.Cloud Functions可以为您准备好计算资源&#xff0c;弹性地、可地运行任务&#xff0c;并提供日志查询、性能监控和报警等功能。 答案&#xff1a;正确(True…...

企业怎么通过数字化工具来实现数字化转型?

数字化转型是使用数字技术和工具从根本上改变公司运营方式并向客户提供价值的过程。它涉及思维方式、流程和技术的全面转变&#xff0c;以跟上快节奏的数字时代。以下是有关公司如何通过数字工具实现数字化转型的分步指南&#xff1a; 1.定义您的愿景和目标&#xff1a; 首先确…...

React函数式写法和类式写法的区别(以一个计数器功能为例子)

函数式写法更加简洁和函数式编程思维导向&#xff0c;适用于无状态、UI纯粹的组件&#xff0c;且可以使用Hooks处理副作用。而类式写法适用于有内部状态、生命周期方法和复杂交互逻辑的组件&#xff0c;提供了更多的灵活性和控制力。 文章目录 一、计数器功能演示 1.函数式写法…...

【根据国防科大学报官网word模板修改的Latex模板】

根据国防科大学报官网word模板修改的Latex模板 学报Word模板链接Latex模板结构编译环境为Texlivevscode或Textstudio 学报Word模板链接 学报官网相关下载链接 点击链接即可前往官网下载相关word模板 Latex模板结构 latex模板 ass.cfg文件 %深层模板文件ass.cls文件 %浅层模板…...

系列十一、Redis中分布式缓存实现

一、缓存 1.1、什么是缓存 内存就是计算机内存中的一段数据。 1.2、内存中的数据特点 读写快断电数据丢失 1.3、缓存解决了什么问题 提高了网站的吞吐量和运行效率减轻了数据库的访问压力 1.4、哪些数据适合加缓存 使用缓存时&#xff0c;一定是数据库中的数据极少发生改…...

Spark大数据分析与实战笔记(第一章 Scala语言基础-4)

文章目录 每日一句正能量1.4 Scala面向对象的特性1.4.1 类与对象的特性1.4.2 继承1.4.3 单例对象和伴生对象1.4.4 特质 每日一句正能量 若要快乐&#xff0c;就要随和&#xff1b;若要幸福&#xff0c;就要随缘。快乐是心的愉悦&#xff0c;幸福是心的满足。别和他人争吵&#…...

腾讯云服务器端口localhost可以访问,外部无法访问解决

搭建frp跳板&#xff0c;发现无法使用。ssh 连接不上。 主要检查2个东西&#xff1a; 1. ubuntu ufw系统防火墙。这个默认是关掉的 2. tencent这个防火墙规则设置后&#xff0c;还要设置到实例上。 以前不是这样的。就掉坑里了。 # systemctl rootVM-4-4-ubuntu:/lib/syst…...

【软考-中级】系统集成项目管理工程师 【16 变更管理】

持续更新。。。。。。。。。。。。。。。 【第十六章】变更管理 (选择2分 考点 1:变更的常见原因考点 2:变更管理的原则是项目基准化、变更管理过程规范化考点 3考点 4考点 5:变更的工作程序考点 6考点 7考点 8考点 9考点 10考点 11考点 12:变更分类系列文章经典语录 考点 1:变…...

【Eclipse】查看版本号

1.在Eclipse的启动页面会出现版本号 2. Eclipse的关于里面 Help - About Eclipse IDE 如下图所示&#xff0c;就为其版本 3.通过查看readme_eclipse.html文件...

论文精讲目录

ViT论文逐段精读【论文精读】MoCo 论文逐段精读【论文精读】对比学习论文综述【论文精读】Swin Transformer论文精读【论文精读】CLIP 论文逐段精读【论文精读】双流网络论文逐段精读【论文精读】I3D 论文精读【论文精读】视频理解论文串讲&#xff08;上&#xff09;【论文精读…...

双飞翼布局和圣杯布局

双飞翼布局和圣杯布局都是一种三栏布局&#xff0c;其中主要内容区域位于中间&#xff0c;左侧栏和右侧栏位于两侧。它们的实现方式类似&#xff0c;但有一些细微的差别。 双飞翼布局的实现原理是通过使用flex布局&#xff0c;给主要内容区域设置flex&#xff1a;1&#xff1b…...

Hive insert插入数据与with子查询

1. insert into 与 insert overwrite区别 insert into 与 insert overwrite 都可以向hive表中插入数据&#xff0c;但是insert into直接追加到表中数据的尾部&#xff0c;而insert overwrite会重写数据&#xff0c;既先进行删除&#xff0c;再写入 注意&#xff1a;如果存在分…...

如何在Django中集成JWT

文章目录 JWT简介在Django中使用JWT1. 安装2. 配置3. 添加认证接口 客户端使用JWT1. 获取新token2. 调用API3. 刷新token 同步发布在个人站点&#xff1a;https://panzhixiang.cn JWT简介 JWT(JSON Web Token)是一种流行的跨域认证解决方案。它可以在令牌中安全地传输用户身份…...

hive进行base64 加密解密函数

加密 select base64(cast(abcd as binary))YWJjZA 解密 -- 直接解密&#xff08;结果字段格式为比binary格式&#xff09; select unbase64(YWJjZA) -- 格式转换 select cast(unbase64(YWJjZA) as string) abcd...

Docker安装GitLab及使用图文教程

作者&#xff1a; 宋发元 GitLab安装及使用教程 官方教程 https://docs.gitlab.com/ee/install/docker.html Docker安装GitLab 宿主机创建容器持久化目录卷 mkdir -p /docker/gitlab/{config,data,logs}拉取GitLab镜像 docker pull gitlab/gitlab-ce:15.3.1-ce.0运行GitLa…...

asp.net酒店管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net酒店管理系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 asp.net 酒店管理系统1 二、功能介绍 …...

Yolov安全帽佩戴检测 危险区域进入检测 - 深度学习 opencv 计算机竞赛

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; Yolov安全帽佩戴检测 危险区域进入检测 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&am…...

vue中动态设置source标签

项目中有个视频播放,路径通过接口返回,而且不带后缀,并不确定是什么类型的视频文件,所以要通过source标签去进行设置. 问题:当video中存在source标签的时候&#xff0c;浏览器渲染之后会自动去获取地址&#xff0c;即便地址改变&#xff0c;浏览器也不会再去获取地址。 解决方…...

【16】基础知识:React路由 - React Router 6

一、概述 了解 React Router 以三个不同的包发布到 npm 上&#xff0c;它们分别为 1、react-router&#xff1a;路由的核心库&#xff0c;提供了很多的组件、钩子。 2、react-router-dom&#xff1a;包含 react-router 所有内容&#xff0c;并添加一些专门用于 DOM 的组件&…...

Unity3D 基础——Coroutine 协同程序

Coroutine 称为协同程序或者协程&#xff0c;协同程序可以和主程序并行运行&#xff0c;和多线程有些类似。协同程序可以用来实现让一段程序等待一段时间后继续运行的效果。例如&#xff0c;执行步骤1&#xff0c;等待3秒&#xff1b;执行步骤2&#xff0c;等待某个条件为 true…...

IDEA报错:前言中不允许有内容

idea启动项目提示前言中不允许有内容 .idea\libraries\Maven__axis2_axis2_1_0.xml: ParseError at [row,col]:[1,1] Message: 前言中不允许有内容。 解决方法&#xff1a; 首先修改设置&#xff1a;IDEA–>File–>Settings–>File Encodings–>with No BOM选中报…...

在线课堂分销商城小程序源码系统 带完整搭建教程

大家好啊&#xff0c;今天来给大家分享一个在线课堂分销商城小程序源码系统&#xff0c;一起来看看吧。以下是部分功能实现的核心代码&#xff1a; 系统特色功能一览&#xff1a; 商品模块。包括实物商品、虚拟商品和电子卡密等&#xff0c;每种商品可以设置对应的商品分类。同…...

【存储系统】0. 序

学习资料&#xff1a;大话存储 存储系统底层架构原理极限剖析 终极版 张冬编著——清华大学出版社 2015.01 文章目录 0.1 序0.1.1 信息存储技术溯源0.1.2 数字化信息推动存储技术发展0.1.3 数字存储技术 0.2 存储系统介绍0.2.1 信息0.2.2 数据0.2.3 数据存储0.2.4 用计算机来处…...

逐字稿 | 2 MoCo 论文逐段精读【论文精读】

bryanyzhu的个人空间-bryanyzhu个人主页-哔哩哔哩视频 评价 今天我们一起来读一下 MOCO 这篇论文。 MOCO 是 CVPR 2020 的最佳论文提名&#xff0c;算是视觉领域里使用对比学习的一个里程碑式的工作。而对比学习作为从 19 年开始一直到现在视觉领域乃至整个机器学习领域里最炙…...

【数据结构】排序算法的稳定性分析

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

Redis AOF持久化和ReWrite

前言 Redis 的 RDB 持久化机制简单直接&#xff0c;把某一时刻的所有键值对以二进制的方式写入到磁盘&#xff0c;特点是恢复速度快&#xff0c;尤其适合数据备份、主从复制场景。但如果你的目的是要保证数据可靠性&#xff0c;RDB 就不太适合了&#xff0c;因为 RDB 持久化不…...

Flink学习之旅:(一)Flink部署安装

1.本地搭建 1.1.下载Flink 进入Flink官网&#xff0c;点击Downloads 往下滑动就可以看到 Flink 的所有版本了&#xff0c;看自己需要什么版本点击下载即可。 1.2.上传解压 上传至服务器&#xff0c;进行解压 tar -zxvf flink-1.17.1-bin-scala_2.12.tgz -C ../module/ 1.3.启…...

Go语言入门心法(六): HTTP面向客户端|服务端编程

Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 一:go语言面向web编程认知 Go语言的最大优势在于并发与性能,其性能可以媲美C和C,并发在网络编程中更是至关重要 使用http发送请…...

为什么非const静态成员变量一定要在类外定义

当我们如下声明了一个类&#xff1a; class A{public:static int sti_data;// 这个语句在c11前不能通过编译&#xff0c;在c11的新标准下&#xff0c;已经能够在声明一个普通变量是就对其进行初始化。int a 10&#xff1b;static const int b 1;//...其他member };// 在类外…...