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 }