webench源码阅读

简介

webbench是一款用C编写的开源工具,主要用来在Linux下进行网站压力测试。最多可以模拟3万个连接去测试网站的负载能力,并可以设置运行的客户端数、测试时间、使用的http协议版本、请求方法、是否需要等待服务器响应等选项,最后统计每分钟相应请求次数(paga/min)和每秒钟传输数据量(byte/sec),以及请求成功数和失败数,表现测试网站的压力承载能力。

源码及其相关函数分析

webbench源码
getopt_long
sigaction
fdopen

工作流程

	首先解析终端输入的命令,然后根据输入的url构建相应的HTTP请求,开始压力测试后,
创建pipe管道,再创建client个子进程,在子进程中向服务端发送HTTP请求进行压力测试,
然后通过管道将测试信息传送给父进程。父进程将通过管道读取每个子进程的测试结果,
进行汇总统计。

测试

代码与测试相结合,有助于理解
./webbench -c 3000 -t 5 http://baidu.com///结果
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.Benchmarking: GET http://baidu.com/
3000 clients, running 5 sec.Speed=28164 pages/min, 22326 bytes/sec.
Requests: 1648 susceed, 699 failed.

源码解析

webbench.c

main()

int main(int argc, char *argv[])
{int opt=0;int options_index=0;char *tmp=NULL;// 如果在终端只输入了./webbench,后面没有跟参数,打印用法,直接退出程序,返回码为2,表示格式错误if(argc==1){usage();return 2;} // ./webbench -c 3000 -t 5 url// 循环解析终端输入选项,每次解析一个选项及其后面可能跟的参数//该函数主要作用就是解析终端输入的参数,是否与(短选项)“912Vfrt:p:c:?h”或者与(长选项)“long_options”中任一符合while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ){switch(opt){case  0 : break;// 如果选项为'f',那么设置不等待服务器响应,发送请求后直接关闭连接case 'f': force=1;break;// 如果选项为'r',那么设置强制代理服务器重新发送请求case 'r': force_reload=1;break; // 如果选项为'9',那么设置在条件允许范围内使用HTTP/0.9协议case '9': http10=0;break;// 如果选项为'1',那么设置在条件允许范围内使用HTTP/1.0协议case '1': http10=1;break;// 如果选项为'2',那么设置在条件允许范围内使用HTTP/1.1协议case '2': http10=2;break;// 如果选项为'V',那么打印程序版本,然后退出程序case 'V': printf(PROGRAM_VERSION"\n");exit(0);// 如果选项为't',那么记录其后所跟参数数值到运行基准时间benchtime中case 't': benchtime=atoi(optarg);break;// 如果选项为'p',那么表示使用代理服务器	     case 'p': /* proxy server parsing server:port */// 记录参数中最后出现字符':'的位置及其之后的内容到tmp中tmp=strrchr(optarg,':');// 记录参数到代理主机proxyhost中proxyhost=optarg;// 如果参数中没有字符':',说明没有端口号,直接退出switchif(tmp==NULL){break;}// 如果参数中只有一个字符':',说明端口号在最前,打印缺失主机名,然后直接返回,返回码为2,表示格式错误if(tmp==optarg){fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);return 2;}// 如果参数中最后一个':'之后没有内容,打印缺失端口号,然后直接返回,返回码为2,表示格式错误if(tmp==optarg+strlen(optarg)-1){fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);return 2;}// 将proxyhost中的内容从最后一个':'处进行截断,只记录':'之前的内容*tmp='\0';// 将最后一个':'之后内容转化为数字并记录在代理服务器端口号proxyport中proxyport=atoi(tmp+1);break;// 如果选项为':'、'h'、'?',那么打印用法,并直接退出程序,返回码为2,表示格式错误case ':':case 'h':case '?': usage();return 2;break;// 如果选项为'c',那么记录其后所跟参数数值到客户端数量clients中case 'c': clients=atoi(optarg);break;}}// 如果参数后没有其它内容,打印缺失测试URL,打印用法后直接退出程序,返回码为2,表示格式错误if(optind==argc) {fprintf(stderr,"webbench: Missing URL!\n");usage();return 2;}// 如果输入客户端数量为0,则设置为默认值1if(clients==0) clients=1;// 如果输入运行测试的秒数为0,则设置为默认值60if(benchtime==0) benchtime=60;/* Copyright */// 打印版权信息fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n""Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n");// 将测试URL作为参数传入build_request方法中,构建http请求build_request(argv[optind]);/* print bench info */// 打印Benchmarking、请求方法、测试URLprintf("\nBenchmarking: ");switch(method){case METHOD_GET:default:printf("GET");break;case METHOD_OPTIONS:printf("OPTIONS");break;case METHOD_HEAD:printf("HEAD");break;case METHOD_TRACE:printf("TRACE");break;}printf(" %s",argv[optind]);// 判断使用的http协议类型,如果使用的是默认的HTTP/1.0则不打印switch(http10){// 如果http10的值为0,打印使用HTTP/0.9case 0: printf(" (using HTTP/0.9)");break;// 如果http10的值为2,打印使用HTTP/1.1case 2: printf(" (using HTTP/1.1)");break;}printf("\n");// 打印客户端数、运行秒数if(clients==1) printf("1 client");elseprintf("%d clients",clients);printf(", running %d sec", benchtime);// 打印是否不等待响应就提前关闭连接、是否通过代理服务器发送请求,是否无缓存if(force) printf(", early socket close");if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport);if(force_reload) printf(", forcing reload");printf(".\n");// 开始压力测试return bench();
}

bench

static int bench(void)
{int i,j,k;	pid_t pid=0;FILE *f;/* check avaibility of target server */// 建立一个TCP连接,检查连接可用性,如果设置了代理服务器,那么连接代理服务器,否则直接连接目标服务器i=Socket(proxyhost==NULL?host:proxyhost,proxyport);// 连接失败那么打印错误信息,并直接退出,返回码为1,表示基准测试失败(服务器未联机)if(i<0) { fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");return 1;}// 关闭连接,这次连接不计入测试close(i);/* create pipe */// 创建管道,如果失败,直接退出程序,返回码为3,表示内部错误if(pipe(mypipe)){perror("pipe failed.");return 3;}/* not needed, since we have alarm() in childrens *//* wait 4 next system clock tick *//*cas=time(NULL);while(time(NULL)==cas)sched_yield();*//* fork childs */// 创建clients个子进程,由子进程进行真正的测试for(i=0;i<clients;i++){pid=fork();// 如果是子进程或者创建失败,休眠1s后退出循环,让父进程先执行,完成初始化,并且保证子进程中不会再fork出新的子进程if(pid <= (pid_t) 0){/* child process or error*/sleep(1); /* make childs faster */break;}}// 如果创建子进程失败,那么打印fork失败,直接退出程序,返回码为3,表示fork失败if( pid< (pid_t) 0){fprintf(stderr,"problems forking worker no. %d\n",i);perror("fork failed.");return 3;}// 在子进程中if(pid== (pid_t) 0){/* I am a child */// 如果不使用代理服务器,那么子进程直接对目标服务器发出http请求,否则向代理服务器发出http请求if(proxyhost==NULL)benchcore(host,proxyport,request);elsebenchcore(proxyhost,proxyport,request);/* write results to pipe */// 获取管道写端的文件指针f=fdopen(mypipe[1],"w");// 获取失败,直接退出,返回码为3,表示内部错误if(f==NULL){perror("open pipe for writing failed.");return 3;}/* fprintf(stderr,"Child - %d %d\n",speed,failed); */// 将子进程的传输速率,测试失败数,总传输字节数写入管道,关闭写端fprintf(f,"%d %d %d\n",speed,failed,bytes);fclose(f);// 返回0,表示运行成功return 0;} else // 父进程中{// 获取管道读端的指针f=fdopen(mypipe[0],"r");// 获取失败,直接退出,返回码为3,表示内部错误if(f==NULL) {perror("open pipe for reading failed.");return 3;}// 设置不使用缓冲。每个I/O操作都被即时写入管道setvbuf(f,NULL,_IONBF,0);// 初始化传输速率,测试失败次数,传输总字节数都为0speed=0;failed=0;bytes=0;// 父进程循环读取数据 while(1){// 循环从管道中每3个一组读取子进程的输出数据,并且获取成功读取的参数个数pid=fscanf(f,"%d %d %d",&i,&j,&k);// 如果成功读取的个数小于2,说明有子进程中途挂掉,直接退出读取循环//把每个子进程输出的三个数据读取,在父进程中统计if(pid<2){fprintf(stderr,"Some of our childrens died.\n");break;}// 否则更新传输速率(测试成功个数),测试失败次数,传输总字节数//父进程中speed,filed,bytes三个变量对clients中的数据进行统计综合//父子进程遵循读时共享,写时复制,分析时请注意下speed+=i;failed+=j;bytes+=k;/* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */// 客户端数减一后如果等于0,说明没有多的客户端数据读取,直接退出循环if(--clients==0) break;}// 关闭读端fclose(f);// 打印统计的总的测试传输速度,请求成功数与失败数printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",(int)((speed+failed)/(benchtime/60.0f)),(int)(bytes/(float)benchtime),speed,failed);}return i;
}

benchcore

void benchcore(const char *host,const int port,const char *req)
{int rlen;char buf[1500];int s,i;struct sigaction sa;/* setup alarm signal handler */// 设置SIGALRM的信号处理函数sa.sa_handler=alarm_handler;sa.sa_flags=0;if(sigaction(SIGALRM,&sa,NULL))exit(3);// 设置计时器时间为运行测试的时间,到期后发送SIGALRM信号alarm(benchtime);// 获取请求报文大小rlen=strlen(req);// 进入循环,每次客户端建立一个连接,计时器时间到期后再退出nexttry:while(1){// 如果timerexpired等于1,说明收到了SIGALRM信号,表示计时器到期了,直接返回if(timerexpired){// 如果失败的测试数大于0,那么失败的测试数减一if(failed>0){/* fprintf(stderr,"Correcting failed by signal\n"); */failed--;}return;}// 建立与目标服务器的TCP连接s=Socket(host,port);// 如果连接失败,测试的失败数加一,继续循环                          if(s<0) { failed++;continue;} // 如果请求报文写入套接字失败,测试的失败数加一,继续循环if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}// 如果使用HTTP/0.9协议,因为会在服务器回复后自动断开连接,所以可以先关闭写端if(http10==0) // 如果写端关闭失败,那么说明是不正常的连接状态,测试的失败数加一,关闭连接,继续循环if(shutdown(s,1)) { failed++;close(s);continue;}// 如果设置需要等待服务器响应,那么还要处理响应数据,否则直接关闭连接if(force==0) {/* read all available data from socket */// 从套接字中读取数据while(1){// 如果计时器到期,结束读取if(timerexpired) break; // 将数据读取进buf中i=read(s,buf,1500);/* fprintf(stderr,"%d\n",i); */// 如果读取失败,测试的失败数加一,关闭连接,继续循环,客户端重新建立连接,直到计时器到期后再退出if(i<0) { failed++;close(s);goto nexttry;}else// 如果已经读取到文件末尾,结束读取数据if(i==0) break;// 如果读取到了数据,将总共传送的字节数加上读取到的数据的字节数elsebytes+=i;}}// 关闭连接,如果失败,测试失败数加一,继续循环if(close(s)) {failed++;continue;}// 传输速率(这里用测试成功数表示,后面除以时间得到真正的传输速率)加一speed++;}
}

socket.c


/* * 建立与目标的TCP连接,返回客户端连接使用的套接字* host: 目标域名或主机名* clientPort: 目标端口*/
int Socket(const char *host, int clientPort)
{int sock;               // 客户端套接字标识符unsigned long inaddr;   // 主机名的IP地址的数字形式struct sockaddr_in ad;  // 套接字地址结构struct hostent *hp;     // 域名IP地址// 初始化目标套接字的地址,指定使用的IP协议为IPv4memset(&ad, 0, sizeof(ad));ad.sin_family = AF_INET;// 将目标主机名的IP地址转换为数字inaddr = inet_addr(host);// 如果返回值不为INADDR_NONE,说明不是无效的IP地址,设置目标套接字的IP地址if (inaddr != INADDR_NONE)memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));// 否则说明是无效的IP地址,host不是主机名而是域名else{// 通过域名获取IP地址hp = gethostbyname(host);// 如果获取失败。返回-1if (hp == NULL)return -1;// 设置目标套接字的IP地址memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);}// 设置目标套接字的端口号ad.sin_port = htons(clientPort);// 创建一个使用TCP协议的socketsock = socket(AF_INET, SOCK_STREAM, 0);// 如果创建失败,直接返回if (sock < 0)return sock;// 进行连接,如果连接不成功,返回-1if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)return -1;// 如果连接成功,返回sockreturn sock;
}

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

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

相关文章

CTF-PWN-堆-【chunk extend/overlapping-1】

文章目录 chunk extend/overlappingfastbin与topchunk相邻free时候不会合并unsortedbinchunk中与topchunk相邻的被free时会合并extend向后overlapping先修改header&#xff0c;再free&#xff0c;再malloc先free&#xff0c;再修改header&#xff0c;再malloc extend向前overla…

Filter简单了解

1、filter能干嘛 过滤器实际上就是对web资源进行拦截&#xff0c;做一些处理后交给下一个过滤器或者servlet处理&#xff0c;通常都是拦截request的&#xff0c;也可以对response进行拦截处理&#xff1b; 2、面试考点&#xff1a;filter能干嘛&#xff08;应用场景&#xff0…

STL标准库(二)序列容器之vector

vector 动态数组 本质是向量&#xff0c;一个无限续存的连续内存空间 int main() { std::vector<int> obj(5); 创建一个容量为5且默认值为0的vector std::vector<int> obj(5&#xff0c;12138); 创建一个容量为5且默认值为12138的vector std::cout << obj.…

多维时序 | Matlab实现CNN-GRU-Mutilhead-Attention卷积门控循环单元融合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现CNN-GRU-Mutilhead-Attention卷积门控循环单元融合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现CNN-GRU-Mutilhead-Attention卷积门控循环单元融合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍…

深度学习-自然语言推断

自然语言推断&#xff08;natural language inference&#xff09;主要研究 假设&#xff08;hypothesis&#xff09;是否可以从前提&#xff08;premise&#xff09;中推断出来&#xff0c; 其中两者都是文本序列。 换言之&#xff0c;自然语言推断决定了一对文本序列之间的逻…

退出微软账号,edge/必应退出账号

微软账号退出&#xff1a;搜的教程都是说改成本地帐号&#xff0c;但是我的已经是本地帐号&#xff0c;操作没用。 但是找到了退出edge/必应浏览器账号的方法&#xff0c;见下图。 参考链接&#xff1a;奶酪真好次个人动态-奶酪真好次动态记录-哔哩哔哩视频 (bilibili.com)

【Linux】基本指令收尾

文章目录 日期查找打包压缩系统信息Linux和Windows互传文件 日期 这篇是基本指令的收尾了&#xff0c;还有几个基本指令我们需要说一下 首先是Date&#xff0c;它是用来显示时间和日期 直接输入date的话显示是有点不好看的&#xff0c;所以我们可以根据自己的喜欢加上分隔符&…

python常用命令汇总

一、下载对应源地址的库 以下是国内的python库原地址 清华&#xff1a;-i https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;-i http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 -i https://pypi.mirrors.ustc.edu.cn/simple/ 华中理工大学&#xff1a;-…

使用 Vector 在 Kubernetes 中收集日志

多年来&#xff0c;我们一直在使用 Vector 在我们的 Kubernetes 平台中收集日志&#xff0c;并成功地将其应用于生产中以满足各种客户的需求&#xff0c;并且非常享受这种体验。因此&#xff0c;我想与更大的社区分享它&#xff0c;以便更多的 K8s 运营商可以看到潜力并考虑他们…

【 CSS 】基础1

“坚持就是胜利。” - 温斯顿丘吉尔 【 CSS 】基础 1 CSS 简介 CSS 是层叠样式表 ( Cascading Style Sheets ) 的简称.有时我们也会称之为 CSS 样式表或级联样式表。CSS 也是一种标记语言CSS 主要用于设置 HTML 页面中的文本内容&#xff08;字体、大小、对齐方式等&#xff…

力扣日记1.22-【回溯算法篇】216. 组合总和 III

力扣日记&#xff1a;【回溯算法篇】216. 组合总和 III 日期&#xff1a;2023.1.22 参考&#xff1a;代码随想录、力扣 216. 组合总和 III 题目描述 难度&#xff1a;中等 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数…

Python使用gRPC入门,定义proto文件和收发消息

gRPC 一开始由 google 开发&#xff0c;是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。 本文通过一个简单的 Hello World 例子来向您介绍 gRPC 。 Grpc官方文档地址&#xff1a;Quick start | Python | gRPC gRPC 是什么&#xff1f; gRPC 也是基于以下理念&#…

Python武器库开发-武器库篇之Quake360-API使用(四十七)

Python武器库开发-武器库篇之Quake360-API使用(四十七) Quake360是一款网络资产搜索引擎&#xff0c;旨在帮助用户快速定位和识别网络上的资产信息。它具有强大的搜索功能&#xff0c;可以搜索并展示各种类型的网络资产&#xff0c;包括域名、IP地址、子域名、端口信息等。同时…

Unity编程#region..#endregion以及面板提示语标签[Tooltip(““)]

C#中的#region..#endregion 在Unity中&#xff0c;#region和#endregion是用于代码折叠的预处理指令。它们并不是Unity特有的&#xff0c;而是C#语言本身提供的功能。 #region用于标记一段代码的开始&#xff0c;而#endregion用于标记一段代码的结束。在编辑器中&#xff0c;可…

流量劵可以用来做什么?如何使用电脑提高工作效率

好久没来看了,一分钱收益也没有,不过倒是起了一点知识的积累传播。正确与否先不去考究,从有文字记载的历史来看,有记录这本身就是一种进步。最近偶尔会用到电脑处理一些EXCEL文档,很多的工作都是重复和简单的数学计算,然后去核对数据是不是勾稽得当。工作虽然不多,但也是…

橡木桶陈酿:木材选择、烤制程度与陈酿时间

在威士忌的酿造过程中&#xff0c;橡木桶陈酿是一个至关重要的环节。橡木桶不仅为威士忌提供了特别的香气和风味&#xff0c;还赋予其丰富的颜色和味蕾。本文将深入探讨橡木桶陈酿的奥秘&#xff0c;特别是木材选择、烤制程度以及陈酿时间对威士忌风味的影响&#xff0c;以雷盛…

【Linux】解决普通用户无法进行sudo提权

当某个普通用户进行sudo指令提权的时候&#xff0c;可能存在无法操作的问题&#xff0c;如下图&#xff1a; 这个图中有一个细节&#xff0c;我们使用sudo进行提权的时候&#xff0c;用的可是zhangsan的密码&#xff0c;因此有人可能会有疑问&#xff0c;这不是有问题吗&#x…

常见java,数据库锁汇总篇,舍我其谁

一 锁概念 1.1 java锁介绍 1.乐观锁 在select的时候不会加锁&#xff0c;是基于程序实现的&#xff0c;所以不会存在死锁的情况。 适用于读多写少的场景&#xff08;写的并发量相对不高&#xff09;&#xff0c;可以提高系统的吞吐量。 因为如果写多的话&#xff0c;乐观锁会…

安泰电子电压放大器的三个特性是指什么

电压放大器是一种主要用于将输入电压信号放大的电子器件。它的工作原理是通过增加电压信号的幅度&#xff0c;使得输出信号比输入信号有更大的电压差。电压放大器通常具有许多特性&#xff0c;其中三个主要特性包括增益、带宽和线性度。 增益是电压放大器最重要的特性之一。增益…

RabbitMQ-消息延迟

一、死信交换机 1、描述 一个队列接收到的消息有过期时间&#xff0c;消息过期之后&#xff0c;如果配置有死信队列&#xff0c;消息就会进去死信队列。 2、图解 3、过程 当生产者将消息发送到exchange1&#xff0c;然后交换机将消息路由到队列queue1&#xff0c;但是队列que…