网站压力测试工具-Webbench源码笔记
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,让写操作变成原子操作。
1 /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ 2 * 3 * This module has been modified by Radim Kolar for OS/2 emx 4 */ 5 6 /*********************************************************************** 7 module: socket.c 8 program: popclient 9 SCCS ID: @(#)socket.c 1.5 4/1/94 10 programmer: Virginia Tech Computing Center 11 compiler: DEC RISC C compiler (Ultrix 4.1) 12 environment: DEC Ultrix 4.3 13 description: UNIX sockets code. 14 ***********************************************************************/ 15 16 #include <sys/types.h> 17 #include <sys/socket.h> 18 #include <fcntl.h> 19 #include <netinet/in.h> 20 #include <arpa/inet.h> 21 #include <netdb.h> 22 #include <sys/time.h> 23 #include <string.h> 24 #include <unistd.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <stdarg.h> 28 29 30 /* 31 根据通信地址和端口号建立网络连接 32 @host:网络地址 33 @clientPort:端口号 34 成功返回建立连接套接字 35 建立套接字失败返回-1 36 */ 37 int Socket(const char *host, int clientPort) 38 { 39 int sock; 40 unsigned long inaddr; 41 struct sockaddr_in ad; 42 struct hostent *hp; 43 44 memset(&ad, 0, sizeof(ad)); 45 ad.sin_family = AF_INET; 46 //将点分十进制的IP地址转化为无符号的长整形 47 inaddr = inet_addr(host); 48 if (inaddr != INADDR_NONE) 49 memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); 50 else 51 { 52 //如果host是域名,则通过域名获取IP地址 53 hp = gethostbyname(host); 54 if (hp == NULL) 55 return -1; 56 memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); 57 } 58 ad.sin_port = htons(clientPort); 59 60 sock = socket(AF_INET, SOCK_STREAM, 0); 61 if (sock < 0) 62 return sock; 63 if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) 64 return -1; 65 return sock; 66 }
1 /* 2 * (C) Radim Kolar 1997-2004 3 * This is free software, see GNU Public License version 2 for 4 * details. 5 * 6 * Simple forking WWW Server benchmark: 7 * 8 * Usage: 9 * webbench --help 10 * 11 * Return codes: 12 * 0 - sucess 13 * 1 - benchmark failed (server is not on-line) 14 * 2 - bad param 15 * 3 - internal error, fork failed 16 * 17 */ 18 #include "socket.c" 19 #include <unistd.h> 20 #include <sys/param.h> 21 #include <rpc/types.h> 22 #include <getopt.h> 23 #include <strings.h> 24 #include <time.h> 25 #include <signal.h> 26 27 /* values */ 28 volatile int timerexpired=0; 29 int speed=0; 30 int failed=0; 31 int bytes=0; 32 /* globals */ 33 int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ 34 /* Allow: GET, HEAD, OPTIONS, TRACE */ 35 #define METHOD_GET 0 36 #define METHOD_HEAD 1 37 #define METHOD_OPTIONS 2 38 #define METHOD_TRACE 3 39 #define PROGRAM_VERSION "1.5" 40 int method=METHOD_GET; 41 int clients=1; 42 int force=0; 43 int force_reload=0; 44 int proxyport=80; 45 char *proxyhost=NULL; 46 int benchtime=30; 47 /* internal */ 48 int mypipe[2]; 49 50 //主机名的最大长度通常是由头文件<sys/param.h>定义的常值MAXHOSTNAMELEN 51 char host[MAXHOSTNAMELEN]; 52 #define REQUEST_SIZE 2048 53 char request[REQUEST_SIZE]; 54 55 static const struct option long_options[]= 56 { 57 {"force",no_argument,&force,1}, 58 {"reload",no_argument,&force_reload,1}, 59 {"time",required_argument,NULL,'t'}, 60 {"help",no_argument,NULL,'?'}, 61 {"http09",no_argument,NULL,'9'}, 62 {"http10",no_argument,NULL,'1'}, 63 {"http11",no_argument,NULL,'2'}, 64 {"get",no_argument,&method,METHOD_GET}, 65 {"head",no_argument,&method,METHOD_HEAD}, 66 {"options",no_argument,&method,METHOD_OPTIONS}, 67 {"trace",no_argument,&method,METHOD_TRACE}, 68 {"version",no_argument,NULL,'V'}, 69 {"proxy",required_argument,NULL,'p'}, 70 {"clients",required_argument,NULL,'c'}, 71 {NULL,0,NULL,0} 72 }; 73 74 /* prototypes */ 75 static void benchcore(const char* host,const int port, const char *request); 76 static int bench(void); 77 static void build_request(const char *url); 78 79 static void alarm_handler(int signal) 80 { 81 timerexpired=1; 82 } 83 84 static void usage(void) 85 { 86 fprintf(stderr, 87 "webbench [option]... URL\n" 88 " -f|--force Don't wait for reply from server.\n" 89 " -r|--reload Send reload request - Pragma: no-cache.\n" 90 " -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n" 91 " -p|--proxy <server:port> Use proxy server for request.\n" 92 " -c|--clients <n> Run <n> HTTP clients at once. Default one.\n" 93 " -9|--http09 Use HTTP/0.9 style requests.\n" 94 " -1|--http10 Use HTTP/1.0 protocol.\n" 95 " -2|--http11 Use HTTP/1.1 protocol.\n" 96 " --get Use GET request method.\n" 97 " --head Use HEAD request method.\n" 98 " --options Use OPTIONS request method.\n" 99 " --trace Use TRACE request method.\n" 100 " -?|-h|--help This information.\n" 101 " -V|--version Display program version.\n" 102 ); 103 }; 104 105 106 107 int main(int argc, char *argv[]) 108 { 109 int opt=0; 110 int options_index=0; 111 char *tmp=NULL; 112 113 if(argc==1) 114 { 115 usage(); 116 return 2; 117 } 118 119 //解析命令行参数 120 while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) 121 { 122 switch(opt) 123 { 124 case 0 : break; 125 case 'f': force=1;break; 126 case 'r': force_reload=1;break; 127 case '9': http10=0;break; 128 case '1': http10=1;break; 129 case '2': http10=2;break; 130 case 'V': printf(PROGRAM_VERSION"\n");exit(0); 131 case 't': benchtime=atoi(optarg);break; 132 case 'p': 133 /* proxy server parsing server:port */ 134 tmp=strrchr(optarg,':'); 135 proxyhost=optarg; 136 if(tmp==NULL) 137 { 138 break; 139 } 140 if(tmp==optarg) 141 { 142 fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 143 return 2; 144 } 145 if(tmp==optarg+strlen(optarg)-1) 146 { 147 fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 148 return 2; 149 } 150 *tmp='\0'; 151 proxyport=atoi(tmp+1); 152 break; 153 case ':': 154 case 'h': 155 case '?': 156 usage(); 157 return 2; 158 break; 159 case 'c': 160 clients=atoi(optarg); 161 break; 162 } 163 } 164 165 //判断命令行参数是否解析完成,如果完成代表没有需要访问的超链接 166 //optind的值随着调用getopt_long次数改变,每调用一次加一 167 if(optind==argc) 168 { 169 fprintf(stderr,"webbench: Missing URL!\n"); 170 usage(); 171 return 2; 172 } 173 174 if(clients==0) 175 { 176 clients=1; 177 } 178 if(benchtime==0) 179 { 180 benchtime=60; 181 } 182 183 /* Copyright */ 184 fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 185 "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"); 186 187 build_request(argv[optind]); 188 189 //对设置参数的一些显示信息 190 /* print bench info */ 191 printf("\n Bench marking: "); 192 193 switch(method) 194 { 195 case METHOD_GET: 196 default: 197 printf("GET"); 198 break; 199 case METHOD_OPTIONS: 200 printf("OPTIONS"); 201 break; 202 case METHOD_HEAD: 203 printf("HEAD"); 204 break; 205 case METHOD_TRACE: 206 printf("TRACE"); 207 break; 208 } 209 210 printf(" %s",argv[optind]); 211 switch(http10) 212 { 213 case 0: printf(" (using HTTP/0.9)");break; 214 case 1: printf(" (using HTTP/1.0)");break; 215 case 2: printf(" (using HTTP/1.1)");break; 216 } 217 printf("\n"); 218 219 //显示一些参数 220 if(clients==1) 221 { 222 printf("1 client"); 223 } 224 else 225 { 226 printf("%d clients",clients); 227 } 228 229 printf(", running %d sec", benchtime); 230 if(force) 231 { 232 printf(", early socket close"); 233 } 234 235 if(proxyhost!=NULL) 236 { 237 printf(", via proxy server %s:%d",proxyhost,proxyport); 238 } 239 240 if(force_reload) 241 { 242 printf(", forcing reload"); 243 } 244 printf(".\n"); 245 246 return bench(); 247 } 248 /* 249 *输入URL的连接 250 *解析URl,构造请求头并且将请求头, 251 *保存到全局变量request中 252 GET /Plugins/YongHu_plug/Pages/loginbysjy.aspx HTTP/1.0 253 User-Agent: WebBench 1.5 254 Host: xyzp.xaut.edu.cn 255 */ 256 void build_request(const char *url) 257 { 258 char tmp[10]; 259 int i; 260 //清空存储区 261 bzero(host,MAXHOSTNAMELEN); 262 bzero(request,REQUEST_SIZE); 263 //force_reload不等待响应,强制刷新 264 //proxyhost是否设置了代理 265 //判断是否设置了Http的通信协议版本 266 if(force_reload && proxyhost!=NULL && http10<1) 267 { 268 http10=1; 269 } 270 //请求方式和协议版本 271 if(method==METHOD_HEAD && http10<1) 272 { 273 http10=1; 274 } 275 if(method==METHOD_OPTIONS && http10<2) 276 { 277 http10=2; 278 } 279 if(method==METHOD_TRACE && http10<2) 280 { 281 http10=2; 282 } 283 284 switch(method) 285 { 286 default: 287 case METHOD_GET: strcpy(request,"GET");break; 288 case METHOD_HEAD: strcpy(request,"HEAD");break; 289 case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 290 case METHOD_TRACE: strcpy(request,"TRACE");break; 291 } 292 293 strcat(request," "); 294 295 //判断Http请求的地址正确性以及有效性 296 if(NULL==strstr(url,"://")) 297 { 298 fprintf(stderr, "\n%s: is not a valid URL.\n",url); 299 exit(2); 300 } 301 if(strlen(url)>1500) 302 { 303 fprintf(stderr,"URL is too long.\n"); 304 exit(2); 305 } 306 307 if(proxyhost==NULL) 308 { 309 if (0!=strncasecmp("http://",url,7)) 310 { 311 fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 312 exit(2); 313 } 314 } 315 //跳过超链接的协议头,找到顶级域名 316 /* protocol/host delimiter */ 317 i=strstr(url,"://")-url+3; 318 /* printf("%d\n",i); */ 319 320 //跳过顶级域名 321 if(strchr(url+i,'/')==NULL) 322 { 323 fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 324 exit(2); 325 } 326 if(proxyhost==NULL) 327 { 328 //获取指定的通信端口,如果不存在则使用默认的80端口,同时获得主机名称-域名,网页的绝对路径 329 /* get port from hostname */ 330 if(index(url+i,':')!=NULL && 331 index(url+i,':')<index(url+i,'/')) 332 { 333 strncpy(host,url+i,strchr(url+i,':')-url-i); 334 bzero(tmp,10); 335 strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1); 336 /* printf("tmp=%s\n",tmp); */ 337 proxyport=atoi(tmp); 338 if(proxyport==0) proxyport=80; 339 } 340 else 341 { 342 strncpy(host,url+i,strcspn(url+i,"/")); 343 } 344 //printf("Host=%s\n",host); 345 strcat(request+strlen(request),url+i+strcspn(url+i,"/")); 346 } 347 else 348 { 349 // printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport); 350 strcat(request,url); 351 } 352 //设置请求协议的版本,拼接请求头 353 if(http10==1) 354 { 355 strcat(request," HTTP/1.0"); 356 } 357 else if (http10==2) 358 { 359 strcat(request," HTTP/1.1"); 360 } 361 strcat(request,"\r\n"); 362 363 if(http10>0) 364 { 365 strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); 366 } 367 368 if(proxyhost==NULL && http10>0) 369 { 370 strcat(request,"Host: "); 371 strcat(request,host); 372 strcat(request,"\r\n"); 373 } 374 if(force_reload && proxyhost!=NULL) 375 { 376 strcat(request,"Pragma: no-cache\r\n"); 377 } 378 if(http10>1) 379 { 380 strcat(request,"Connection: close\r\n"); 381 } 382 383 /* add empty line at end */ 384 if(http10>0) 385 { 386 strcat(request,"\r\n"); 387 } 388 //printf("Req=%s\n",request); 389 } 390 391 /* vraci system rc error kod */ 392 static int bench(void) 393 { 394 int i,j,k; 395 pid_t pid=0; 396 FILE *f; 397 398 /* check avaibility of target server */ 399 //测试远程主机的是否可用 400 i=Socket(proxyhost==NULL?host:proxyhost,proxyport); 401 if(i<0) 402 { 403 fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); 404 return 1; 405 } 406 close(i); 407 408 /* create pipe */ 409 //创建无名管道 410 if(pipe(mypipe)) 411 { 412 perror("pipe failed."); 413 return 3; 414 } 415 416 /* not needed, since we have alarm() in childrens */ 417 /* wait 4 next system clock tick */ 418 /* 419 cas=time(NULL); 420 while(time(NULL)==cas) 421 sched_yield(); 422 */ 423 424 /* fork childs */ 425 //创建进程 426 for(i = 0;i < clients;i++) 427 { 428 /* 429 调用fork有一个特殊的地方,就是调用一次却能返回两次,有三种不同的返回值: 430 1、在父进程中,fork返回新创建的子进程的进程ID 431 2、在子进程中,fork返回0 432 3、如果出现错误,fork返回一个负值 433 */ 434 pid=fork(); 435 if(pid <= (pid_t) 0) 436 { 437 /* child process or error*/ 438 sleep(1); /* make childs faster */ 439 /*这个break很重要,它主要让子进程只能从父进程生成, 440 否则子进程会在创建子进程,子子孙孙无穷尽*/ 441 break; 442 } 443 } 444 445 if( pid< (pid_t) 0) 446 { 447 fprintf(stderr,"problems forking worker no. %d\n",i); 448 perror("fork failed."); 449 return 3; 450 } 451 452 if(pid== (pid_t) 0) 453 { 454 /* I am a child */ 455 //子进程执行请求,所有子进程都发出请求 456 if(proxyhost==NULL) 457 { 458 benchcore(host,proxyport,request); 459 } 460 else 461 { 462 benchcore(proxyhost,proxyport,request); 463 } 464 /* write results to pipe */ 465 //打开无名管道的写端口 466 f = fdopen(mypipe[1],"w"); 467 if(f==NULL) 468 { 469 perror("open pipe for writing failed."); 470 return 3; 471 } 472 /* fprintf(stderr,"Child - %d %d\n",speed,failed); */ 473 //向管道中写入数据 474 fprintf(f,"%d %d %d\n",speed,failed,bytes); 475 fclose(f); 476 return 0; 477 } 478 else 479 { 480 //父进程中打开管道的读端口 481 f=fdopen(mypipe[0],"r"); 482 if(f==NULL) 483 { 484 perror("open pipe for reading failed."); 485 return 3; 486 } 487 //设置缓冲区大小 488 setvbuf(f,NULL,_IONBF,0); 489 //初始化访问速度、失败次数、字节数 490 speed=0; 491 failed=0; 492 bytes=0; 493 494 while(1) 495 { 496 //获取无名管道中的数据,进行统计 497 pid=fscanf(f,"%d %d %d",&i,&j,&k); 498 if(pid<2) 499 { 500 fprintf(stderr,"Some of our childrens died.\n"); 501 break; 502 } 503 speed+=i; 504 failed+=j; 505 bytes+=k; 506 /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */ 507 if(--clients==0) 508 { 509 break; 510 } 511 } 512 fclose(f); 513 //显示统计结果 514 printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n", 515 (int)((speed+failed)/(benchtime/60.0f)), 516 (int)(bytes/(float)benchtime), 517 speed, 518 failed); 519 } 520 return i; 521 } 522 523 void benchcore(const char *host,const int port,const char *req) 524 { 525 int rlen; 526 char buf[1500]; 527 int s,i; 528 struct sigaction sa; 529 530 /* setup alarm signal handler */ 531 //这个是关键,当程序执行到指定的秒数之后,发送 SIGALRM 信号 532 sa.sa_handler=alarm_handler; 533 sa.sa_flags=0; 534 if(sigaction(SIGALRM,&sa,NULL)) 535 { 536 exit(3); 537 } 538 539 alarm(benchtime); 540 541 rlen=strlen(req); 542 //无限执行请求,并发操作,直到接收到 SIGALRM 信号将 timerexpired 设置为 1 时 543 nexttry:while(1) 544 { 545 if(timerexpired) 546 { 547 if(failed>0) 548 { 549 /* fprintf(stderr,"Correcting failed by signal\n"); */ 550 failed--; 551 } 552 return; 553 } 554 //连接服务器 555 s=Socket(host,port); 556 if(s<0) 557 { 558 failed++; 559 continue; 560 } 561 //发送请求头 562 if( rlen!=write(s,req,rlen) ) 563 { 564 failed++; 565 close(s); 566 continue; 567 } 568 569 if(http10==0) 570 { 571 //如果是 http/0.9 则关闭socket的写操作 572 if(shutdown(s,1)) 573 { 574 failed++; 575 close(s); 576 continue; 577 } 578 } 579 580 if(force==0) 581 { 582 /* read all available data from socket */ 583 //读取服务器的响应数据,计算传输的字节数 584 while(1) 585 { 586 if(timerexpired) 587 { 588 break; 589 } 590 i = read(s,buf,1500); 591 /* fprintf(stderr,"%d\n",i); */ 592 //响应失败关闭连接,执行下一次请求,记录失败的次数 593 if( i < 0 ) 594 { 595 failed++; 596 close(s); 597 goto nexttry; 598 } 599 else if(i==0) 600 { 601 break; 602 } 603 else 604 { 605 bytes+=i; 606 } 607 } 608 } 609 //关闭连接,执行下一次请求 610 if(close(s)) 611 { 612 failed++; 613 continue; 614 } 615 //记录请求成功的次数 616 speed++; 617 } 618 }
本文来自博客园,作者:suntl,转载请注明原文链接:https://www.cnblogs.com/stlong/p/4483592.html