zoukankan      html  css  js  c++  java
  • 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
    "
    249     "  -f|--force               Don't wait for reply from server.
    "
    250     "  -r|--reload              Send reload request - Pragma: no-cache.
    "
    251     "  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.
    "
    252     "  -p|--proxy <server:port> Use proxy server for request.
    "
    253     "  -c|--clients <n>         Run <n> HTTP clients at once. Default one.
    "
    254     "  -9|--http09              Use HTTP/0.9 style requests.
    "
    255     "  -1|--http10              Use HTTP/1.0 protocol.
    "
    256     "  -2|--http11              Use HTTP/1.1 protocol.
    "
    257     "  --get                    Use GET request method.
    "
    258     "  --head                   Use HEAD request method.
    "
    259     "  --options                Use OPTIONS request method.
    "
    260     "  --trace                  Use TRACE request method.
    "
    261     "  -?|-h|--help             This information.
    "
    262     "  -V|--version             Display program version.
    "
    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"
    ");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.
    ",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.
    ",optarg);
    325                       return 2;
    326                   }
    327                   /**
    328                    * 将':'换成'',这样就得到了IP(或域名)字符串和port字符串,并通过atoi()获取端口号
    329                    */
    330                   *tmp='';
    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!
    ");
    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"
    "
    356         "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
    "
    357         );
    358 
    359     /**
    360      * 创建发送给http服务器的请求头
    361      */
    362     build_request(argv[optind]);
    363 
    364     /* print bench info */
    365     /**
    366      * 接下来这部分都是打印出初步解析出来的数据,可以用来检查是否符合要求
    367      */
    368     printf("
    Benchmarking: ");
    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("
    ");
    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(".
    ");
    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, "
    %s: is not a valid URL.
    ",url);
    459           exit(2);
    460     }
    461 
    462     /**
    463      * url字符串长度不能长于1500字节
    464      */
    465     if(strlen(url)>1500)
    466     {
    467         fprintf(stderr,"URL is too long.
    ");
    468           exit(2);
    469     }
    470 
    471     /**
    472      * 如果没有设置代理服务器,并且协议不是http,说明出错了.
    473      */
    474     if(proxyhost==NULL)
    475           if (0!=strncasecmp("http://",url,7)) 
    476           { 
    477             fprintf(stderr,"
    Only HTTP protocol is directly supported, set --proxy for others.
    ");
    478             exit(2);
    479         }
    480 
    481     /* protocol/host delimiter */
    482     /**
    483      * 接下来是解析URL并合成请求行
    484      */
    485 
    486     i=strstr(url,"://")-url+3;
    487     /* printf(" %d
    ",i); */    //如果url = "http://www.baidu.com:80/", i = 7
    488     if(strchr(url+i,'/')==NULL) 
    489     {
    490         fprintf(stderr,"
    Invalid URL syntax - hostname don't ends with '/'.
    ");
    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
    ",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
    ",host);
    524         strcat(request+strlen(request),url+i+strcspn(url+i,"/")); //这里是获取URI
    525     } else
    526     {
    527         // printf("ProxyHost=%s
    ProxyPort=%d
    ",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,"
    ");
    540 
    541     /**
    542      * 到这里请求行就已经合成完毕了,接下来要合成消息头
    543      */
    544     if(http10>0)
    545           strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"
    ");
    546     if(proxyhost==NULL && http10>0)
    547     {
    548           strcat(request,"Host: ");
    549           strcat(request,host);
    550           strcat(request,"
    ");
    551     }
    552     /**
    553      * 不缓存请求页面,查资料说是对浏览器有效,难道服务器也需要,
    554      * 不知为何这里也需要?
    555      */
    556     if(force_reload && proxyhost!=NULL)
    557     {
    558           strcat(request,"Pragma: no-cache
    ");
    559     }
    560     /**
    561      * 使用短连接,即服务器返回后就断开连接
    562      */
    563     if(http10>1)
    564           strcat(request,"Connection: close
    ");
    565 
    566     /* add empty line at end */
    567     /**
    568      * 按不同http协议要求,是否空出一行,下面是请求实体,如果是post请求,
    569      * 需要把请求参数放在这部分.到这里,消息头也就合成完了,接下
    570      * 来就是是用这个请求头去访问http服务器了
    571      */
    572     if(http10>0) strcat(request,"
    "); 
    573     // printf("Req=%s
    ",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,"
    Connect to server failed. Aborting benchmark.
    ");
    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
    ",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
    ",speed,failed); */
    697           fprintf(f,"%d %d %d
    ",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.
    ");
    720                 break;
    721             }
    722                 speed+=i;
    723                 failed+=j;
    724                 bytes+=k;
    725                 /* fprintf(stderr,"*Knock* %d %d read=%d
    ",speed,failed,pid); */
    726                 if(--clients==0) break;
    727           }
    728           fclose(f);
    729   
    730         printf("
    Speed=%d pages/min, %d bytes/sec.
    Requests: %d susceed, %d failed.
    ",
    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
    "); */
    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
    ",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 }
  • 相关阅读:
    css颜色表示法&颜色表
    css单位
    DOM与BOM
    position定位
    grid layout
    Linux禁止Ping方法
    tracert(traceroute)与ping
    服务器负载均衡技术的原理
    Struts2与webx的比较
    SpringAOP的原理
  • 原文地址:https://www.cnblogs.com/zengjfgit/p/4361568.html
Copyright © 2011-2022 走看看