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='