webbench 代码阅读

 

简介:

Webbech能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况.webBech的标准测试可以向我们展示服务器的两项 内容:每秒钟相应请求数和每秒钟传输数据量.webbench不但能具有便准静态页面的测试能力,还能对动态页面(ASP,PHP,JAVA,CGI)进 行测试的能力.还有就是他支持对含有SSL的安全网站例如电子商务网站进行静态或动态的性能测试.

×××××××××××××××××××××××××吐槽,可忽略××××××××××××××××××××××

 好吧 , 我承认好高端,但整个代码也就几百行,还是算上注释呀,看完之后,完全没有什么动态网页测试,SSL 安全网站测试什么选项呀!!!!

,只有选择http协议的请求头选项呀,什么GET,HEAD,之类的

×××××××××××××××××××××××××××××××××××××××××××××××××××××

了解代码,你先得了解它的业务流程

 难点一:获取web服务器信息

     解决这个问题,首先你的了解你要测试什么? 你要测试的是一个web服务器应对大量请求的能力,就是所说的压力测试。你要做的是同时构造大量的请求,达到测试目的。

     请求是由 URL 地址连接 指示的,你要明白URL 地址连接由什么组成,你需要从其中获取那些必要的信息构成请求。url的一般模式为

    http://myname:mypass@www.vimer.cn:80/mydir/myfile.html?myvar=myvalue#myfrag

      

URI部分

意义

http

协议名称

myname

用户名(可选)

mypass

密码(可选)

www.vimer.cn

主机网络地址

80

端口号(可选)

/mydir/myfile.html

资源路径

myvar=myvalue

查询字符串(可选)

myfrag

锚点(可选)

  要从url 提取 host 和 request ,host是主机网络地址,reques 是利用http请求头和资源路径构成的一个http协议请求,web服务器会处理这个请求并且返回资源,一个例子:  

    输入: http://www.vimer.cn/2010/02/%e7%ae%80%e6%98%8ehttp%e5%8d%8f%e8%ae%ae.html
    host  www.vimer.cn 
    request  GET /2010/02/%e7%ae%80%e6%98%8ehttp%e5%8d%8f%e8%ae%ae.html HTTP/1.0

如果你纵览了代码,可以说60%的功能都用来进行字符串操作,来获得 hostrequest  不无论是mian函数还build_request 都是来实现这个功能的。具体的细节实现可以参考代码注释,注意一下,使用了大量的字符串函数,随时要准备man一下。

难点二:如何测评,依据是什么

   测试就的有一个标准,webbench的标准是成功使用stock建立的http链接,benchwork函数做的就是这样一项工作。webbench使用的是多进程操作,

它选用了通道作为parent和childern的联系方式,来告知parent,child成功或失败建立了几个连接,传送的总的字节数是多少。在博文的最后有一个benchwork函数的流程图,有兴趣的可以看一下

代码

webbench.c

  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 /*  volatile  
 30  *  提示编译器所定义的变量随时可能改变,因此编译后的程序每次需要存储或是i
 31  *  读取该变量的时候,都会直接从变量地址读取数据。
 32  *  之所以要这样做,是因为编译器会对代码的读取和存储进行优化,可能暂时使用
 33  *  寄存器的值。
 34  * */
 35 int speed=0;
 36 int failed=0;
 37 int bytes=0;
 38 /* globals */
 39 int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
 40 /* Allow: GET, HEAD, OPTIONS, TRACE */
 41 #define METHOD_GET 0
 42 #define METHOD_HEAD 1
 43 #define METHOD_OPTIONS 2
 44 #define METHOD_TRACE 3
 45 #define PROGRAM_VERSION "1.5"
 46 int method=METHOD_GET;
 47 int clients=1;/* 测试进程数,默认1:w*/
 48 int force=0;/* 等待服务器返回标志 ,默认为等待返回 0 */
 49 int force_reload=0;
 50 int proxyport=80;/* 服务器端口号,http协议默认为80端口*/
 51 char *proxyhost=NULL;//代理服务器
 52 int benchtime=30;/* 运行时间,默认为30 */
 53 /* internal */
 54 int mypipe[2];                    /* 通道 */
 55 char host[MAXHOSTNAMELEN];/* 域名 */
 56 #define REQUEST_SIZE 2048
 57 char request[REQUEST_SIZE];/* http 请求头 */
 58 
 59 static const struct option long_options[]=
 60 {
 61  {"force",no_argument,&force,1},
 62  {"reload",no_argument,&force_reload,1},
 63  {"time",required_argument,NULL,'t'},
 64  {"help",no_argument,NULL,'?'},
 65  {"http09",no_argument,NULL,'9'},
 66  {"http10",no_argument,NULL,'1'},
 67  {"http11",no_argument,NULL,'2'},
 68  {"get",no_argument,&method,METHOD_GET},
 69  {"head",no_argument,&method,METHOD_HEAD},
 70  {"options",no_argument,&method,METHOD_OPTIONS},
 71  {"trace",no_argument,&method,METHOD_TRACE},
 72  {"version",no_argument,NULL,'V'},
 73  {"proxy",required_argument,NULL,'p'},
 74  {"clients",required_argument,NULL,'c'},
 75  {NULL,0,NULL,0}
 76 };
 77 /*  静态函数
 78  *  特性:
 79  *  1.周期:整个程序,范围:本文件
 80  *  2.使用static作为前缀,仅可以本文件函数调用,
 81  *  不能被同一程序的其他文件调用
 82  *  3.可以在不同文件里使用相同的函数名,不用担心冲突
 83  * */
 84 /* prototypes */
 85 static void benchcore(const char* host,const int port, const char *request);
 86 /* 测试 host 的连接 功能函数 */
 87 static int bench(void);
 88 /* 测试 host 的前期准备 和 多进程操作与同行 */
 89 static void build_request(const char *url);
 90 /* 解析 request */
 91 
 92 static void alarm_handler(int signal)
 93 /* 和sig 和signaction 构成一个时钟 */
 94 {
 95    timerexpired=1;
 96 }    
 97 
 98 static void usage(void)
 99 /* 帮助 */
100 {
101    fprintf(stderr,
102     "webbench [option]... URL
"
103     "  -f|--force               Don't wait for reply from server.
"
104     "  -r|--reload              Send reload request - Pragma: no-cache.
"
105     "  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.
"
106     "  -p|--proxy <server:port> Use proxy server for request.
"
107     "  -c|--clients <n>         Run <n> HTTP clients at once. Default one.
"
108     "  -9|--http09              Use HTTP/0.9 style requests.
"
109     "  -1|--http10              Use HTTP/1.0 protocol.
"
110     "  -2|--http11              Use HTTP/1.1 protocol.
"
111     "  --get                    Use GET request method.
"
112     "  --head                   Use HEAD request method.
"
113     "  --options                Use OPTIONS request method.
"
114     "  --trace                  Use TRACE request method.
"
115     "  -?|-h|--help             This information.
"
116     "  -V|--version             Display program version.
"
117     );
118 };
119 int main(int argc, char *argv[])
120 {
121  int opt=0;
122  int options_index=0;
123  char *tmp=NULL;
124 
125  if(argc==1)
126  {
127       usage();
128           return 2;
129  } 
130 
131  while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
132 /* 使用getopt 函数 获取分析命令,对一些参数进行设置 */
133  {
134   switch(opt)
135   {
136    case  0 : break;
137    case 'f': force=1;break;//Don't wait for reply from server
138    case 'r': force_reload=1;break;//Send reload request - Pragma: no-cache
139    case '9': http10=0;break;
140    case '1': http10=1;break;
141    case '2': http10=2;break;
142    case 'V': printf(PROGRAM_VERSION"
");exit(0);
143    case 't': benchtime=atoi(optarg);break;//optarg[ getopt的全局变量 ] : 指向当前选项参数的指针         
144    case 'p': 
145           /* proxy server parsing server:port */
146          tmp=strrchr(optarg,':');
147              /*  char * strrchr(const char * s,int c )
148               *  返回 字符c 在字符串s末次出现的位置【返回值指向的是'c'
149               * */
150              /* 返回的是端口号
151               * */
152          proxyhost=optarg;
153          if(tmp==NULL)
154          {
155              break;
156          }
157          if(tmp==optarg)
158          {
159              fprintf(stderr,"Error in option --proxy %s: Missing hostname.
",optarg);
160              return 2;
161          }//只有 :port
162          if(tmp==optarg+strlen(optarg)-1)
163          {
164              fprintf(stderr,"Error in option --proxy %s Port number is missing.
",optarg);
165              return 2;
166          }//只有 server: 
167          *tmp='';
168          proxyport=atoi(tmp+1);break;//获取端口号
169    case ':':
170    case 'h':
171    case '?': usage();return 2;break;
172    case 'c': clients=atoi(optarg);break;
173   }
174  }
175  
176  if(optind==argc) {
177          /* optind getopt_long 函数的全局变量 ,表示下一个要解析的参数的位置 
178           * 初始值为 1 
179           
180             可以这么理解:
181             如果一个程序有一个参数是必须需要的,则optind < argv,例如 webbench需要一个测试
182             目标URL,那么 webbench 会占用  0 的下标,而optind 已经默认初始化为1,可以预见
183             参数和参数的选项都会被getopt()操作后,optind 会指向下一个下标[无论是否存在],
184             不存在的话, optind就会和 argc 相等,相当于只输入了一个webbench , 
185             optind == argc == 1 ,
186           * */
187                       fprintf(stderr,"webbench: Missing URL!
");
188               usage();
189               return 2;
190                     }
191 
192  if(clients==0) clients=1;
193  if(benchtime==0) benchtime=60;
194  /* Copyright */
195  fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"
"
196      "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
"
197      );
198  build_request(argv[optind]);
199  /* print bench info */
200  printf("
Benchmarking: ");
201  switch(method)
202  {
203      case METHOD_GET:
204      default:
205          printf("GET");break;
206      case METHOD_OPTIONS:
207          printf("OPTIONS");break;
208      case METHOD_HEAD:
209          printf("HEAD");break;
210      case METHOD_TRACE:
211          printf("TRACE");break;
212  }
213 // printf(" %s",argv[optind]);
214  switch(http10)
215  {
216      case 0: printf(" (using HTTP/0.9)");break;
217      case 2: printf(" (using HTTP/1.1)");break;
218  }
219  printf("
");
220  if(clients==1) printf("1 client");
221  else
222    printf("%d clients",clients);
223 
224  printf(", running %d sec", benchtime);
225  if(force) printf(", early socket close");
226  if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport);
227  if(force_reload) printf(", forcing reload");
228  printf(".
");
229  return bench();
230 }
231 
232 void build_request(const char *url)
233 {
234   char tmp[10];
235   int i;
236 
237   bzero(host,MAXHOSTNAMELEN);
238   bzero(request,REQUEST_SIZE);
239   /* 初始化空间为0 */
240 
241   if(force_reload && proxyhost!=NULL && http10<1) http10=1;
242   if(method==METHOD_HEAD && http10<1) http10=1;
243   if(method==METHOD_OPTIONS && http10<2) http10=2;
244   if(method==METHOD_TRACE && http10<2) http10=2;
245   /* 不知所以 ,缺少fftp的基本的基本知识 */
246 
247   switch(method)
248   {
249       default:
250       case METHOD_GET: strcpy(request,"GET");break;
251       case METHOD_HEAD: strcpy(request,"HEAD");break;
252       case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
253       case METHOD_TRACE: strcpy(request,"TRACE");break;
254   }
255           
256   strcat(request," ");
257 
258   if(NULL==strstr(url,"://"))
259   {
260       fprintf(stderr, "
%s: is not a valid URL.
",url);
261       exit(2);
262   }
263   if(strlen(url)>1500)
264   {
265          fprintf(stderr,"URL is too long.
");
266      exit(2);
267   }
268   if(proxyhost==NULL)
269        if (0!=strncasecmp("http://",url,7)) 
270                    /* int strncasecmp(char *s1,char *s2,int i)
271                     * 只比较s1的前 i 位和 s2的关系  
272                     * */
273        { fprintf(stderr,"
Only HTTP protocol is directly supported, set --proxy for others.
");
274              exit(2);
275            }
276   /* protocol/host delimiter */
277   i=strstr(url,"://")-url+3;
278   //printf("strstr(url,""): %p, url :%p, i = %d  
",strstr(url,"://"),url,i);
279   /*  根据 数组在内存中存放的形式, 可以利用 strstr(..) 求的地址A,同时利用 url的
280    *  地址 B, A-B = 4[ 从低位开始放,就像压栈一样 ], 
281    *  最后的 i 表示 一个http网址中, :// 之后的第一字符的下标
282    *  例如: http;//www.yankanshu.com .i 表示 w的下标位置
283    * */
284 
285   if(strchr(url+i,'/')==NULL) {
286                                 fprintf(stderr,"
Invalid URL syntax - hostname don't ends with '/'.
");
287                                 exit(2);
288                               }
289   if(proxyhost==NULL)
290   {
291    /* get port from hostname */
292    if(index(url+i,':')!=NULL &&
293       index(url+i,':')<index(url+i,'/'))
294    {
295     //       printf("test 1
 ");
296        strncpy(host,url+i,strchr(url+i,':')-url-i);
297        bzero(tmp,10);
298        strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
299        /* printf("tmp=%s
",tmp); */
300        proxyport=atoi(tmp);
301        if(proxyport==0) proxyport=80;
302    } else
303    {
304   //   printf("test 2
 ");
305      strncpy(host,url+i,strcspn(url+i,"/"));
306    }
307   // printf("Host=%s
",host);
308    /* 最后的host 是不带 http: 和 最后的 
309     * 例如: http://baike.baidu.com/
310     * host = baike.baidu.com
311     * */
312    strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
313    /*没有理解目的 :
314     * 根据测试:
315     *  GET 变成了 GET /.....*/
316   } else
317   {
318    // printf("ProxyHost=%s
ProxyPort=%d
",proxyhost,proxyport);
319    strcat(request,url);
320   }
321   if(http10==1)
322       strcat(request," HTTP/1.0");
323   else if (http10==2)
324       strcat(request," HTTP/1.1");
325   strcat(request,"
");
326   if(http10>0)
327       strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"
");
328           /*  字符串组合技巧
329            * */
330   if(proxyhost==NULL && http10>0)
331   {
332       strcat(request,"Host: ");
333       strcat(request,host);
334       strcat(request,"
");
335   }
336   if(force_reload && proxyhost!=NULL)
337   {
338       strcat(request,"Pragma: no-cache
");
339   }
340   if(http10>1)
341       strcat(request,"Connection: close
");
342   /* add empty line at end */
343   if(http10>0) strcat(request,"
"); 
344   // printf("Req=%s
",request);
345 }
346 
347 /* vraci system rc error kod */
348 static int bench(void)
349 {
350   int i,j,k;    
351   pid_t pid=0;
352   FILE *f;
353 
354   /* check avaibility of target server */
355  // printf("commmd: proxyhost= host %s proxhost %s
",host,proxyhost);
356   i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
357   //printf("commd: test3
");
358 
359   /*  建立测试网址的套接字 
360    *  可以后的host的直接使用host
361    *  不可以的利用gethostbyname函数通过域名解析获得host
362    * */
363   if(i<0) { 
364        fprintf(stderr,"
Connect to server failed. Aborting benchmark.
");
365            return 1;
366          }
367   close(i);
368   /* create pipe */
369   if(pipe(mypipe))
370           /* 进程间的通信方式 
371            * 通道
372            * */
373   {
374       perror("pipe failed.");
375       return 3;
376   }
377 
378   /* not needed, since we have alarm() in childrens */
379   /* wait 4 next system clock tick */
380   /*
381   cas=time(NULL);
382   while(time(NULL)==cas)
383         sched_yield();
384   */
385 
386   /* fork childs */
387   for(i=0;i<clients;i++)
388   {
389        pid=fork();
390        if(pid <= (pid_t) 0)// 为了防止什么 ????
391        {
392            /* child process or error*/
393                sleep(1); /* make childs faster */
394            break;
395        }
396   }
397 
398   if( pid< (pid_t) 0)
399   {
400           fprintf(stderr,"problems forking worker no. %d
",i);
401       perror("fork failed.");
402       return 3;
403   }
404 
405   if(pid== (pid_t) 0)
406   {
407     printf("commnd:request:%s
",request);
408     /* I am a child */
409     if(proxyhost==NULL)
410       benchcore(host,proxyport,request);
411          else
412       benchcore(proxyhost,proxyport,request);
413 
414          /* write results to pipe */
415      f=fdopen(mypipe[1],"w");
416      if(f==NULL)
417      {
418          perror("open pipe for writing failed.");
419          return 3;
420      }
421      /* fprintf(stderr,"Child - %d %d
",speed,failed); */
422      fprintf(f,"%d %d %d
",speed,failed,bytes);
423      fclose(f);
424      return 0;
425   } else
426   {
427       f=fdopen(mypipe[0],"r");
428       if(f==NULL) 
429       {
430           perror("open pipe for reading failed.");
431           return 3;
432       }
433           /*  作为一个读的文件流,为什么要设置成 nobuf ????
434            * */
435       setvbuf(f,NULL,_IONBF,0);
436       speed=0;
437           failed=0;
438           bytes=0;
439 
440       while(1)
441       {
442           pid=fscanf(f,"%d %d %d",&i,&j,&k);
443           if(pid<2)
444                   {
445                        fprintf(stderr,"Some of our childrens died.
");
446                        break;
447                   }
448           speed+=i;
449           failed+=j;
450           bytes+=k;
451           /* fprintf(stderr,"*Knock* %d %d read=%d
",speed,failed,pid); */
452           if(--clients==0) break;
453       }
454       fclose(f);
455 
456   printf("
Speed=%d pages/min, %d bytes/sec.
Requests: %d susceed, %d failed.
",
457           (int)((speed+failed)/(benchtime/60.0f)),
458           (int)(bytes/(float)benchtime),
459           speed,
460           failed);
461   }
462   return i;
463 }
464 
465 void benchcore(const char *host,const int port,const char *req)
466 {
467  int rlen;
468  char buf[1500];
469  int s,i;
470  struct sigaction sa;
471 
472  /* setup alarm signal handler */
473  sa.sa_handler=alarm_handler;
474  sa.sa_flags=0;
475  if(sigaction(SIGALRM,&sa,NULL))
476          /* SIGALRM 是 定时器终止时发送给进程的信号
477           * SIG 是信号名的通用前缀,ALRM是alarm的缩写
478           * 通常作为长时间操作的超时信号
479           * 或者提供一种隔一定时间处理某些操作的方式
480           * 一般是在调用 alarm(t ) t秒后出现
481           * */
482     exit(3);
483  alarm(benchtime);
484  /* sigcation 和 alarm 设置了一个闹钟,用来定时退出 */
485 
486  rlen=strlen(req);
487  nexttry:while(1)
488  {
489     if(timerexpired)
490     {
491        if(failed>0)
492        {
493           /* fprintf(stderr,"Correcting failed by signal
"); */
494           failed--;
495        }
496        return;
497     }
498     s=Socket(host,port);                          
499     if(s<0) { failed++;continue;} 
500     /*  申请建立连接失败, fail++
501      * */
502     if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}
503     /* 无法写入套接字,算是失败吧 ????
504      * fail++
505      * */
506     if(http10==0) 
507             /*http 0.9 进行关闭操作 ????*/
508         if(shutdown(s,1))
509                     /* 1 终止 传送操作 */
510             { failed++;close(s);continue;}
511     if(force==0) 
512             /*  阻塞等待回应
513              */
514     {
515             /* read all available data from socket */
516         while(1)
517         {
518               if(timerexpired) break; 
519           i=read(s,buf,1500);
520                //fprintf(stderr,"%d
",i); 
521               // fprintf(stderr,"%s",buf);
522           if(i<0) 
523               { 
524                  failed++;
525                  close(s);
526                  goto nexttry;
527               }
528            else
529                if(i==0) break;
530                else
531                    bytes+=i;
532         }
533     }
534     if(close(s)) {failed++;continue;}
535     speed++;
536  }
537 }

socket.c

 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 int Socket(const char *host, int clientPort)
30 {
31     int sock;
32     unsigned long inaddr;
33     struct sockaddr_in ad;
34     struct hostent *hp;
35     
36 //    printf("%s
",host);
37     memset(&ad, 0, sizeof(ad));
38     ad.sin_family = AF_INET;
39     
40     inaddr = inet_addr(host);
41     if (inaddr != INADDR_NONE)
42             /* 无符号长整型和负数比较
43              * 上面这句话有问题, iner_addr 返回一个FFFFFFFF,即-1为错误,在ip地址翻译上 
44              * 255.255.255.255,这个也就是 为什么不建议使用 inet_addr的原因*/
45         memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
46 else
47     {
48          //   printf("test 4
");
49         hp = gethostbyname(host);
50         //    printf("test 5
");
51         if (hp == NULL)
52             return -1;
53         memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
54     }
55     ad.sin_port = htons(clientPort);
56     
57     sock = socket(AF_INET, SOCK_STREAM, 0);
58     if (sock < 0)
59         return sock;
60     if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
61         return -1;
62     return sock;
63 }

 

**************************************************************************************************

最后放一些写个自己的东西

1. 我学到的字符串更加灵活的应用,尤其是几个以前没有见过的string.h库里的函数

2. 对http协议有了个初步的了解 : http简明解析

3. getopt函数的使用的了解

 

  

   

原文地址:https://www.cnblogs.com/dilidingzhi/p/4298096.html