博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
webbench-1.5_hacking
阅读量:6164 次
发布时间:2019-06-21

本文共 18007 字,大约阅读时间需要 60 分钟。

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  *     else101  *     {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 
124 #include
125 #include
126 #include
127 #include
128 #include
129 #include
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.0140 * 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 0148 #define METHOD_HEAD 1149 #define METHOD_OPTIONS 2150 #define METHOD_TRACE 3151 #define PROGRAM_VERSION "1.5"152 int method = METHOD_GET; //默认请求方式get153 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 2048181 char request[REQUEST_SIZE]; 182 183 /**184 * struct option是getopt.h中定义的结构体:185 * 186 * struct option187 * {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
Run benchmark for
seconds. Default 30.\n"252 " -p|--proxy
Use proxy server for request.\n"253 " -c|--clients
Run
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 * 传入的参数格式:
或者
<域名: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 * 命令行参数没有给出URL341 */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 else391 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 = 7488 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,':')
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
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 else837 if(i==0) 838 break;839 else840 bytes+=i; //统计一共读取了多少字节841 }842 }843 844 /**845 * 关闭socket文件,如果出错,那么增加失败的统计数据846 */847 if(close(s)) 848 {849 failed++;850 continue;851 }852 /**853 * 成功完成连接,数据传输,获取等等工作,speed统计数据+1854 */855 speed++;856 }857 }

 

转载地址:http://eqkba.baihongyu.com/

你可能感兴趣的文章
使用Formik轻松开发更高质量的React表单(三)<Formik />解析
查看>>
也问腾讯:你把用户放在什么位置?
查看>>
CSS Sprites 样式生成工具(bg2css)
查看>>
[转]如何重构代码--重构计划
查看>>
类中如何对list泛型做访问器??
查看>>
C++解析XML--使用CMarkup类解析XML
查看>>
P2P应用层组播
查看>>
Sharepoint学习笔记—修改SharePoint的Timeouts (Execution Timeout)
查看>>
CSS引入的方式有哪些? link和@import的区别?
查看>>
Redis 介绍2——常见基本类型
查看>>
asp.net开发mysql注意事项
查看>>
(转)Cortex-M3 (NXP LPC1788)之EEPROM存储器
查看>>
ubuntu set defult jdk
查看>>
[译]ECMAScript.next:TC39 2012年9月会议总结
查看>>
【Xcode】编辑与调试
查看>>
用tar和split将文件分包压缩
查看>>
[BTS] Could not find stored procedure 'mp_sap_check_tid'
查看>>
PLSQL DBMS_DDL.ALTER_COMPILE
查看>>
Activity生命周期
查看>>
高仿UC浏览器弹出菜单效果
查看>>