欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

webbench网站测压工具源码分析

程序员文章站 2022-05-26 12:39:08
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 benc... ......
  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;//记录失败的数量(speed表示成功数,failed表示失败数)
 31 int bytes=0;//记录进程成功读取的字节数
 32 /* globals */
 33 int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 *///http版本
 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;//定义http请求方法:默认方式get请求
 41 int clients=1;//并发数目,默认只有一个进程发送请求,通过 -c 参数设置
 42 int force=0;//是否需要等待读取从server返回的数据,0表示要等待读取
 43 int force_reload=0;//是否使用缓存,1表示不缓存,0表示可以缓存页面
 44 int proxyport=80;//代理服务器的端口
 45 char *proxyhost=null;//代理服务器的端口
 46 int benchtime=30;//测压时间,默认30秒,通过 -t 参数设置
 47 /* internal */
 48 int mypipe[2];//使用管道进行父进程和子进程的通信
 49 char host[maxhostnamelen];//服务器端ip
 50 #define request_size 2048
 51 char request[request_size];//发送http请求的内容
 52 
 53 static const struct option long_options[]=
 54 {
 55  {"force",no_argument,&force,1},
 56  {"reload",no_argument,&force_reload,1},
 57  {"time",required_argument,null,'t'},
 58  {"help",no_argument,null,'?'},
 59  {"http09",no_argument,null,'9'},
 60  {"http10",no_argument,null,'1'},
 61  {"http11",no_argument,null,'2'},
 62  {"get",no_argument,&method,method_get},
 63  {"head",no_argument,&method,method_head},
 64  {"options",no_argument,&method,method_options},
 65  {"trace",no_argument,&method,method_trace},
 66  {"version",no_argument,null,'v'},
 67  {"proxy",required_argument,null,'p'},
 68  {"clients",required_argument,null,'c'},
 69  {null,0,null,0}
 70 };
 71 
 72 /* prototypes */
 73 static void benchcore(const char* host,const int port, const char *request);
 74 static int bench(void);
 75 static void build_request(const char *url);
 76 
 77 /*
 78      webbench在运行时可以设定压测的持续时间,以秒为单位。
 79      例如我们希望测试30秒,也就意味着压测30秒后程序应该退出了。
 80      webbench中使用信号(signal)来控制程序结束。
 81      函数1是在到达结束时间时运行的信号处理函数。
 82      它仅仅是将一个记录是否超时的变量timerexpired标记为true。
 83      后面会看到,在程序的while循环中会不断检测此值,
 84      只有timerexpired=1,程序才会跳出while循环并返回。
 85 */
 86 static void alarm_handler(int signal)
 87 {
 88    timerexpired=1;
 89 }    
 90 
 91 /*
 92     教你如何使用webbench的函数,
 93     在linux命令行调用webbench方法不对的时候运行,作为提示。
 94     有一些比较常用的,比如-c来指定并发进程的多少;
 95     -t指定压测的时间,以秒为单位;
 96     支持http0.9,http1.0,http1.1三个版本;
 97     支持get,head,options,trace四种请求方式。
 98     不要忘了调用时,命令行最后还应该附上要测的服务端url。
 99 */
100 static void usage(void)
101 {
102    fprintf(stderr,
103     "webbench [option]... url\n"
104     "  -f|--force               don't wait for reply from server.\n"
105     "  -r|--reload              send reload request - pragma: no-cache.\n"
106     "  -t|--time <sec>          run benchmark for <sec> seconds. default 30.\n"
107     "  -p|--proxy <server:port> use proxy server for request.\n"
108     "  -c|--clients <n>         run <n> http clients at once. default one.\n"
109     "  -9|--http09              use http/0.9 style requests.\n"
110     "  -1|--http10              use http/1.0 protocol.\n"
111     "  -2|--http11              use http/1.1 protocol.\n"
112     "  --get                    use get request method.\n"
113     "  --head                   use head request method.\n"
114     "  --options                use options request method.\n"
115     "  --trace                  use trace request method.\n"
116     "  -?|-h|--help             this information.\n"
117     "  -v|--version             display program version.\n"
118     );
119 };
120 int main(int argc, char *argv[])
121 {
122  int opt=0;
123  int options_index=0;
124  char *tmp=null;
125 
126  if(argc==1)
127  {
128       usage();
129           return 2;
130  } 
131 
132  while((opt=getopt_long(argc,argv,"912vfrt:p:c:?h",long_options,&options_index))!=eof )
133  {
134   switch(opt)
135   {
136    case  0 : break;
137    case 'f': force=1;break;
138    case 'r': force_reload=1;break; 
139    case '9': http10=0;break;
140    case '1': http10=1;break;
141    case '2': http10=2;break;
142    case 'v': printf(program_version"\n");exit(0);
143    /**
144        *c 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)
145        *int atoi(const char *str)
146        *str -- 要转换为整数的字符串。
147        *该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
148        */
149    case 't': benchtime=atoi(optarg);break;         
150    case 'p': 
151          /* proxy server parsing server:port */
152            /**
153            *strrchr() 函数用于查找某字符在字符串中最后一次出现的位置,其原型为:
154         *char * strrchr(const char *str, int c);
155         *【参数】str 为要查找的字符串,c 为要查找的字符。
156         *strrchr() 将会找出 str 字符串中最后一次出现的字符 c 的地址,然后将该地址返回。
157         *注意:字符串 str 的结束标志 nul 也会被纳入检索范围,所以 str 的组后一个字符也可以被定位。
158         *【返回值】如果找到就返回该字符最后一次出现的位置,否则返回 null。
159         *返回的地址是字符串在内存中随机分配的地址再加上你所搜索的字符在字符串位置。设字符在字符串中首次出现的位置为 i,那么返回的地址可以理解为 str + i。
160            */
161            /**
162            *optarg : char *optarg;  //选项的参数指针
163            */
164          tmp=strrchr(optarg,':');
165          proxyhost=optarg;
166          if(tmp==null)
167          {
168              break;
169          }
170          if(tmp==optarg)
171          {
172              fprintf(stderr,"error in option --proxy %s: missing hostname.\n",optarg);
173              return 2;
174          }
175          /**
176          *c 库函数 size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
177          *size_t strlen(const char *str)
178          *参数:str -- 要计算长度的字符串。
179          *该函数返回字符串的长度。
180          */
181          if(tmp==optarg+strlen(optarg)-1)
182          {
183              fprintf(stderr,"error in option --proxy %s port number is missing.\n",optarg);
184              return 2;
185          }
186          *tmp='\0';//把:替换为\0
187          //从\0之后到\0之前
188          proxyport=atoi(tmp+1);break;
189    case ':':
190    case 'h':
191    case '?': usage();return 2;break;
192    case 'c': clients=atoi(optarg);break;
193   }
194  }
195  //int optind:argv的当前索引值。当getopt函数在while循环中使用时,剩下的字符串为操作数,下标从optind到argc-1
196  //argc,argv 参考:https://www.cnblogs.com/lanshanxiao/p/11568037.html
197  //getopt_long()中的函数,参考:https://www.cnblogs.com/xhg940420/p/7016574.html
198  //扫描完毕后,optind指向非长选项和非短选项和非参数的字段,这里应该指向url
199  if(optind==argc) {
200                       fprintf(stderr,"webbench: missing url!\n");
201               usage();
202               return 2;
203                     }
204 
205  if(clients==0) clients=1;
206  if(benchtime==0) benchtime=60;
207  /* copyright */
208  fprintf(stderr,"webbench - simple web benchmark "program_version"\n"
209      "copyright (c) radim kolar 1997-2004, gpl open source software.\n"
210      );
211  /**
212   *命令读取完成后,argv[optind]中应该存放着url,
213   *建立完整的http请求,http请求存放在变量char request[request_size]中
214   */
215  build_request(argv[optind]);
216  /* print bench info *///输出平台信息
217  printf("\nbenchmarking: ");
218  switch(method)
219  {
220      case method_get:
221      default:
222          printf("get");break;
223      case method_options:
224          printf("options");break;
225      case method_head:
226          printf("head");break;
227      case method_trace:
228          printf("trace");break;
229  }
230  printf(" %s",argv[optind]);//打印出url
231  switch(http10)
232  {
233      case 0: printf(" (using http/0.9)");break;
234      case 2: printf(" (using http/1.1)");break;
235  }
236  printf("\n");
237  if(clients==1) printf("1 client");
238  else
239    printf("%d clients",clients);
240 
241  printf(", running %d sec", benchtime);
242  if(force) printf(", early socket close");
243  if(proxyhost!=null) printf(", via proxy server %s:%d",proxyhost,proxyport);
244  if(force_reload) printf(", forcing reload");
245  printf(".\n");
246  //压力测试最后一句话,所有的压力测试都在bench函数中实现
247  return bench();
248 }
249 
250 /*
251     函数主要操作全局变量char request[request_size],根据url填充其内容。
252     典型的http的get请求:
253     get /test.jpg http/1.1
254     user-agent: webbench 1.5
255     host:192.168.10.1
256     pragma: no-cache
257     connection: close
258 
259     build_request函数的目的就是要把
260     类似于以上这一大坨信息全部存到全局变量request[request_size]中,
261     其中换行操作使用的是”\r\n”。
262     而以上这一大坨信息的具体内容是要根据命令行输入的参数,以及url来确定的。
263     该函数使用了大量的字符串操作函数,
264     例如strcpy,strstr,strncasecmp,strlen,strchr,index,strncpy,strcat。
265     对这些基础函数不太熟悉的同学可以借这个函数复习一下。
266 */
267 void build_request(const char *url)
268 {
269   char tmp[10];
270   int i;
271 
272   bzero(host,maxhostnamelen);//bzero():置host(字节字符串)前maxhostnamelen个字节为0,包括'\0')
273   bzero(request,request_size);
274   
275   if(force_reload && proxyhost!=null && http10<1) http10=1;//满足一定条件,更换http协议
276   if(method==method_head && http10<1) http10=1;
277   if(method==method_options && http10<2) http10=2;
278   if(method==method_trace && http10<2) http10=2;
279 
280   switch(method)
281   {
282       default:
283       //strcpy() 函数用于对字符串进行复制(拷贝)。
284       //char* strcpy(char* strdestination, const char* strsource);
285       //strsource 指向的字符串复制到 strdestination
286       case method_get: strcpy(request,"get");break;
287       case method_head: strcpy(request,"head");break;
288       case method_options: strcpy(request,"options");break;
289       case method_trace: strcpy(request,"trace");break;
290   }
291 
292   //char*strcat(char* strdestination, const char* strsource);
293   /*
294       strcat() 函数用来将两个字符串连接(拼接)起来。
295       strcat() 函数把 strsource 所指向的字符串追加到 strdestination 所指向的字符串的结尾,
296       所以必须要保证 strdestination 有足够的内存空间来容纳两个字符串,否则会导致溢出错误。
297       注意:strdestination 末尾的\0会被覆盖,strsource 末尾的\0会一起被复制过去,最终的字符串只有一个\0。
298   */
299   strcat(request," ");
300 
301   /*
302     char *strstr(const char *haystack, const char *needle) 
303     在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 '\0'。
304     该函数返回在 haystack 中第一次出现 needle 的地址,如果未找到则返回 null。
305   */
306   if(null==strstr(url,"://"))
307   {
308       fprintf(stderr, "\n%s: is not a valid url.\n",url);
309       exit(2);
310   }
311 
312   /*
313       strlen(char *);
314       检测字符串实际长度。
315       strlen(char *)检测的是'\0',strlen(char *)碰到'\0'就返回'\0'以前的字符数(不包括'\0')。
316       strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',
317       如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。
318   */
319   if(strlen(url)>1500)
320   {
321      fprintf(stderr,"url is too long.\n");
322      exit(2);
323   }
324   if(proxyhost==null)
325          /*
326                  int strncasecmp(const char *s1, const char *s2, size_t n);
327                  strncasecmp()用来比较参数s1 和s2 字符串前n个字符,比较时会自动忽略大小写的差异。
328                  若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值,s1 若小于s2 则返回小于0 的值。
329          */
330        if (0!=strncasecmp("http://",url,7)) 
331        { 
332                fprintf(stderr,"\nonly http protocol is directly supported, set --proxy for others.\n");
333             exit(2);
334        }
335   /* protocol/host delimiter */
336   i=strstr(url,"://")-url+3;
337   /* printf("%d\n",i); */
338 
339   /*
340     char *strchr(const char *str, char c) 
341     该函数返回在字符串 str 中第一次出现字符 c 的地址,如果未找到该字符则返回 null。
342   */
343   if(strchr(url+i,'/')==null) {
344                                 fprintf(stderr,"\ninvalid url syntax - hostname don't ends with '/'.\n");
345                                 exit(2);
346                               }
347   if(proxyhost==null)
348   {
349    /* get port from hostname */
350    if(index(url+i,':')!=null &&
351       index(url+i,':')<index(url+i,'/'))
352    {
353           /*
354             char * strncpy(char *s1,char *s2,size_t n);
355               将字符串s2中最多n个字符复制到字符数组s1中,返回指向s1的指针。
356               注意:如果源串长度大于n,则strncpy不复制最后的'\0'结束符,
357             所以是不安全的,复制完后需要手动添加字符串的结束符才行。
358            */
359        strncpy(host,url+i,strchr(url+i,':')-url-i);
360        bzero(tmp,10);
361        strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
362        /* printf("tmp=%s\n",tmp); */
363 
364        /*
365             c语言库函数名: atoi
366               功 能: 把字符串转换成整型数.
367               名字来源:array to integer 的缩写.
368               函数说明: atoi()会扫描参数nptr字符串,如果第一个字符不是数字也不是正负号返回零,
369             否则开始做类型转换,之后检测到非数字或结束符 \0 时停止转换,返回整型数。
370               原型: int atoi(const char *nptr);
371        */
372        proxyport=atoi(tmp);
373        if(proxyport==0) proxyport=80;
374    } else
375    {
376         /*
377         size_t strcspn(const char *s, const char * reject);
378         函数说明:strcspn()从参数s 字符串的开头计算连续的字符,
379         而这些字符都完全不在参数reject 所指的字符串中。
380         简单地说, 若strcspn()返回的数值为n,则代表字符串s 开头连续有n 个字符都不含字符串reject 内的字符。
381         返回值:返回字符串s 开头连续不含字符串reject 内的字符数目。
382          */
383      strncpy(host,url+i,strcspn(url+i,"/"));
384    }
385    // printf("host=%s\n",host);
386    strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
387   } else
388   {
389    // printf("proxyhost=%s\nproxyport=%d\n",proxyhost,proxyport);
390    strcat(request,url);
391   }
392   if(http10==1)
393       strcat(request," http/1.0");
394   else if (http10==2)
395       strcat(request," http/1.1");
396   strcat(request,"\r\n");
397   if(http10>0)
398       strcat(request,"user-agent: webbench "program_version"\r\n");
399   if(proxyhost==null && http10>0)
400   {
401       strcat(request,"host: ");
402       strcat(request,host);
403       strcat(request,"\r\n");
404   }
405   if(force_reload && proxyhost!=null)
406   {
407       strcat(request,"pragma: no-cache\r\n");
408   }
409   if(http10>1)
410       strcat(request,"connection: close\r\n");
411   /* add empty line at end */
412   if(http10>0) strcat(request,"\r\n"); 
413   // printf("req=%s\n",request);
414 }
415 
416 /**
417 *先进行了一次socket连接,确认能连通以后,才进行后续步骤。
418 *调用pipe函数初始化一个管道,用于子进行向父进程汇报测试数据。
419 *子进程根据clients数量fork出来。
420 *每个子进程都调用函数5进行测试,并将结果输出到管道,供父进程读取。
421 *父进程负责收集所有子进程的测试数据,并汇总输出。
422 */
423 /* vraci system rc error kod */
424 static int bench(void)
425 {
426   int i,j,k;    
427   pid_t pid=0;
428   file *f;
429 
430   /* check avaibility of target server */
431   //检测目标服务器的可用性,调用socket.c文件中的函数
432   i=socket(proxyhost==null?host:proxyhost,proxyport);
433   if(i<0) {//处理错误
434        fprintf(stderr,"\nconnect to server failed. aborting benchmark.\n");
435            return 1;
436          }
437   close(i);
438   /* create pipe */
439   if(pipe(mypipe))//管道用于子进程向父进程回报数据
440   {//错误处理
441       perror("pipe failed.");
442       return 3;
443   }
444 
445   /* not needed, since we have alarm() in childrens */
446   /* wait 4 next system clock tick */
447   /*
448   cas=time(null);
449   while(time(null)==cas)
450         sched_yield();
451   */
452 
453   /* fork childs */
454   for(i=0;i<clients;i++)//根据clients大小fork出来足够的子进程进行测试
455   {
456        pid=fork();
457        if(pid <= (pid_t) 0)
458        {
459            /* child process or error*/
460                sleep(1); /* make childs faster */
461            break;
462        }
463   }
464 
465   if( pid< (pid_t) 0)
466   {//错误处理
467           fprintf(stderr,"problems forking worker no. %d\n",i);
468       perror("fork failed.");
469       return 3;
470   }
471 
472   if(pid== (pid_t) 0)//若是子进程,调用benchcore进行测试
473   {
474     /* i am a child */
475     if(proxyhost==null)
476       benchcore(host,proxyport,request);
477          else
478       benchcore(proxyhost,proxyport,request);
479 
480          /* write results to pipe */
481      f=fdopen(mypipe[1],"w");//子进程将测试结果输出到管道
482      if(f==null)
483      {//错误处理
484          perror("open pipe for writing failed.");
485          return 3;
486      }
487      /* fprintf(stderr,"child - %d %d\n",speed,failed); */
488      fprintf(f,"%d %d %d\n",speed,failed,bytes);
489      fclose(f);
490      return 0;
491   } else
492   {//若是父进程,则从管道读取子进程输出,并做汇总
493       f=fdopen(mypipe[0],"r");
494       if(f==null) 
495       {//错误处理
496           perror("open pipe for reading failed.");
497           return 3;
498       }
499       setvbuf(f,null,_ionbf,0);
500       speed=0;
501           failed=0;
502           bytes=0;
503 
504       while(1)
505       {//从管道读取数据,fscanf为阻塞式函数
506           pid=fscanf(f,"%d %d %d",&i,&j,&k);
507           if(pid<2)
508                   {//错误处理
509                        fprintf(stderr,"some of our childrens died.\n");
510                        break;
511                   }
512           speed+=i;
513           failed+=j;
514           bytes+=k;
515           /* fprintf(stderr,"*knock* %d %d read=%d\n",speed,failed,pid); */
516           if(--clients==0) break;//这句用于记录已经读了多少个子进程的数据,读完就退出
517       }
518       fclose(f);
519  //最后将结果打印到屏幕上
520   printf("\nspeed=%d pages/min, %d bytes/sec.\nrequests: %d susceed, %d failed.\n",
521           (int)((speed+failed)/(benchtime/60.0f)),
522           (int)(bytes/(float)benchtime),
523           speed,
524           failed);
525   }
526   return i;
527 }
528 
529 /**
530 *benchcore是子进程进行压力测试的函数,被每个子进程调用。
531 *这里使用了sigalrm信号来控制时间,
532 *alarm函数设置了多少时间之后产生sigalrm信号,一旦产生此信号,将运行alarm_handler(),
533 *使得timerexpired=1,这样可以通过判断timerexpired值来退出程序。
534 *另外,全局变量force表示我们是否在发出请求后需要等待服务器的响应结果
535 */
536 void benchcore(const char *host,const int port,const char *req)
537 {
538  int rlen;
539  char buf[1500];//记录服务器响应请求所返回的数据
540  int s,i;
541  struct sigaction sa;
542 
543  /* setup alarm signal handler */
544  sa.sa_handler=alarm_handler;//将函数alarm_handler地址赋值给sa.alarm_handler,作为信号处理函数
545  sa.sa_flags=0;
546  if(sigaction(sigalrm,&sa,null))//超时会产生信号sigalrm,用sa中的指定函数处理
547     exit(3);
548  alarm(benchtime);//开始计时
549 
550  rlen=strlen(req);
551  nexttry:while(1)
552  {
553     if(timerexpired)//一旦超时则返回
554     {
555        if(failed>0)
556        {
557           /* fprintf(stderr,"correcting failed by signal\n"); */
558           failed--;
559        }
560        return;
561     }
562     s=socket(host,port);//调用socket建立tcp连接                          
563     if(s<0) { failed++;continue;} 
564     if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}//发出请求
565     if(http10==0) //针对http0.9做的特殊处理
566         if(shutdown(s,1)) { failed++;close(s);continue;}
567     if(force==0) //全局变量force表示是否要等待服务器返回的数据
568     {
569             /* read all available data from socket */
570         while(1)
571         {
572               if(timerexpired) break; 
573           i=read(s,buf,1500);//从socket读取返回数据
574               /* fprintf(stderr,"%d\n",i); */
575           if(i<0) 
576               { 
577                  failed++;
578                  close(s);
579                  goto nexttry;
580               }
581            else
582                if(i==0) break;
583                else
584                    bytes+=i;
585         }
586     }
587     if(close(s)) {failed++;continue;}
588     speed++;
589  }
590 }