1 /* 2 * (C) Radim Kolar 1997-2004 3 * This is free software, see GNU Public License version 2 for 4 * details. 5 * 6 * Simple forking WWW Server benc... ...
1 /* 2 * (C) Radim Kolar 1997-2004 3 * This is free software, see GNU Public License version 2 for 4 * details. 5 * 6 * Simple forking WWW Server benchmark: 7 * 8 * Usage: 9 * webbench --help 10 * 11 * Return codes: 12 * 0 - sucess 13 * 1 - benchmark failed (server is not on-line) 14 * 2 - bad param 15 * 3 - internal error, fork failed 16 * 17 */ 18 #include "socket.c" 19 #include <unistd.h> 20 #include <sys/param.h> 21 #include <rpc/types.h> 22 #include <getopt.h> 23 #include <strings.h> 24 #include <time.h> 25 #include <signal.h> 26 27 /* values */ 28 volatile int timerexpired=0;//判斷測壓市場是否已經達到設定的時間 29 int speed=0;//記錄進程成功得到伺服器相應的數量 30 int failed=0;//記錄失敗的數量(speed表示成功數,failed表示失敗數) 31 int bytes=0;//記錄進程成功讀取的位元組數 32 /* globals */ 33 int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 *///HTTP版本 34 /* Allow: GET, HEAD, OPTIONS, TRACE */ 35 #define METHOD_GET 0 36 #define METHOD_HEAD 1 37 #define METHOD_OPTIONS 2 38 #define METHOD_TRACE 3 39 #define PROGRAM_VERSION "1.5" 40 int method=METHOD_GET;//定義HTTP請求方法:預設方式GET請求 41 int clients=1;//併發數目,預設只有一個進程發送請求,通過 -c 參數設置 42 int force=0;//是否需要等待讀取從server返回的數據,0表示要等待讀取 43 int force_reload=0;//是否使用緩存,1表示不緩存,0表示可以緩存頁面 44 int proxyport=80;//代理伺服器的埠 45 char *proxyhost=NULL;//代理伺服器的埠 46 int benchtime=30;//測壓時間,預設30秒,通過 -t 參數設置 47 /* internal */ 48 int mypipe[2];//使用管道進行父進程和子進程的通信 49 char host[MAXHOSTNAMELEN];//伺服器端IP 50 #define REQUEST_SIZE 2048 51 char request[REQUEST_SIZE];//發送HTTP請求的內容 52 53 static const struct option long_options[]= 54 { 55 {"force",no_argument,&force,1}, 56 {"reload",no_argument,&force_reload,1}, 57 {"time",required_argument,NULL,'t'}, 58 {"help",no_argument,NULL,'?'}, 59 {"http09",no_argument,NULL,'9'}, 60 {"http10",no_argument,NULL,'1'}, 61 {"http11",no_argument,NULL,'2'}, 62 {"get",no_argument,&method,METHOD_GET}, 63 {"head",no_argument,&method,METHOD_HEAD}, 64 {"options",no_argument,&method,METHOD_OPTIONS}, 65 {"trace",no_argument,&method,METHOD_TRACE}, 66 {"version",no_argument,NULL,'V'}, 67 {"proxy",required_argument,NULL,'p'}, 68 {"clients",required_argument,NULL,'c'}, 69 {NULL,0,NULL,0} 70 }; 71 72 /* prototypes */ 73 static void benchcore(const char* host,const int port, const char *request); 74 static int bench(void); 75 static void build_request(const char *url); 76 77 /* 78 webbench在運行時可以設定壓測的持續時間,以秒為單位。 79 例如我們希望測試30秒,也就意味著壓測30秒後程式應該退出了。 80 webbench中使用信號(signal)來控製程序結束。 81 函數1是在到達結束時間時運行的信號處理函數。 82 它僅僅是將一個記錄是否超時的變數timerexpired標記為true。 83 後面會看到,在程式的while迴圈中會不斷檢測此值, 84 只有timerexpired=1,程式才會跳出while迴圈並返回。 85 */ 86 static void alarm_handler(int signal) 87 { 88 timerexpired=1; 89 } 90 91 /* 92 教你如何使用webbench的函數, 93 在linux命令行調用webbench方法不對的時候運行,作為提示。 94 有一些比較常用的,比如-c來指定併發進程的多少; 95 -t指定壓測的時間,以秒為單位; 96 支持HTTP0.9,HTTP1.0,HTTP1.1三個版本; 97 支持GET,HEAD,OPTIONS,TRACE四種請求方式。 98 不要忘了調用時,命令行最後還應該附上要測的服務端URL。 99 */ 100 static void usage(void) 101 { 102 fprintf(stderr, 103 "webbench [option]... URL\n" 104 " -f|--force Don't wait for reply from server.\n" 105 " -r|--reload Send reload request - Pragma: no-cache.\n" 106 " -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n" 107 " -p|--proxy <server:port> Use proxy server for request.\n" 108 " -c|--clients <n> Run <n> HTTP clients at once. Default one.\n" 109 " -9|--http09 Use HTTP/0.9 style requests.\n" 110 " -1|--http10 Use HTTP/1.0 protocol.\n" 111 " -2|--http11 Use HTTP/1.1 protocol.\n" 112 " --get Use GET request method.\n" 113 " --head Use HEAD request method.\n" 114 " --options Use OPTIONS request method.\n" 115 " --trace Use TRACE request method.\n" 116 " -?|-h|--help This information.\n" 117 " -V|--version Display program version.\n" 118 ); 119 }; 120 int main(int argc, char *argv[]) 121 { 122 int opt=0; 123 int options_index=0; 124 char *tmp=NULL; 125 126 if(argc==1) 127 { 128 usage(); 129 return 2; 130 } 131 132 while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) 133 { 134 switch(opt) 135 { 136 case 0 : break; 137 case 'f': force=1;break; 138 case 'r': force_reload=1;break; 139 case '9': http10=0;break; 140 case '1': http10=1;break; 141 case '2': http10=2;break; 142 case 'V': printf(PROGRAM_VERSION"\n");exit(0); 143 /** 144 *C 庫函數 int atoi(const char *str) 把參數 str 所指向的字元串轉換為一個整數(類型為 int 型) 145 *int atoi(const char *str) 146 *str -- 要轉換為整數的字元串。 147 *該函數返迴轉換後的長整數,如果沒有執行有效的轉換,則返回零。 148 */ 149 case 't': benchtime=atoi(optarg);break; 150 case 'p': 151 /* proxy server parsing server:port */ 152 /** 153 *strrchr() 函數用於查找某字元在字元串中最後一次出現的位置,其原型為: 154 *char * strrchr(const char *str, int c); 155 *【參數】str 為要查找的字元串,c 為要查找的字元。 156 *strrchr() 將會找出 str 字元串中最後一次出現的字元 c 的地址,然後將該地址返回。 157 *註意:字元串 str 的結束標誌 NUL 也會被納入檢索範圍,所以 str 的組後一個字元也可以被定位。 158 *【返回值】如果找到就返回該字元最後一次出現的位置,否則返回 NULL。 159 *返回的地址是字元串在記憶體中隨機分配的地址再加上你所搜索的字元在字元串位置。設字元在字元串中首次出現的位置為 i,那麼返回的地址可以理解為 str + i。 160 */ 161 /** 162 *optarg : char *optarg; //選項的參數指針 163 */ 164 tmp=strrchr(optarg,':'); 165 proxyhost=optarg; 166 if(tmp==NULL) 167 { 168 break; 169 } 170 if(tmp==optarg) 171 { 172 fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 173 return 2; 174 } 175 /** 176 *C 庫函數 size_t strlen(const char *str) 計算字元串 str 的長度,直到空結束字元,但不包括空結束字元。 177 *size_t strlen(const char *str) 178 *參數:str -- 要計算長度的字元串。 179 *該函數返回字元串的長度。 180 */ 181 if(tmp==optarg+strlen(optarg)-1) 182 { 183 fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 184 return 2; 185 } 186 *tmp='\0';//把:替換為\0 187 //從\0之後到\0之前 188 proxyport=atoi(tmp+1);break; 189 case ':': 190 case 'h': 191 case '?': usage();return 2;break; 192 case 'c': clients=atoi(optarg);break; 193 } 194 } 195 //int optind:argv的當前索引值。當getopt函數在while迴圈中使用時,剩下的字元串為操作數,下標從optind到argc-1 196 //argc,argv 參考:https://www.cnblogs.com/lanshanxiao/p/11568037.html 197 //getopt_long()中的函數,參考:https://www.cnblogs.com/xhg940420/p/7016574.html 198 //掃描完畢後,optind指向非長選項和非短選項和非參數的欄位,這裡應該指向URL 199 if(optind==argc) { 200 fprintf(stderr,"webbench: Missing URL!\n"); 201 usage(); 202 return 2; 203 } 204 205 if(clients==0) clients=1; 206 if(benchtime==0) benchtime=60; 207 /* Copyright */ 208 fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 209 "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" 210 ); 211 /** 212 *命令讀取完成後,argv[optind]中應該存放著URL, 213 *建立完整的http請求,http請求存放在變數char request[REQUEST_SIZE]中 214 */ 215 build_request(argv[optind]); 216 /* print bench info *///輸出平臺信息 217 printf("\nBenchmarking: "); 218 switch(method) 219 { 220 case METHOD_GET: 221 default: 222 printf("GET");break; 223 case METHOD_OPTIONS: 224 printf("OPTIONS");break; 225 case METHOD_HEAD: 226 printf("HEAD");break; 227 case METHOD_TRACE: 228 printf("TRACE");break; 229 } 230 printf(" %s",argv[optind]);//列印出URL 231 switch(http10) 232 { 233 case 0: printf(" (using HTTP/0.9)");break; 234 case 2: printf(" (using HTTP/1.1)");break; 235 } 236 printf("\n"); 237 if(clients==1) printf("1 client"); 238 else 239 printf("%d clients",clients); 240 241 printf(", running %d sec", benchtime); 242 if(force) printf(", early socket close"); 243 if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); 244 if(force_reload) printf(", forcing reload"); 245 printf(".\n"); 246 //壓力測試最後一句話,所有的壓力測試都在bench函數中實現 247 return bench(); 248 } 249 250 /* 251 函數主要操作全局變數char request[REQUEST_SIZE],根據url填充其內容。 252 典型的HTTP的GET請求: 253 GET /test.jpg HTTP/1.1 254 User-Agent: WebBench 1.5 255 Host:192.168.10.1 256 Pragma: no-cache 257 Connection: close 258 259 build_request函數的目的就是要把 260 類似於以上這一大坨信息全部存到全局變數request[REQUEST_SIZE]中, 261 其中換行操作使用的是”\r\n”。 262 而以上這一大坨信息的具體內容是要根據命令行輸入的參數,以及url來確定的。 263 該函數使用了大量的字元串操作函數, 264 例如strcpy,strstr,strncasecmp,strlen,strchr,index,strncpy,strcat。 265 對這些基礎函數不太熟悉的同學可以借這個函數複習一下。 266 */ 267 void build_request(const char *url) 268 { 269 char tmp[10]; 270 int i; 271 272 bzero(host,MAXHOSTNAMELEN);//bzero():置host(位元組字元串)前MAXHOSTNAMELEN個位元組為0,包括'\0') 273 bzero(request,REQUEST_SIZE); 274 275 if(force_reload && proxyhost!=NULL && http10<1) http10=1;//滿足一定條件,更換HTTP協議 276 if(method==METHOD_HEAD && http10<1) http10=1; 277 if(method==METHOD_OPTIONS && http10<2) http10=2; 278 if(method==METHOD_TRACE && http10<2) http10=2; 279 280 switch(method) 281 { 282 default: 283 //strcpy() 函數用於對字元串進行複製(拷貝)。 284 //char* strcpy(char* strDestination, const char* strSource); 285 //strSource 指向的字元串複製到 strDestination 286 case METHOD_GET: strcpy(request,"GET");break; 287 case METHOD_HEAD: strcpy(request,"HEAD");break; 288 case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 289 case METHOD_TRACE: strcpy(request,"TRACE");break; 290 } 291 292 //char*strcat(char* strDestination, const char* strSource); 293 /* 294 strcat() 函數用來將兩個字元串連接(拼接)起來。 295 strcat() 函數把 strSource 所指向的字元串追加到 strDestination 所指向的字元串的結尾, 296 所以必須要保證 strDestination 有足夠的記憶體空間來容納兩個字元串,否則會導致溢出錯誤。 297 註意:strDestination 末尾的\0會被覆蓋,strSource 末尾的\0會一起被覆制過去,最終的字元串只有一個\0。 298 */ 299 strcat(request," "); 300 301 /* 302 char *strstr(const char *haystack, const char *needle) 303 在字元串 haystack 中查找第一次出現字元串 needle 的位置,不包含終止符 '\0'。 304 該函數返回在 haystack 中第一次出現 needle 的地址,如果未找到則返回 null。 305 */ 306 if(NULL==strstr(url,"://")) 307 { 308 fprintf(stderr, "\n%s: is not a valid URL.\n",url); 309 exit(2); 310 } 311 312 /* 313 strlen(char *); 314 檢測字元串實際長度。 315 strlen(char *)檢測的是'\0',strlen(char *)碰到'\0'就返回'\0'以前的字元數(不包括'\0')。 316 strlen(char*)函數求的是字元串的實際長度,它求得方法是從開始到遇到第一個'\0', 317 如果你只定義沒有給它賦初值,這個結果是不定的,它會從aa首地址一直找下去,直到遇到'\0'停止。 318 */ 319 if(strlen(url)>1500) 320 { 321 fprintf(stderr,"URL is too long.\n"); 322 exit(2); 323 } 324 if(proxyhost==NULL) 325 /* 326 int strncasecmp(const char *s1, const char *s2, size_t n); 327 strncasecmp()用來比較參數s1 和s2 字元串前n個字元,比較時會自動忽略大小寫的差異。 328 若參數s1 和s2 字元串相同則返回0。s1 若大於s2 則返回大於0 的值,s1 若小於s2 則返回小於0 的值。 329 */ 330 if (0!=strncasecmp("http://",url,7)) 331 { 332 fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 333 exit(2); 334 } 335 /* protocol/host delimiter */ 336 i=strstr(url,"://")-url+3; 337 /* printf("%d\n",i); */ 338 339 /* 340 char *strchr(const char *str, char c) 341 該函數返回在字元串 str 中第一次出現字元 c 的地址,如果未找到該字元則返回 NULL。 342 */ 343 if(strchr(url+i,'/')==NULL) { 344 fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 345 exit(2); 346 } 347 if(proxyhost==NULL) 348 { 349 /* get port from hostname */ 350 if(index(url+i,':')!=NULL && 351 index(url+i,':')<index(url+i,'/')) 352 { 353 /* 354 char * strncpy(char *s1,char *s2,size_t n); 355 將字元串s2中最多n個字元複製到字元數組s1中,返回指向s1的指針。 356 註意:如果源串長度大於n,則strncpy不複製最後的'\0'結束符, 357 所以是不安全的,複製完後需要手動添加字元串的結束符才行。 358 */ 359 strncpy(host,url+i,strchr(url+i,':')-url-i); 360 bzero(tmp,10); 361 strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1); 362 /* printf("tmp=%s\n",tmp); */ 363 364 /* 365 C語言庫函數名: atoi 366 功 能: 把字元串轉換成整型數. 367 名字來源:array to integer 的縮寫. 368 函數說明: atoi()會掃描參數nptr字元串,如果第一個字元不是數字也不是正負號返回零, 369 否則開始做類型轉換,之後檢測到非數字或結束符 \0 時停止轉換,返回整型數。 370 原型: int atoi(const char *nptr); 371 */ 372 proxyport=atoi(tmp); 373 if(proxyport==0) proxyport=80; 374 } else 375 { 376 /* 377 size_t strcspn(const char *s, const char * reject); 378 函數說明:strcspn()從參數s 字元串的開頭計算連續的字元, 379 而這些字元都完全不在參數reject 所指的字元串中。 380 簡單地說, 若strcspn()返回的數值為n,則代表字元串s 開頭連續有n 個字元都不含字元串reject 內的字元。 381 返回值:返回字元串s 開頭連續不含字元串reject 內的字元數目。 382 */ 383 strncpy(host,url+i,strcspn(url+i,"/")); 384 } 385 // printf("Host=%s\n",host); 386 strcat(request+strlen(request),url+i+strcspn(url+i,"/")); 387 } else 388 { 389 // printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport); 390 strcat(request,url); 391 } 392 if(http10==1)