webbench-1.5_hacking

  1 /****************************************************************************
  2  *
  3  *                        webbench-1.5_hacking
  4  *
  5  * 1.这是webbench-1.5版本中webbench.c(主程序)的源码,源码不到700行(除去注释).
  6  * 2.通过分析、阅读该源码,可以一窥浏览器访问服务器的原理以及web服务器
  7  *     压力测试的原理.
  8  * 3.知识量:
  9  *     1.C语言;
 10  *     2.Unix或类Unix系统编程;
 11  *     3.微量的http协议(请求行、消息头、实体内容);
 12  *     4.如何阅读别人的代码( 从main函数开始 :) );
 13  * 4.webbench-1.5 文件结构如下:
 14  *     .
 15  *     |-- COPYRIGHT -> debian/copyright
 16  *     |-- ChangeLog -> debian/changelog
 17  *     |-- Makefile           -------->makefile 文件
 18  *     |-- debian
 19  *     |   |-- changelog
 20  *     |   |-- control
 21  *     |   |-- copyright
 22  *     |   |-- dirs
 23  *     |   `-- rules
 24  *     |-- socket.c     -------->里面定义了Socket()函数供webbench.c调用
 25  *     |-- webbench.1   -------->说明文档,使用shell命令查看: less webbench.1
 26  *     `-- webbench.c   -------->你接下来要阅读的文件
 27  *
 28  * 5.如何阅读该文档:
 29  *     1.linux下使用vi/vim配和ctags,windows下使用Source Insight,当然你也
 30  *          可以用其他文本编辑器看.
 31  *     2.先找到main函数,然后就可以开始阅读了,遇到对应的函数,就去看对应的
 32  *          函数.
 33  *     3.对于有些函数,本人没有添加注释,或者说本人觉得没必要.
 34  *     4.祝您好运.  :)
 35  *
 36  * 6.webbench-1.5版本下载url: http://home.tiscali.cz/~cz210552/webbench.html
 37  * 
 38  * 如果您对本文有任何意见、提议,可以发邮件至zengjf42@163.com,会尽快回复.
 39  * 本文的最终解释权归本人(曾剑锋)所有,仅供学习、讨论.
 40  *
 41  *                                          2015-3-24 阴 深圳 尚观 Var
 42  *
 43  ***************************************************************************/
 44 
 45 /*
 46  * (C) Radim Kolar 1997-2004
 47  * This is free software, see GNU Public License version 2 for
 48  * details.
 49  *
 50  * Simple forking WWW Server benchmark:
 51  *
 52  * Usage:
 53  *   webbench --help
 54  *
 55  * Return codes:
 56  *    0 - sucess
 57  *    1 - benchmark failed (server is not on-line)
 58  *    2 - bad param
 59  *    3 - internal error, fork failed
 60  * 
 61  */ 
 62 
 63 #include "socket.c"
 64 /**
 65  * 以下是socket.c中的主要代码:
 66  *
 67  * //
 68  * // Socket函数完成的工作:
 69  * //     1. 转换IP,域名,填充struct sockaddr_in,获取对应的socket描述符;
 70  * //     2. 连接服务器;
 71  * //
 72  *
 73  * int Socket(const char *host, int clientPort)
 74  * {
 75  *     //
 76  *     // 局部变量说明:
 77  *     //     1. sock   : 用来保存要返回的socket文件描述符;
 78  *     //     2. inaddr : 保存转换为网络序列的二进制数据;
 79  *     //     3. ad     : 保存连接网络服务器的地址,端口等信息;
 80  *     //     4. hp     : 指向通过gethostbyname()获取的服务器信息;
 81  *     //
 82  *
 83  *     int sock;
 84  *     unsigned long inaddr;
 85  *     struct sockaddr_in ad;
 86  *     struct hostent *hp;
 87  *     
 88  *     memset(&ad, 0, sizeof(ad));
 89  *     ad.sin_family = AF_INET;
 90  * 
 91  *     //
 92  *     // 这一段主要完成以下功能:
 93  *     //     1. 如果传入的是点分十进制的IP,那么直接转换得到网络字节序列IP;
 94  *     //     2. 如果传入的域名,这需要是用gethostbyname()解析域名获取主机IP;
 95  *     //
 96  *     inaddr = inet_addr(host);    //将点分十进制IP转换成网络序列的二进制数据
 97  *                                  //如果host不是点分十进制的,那么返回INADDR_NONE
 98  *     if (inaddr != INADDR_NONE)
 99  *         memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
100  *     else
101  *     {
102  *         hp = gethostbyname(host); 
103  *         if (hp == NULL)
104  *             return -1;
105  *         memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
106  *     }
107  *     ad.sin_port = htons(clientPort);
108  *     
109  *     //
110  *     // 这一段主要完成的工作:
111  *     //     1. 获取socket;
112  *     //     2. 连接网络服务器;
113  *     //
114  *     sock = socket(AF_INET, SOCK_STREAM, 0);
115  *     if (sock < 0)
116  *         return sock;
117  *     if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
118  *         return -1;
119  *     return sock;
120  * }
121  *
122  */
123 #include <unistd.h>
124 #include <sys/param.h>
125 #include <rpc/types.h>
126 #include <getopt.h>
127 #include <strings.h>
128 #include <time.h>
129 #include <signal.h>
130 
131 /* values */
132 volatile int timerexpired=0;        //定时器定时到了的标志,子进程根据这个标志退出
133 int speed=0;                        //正常响应请求数
134 int failed=0;                       //不正常响应请求数
135 int bytes=0;                        //从服务器接收返回的数据字节数
136 
137 /* globals */
138 /**
139  * 支持的网络协议,默认是: http/1.0
140  *     1. 0 - http/0.9  
141  *     2. 1 - http/1.0  
142  *     3. 2 - http/1.1 
143  */
144 int http10=1; 
145 
146 /* Allow: GET, HEAD, OPTIONS, TRACE */
147 #define METHOD_GET 0
148 #define METHOD_HEAD 1
149 #define METHOD_OPTIONS 2
150 #define METHOD_TRACE 3
151 #define PROGRAM_VERSION "1.5"
152 int method       = METHOD_GET;      //默认请求方式get
153 
154 int clients      = 1;               //默认的客户端数量
155 int force        = 0;               //连接访问web后是否接收服务器返回的数据
156 int force_reload = 0;               //是否让浏览器缓存页面
157 int proxyport    = 80;              //默认代理端口
158 char *proxyhost  = NULL;            //代理主机域名
159 /**
160  * 默认的测试时间:30s,主要是给子进程的闹钟,时间到了timerexpired会置1,
161  * 子进程会根据这个条件退出循环,并通过管道给父进程发送benchtime对应的
162  * 时间内对服务器访问的数据:speed,failed,bytes.
163  */
164 int benchtime    = 30;              
165 
166 /* internal */
167 /**
168  * 父进程与子进程通信通过管道进行通信:
169  *     1. mypipe[0] : 是读的管道端口;
170  *     2. mypipe[1] : 是写的管道端口;
171  */
172 int mypipe[2];                
173 /** 
174  * 存放点分十进制字符串或者域名 
175  */
176 char host[MAXHOSTNAMELEN];          
177 /*
178  * 保存http协议请求头,主要是在build_request()中完成相关操作;
179  */
180 #define REQUEST_SIZE 2048
181 char request[REQUEST_SIZE];   
182 
183 /**
184  * struct option是getopt.h中定义的结构体:
185  * 
186  * struct option
187  * {
188  *   const char *name;  //表示长参数名
189  *
190  *   //
191  *   // # define no_argument       0 //表示该参数后面没有参数
192  *   // # define required_argument 1 //表示该参数后面一定要跟个参数
193  *   // # define optional_argument 2 //表示该参数后面可以跟,也可以不跟参数值
194  *   //
195  *   int has_arg; 
196  *
197  *   //
198  *   // 用来决定,getopt_long()的返回值到底是什么:
199  *   //    1. 如果flag是NULL(通常情况),则函数会返回与该项option匹配的val值;
200  *   //    2. 如果flag不是NULL,则将val值赋予flag所指向的内存,并且返回值设置为0;
201  *   //
202  *   int *flag;     
203  *   int val;           //和flag联合决定返回值
204  * };
205  */
206 static const struct option long_options[]=
207 {
208     {"force",no_argument,&force,1},
209     {"reload",no_argument,&force_reload,1},
210     {"time",required_argument,NULL,'t'},
211     {"help",no_argument,NULL,'?'},
212     {"http09",no_argument,NULL,'9'},
213     {"http10",no_argument,NULL,'1'},
214     {"http11",no_argument,NULL,'2'},
215     {"get",no_argument,&method,METHOD_GET},
216     {"head",no_argument,&method,METHOD_HEAD},
217     {"options",no_argument,&method,METHOD_OPTIONS},
218     {"trace",no_argument,&method,METHOD_TRACE},
219     {"version",no_argument,NULL,'V'},
220     {"proxy",required_argument,NULL,'p'},
221     {"clients",required_argument,NULL,'c'},
222     {NULL,0,NULL,0}
223 };
224 
225 /* prototypes */
226 static void benchcore(const char* host,const int port, const char *request);
227 static int bench(void);
228 static void build_request(const char *url);
229 
230 /**
231  * alarm_handler函数功能:
232  *     1. 闹钟信号处理函数,当时间到了的时候,timerexpired被置1,表示时间到了;
233  *     2. benchcore()中会根据timerexpired值来判断子进程的运行;
234  *
235  */
236 static void alarm_handler(int signal)
237 {
238    timerexpired=1;
239 }    
240 
241 /**
242  * usage函数功能:
243  *      输出webbench的基本是用方法.
244  */
245 static void usage(void)
246 {
247    fprintf(stderr,
248     "webbench [option]... URL\n"
249     "  -f|--force               Don't wait for reply from server.\n"
250     "  -r|--reload              Send reload request - Pragma: no-cache.\n"
251     "  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.\n"
252     "  -p|--proxy <server:port> Use proxy server for request.\n"
253     "  -c|--clients <n>         Run <n> HTTP clients at once. Default one.\n"
254     "  -9|--http09              Use HTTP/0.9 style requests.\n"
255     "  -1|--http10              Use HTTP/1.0 protocol.\n"
256     "  -2|--http11              Use HTTP/1.1 protocol.\n"
257     "  --get                    Use GET request method.\n"
258     "  --head                   Use HEAD request method.\n"
259     "  --options                Use OPTIONS request method.\n"
260     "  --trace                  Use TRACE request method.\n"
261     "  -?|-h|--help             This information.\n"
262     "  -V|--version             Display program version.\n"
263     );
264 };
265 
266 /**
267  * main函数完成功能:
268  *     1. 解析命令行参数;
269  *     2. 组合http请求头;
270  *     3. 打印一些初步解析出来的基本信息,用于查看对比信息;
271  *     4. 调用bench创建子进程去访问服务器;
272  */
273 int main(int argc, char *argv[])
274 {
275     /**
276      * 局部变量说明:
277      *     1. opt           : 返回的操作符;
278      *     2. options_index : 当前解析到的长命令行参数的下标;
279      *     3. tmp           : 指向字符的指针;
280      */
281     int opt=0;
282     int options_index=0;
283     char *tmp=NULL;
284 
285     if(argc==1)
286     {
287          usage();
288              return 2;
289     } 
290 
291     while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
292     {
293         switch(opt)
294         {
295             case  0 : break;
296             case 'f': force=1;break;          //不接收服务器返回的数据
297             case 'r': force_reload=1;break;   //不缓存请求数据,目前不知有何用
298             case '9': http10=0;break;         //选择http/0.9 
299             case '1': http10=1;break;         //选择http/1.0 
300             case '2': http10=2;break;         //选择http/1.1 
301             case 'V': printf(PROGRAM_VERSION"\n");exit(0);   //返回webbench版本号
302             case 't': benchtime=atoi(optarg);break;          //设置基准测试时间
303             case 'p': 
304                   /* proxy server parsing server:port */
305                   /**
306                    * 传入的参数格式:<IP:port>或者<域名:port>,可能的错误有以下3种可能:
307                    *     1. 传入的参数没有是用:分开IP(或域名)和端口号,包括了没有传参数;
308                    *     2. 使用了':'号,但是没有给出IP;
309                    *     3. 使用了':'号,但是没有给出端口号;
310                    */
311                   tmp=strrchr(optarg,':');
312                   proxyhost=optarg;
313                   if(tmp==NULL)
314                   {
315                       break;
316                   }
317                   if(tmp==optarg)
318                   {
319                       fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
320                       return 2;
321                   }
322                   if(tmp==optarg+strlen(optarg)-1)
323                   {
324                       fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
325                       return 2;
326                   }
327                   /**
328                    * 将':'换成'\0',这样就得到了IP(或域名)字符串和port字符串,并通过atoi()获取端口号
329                    */
330                   *tmp='\0';
331                   proxyport=atoi(tmp+1);break;
332             case ':':
333             case 'h':
334             case '?': usage();return 2;break;
335             case 'c': clients=atoi(optarg);break;   //指定要生成多少个客户端,默认是1个
336         }
337     }
338     
339     /**
340      * 命令行参数没有给出URL
341      */
342     if(optind==argc) {
343         fprintf(stderr,"webbench: Missing URL!\n");
344         usage();
345         return 2;
346     }
347 
348     /**
349      * 修正客户端数量和运行时间
350      */
351     if(clients==0) clients=1;
352     if(benchtime==0) benchtime=60;
353 
354     /* Copyright */
355     fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
356         "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
357         );
358 
359     /**
360      * 创建发送给http服务器的请求头
361      */
362     build_request(argv[optind]);
363 
364     /* print bench info */
365     /**
366      * 接下来这部分都是打印出初步解析出来的数据,可以用来检查是否符合要求
367      */
368     printf("\nBenchmarking: ");
369     switch(method)
370     {
371         case METHOD_GET:
372         default:
373                printf("GET");break;
374         case METHOD_OPTIONS:
375                printf("OPTIONS");break;
376         case METHOD_HEAD:
377                printf("HEAD");break;
378         case METHOD_TRACE:
379                printf("TRACE");break;
380     }
381     printf(" %s",argv[optind]);
382     switch(http10)
383     {
384         case 0: printf(" (using HTTP/0.9)");break;
385         case 2: printf(" (using HTTP/1.1)");break;
386     }
387     printf("\n");
388     if(clients==1) 
389         printf("1 client");
390     else
391         printf("%d clients",clients);
392 
393     printf(", running %d sec", benchtime);
394     if(force) 
395         printf(", early socket close");
396     if(proxyhost!=NULL) 
397         printf(", via proxy server %s:%d",proxyhost,proxyport);
398     if(force_reload) 
399         printf(", forcing reload");
400     printf(".\n");
401 
402     /**
403      * 调用bench函数,完成相应的功能
404      */
405     return bench();
406 }
407 
408 /**
409  * build_request函数完成功能:
410  *     1. 初始化host和request数组;
411  *     2. 检查给出的url参数是否合法;
412  *     3. 合成对应http协议的请求头;
413  */
414 void build_request(const char *url)
415 {
416     /**
417      * 局部变量说明:
418      *     1. tmp : 用于存储端口号;
419      *     2. i   : 循环计数;
420      */
421     char tmp[10];
422     int i;
423   
424     /**
425      * 初始化host和request数组,为下面的操作作准备
426      */
427     bzero(host,MAXHOSTNAMELEN);
428     bzero(request,REQUEST_SIZE);
429   
430     /**
431      * 不同的请求方式,对应不同的协议标准,这里相当于校正请求协议
432      */
433     if(force_reload && proxyhost!=NULL && http10<1) 
434         http10=1;
435     if(method==METHOD_HEAD && http10<1) 
436         http10=1;
437     if(method==METHOD_OPTIONS && http10<2) 
438         http10=2;
439     if(method==METHOD_TRACE && http10<2) 
440         http10=2;
441   
442     switch(method)
443     {
444           default:
445           case METHOD_GET: strcpy(request,"GET");break;
446           case METHOD_HEAD: strcpy(request,"HEAD");break;
447           case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
448           case METHOD_TRACE: strcpy(request,"TRACE");break;
449     }
450             
451     strcat(request," ");
452   
453     /**
454      * url中如果不存在"://"说明是非法的URL地址
455      */
456     if(NULL==strstr(url,"://"))
457     {
458           fprintf(stderr, "\n%s: is not a valid URL.\n",url);
459           exit(2);
460     }
461 
462     /**
463      * url字符串长度不能长于1500字节
464      */
465     if(strlen(url)>1500)
466     {
467         fprintf(stderr,"URL is too long.\n");
468           exit(2);
469     }
470 
471     /**
472      * 如果没有设置代理服务器,并且协议不是http,说明出错了.
473      */
474     if(proxyhost==NULL)
475           if (0!=strncasecmp("http://",url,7)) 
476           { 
477             fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
478             exit(2);
479         }
480 
481     /* protocol/host delimiter */
482     /**
483      * 接下来是解析URL并合成请求行
484      */
485 
486     i=strstr(url,"://")-url+3;
487     /* printf(" %d\n",i); */    //如果url = "http://www.baidu.com:80/", i = 7
488     if(strchr(url+i,'/')==NULL) 
489     {
490         fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n");
491         exit(2);
492     }
493 
494     /**
495      * 这段代码主要完成一下内容:
496      *     1. 如果是通过代理服务器对服务器进行访问,那么proxyhost和proxyport就直接
497      *         是代理服务器的IP和端口号;
498      *     2. 如果是直接访问目标服务器,则需要从URL地址中解析出IP(或者域名)和端口号;
499      */
500     if(proxyhost==NULL)
501     {
502         /* get port from hostname */
503         if(index(url+i,':')!=NULL &&
504            index(url+i,':')<index(url+i,'/'))
505         {
506             //获取host主机域名
507               strncpy(host,url+i,strchr(url+i,':')-url-i);
508 
509             //没有给出端口号,就直接是用默认的端口号: 80
510               bzero(tmp,10);
511               strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
512               /* printf("tmp=%s\n",tmp); */
513               proxyport=atoi(tmp);
514               if(proxyport==0) proxyport=80;
515         } 
516         else
517         {
518             /**
519              * 获取host主机域名,没有给出端口号,就直接是用默认的端口号: 80
520              */
521             strncpy(host,url+i,strcspn(url+i,"/"));
522         }
523         // printf("Host=%s\n",host);
524         strcat(request+strlen(request),url+i+strcspn(url+i,"/")); //这里是获取URI
525     } else
526     {
527         // printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
528         // 代理服务器需要完整的URL去访问服务器,而不仅仅是URI
529         strcat(request,url);
530     }
531 
532     /**
533      * 当前的使用的http协议的版本
534      */
535     if(http10==1)
536           strcat(request," HTTP/1.0");
537     else if (http10==2)
538           strcat(request," HTTP/1.1");
539     strcat(request,"\r\n");
540 
541     /**
542      * 到这里请求行就已经合成完毕了,接下来要合成消息头
543      */
544     if(http10>0)
545           strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");
546     if(proxyhost==NULL && http10>0)
547     {
548           strcat(request,"Host: ");
549           strcat(request,host);
550           strcat(request,"\r\n");
551     }
552     /**
553      * 不缓存请求页面,查资料说是对浏览器有效,难道服务器也需要,
554      * 不知为何这里也需要?
555      */
556     if(force_reload && proxyhost!=NULL)
557     {
558           strcat(request,"Pragma: no-cache\r\n");
559     }
560     /**
561      * 使用短连接,即服务器返回后就断开连接
562      */
563     if(http10>1)
564           strcat(request,"Connection: close\r\n");
565 
566     /* add empty line at end */
567     /**
568      * 按不同http协议要求,是否空出一行,下面是请求实体,如果是post请求,
569      * 需要把请求参数放在这部分.到这里,消息头也就合成完了,接下
570      * 来就是是用这个请求头去访问http服务器了
571      */
572     if(http10>0) strcat(request,"\r\n"); 
573     // printf("Req=%s\n",request);
574 }
575 
576 /* vraci system rc error kod */
577 /**
578  * bench函数完成以下功能:
579  *     1. 试探性的尝试一次是否能够正常连接服务器,如果连接失败,也就没必要继续后续处理了;
580  *     2. 创建管道,用于父子进程通信;
581  *     3. 创建clients对应数量的子进程;
582  *     4. 子进程:
583  *         1. 对服务器进行benchtime秒的连接访问,获取对应的failed,speed,bytes值;
584  *         2. 当时间到了benchtime秒以后,打开写管道;
585  *         3. 将子进程自己测试得到的failed,speed,bytes值发送给父进程;
586  *         4. 关闭写管道文件描述符;
587  *     5. 父进程:
588  *         1. 打开读管道;
589  *         2. 设置管道一些参数,初始化父进程的failed,speed,bytes变量;
590  *         3. while循环不断去获取子进程传输过来的数据,直到子进程全部退出;
591  *         4. 关闭读管道;
592  *         5. 对数据进行处理,并打印输出;
593  */
594 static int bench(void)
595 {
596     /**
597      * 局部变量说明:
598      *     1. i   : for循环暂存变量,这里也暂存了一下阿socket文件描述符,
599      *                 还暂存从子进程传给父进程的speed数据;;
600      *     2. j   : 暂存从子进程传给父进程的failed数据;
601      *     3. k   : 暂存从子进程传给父进程的byttes数据;
602      *     4. pid : fork()出子进程时,保存进程描述符的;
603      *     5. f   : 保存打开的管道的文件指针;
604      */
605     int i,j,k;    
606     pid_t pid=0;
607     FILE *f;
608   
609     /* check avaibility of target server */
610     /**
611      * 测试一下我们要测试的服务器是否能够正常连接.
612      * Socket函数完成的工作:
613      *     1. 转换IP,域名,填充struct sockaddr_in,获取对应的socket描述符;
614      *     2. 连接服务器;
615      */
616     i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
617     if(i<0) { 
618           fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
619         return 1;
620     }
621     close(i);
622 
623     /* create pipe */
624     /**
625      * 创建管道,主要用于父进程和子进程通信
626      */
627     if(pipe(mypipe))
628     {
629           perror("pipe failed.");
630           return 3;
631     }
632   
633     /* not needed, since we have alarm() in childrens */
634     /* wait 4 next system clock tick */
635     /*
636     cas=time(NULL);
637     while(time(NULL)==cas)
638           sched_yield();
639     */
640   
641     /* fork childs */
642     for(i=0;i<clients;i++)
643     {
644           pid=fork();
645         /**
646          * 子进程获取到的pid=0,所以子进程会理解跳出循环
647          * 不会再创建子进程,而父进程则会跳过这个判断,继续
648          * 创建子进程,知道数量达到clients的值.
649          */
650           if(pid <= (pid_t) 0)
651           {
652              /* child process or error*/
653               sleep(1); /* make childs faster */
654               break;
655           }
656     }
657   
658     //创建子进程失败
659     if( pid< (pid_t) 0)
660     {
661         fprintf(stderr,"problems forking worker no. %d\n",i);
662           perror("fork failed.");
663           return 3;
664     }
665 
666     /**
667      * 这一部分完成的工作:
668      *     1. 子进程:
669      *         1. 对服务器进行benchtime秒的连接访问,获取对应的failed,speed,bytes值;
670      *         2. 当时间到了benchtime以后,打开写管道;
671      *         3. 将子进程自己测试得到的failed,speed,bytes值发送给父进程;
672      *         4. 关闭写管道文件描述符;
673      *     2. 父进程:
674      *         1. 打开读管道;
675      *         2. 设置管道一些参数,初始化父进程的failed,speed,bytes变量;
676      *         3. while循环不断去获取子进程传输过来的数据,直到子进程全部退出;
677      *         4. 关闭读管道;
678      *         5. 对数据进行处理,并打印输出;
679      */
680   
681     if(pid== (pid_t) 0)
682     {
683         /* I am a child */
684         if(proxyhost==NULL)
685             benchcore(host,proxyport,request);
686         else
687             benchcore(proxyhost,proxyport,request);
688   
689            /* write results to pipe */
690           f=fdopen(mypipe[1],"w");
691           if(f==NULL)
692           {
693               perror("open pipe for writing failed.");
694               return 3;
695           }
696           /* fprintf(stderr,"Child - %d %d\n",speed,failed); */
697           fprintf(f,"%d %d %d\n",speed,failed,bytes);
698           fclose(f);
699           return 0;
700     } 
701     else
702     {
703           f=fdopen(mypipe[0],"r");
704           if(f==NULL) 
705           {
706                 perror("open pipe for reading failed.");
707                 return 3;
708           }
709           setvbuf(f,NULL,_IONBF,0); //设置管道为无缓冲类型
710           speed=0;
711         failed=0;
712         bytes=0;
713   
714           while(1)
715           {
716                 pid=fscanf(f,"%d %d %d",&i,&j,&k);
717                 if(pid<2)
718             {
719                 fprintf(stderr,"Some of our childrens died.\n");
720                 break;
721             }
722                 speed+=i;
723                 failed+=j;
724                 bytes+=k;
725                 /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
726                 if(--clients==0) break;
727           }
728           fclose(f);
729   
730         printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
731                 (int)((speed+failed)/(benchtime/60.0f)),
732                 (int)(bytes/(float)benchtime),
733                 speed,
734                 failed);
735     }
736     return i;
737 }
738 
739 /**
740  * benchcore函数完成功能:
741  *     1. 注册闹钟处理函数,设置闹钟时间,具体时间由benchtime给出,默认是30s;
742  *     2. while循环里判断闹钟时间是否到了,如果到了退出循环;
743  *     3. while循环里连接服务器;
744  *     4. while循环里发送http请求头给服务器;
745  *     5. while循环里判断是否需要接受服务器数据;
746  *     6. 关闭与服务器的连接;
747  *     7. 在整个过程中,以下变量会统计在benchtime给出的时间内的一些信息:
748  *         1. failed : 连接服务器失败和传输数据过程中失败的连接数;
749  *         2. speed  : 正常连接服务器,并且正常传输数据的连接数
750  *         3. bytes  : 从服务器获取到的字节数;
751  */
752 void benchcore(const char *host,const int port,const char *req)
753 {
754     /**
755      * 局部变量说明:
756      *     1. rlen : 请求字符串的长度;
757      *     2. buf  : 保存从服务器获取的数据;
758      *     3. s    : socket文件描述符;
759      *     4. i    : 保存从服务器读到的字节数;
760      *     5. sa   : 信号结构体变量;
761      */
762     int rlen;
763     char buf[1500];
764     int s,i;
765     struct sigaction sa;
766 
767     /* setup alarm signal handler */
768     /**
769      * 注册闹钟信号处理函数,并设置闹钟时间为benchtime,默认是30s;
770      */
771     sa.sa_handler=alarm_handler;
772     sa.sa_flags=0;
773     if(sigaction(SIGALRM,&sa,NULL))
774         exit(3);
775     alarm(benchtime);
776 
777     rlen=strlen(req);
778 nexttry:
779     while(1)
780     {
781         if(timerexpired) //检查闹钟时间是否到了,如果到了,子进程就退出
782         {
783             if(failed>0)
784             {
785                /* fprintf(stderr,"Correcting failed by signal\n"); */
786                failed--;
787             }
788             return;
789         }
790 
791         s=Socket(host,port);                          
792         if(s<0) 
793         { 
794             failed++;
795             continue;
796         } 
797 
798         /**
799          * 将请求头发给web服务器
800          */
801         if(rlen!=write(s,req,rlen)) 
802         {
803             /**
804              * 写数据失败,代表当前连接有问题,也就是失败了
805              */
806             failed++;
807             close(s);
808             continue;
809         }
810 
811         if(http10==0)  // http/0.9协议
812             if(shutdown(s,1)) 
813             { 
814                 failed++;
815                 close(s);
816                 continue;
817             }
818 
819         if(force==0) //是否读取服务器返回数据
820         {
821             /* read all available data from socket */
822             while(1)
823             {
824                 if(timerexpired) break; //判断是否已经闹钟到时
825                 i=read(s,buf,1500);
826                 /* fprintf(stderr,"%d\n",i); */
827                 /**
828                  * 对当前次连接数据读取错误,那么重来
829                  */
830                 if(i<0) 
831                 { 
832                     failed++;
833                     close(s);
834                     goto nexttry;
835                 }
836                 else
837                     if(i==0) 
838                         break;
839                     else
840                         bytes+=i; //统计一共读取了多少字节
841             }
842         }
843 
844         /**
845          * 关闭socket文件,如果出错,那么增加失败的统计数据
846          */
847         if(close(s)) 
848         {
849             failed++;
850             continue;
851         }
852         /**
853          * 成功完成连接,数据传输,获取等等工作,speed统计数据+1
854          */
855         speed++;
856     }
857 }

 

posted on 2015-03-24 00:56  zengjf  阅读(465)  评论(0编辑  收藏  举报

导航