webbench是用c语言来实现的网站压力测试工具,可以并发产生3万个链接测试网站。在学习webbench时候最好先简单了解一下http协议,推荐小日本的一本书《图解http》,适合入门,图文并茂,先简单介绍下wenbench的使用方法,在ubuntu14.04上安装完后,执行:

root@zhuzhu:webbench-1.5#webbenchwebbench[option]...URL-f|--forceDon'twaitforreplyfromserver.-r|--reloadSendreloadrequest-Pragma:no-cache.-t|--time<sec>Runbenchmarkfor<sec>seconds.Default30.-p|--proxy<server:port>Useproxyserverforrequest.-c|--clients<n>Run<n>HTTPclientsatonce.Defaultone.-9|--http09UseHTTP/0.9stylerequests.-1|--http10UseHTTP/1.0protocol.-2|--http11UseHTTP/1.1protocol.--getUseGETrequestmethod.--headUseHEADrequestmethod.--optionsUseOPTIONSrequestmethod.--traceUseTRACErequestmethod.-?|-h|--helpThisinformation.-V|--versionDisplayprogramversion.root@zhuzhu:webbench-1.5#

可以看到全部的参数,常用的参数 -c 表示模拟的用户数,-t 表示持续发起连接的时间:

webbench -c 5 -t 10 http://www.baidu.com/

其结果如下:

root@zhuzhu:webbench-1.5#./webbench-c5-t10http://www.baidu.com/Webbench-SimpleWebBenchmark1.5Copyright(c)RadimKolar1997-2004,GPLOpenSourceSoftware.Benchmarking:GEThttp://www.baidu.com/5clients,running10sec.Speed=594pages/min,1036972bytes/sec.Requests:99susceed,0failed.

下面开始介绍下源码,对webbench做了一些简单的修改,但基本内容和组织没有变。全部源码见:

https://github.com/zhukunbo/webbench


1、软件流程


2、代码详解

#include"socket.c"#include<stdio.h>#include<string.h>#include<unistd.h>#include<stdlib.h>#include<rpc/types.h>#include<getopt.h>#include<strings.h>#include<time.h>#include<signal.h>#include"webbench.h"staticintmypipe[2];staticintforce;staticintforce_reload;staticinthttp10=1;staticintbench_time;staticintproxy_port=HTTP_DEF_PORT;staticintclients;staticintspeed;staticintfailed;staticintbytes;staticinttime_out;staticchar*proxy_host;staticintmethod=METHOD_GET;staticcharhost[MAXHOSTNAMELEN];staticcharrequest[REQUEST_SIZE];intdebug_open=1;staticconststructoptionlong_options[]={{"force",no_argument,&force,1},{"reload",no_argument,&force_reload,1},{"time",required_argument,NULL,'t'},{"help",no_argument,NULL,'?'},{"http09",no_argument,NULL,'9'},{"http10",no_argument,NULL,'1'},{"http11",no_argument,NULL,'2'},{"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,0,NULL,0}};staticvoidusage(void){fprintf(stderr,"webbench[option]...URL\n""-f|--forceDon'twaitforreplyfromserver.\n""-r|--reloadSendreloadrequest-Pragma:no-cache.\n""-t|--time<sec>Runbenchmarkfor<sec>seconds.Default30.\n""-p|--proxy<server:port>Useproxyserverforrequest.\n""-c|--clients<n>Run<n>HTTPclientsatonce.Defaultone.\n""-9|--http09UseHTTP/0.9stylerequests.\n""-1|--http10UseHTTP/1.0protocol.\n""-2|--http11UseHTTP/1.1protocol.\n""--getUseGETrequestmethod.\n""--headUseHEADrequestmethod.\n""--optionsUseOPTIONSrequestmethod.\n""--traceUseTRACErequestmethod.\n""-?|-h|--helpThisinformation.\n""-V|--versionDisplayprogramversion.\n");}staticvoidbuild_requst(constchar*url){inti;chartmp[16];bzero(host,MAXHOSTNAMELEN);bzero(request,REQUEST_SIZE);if(force_reload&&(proxy_host!=NULL)&&(http10<1)){http10=1;}if((method==METHOD_HEAD)&&(http10<1)){http10=1;}if((method==METHOD_OPTIONS)&&(http10<2)){http10=2;}if((method==METHOD_TRACE)&&(http10<2)){http10=2;}/*客户端发起的请求类型,默认为GET,请求资源*/switch(method){caseMETHOD_GET:strcpy(request,"GET");break;caseMETHOD_HEAD:strcpy(request,"HEAD");break;caseMETHOD_OPTIONS:strcpy(request,"OPTIONS");break;caseMETHOD_TRACE:strcpy(request,"TRACE");break;}/*下面主要是判断域名的合法性*/strcat(request,"");if(strstr(url,"://")==NULL){fprintf(stderr,"\n%sisnoavaildurl.\n",url);exit(EXIT_FAILURE);}if(strlen(url)>1500){fprintf(stderr,"\nURListoolong\n");exit(EXIT_FAILURE);}if(proxy_host==NULL){if(strncasecmp("http://",url,7)!=0){fprintf(stderr,"\nOnlyHTTPprotocolisdirectlysupported,set--proxyforothers.\n");exit(EXIT_FAILURE);}}i=strstr(url,"://")-url+3;if(strstr(url+i,"/")==NULL){fprintf(stderr,"\nInvalidURLsyntax-hostnamedon'tendswith'/'.\n");exit(EXIT_FAILURE);}if(proxy_host==NULL){if((index(url+i,':')!=NULL)&&(index(url+i,':')<index(url+i,'/'))){strncpy(host,url+i,strchr(url+i,':')-url-i);bzero(tmp,sizeof(tmp));strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);proxy_port=atoi(tmp);if(proxy_port==0){proxy_port=HTTP_DEF_PORT;}}else{strncpy(host,url+i,strcspn(url+i,"/"));}PRINT_DEG("hostis%s,proxy_port=%d",host,proxy_port);strcat(request+strlen(request),url+i+strcspn(url+i,"/"));}else{strcat(request,url);}PRINT_DEG("therequstis%s\n",request);if(http10==1){strcat(request,"HTTP/1.0");}elseif(http10==2){strcat(request,"HTTP/1.1");}strcat(request,"\r\n");if(http10>0){strcat(request,"User-Agent:WebBench"PRG_VERSION"\r\n");}if((proxy_host==NULL)&&(http10>0)){strcat(request,"Host:");strcat(request,host);strcat(request,"\r\n");}if(force_reload&&(proxy_host!=NULL)){strcat(request,"Pragma:no-cache\r\n");}if(http10>1){strcat(request,"Connection:close\r\n");}/*addemptylineintheend*/if(http10>0){strcat(request,"\r\n");}}staticvoidalarm_handler(intsignal){PRINT_DEG("itistimeout");time_out=1;}staticvoidbench_calc(constchar*host,constintport,constchar*req){intlen,n;intsock_id;charbuff[1500];structsigactionsa;/*setupalarmsignalhandler*/sa.sa_handler=alarm_handler;sa.sa_flags=0;if(sigaction(SIGALRM,&sa,NULL)){exit(3);}alarm(bench_time);len=strlen(req);re_try:while(1){if(time_out){if(failed>0){--failed;}return;}sock_id=create_socket_info(host,port);if(sock_id<0){failed++;continue;}if(len!=write(sock_id,req,len)){failed++;close(sock_id);continue;}if(http10==0){if(shutdown(sock_id,1)){failed++;close(sock_id);continue;}}if(force==0){/*若是强制则不要等待服务器相应*/while(1){if(time_out){break;}n=read(sock_id,buff,1500);if(n<0){failed++;close(sock_id);gotore_try;}elseif(n==0){break;}else{bytes+=n;}}}if(close(sock_id)){failed++;continue;}speed++;}}staticintcore_process(void){inti;intn,m,k;pid_tpid;FILE*pipe_fd;/*创建管道*/if(pipe(mypipe)){perror("pipefailed.");return-1;}/*forkprocessnumberofclients*/for(i=0;i<clients;i++){/*创建线程*/pid=fork();if(pid<=(pid_t)0){sleep(1);/*让父进程先运行,防止子进程先运行后,写管道失败*/break;}}if(pid<(pid_t)0){fprintf(stderr,"problemsforkingworkerno.%d\n",i);perror("forkfailed.");return-1;}if(pid==0){/*thisisachildprocess*/bench_calc(((proxy_host==NULL)?host:proxy_host),proxy_port,request);/*向管道中写统计好的数据*/pipe_fd=fdopen(mypipe[1],"w");if(pipe_fd==NULL){perror("openpipeforwritingfailed.");return-1;}PRINT_DEG("speed,failed,bytes=%d%d%d",speed,failed,bytes);fprintf(pipe_fd,"%d%d%d\n",speed,failed,bytes);fclose(pipe_fd);return0;}else{PRINT_DEG("thisisfather");pipe_fd=fdopen(mypipe[0],"r");if(pipe_fd==NULL){perror("openpipeforreadingfailed.");return-1;}setvbuf(pipe_fd,NULL,_IONBF,0);speed=0;failed=0;bytes=0;PRINT_DEG("speed,failed,bytes=%d%d%d",speed,failed,bytes);while(1){/*读管道*/pid=fscanf(pipe_fd,"%d%d%d",&m,&n,&k);if(pid<2){fprintf(stderr,"Someofourchildrensdied.\n");break;}speed+=m;failed+=n;bytes+=k;if(--clients==0){break;}PRINT_DEG("clients=%d",clients);}fclose(pipe_fd);printf("\nSpeed=%dpages/min,%dbytes/sec.\nRequests:%dsusceed,%dfailed.\n",(int)((speed+failed)/(bench_time/60.0f)),(int)(bytes/(float)bench_time),speed,failed);}returnspeed;}intmain(intargc,char*argv[]){intopt;intoptions_index=0;intsocket_fd;char*tmp=NULL;/*1、参数检查*/if(argc==1){usage();exit(EXIT_FAILURE);}/*2、参数解析*/while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF){switch(opt){case0:break;case'f':force=1;break;case'r':force_reload=1;break;case'9':http10=0;break;case'1':http10=1;break;case'2':http10=2;break;case'V':printf(PRG_VERSION);exit(EXIT_SUCCESS);case't':bench_time=atoi(optarg);break;case'p':/*获取代理服务器端口号,strrchr,返回从左边开始最后一个比配的字符以后的字符串*/tmp=strrchr(optarg,':');proxy_host=optarg;if(tmp==NULL){break;}if(tmp==optarg){fprintf(stderr,"Errorinoption--proxy%s:Missinghostname.\n",optarg);exit(EXIT_FAILURE);}if(tmp==(optarg+strlen(optarg)-1)){fprintf(stderr,"Errorinoption--proxy%sPortnumberismissing.\n",optarg);exit(EXIT_FAILURE);}proxy_port=atoi(tmp+1);break;case'':case'?':case'h':usage();exit(EXIT_FAILURE);case'c':clients=atoi(optarg);break;default:usage();exit(EXIT_FAILURE);}}if(optind==argc){fprintf(stderr,"webbench:MissingURL!\n");usage();exit(EXIT_FAILURE);}/*设置默认的参数*/if(clients==0){clients=1;/*默认模拟一个用户*/}if(bench_time==0){bench_time=30;/*默认发起连接时间为30秒*/}/**********************infoprint*************************/fprintf(stderr,"Webbench-SimpleWebBenchmark"PRG_VERSION"\n""Copyright(c)RadimKolar1997-2004,GPLOpenSourceSoftware.\n");build_requst(argv[optind]);/*http协议参数相关参数设置*/printf("\nBenchmarking:");switch(method){caseMETHOD_GET:default:printf("GET");break;caseMETHOD_OPTIONS:printf("OPTIONS");break;caseMETHOD_HEAD:printf("HEAD");break;caseMETHOD_TRACE:printf("TRACE");break;}printf("%s",argv[optind]);switch(http10){case0:printf("(usingHTTP/0.9)");break;case2:printf("(usingHTTP/1.1)");break;}printf("\n");printf("%dclients\n",clients);printf("running%dsec",bench_time);if(force){printf(",earlysocketclose");}if(proxy_host!=NULL){printf(",viaproxyserver%s:%d",proxy_host,proxy_port);}if(force_reload){printf(",forcingreload");}printf(".\n");/**********************infoprintend*************************//*3、checkavaibilityoftargetserver*/socket_fd=create_socket_info((proxy_host==NULL)?host:proxy_host,proxy_port);/*创建套接字,主要用来测试服务器可用*/if(socket_fd<0){fprintf(stderr,"\nConnecttoserverfailed.Abortingbenchmark.\n");exit(EXIT_FAILURE);}close(socket_fd);/*4、核心处理*/returncore_process();}

/**host:thehostaddr*client_port:thedestportnum**/intcreate_socket_info(constchar*host,intcli_port){intsock;unsignedlonginaddr;structhostent*hp;structsockaddr_inaddr;memset(&addr,0,sizeof(addr));inaddr=inet_addr(host);if(inaddr!=INADDR_NONE){memcpy(&addr.sin_addr,&inaddr,sizeof(inaddr));}else{hp=gethostbyname(host);if(hp==NULL){return-1;}memcpy(&addr.sin_addr,hp->h_addr,hp->h_length);}addr.sin_port=htons(cli_port);addr.sin_family=AF_INET;sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){returnsock;}/*connecttotheserver*/if(connect(sock,(structsockaddr*)&addr,sizeof(addr))<0){return-1;}returnsock;}