网站压力测试工具-Webbench源码笔记

时间:2023-12-16 11:42:32

Ubuntu 下安装使用

1、安装依赖包CTAGS

sudo apt-get install ctage

2、下载及安装 Webbench

http://home.tiscali.cz/~cz210552/webbench.html

解压:

tar -zxvf webbench-1.5.tar.gz

切换到解压后的目录:

cd webbench-1.5

编译:

make

安装:

sudo make install

webbench使用

#webbench -? (查看命令帮助)

常用参数 说明,-c 表示客户端数,-t 表示时间

./webbench -c 500 -t 30 http://xyzp.xaut.edu.cn/Plugins/YongHu_plug/Pages/loginbysjy.aspx

代码学习:

众所周知,C程序的主函数有两个参数,其中第一个参数是整数,可以获得包括程序名字的参数个数,第二个参数是字符数组或字符指针的指针,可以按顺序获得命令行上各个字符串的参数。其原型是:

int main(int argc, char const *argv[])

或者

int main(int argc, char const **argv)

有鉴于此,在Unix和Linux的正式项目上,程序员通常会使用getopt()或者getopt_long()来获得输入的参数。两者的区别在于getopt()仅支持短格式参数,而getopt_long()既支持短格式参数,也支持长格式参数。

./webbench -V

1.5

./webbench --version

1.5

关于getopt_long()的具体用法参考:man getopt_long

在处理命令行参数时,用到一个变量 optind, 原来是系统定义的。

可以在命令行中,通过 man optind 来看相关信息

optind: the index of the next element to be processed in the argv.  The system initializes it to 1. The caller can reset it to 1 to restart scanning of the same argv or scanning a new argument vector.

当调用 getopt() , getopt_long() 之类的函数时, optind 的值会变化。如:

执行 $  ./a.out -ab         当调用  一次  getopt() , 则 optind 值会 +1

进程间通信的方式之管道:管道分为无名管道(匿名管道)和命名管道

使用无名管道,则是通信的进程之间需要一个父子关系,通信的两个进程一定是由一个共同的祖先进程启动。但是无名管道没有数据交叉的问题。

使用命名管道可以解决无名管道中出现的通信的两个进程一定是由通一个共同的祖先进程启动的问题,但是为了数据的安全,很多时候要采用阻塞的FIFO,让写操作变成原子操作。

只有两个源文件:webbench.c, socket.c。
 /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $
*
* This module has been modified by Radim Kolar for OS/2 emx
*/ /***********************************************************************
module: socket.c
program: popclient
SCCS ID: @(#)socket.c 1.5 4/1/94
programmer: Virginia Tech Computing Center
compiler: DEC RISC C compiler (Ultrix 4.1)
environment: DEC Ultrix 4.3
description: UNIX sockets code.
***********************************************************************/ #include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h> /*
根据通信地址和端口号建立网络连接
@host:网络地址
@clientPort:端口号
成功返回建立连接套接字
建立套接字失败返回-1
*/
int Socket(const char *host, int clientPort)
{
int sock;
unsigned long inaddr;
struct sockaddr_in ad;
struct hostent *hp; memset(&ad, , sizeof(ad));
ad.sin_family = AF_INET;
//将点分十进制的IP地址转化为无符号的长整形
inaddr = inet_addr(host);
if (inaddr != INADDR_NONE)
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
else
{
//如果host是域名,则通过域名获取IP地址
hp = gethostbyname(host);
if (hp == NULL)
return -;
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
}
ad.sin_port = htons(clientPort); sock = socket(AF_INET, SOCK_STREAM, );
if (sock < )
return sock;
if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < )
return -;
return sock;
}

socket.c

 /*
* (C) Radim Kolar 1997-2004
* This is free software, see GNU Public License version 2 for
* details.
*
* Simple forking WWW Server benchmark:
*
* Usage:
* webbench --help
*
* Return codes:
* 0 - sucess
* 1 - benchmark failed (server is not on-line)
* 2 - bad param
* 3 - internal error, fork failed
*
*/
#include "socket.c"
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h> /* values */
volatile int timerexpired=;
int speed=;
int failed=;
int bytes=;
/* globals */
int http10=; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
/* Allow: GET, HEAD, OPTIONS, TRACE */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
int method=METHOD_GET;
int clients=;
int force=;
int force_reload=;
int proxyport=;
char *proxyhost=NULL;
int benchtime=;
/* internal */
int mypipe[]; //主机名的最大长度通常是由头文件<sys/param.h>定义的常值MAXHOSTNAMELEN
char host[MAXHOSTNAMELEN];
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE]; static const struct option long_options[]=
{
{"force",no_argument,&force,},
{"reload",no_argument,&force_reload,},
{"time",required_argument,NULL,'t'},
{"help",no_argument,NULL,'?'},
{"http09",no_argument,NULL,''},
{"http10",no_argument,NULL,''},
{"http11",no_argument,NULL,''},
{"get",no_argument,&method,METHOD_GET},
{"head",no_argument,&method,METHOD_HEAD},
{"options",no_argument,&method,METHOD_OPTIONS},
{"trace",no_argument,&method,METHOD_TRACE},
{"version",no_argument,NULL,'V'},
{"proxy",required_argument,NULL,'p'},
{"clients",required_argument,NULL,'c'},
{NULL,,NULL,}
}; /* prototypes */
static void benchcore(const char* host,const int port, const char *request);
static int bench(void);
static void build_request(const char *url); static void alarm_handler(int signal)
{
timerexpired=;
} 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"
);
}; int main(int argc, char *argv[])
{
int opt=;
int options_index=;
char *tmp=NULL; if(argc==)
{
usage();
return ;
} //解析命令行参数
while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
{
switch(opt)
{
case : break;
case 'f': force=;break;
case 'r': force_reload=;break;
case '': http10=;break;
case '': http10=;break;
case '': http10=;break;
case 'V': printf(PROGRAM_VERSION"\n");exit();
case 't': benchtime=atoi(optarg);break;
case 'p':
/* proxy server parsing server:port */
tmp=strrchr(optarg,':');
proxyhost=optarg;
if(tmp==NULL)
{
break;
}
if(tmp==optarg)
{
fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
return ;
}
if(tmp==optarg+strlen(optarg)-)
{
fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
return ;
}
*tmp='\0';
proxyport=atoi(tmp+);
break;
case ':':
case 'h':
case '?':
usage();
return ;
break;
case 'c':
clients=atoi(optarg);
break;
}
} //判断命令行参数是否解析完成,如果完成代表没有需要访问的超链接
//optind的值随着调用getopt_long次数改变,每调用一次加一
if(optind==argc)
{
fprintf(stderr,"webbench: Missing URL!\n");
usage();
return ;
} if(clients==)
{
clients=;
}
if(benchtime==)
{
benchtime=;
} /* Copyright */
fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
"Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"); build_request(argv[optind]); //对设置参数的一些显示信息
/* print bench info */
printf("\n Bench marking: "); 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]);
switch(http10)
{
case : printf(" (using HTTP/0.9)");break;
case : printf(" (using HTTP/1.0)");break;
case : printf(" (using HTTP/1.1)");break;
}
printf("\n"); //显示一些参数
if(clients==)
{
printf("1 client");
}
else
{
printf("%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();
}
/*
*输入URL的连接
*解析URl,构造请求头并且将请求头,
*保存到全局变量request中
GET /Plugins/YongHu_plug/Pages/loginbysjy.aspx HTTP/1.0
User-Agent: WebBench 1.5
Host: xyzp.xaut.edu.cn
*/
void build_request(const char *url)
{
char tmp[];
int i;
//清空存储区
bzero(host,MAXHOSTNAMELEN);
bzero(request,REQUEST_SIZE);
//force_reload不等待响应,强制刷新
//proxyhost是否设置了代理
//判断是否设置了Http的通信协议版本
if(force_reload && proxyhost!=NULL && http10<)
{
http10=;
}
//请求方式和协议版本
if(method==METHOD_HEAD && http10<)
{
http10=;
}
if(method==METHOD_OPTIONS && http10<)
{
http10=;
}
if(method==METHOD_TRACE && http10<)
{
http10=;
} switch(method)
{
default:
case METHOD_GET: strcpy(request,"GET");break;
case METHOD_HEAD: strcpy(request,"HEAD");break;
case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
case METHOD_TRACE: strcpy(request,"TRACE");break;
} strcat(request," "); //判断Http请求的地址正确性以及有效性
if(NULL==strstr(url,"://"))
{
fprintf(stderr, "\n%s: is not a valid URL.\n",url);
exit();
}
if(strlen(url)>)
{
fprintf(stderr,"URL is too long.\n");
exit();
} if(proxyhost==NULL)
{
if (!=strncasecmp("http://",url,))
{
fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
exit();
}
}
//跳过超链接的协议头,找到*域名
/* protocol/host delimiter */
i=strstr(url,"://")-url+;
/* printf("%d\n",i); */ //跳过*域名
if(strchr(url+i,'/')==NULL)
{
fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n");
exit();
}
if(proxyhost==NULL)
{
//获取指定的通信端口,如果不存在则使用默认的80端口,同时获得主机名称-域名,网页的绝对路径
/* get port from hostname */
if(index(url+i,':')!=NULL &&
index(url+i,':')<index(url+i,'/'))
{
strncpy(host,url+i,strchr(url+i,':')-url-i);
bzero(tmp,);
strncpy(tmp,index(url+i,':')+,strchr(url+i,'/')-index(url+i,':')-);
/* printf("tmp=%s\n",tmp); */
proxyport=atoi(tmp);
if(proxyport==) proxyport=;
}
else
{
strncpy(host,url+i,strcspn(url+i,"/"));
}
//printf("Host=%s\n",host);
strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
}
else
{
// printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
strcat(request,url);
}
//设置请求协议的版本,拼接请求头
if(http10==)
{
strcat(request," HTTP/1.0");
}
else if (http10==)
{
strcat(request," HTTP/1.1");
}
strcat(request,"\r\n"); if(http10>)
{
strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");
} if(proxyhost==NULL && http10>)
{
strcat(request,"Host: ");
strcat(request,host);
strcat(request,"\r\n");
}
if(force_reload && proxyhost!=NULL)
{
strcat(request,"Pragma: no-cache\r\n");
}
if(http10>)
{
strcat(request,"Connection: close\r\n");
} /* add empty line at end */
if(http10>)
{
strcat(request,"\r\n");
}
//printf("Req=%s\n",request);
} /* vraci system rc error kod */
static int bench(void)
{
int i,j,k;
pid_t pid=;
FILE *f; /* check avaibility of target server */
//测试远程主机的是否可用
i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
if(i<)
{
fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
return ;
}
close(i); /* create pipe */
//创建无名管道
if(pipe(mypipe))
{
perror("pipe failed.");
return ;
} /* 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 */
//创建进程
for(i = ;i < clients;i++)
{
/*
调用fork有一个特殊的地方,就是调用一次却能返回两次,有三种不同的返回值:
1、在父进程中,fork返回新创建的子进程的进程ID
2、在子进程中,fork返回0
3、如果出现错误,fork返回一个负值
*/
pid=fork();
if(pid <= (pid_t) )
{
/* child process or error*/
sleep(); /* make childs faster */
/*这个break很重要,它主要让子进程只能从父进程生成,
否则子进程会在创建子进程,子子孙孙无穷尽*/
break;
}
} if( pid< (pid_t) )
{
fprintf(stderr,"problems forking worker no. %d\n",i);
perror("fork failed.");
return ;
} if(pid== (pid_t) )
{
/* I am a child */
//子进程执行请求,所有子进程都发出请求
if(proxyhost==NULL)
{
benchcore(host,proxyport,request);
}
else
{
benchcore(proxyhost,proxyport,request);
}
/* write results to pipe */
//打开无名管道的写端口
f = fdopen(mypipe[],"w");
if(f==NULL)
{
perror("open pipe for writing failed.");
return ;
}
/* fprintf(stderr,"Child - %d %d\n",speed,failed); */
//向管道中写入数据
fprintf(f,"%d %d %d\n",speed,failed,bytes);
fclose(f);
return ;
}
else
{
//父进程中打开管道的读端口
f=fdopen(mypipe[],"r");
if(f==NULL)
{
perror("open pipe for reading failed.");
return ;
}
//设置缓冲区大小
setvbuf(f,NULL,_IONBF,);
//初始化访问速度、失败次数、字节数
speed=;
failed=;
bytes=; while()
{
//获取无名管道中的数据,进行统计
pid=fscanf(f,"%d %d %d",&i,&j,&k);
if(pid<)
{
fprintf(stderr,"Some of our childrens died.\n");
break;
}
speed+=i;
failed+=j;
bytes+=k;
/* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
if(--clients==)
{
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;
} void benchcore(const char *host,const int port,const char *req)
{
int rlen;
char buf[];
int s,i;
struct sigaction sa; /* setup alarm signal handler */
//这个是关键,当程序执行到指定的秒数之后,发送 SIGALRM 信号
sa.sa_handler=alarm_handler;
sa.sa_flags=;
if(sigaction(SIGALRM,&sa,NULL))
{
exit();
} alarm(benchtime); rlen=strlen(req);
//无限执行请求,并发操作,直到接收到 SIGALRM 信号将 timerexpired 设置为 1 时
nexttry:while()
{
if(timerexpired)
{
if(failed>)
{
/* fprintf(stderr,"Correcting failed by signal\n"); */
failed--;
}
return;
}
//连接服务器
s=Socket(host,port);
if(s<)
{
failed++;
continue;
}
//发送请求头
if( rlen!=write(s,req,rlen) )
{
failed++;
close(s);
continue;
} if(http10==)
{
//如果是 http/0.9 则关闭socket的写操作
if(shutdown(s,))
{
failed++;
close(s);
continue;
}
} if(force==)
{
/* read all available data from socket */
//读取服务器的响应数据,计算传输的字节数
while()
{
if(timerexpired)
{
break;
}
i = read(s,buf,);
/* fprintf(stderr,"%d\n",i); */
//响应失败关闭连接,执行下一次请求,记录失败的次数
if( i < )
{
failed++;
close(s);
goto nexttry;
}
else if(i==)
{
break;
}
else
{
bytes+=i;
}
}
}
//关闭连接,执行下一次请求
if(close(s))
{
failed++;
continue;
}
//记录请求成功的次数
speed++;
}
}