原文链接:点击打开链接
Webbench是一个在Linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能。Webbench使用C语言编写,下面是其下载链接:
http://home.tiscali.cz/~cz210552/webbench.html
Webbench架构
该测试工具原理较为简单,使用fork创建子进程,通过子进程来测试http连接,把测试结果写到管道,再有父进程读取管道信息来计算测试结果。流程图下:
webbench源码
webbench的源代码,代码文件只有两个,Socket.c和webbench.c。首先看一下Socket.c,它当中只有一个函数int Socket(const char *host, int clientPort),大致内容如下:
- int Socket(const char *host, int clientPort)
- {
-
-
-
-
- }
接着我们来瞧一下webbench.c文件。这个文件中包含了以下几个函数,我们一一列举出来:
- <strong>static void alarm_handler(int signal); //为方便下文引用,我们称之为函数1。
- static void usage(void); //函数2
- void build_request(const char *url); //函数3
- static int bench(void); //函数4
- void benchcore(const char *host, const int port, const char *req); //函数5
- int main(int argc, char *argv[]); //函数6</strong>
(1)全局变量列表
源文件中出现在所有函数前面的全局变量,主要有以下几项,我们以注释的方式解释其在程序中的用途
- <strong>volatile int timerexpired=0;
- int speed=0;
- int failed=0;
- int bytes=0;
- int http10=1;
- int method=METHOD_GET;
- int clients=1;
- int force=0;
- int force_reload=0;
- int proxyport=80;
- char *proxyhost=NULL;
- int benchtime=30;
- int mypipe[2];
- char host[MAXHOSTNAMELEN];
- char request[REQUEST_SIZE];
(2)函数1: static void alarm_handler(int signal);
首先,来看一下最简单的函数,即函数1,它的内容如下:
- <strong>static void alarm_handler(int signal)
- {
- timerexpired=1;
- }</strong>
webbench
在运行时可以设定压测的持续时间,以秒为单位。例如我们希望测试
30
秒,也就意味着压测
30
秒后程序应该退出了。
webbench
中使用信号(
signal
)来控制程序结束。函数
1
是在到达结束时间时运行的信号处理函数。它仅仅是将一个记录是否超时的变量
timerexpired
标记为
true
。后面会看到,在程序的
while
循环中会不断检测此值,只有
timerexpired=1
,程序才会跳出
while
循环并返回。
(3)函数2 :static void usage(void);
- <strong>static void usage(void)
- {
- fprintf(stderr,
- "webbench [option]... URL\n"
- " -f|--force Don't wait for reply from server.\n"
- " -r|--reload Send reload request - Pragma: no-cache.\n"
- " -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n"
- " -p|--proxy <server:port> Use proxy server for request.\n"
- " -c|--clients <n> Run <n> HTTP clients at once. Default one.\n"
- " -9|--http09 Use HTTP/0.9 style requests.\n"
- " -1|--http10 Use HTTP/1.0 protocol.\n"
- " -2|--http11 Use HTTP/1.1 protocol.\n"
- " --get Use GET request method.\n"
- " --head Use HEAD request method.\n"
- " --options Use OPTIONS request method.\n"
- " --trace Use TRACE request method.\n"
- " -?|-h|--help This information.\n"
- " -V|--version Display program version.\n"
- );
- };</strong>
(4)函数3:void build_request(const char *url);
这个函数主要操作全局变量char request[REQUEST_SIZE],根据url填充其内容。一个典型的http GET请求如下:
- <strong>GET /test.jpg HTTP/1.1
- User-Agent: WebBench 1.5
- Host:192.168.10.1
- Pragma: no-cache
- Connection: close</strong>
build_request
函数的目的就是要把类似于以上这一大坨信息全部存到全局变量
request[REQUEST_SIZE]
中,其中换行操作使用的是”
\r\n”
。而以上这一大坨信息的具体内容是要根据命令行输入的参数,以及
url
来确定的。该函数使用了大量的字符串操作函数,例如
strcpy
,
strstr
,
strncasecmp
,
strlen
,
strchr
,
index
,
strncpy
,
strcat
。对这些基础函数不太熟悉的同学可以借这个函数复习一下。
build_request
的具体内容在此不做过多阐述。
(5)函数6:int main(int argc, char *argv[]);
之所以把函数6放在了函数4和函数5之前,是因为函数4和5是整个工具的最核心代码,我们把他放在最后分析。先来看一下整个程序的起始点:主函数(即函数6)。
- <strong>int main(int argc, char *argv[])
- {
-
-
-
-
-
-
-
- build_request(argv[optind]);
-
-
-
-
-
-
-
-
- return bench();
-
- }</strong>
(
6
)函数
4
:
static int bench(void);
- static int bench(void){
- int i,j,k;
- pid_t pid=0;
- FILE *f;
-
- i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
- if(i<0){ }
- close(i);
-
- if(pipe(mypipe)){ }
- for(i=0;i<clients;i++){
- pid=fork();
- if(pid <= (pid_t) 0){
- sleep(1);
- break;
- }
- }
- if( pid< (pid_t) 0){ }
-
- if(pid== (pid_t) 0){
- if(proxyhost==NULL)
- benchcore(host,proxyport,request);
- else
- benchcore(proxyhost,proxyport,request);
-
- f=fdopen(mypipe[1],"w");
- if(f==NULL){ }
- fprintf(f,"%d %d %d\n",speed,failed,bytes);
- fclose(f);
- return 0;
- } else{
- f=fdopen(mypipe[0],"r");
- if(f==NULL) { }
- setvbuf(f,NULL,_IONBF,0);
- speed=0; failed=0; bytes=0;
-
- while(1){
- pid=fscanf(f,"%d %d %d",&i,&j,&k);
- if(pid<2){ }
- speed+=i; failed+=j; bytes+=k;
- 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;
- }
这段代码,一上来先进行了一次socket连接,确认能连通以后,才进行后续步骤。调用pipe函数初始化一个管道,用于子进行向父进程汇报测试数据。子进程根据clients数量fork出来。每个子进程都调用函数5进行测试,并将结果输出到管道,供父进程读取。父进程负责收集所有子进程的测试数据,并汇总输出。
(7)函数5:void benchcore(const char *host,const int port,const char *req);
- void benchcore(const char *host,const int port,const char *req){
- int rlen;
- char buf[1500];
- int s,i;
- struct sigaction sa;
-
- sa.sa_handler=alarm_handler;
- sa.sa_flags=0;
- if(sigaction(SIGALRM,&sa,NULL))
- exit(3);
-
- alarm(benchtime);
- rlen=strlen(req);
- nexttry:while(1){
- if(timerexpired){
- if(failed>0){failed--;}
- return;
- }
- s=Socket(host,port);
- if(s<0) { failed++;continue;}
- if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}
- if(http10==0)
- if(shutdown(s,1)) { failed++;close(s);continue;}
-
- if(force==0){
- while(1){
- if(timerexpired) break;
- i=read(s,buf,1500);
- if(i<0) {
- failed++;
- close(s);
- goto nexttry;
- }else{
- if(i==0) break;
- else
- bytes+=i;
- }
- }
- }
- if(close(s)) {failed++;continue;}
- speed++;
- }
- }
benchcore是子进程进行压力测试的函数,被每个子进程调用。这里使用了SIGALRM信号来控制时间,alarm函数设置了多少时间之后产生SIGALRM信号,一旦产生此信号,将运行函数1,使得timerexpired=1,这样可以通过判断timerexpired值来退出程序。另外,全局变量force表示我们是否在发出请求后需要等待服务器的响应结果。