webbench源码浅析

Webbench是一个在Linux下使用的非常简单的网站侧压工具。它使用fork()模拟多个客户端同时访问url,测试网站在压力下工作的性能。
只有socket.c和webbench.c两个文件.

编译运行

1
2
3
4
cd webbench
sudo make
sudo make install webbenchpath
./webbench -c 40 -t 10 -f --get http://www.baidu.com/index.html

工作原理

父进程床架多个子进程,由每个子进程来创建socket连接,发送http请求,最后等时间到了之后,将数据写入管道.
然后父进程不断地从管道中读出数据,汇总结果输出.

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//socket.c
/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $
*
* This module has been modified by Radim Kolar for OS/2 emx
*/

/***********************************************************************
module: socket.c
program: popclient
SCCS ID: @(#)socket.c 1.5 4/1/94
programmer: Virginia Tech Computing Center
compiler: DEC RISC C compiler (Ultrix 4.1)
environment: DEC Ultrix 4.3
description: UNIX sockets code.
***********************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

int Socket(const char *host, int clientPort)
{
int sock;
unsigned long inaddr;
struct sockaddr_in ad;
struct hostent *hp;

/*
初始化地址
struct sockaddr_in{
short sin_family;//Address family一般来说AF_INET(地址族)PF_INET(协议族)
unsigned short sin_port;//Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)
struct in_addr sin_addr;//IP address in network byte order(Internet address)
unsigned char sin_zero[8];//Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
};
struct in_addr {
in_addr_t s_addr;
};
in_addr_t 一般为 32位的unsigned int,其字节顺序为网络顺序(大端字节序)
*/

memset(&ad, 0, sizeof(ad));

//AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,两者有着相同的宏定义.
ad.sin_family = AF_INET;

//inet_addr() 将字符串形式的IP地址 -> 网络字节顺序 的整型值
inaddr = inet_addr(host);

//INADDR_NONE 是个宏定义,代表无效的IP地址
if (inaddr != INADDR_NONE)
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
else
{
/*
gethostbyname()通过名字获得主机的相关信息
返回值是一个结构体
struct hostent{
char *h_name;
char ** h_aliases;
short h_addrtype;
short h_length;
char ** h_addr_list;
};
*/
hp = gethostbyname(host);
if (hp == NULL)
return -1;

//此处用h_addr是因为有这样一个宏定义
//#define h_addr h_addr_list[0]
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
}
/*
htons主机字节序 >> 网络字节序
主机:小端字节序
网络:大端字节序
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"
*/
ad.sin_port = htons(clientPort);

/*
socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。
*/
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return sock;
//connect()用于建立与指定socket的连接。
if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
return -1;
return sock;
}

webbench.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
webbench.c
/*
* (C) Radim Kolar 1997-2004
* This is free software, see GNU Public License version 2 for
* details.
*
* Simple forking WWW Server benchmark:
*
* Usage:
* webbench --help
*
* Return codes:
* 0 - sucess
* 1 - benchmark failed (server is not on-line)
* 2 - bad param
* 3 - internal error, fork failed
*
*/
#include "socket.c"
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>

/* values */
//volatile 防止编译器对代码进行优化
//如果到达规定的时间,把该变量置1
volatile int timerexpired=0;
//存储成功的http请求数,最后除以时间,就得到速度.
int speed=0;
//存储失败http连接数
int failed=0;
//传输过的字节数
int bytes=0;

/* globals */
int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
/* Allow: GET, HEAD, OPTIONS, TRACE */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
int method=METHOD_GET;
int clients=1;
//如果命令行参数加了-f,就会将force置1,代表忽略返回的数据
int force=0;
//强制加载,如果添加-r参数,会置1,会将http的pragma字段设置为no-cache,即不使用缓存
int force_reload=0;
int proxyport=80;
char *proxyhost=NULL;
int benchtime=30;

//父子进程用来通信的管道
/* internal */
int mypipe[2];
char host[MAXHOSTNAMELEN];
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE];

/*
struct option指明了一个“长参数”(即形如--name的参数)名称和性质

struct option {
const char *name; 参数名
int has_arg; 0-无参数 1-后面一定跟个参数 2-可跟可不跟
int *flag; 用来决定getopt_long()的返回值是什么,flag是null,则函数会返回与该项option匹配的val值
参数不为空,那么当选中某个长选项的时候,getopt_long将返回0,并且将flag指针参数指向val值
int val; 和flag联合决定返回值
}
*/
static const struct option long_options[]=
{
{"force",no_argument,&force,1},
{"reload",no_argument,&force_reload,1},
{"time",required_argument,NULL,'t'},
{"help",no_argument,NULL,'?'},
{"http09",no_argument,NULL,'9'},
{"http10",no_argument,NULL,'1'},
{"http11",no_argument,NULL,'2'},
{"get",no_argument,&method,METHOD_GET},
{"head",no_argument,&method,METHOD_HEAD},
{"options",no_argument,&method,METHOD_OPTIONS},
{"trace",no_argument,&method,METHOD_TRACE},
{"version",no_argument,NULL,'V'},
{"proxy",required_argument,NULL,'p'},
{"clients",required_argument,NULL,'c'},
{NULL,0,NULL,0}
};

/* prototypes原型 */
static void benchcore(const char* host,const int port, const char *request);
static int bench(void);
static void build_request(const char *url);

//子进程中的信号处理函数
static void alarm_handler(int signal)
{
timerexpired=1;
}

//用法
static void usage(void)
{
fprintf(stderr,
"webbench [option]... URL\n"
" -f|--force Don't wait for reply from server.\n"
" -r|--reload Send reload request - Pragma: no-cache.\n"
" -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n"
" -p|--proxy <server:port> Use proxy server for request.\n"
" -c|--clients <n> Run <n> HTTP clients at once. Default one.\n"
" -9|--http09 Use HTTP/0.9 style requests.\n"
" -1|--http10 Use HTTP/1.0 protocol.\n"
" -2|--http11 Use HTTP/1.1 protocol.\n"
" --get Use GET request method.\n"
" --head Use HEAD request method.\n"
" --options Use OPTIONS request method.\n"
" --trace Use TRACE request method.\n"
" -?|-h|--help This information.\n"
" -V|--version Display program version.\n"
);
}

int main(int argc, char *argv[])
{
int opt=0;
int options_index=0;
char *tmp=NULL;

if(argc==1)
{
usage();
return 2;
}

/*
命令行参数可以分为两类,一类是短选项,一类是长选项,短选项在参数前加一杠"-",长选项在参数前连续加两杠"--"
getopt函数只能处理短选项,而getopt_long函数两者都可以
int getopt_long(int argc,char* const argv[],const char *optstring,const struct option *longopts,int *longindex);
optstring: 表示短选项字符串
形式如“a:b::cd:“,分别表示程序支持的命令行短选项有-a、-b、-c、-d,冒号含义如下:
    (1)只有一个字符,不带冒号——只表示选项,如-c 
    (2)一个字符,后接一个冒号——表示选项后面带一个参数,如-a 100
    (3)一个字符,后接两个冒号——表示选项后面带一个可选参数,即参数可有可无,如果带参数,则选项与参数直接不能有空格如-b200
*/
while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
{
switch(opt)
{
case 0 : break;
case 'f': force=1;break;
case 'r': force_reload=1;break;
case '9': http10=0;break;
case '1': http10=1;break;
case '2': http10=2;break;
case 'V': printf(PROGRAM_VERSION"\n");exit(0);
case 't': benchtime=atoi(optarg);break;
case 'p':
/* proxy server parsing server:port */
// strrchr(const char *str, int c)
// 在参数str所指向的字符串中搜索最后一次出现字符c(一个无符号字符)的位置,如果未找到该值,则函数返回一个空指针
tmp=strrchr(optarg,':');
proxyhost=optarg;
if(tmp==NULL)
{
break;
}
if(tmp==optarg)
{
fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
return 2;
}
if(tmp==optarg+strlen(optarg)-1)
{
fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
return 2;
}
*tmp='\0';
//把字符串转换为一个整数
proxyport=atoi(tmp+1);break;
case ':':
case 'h':
case '?': usage();return 2;//break; 这里的break永远不会执行到呀
case 'c': clients=atoi(optarg);break;
}
}

//optind:表示的是下一个将被处理到的参数在argv中的下标值。
if(optind==argc) {
fprintf(stderr,"webbench: Missing URL!\n");
usage();
return 2;
}

if(clients==0) clients=1;
if(benchtime==0) benchtime=30;

/* Copyright */
fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
"Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
);

//构造http的请求消息
build_request(argv[optind]);

// print request info ,do it in function build_request
/*printf("Benchmarking: ");

switch(method)
{
case METHOD_GET:
default:
printf("GET");break;
case METHOD_OPTIONS:
printf("OPTIONS");break;
case METHOD_HEAD:
printf("HEAD");break;
case METHOD_TRACE:
printf("TRACE");break;
}

printf(" %s",argv[optind]);

switch(http10)
{
case 0: printf(" (using HTTP/0.9)");break;
case 2: printf(" (using HTTP/1.1)");break;
}

printf("\n");
*/

printf("Runing info: ");

if(clients==1)
printf("1 client");
else
printf("%d clients",clients);

printf(", running %d sec", benchtime);

if(force) printf(", early socket close");
if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport);
if(force_reload) printf(", forcing reload");

printf(".\n");

return bench();
}

//用来创建http的请求
void build_request(const char *url)
{
char tmp[10];
int i;

//bzero(host,MAXHOSTNAMELEN);
//bzero(request,REQUEST_SIZE);
memset(host,0,MAXHOSTNAMELEN);
memset(request,0,REQUEST_SIZE);

if(force_reload && proxyhost!=NULL && http10<1) http10=1;
if(method==METHOD_HEAD && http10<1) http10=1;
if(method==METHOD_OPTIONS && http10<2) http10=2;
if(method==METHOD_TRACE && http10<2) http10=2;

switch(method)
{
default:
case METHOD_GET: strcpy(request,"GET");break;
case METHOD_HEAD: strcpy(request,"HEAD");break;
case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
case METHOD_TRACE: strcpy(request,"TRACE");break;
}

strcat(request," ");

if(NULL==strstr(url,"://"))
{
fprintf(stderr, "\n%s: is not a valid URL.\n",url);
exit(2);
}
if(strlen(url)>1500)
{
fprintf(stderr,"URL is too long.\n");
exit(2);
}
if (0!=strncasecmp("http://",url,7))
{
fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
exit(2);
}

/* protocol/host delimiter */
i=strstr(url,"://")-url+3;

if(strchr(url+i,'/')==NULL) {
fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n");
exit(2);
}

if(proxyhost==NULL)
{
/* get port from hostname */
if(index(url+i,':')!=NULL && index(url+i,':')<index(url+i,'/'))
{
strncpy(host,url+i,strchr(url+i,':')-url-i);
//bzero(tmp,10);
memset(tmp,0,10);
strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
/* printf("tmp=%s\n",tmp); */
proxyport=atoi(tmp);
if(proxyport==0) proxyport=80;
}
else
{
strncpy(host,url+i,strcspn(url+i,"/"));
}
// printf("Host=%s\n",host);
strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
}
else
{
// printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
strcat(request,url);
}

if(http10==1)
strcat(request," HTTP/1.0");
else if (http10==2)
strcat(request," HTTP/1.1");

strcat(request,"\r\n");

if(http10>0)
strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");
if(proxyhost==NULL && http10>0)
{
strcat(request,"Host: ");
strcat(request,host);
strcat(request,"\r\n");
}

if(force_reload && proxyhost!=NULL)
{
strcat(request,"Pragma: no-cache\r\n");
}

if(http10>1)
strcat(request,"Connection: close\r\n");

/* add empty line at end */
if(http10>0) strcat(request,"\r\n");

printf("\nRequest:\n%s\n",request);
}

//用来创建多个子进程,然后每个子进程会分别调用benchcore函数对服务器进行测试.
/* vraci system rc error kod */
static int bench(void)
{
int i,j,k;
pid_t pid=0;
FILE *f;

//先测试一下此网址是否正常.
/* check avaibility of target server */
i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
if(i<0) {
fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
return 1;
}
close(i);

/* create pipe */
if(pipe(mypipe))
{
perror("pipe failed.");
return 3;
}

/* not needed, since we have alarm() in childrens */
/* wait 4 next system clock tick */
/*
cas=time(NULL);
while(time(NULL)==cas)
sched_yield();
*/

/* fork childs */
for(i=0;i<clients;i++)
{
pid=fork();
if(pid <= (pid_t) 0)
{
/* child process or error*/
sleep(1); /* make childs faster */
break;
}
}

if( pid < (pid_t) 0)
{
fprintf(stderr,"problems forking worker no. %d\n",i);
perror("fork failed.");
return 3;
}

if(pid == (pid_t) 0)
{
//子进程
/* I am a child */
if(proxyhost==NULL)
benchcore(host,proxyport,request);
else
benchcore(proxyhost,proxyport,request);

/* write results to pipe */
f=fdopen(mypipe[1],"w");
if(f==NULL)
{
perror("open pipe for writing failed.");
return 3;
}
//向管道写入小于PIPE_BUF的数据的操作可以保证是原子的
/* fprintf(stderr,"Child - %d %d\n",speed,failed); */
fprintf(f,"%d %d %d\n",speed,failed,bytes);
fclose(f);

return 0;
}
else
{
//父进程
f=fdopen(mypipe[0],"r");
if(f==NULL)
{
perror("open pipe for reading failed.");
return 3;
}

/*
设置流的缓冲区
int setvbuf(FILE *stream, char *buf, int type, unsigned size);
type : 期望缓冲区的类型:
_IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
_IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
_IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
size : 缓冲区内字节的数量。
*/
setvbuf(f,NULL,_IONBF,0);

speed=0;
failed=0;
bytes=0;

//父进程不断地对管道进行读取,汇总每个子进程的测试结果.
while(1)
{
//当管道中无数据时,应该会阻塞吧
pid=fscanf(f,"%d %d %d",&i,&j,&k);
if(pid<2)
{
fprintf(stderr,"Some of our childrens died.\n");
break;
}

speed+=i;
failed+=j;
bytes+=k;

/* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
if(--clients==0) break;
}

fclose(f);

printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
(int)((speed+failed)/(benchtime/60.0f)),
(int)(bytes/(float)benchtime),
speed,
failed);
}

return i;
}

//测试的核心函数,该函数在子进程中执行,不断地发送http请求,force==0就会接受数据,force!=0就忽略掉返回的数据.
//该函数给自己设置了个定时器,到达时间就会让自己把timeexpired置为1,然后会退出循环.
void benchcore(const char *host,const int port,const char *req)
{
int rlen;
char buf[1500];
int s,i;
/*
struct sigaction {
void (*sa_handler)(int); 信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; 信号掩码,执行信号处理函数时将屏蔽这些信号
int sa_flags;
void (*sa_restorer)(void);
}
*/
struct sigaction sa;

/* setup alarm signal handler */
sa.sa_handler=alarm_handler;
sa.sa_flags=0;
//设置信号处理函数
if(sigaction(SIGALRM,&sa,NULL))
exit(3);

//设置定时器,benchtime之后就发送信号.
//unsigned int alarm(unsigned int seconds);
alarm(benchtime); // after benchtime,then exit

rlen=strlen(req);

//该循环负责不断地创建socket连接,然后给服务器发送http请求.
nexttry:while(1)
{
if(timerexpired)
{
if(failed>0)
{
/* fprintf(stderr,"Correcting failed by signal\n"); */
failed--;
}
return;
}

s=Socket(host,port);
//printf("socket discriptr: %d,getpid: %d.\n",s,getpid());
if(s<0) { failed++;continue;}
if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}
if(http10==0)
/*
禁止在一个套接口上进行数据的接收与发送。
int shutdown(int sockfd,int how);
s:用于标识一个套接口的描述字。
how:标志,用于描述禁止哪些操作。
how的方式有三种分别是
SHUT_RD(0):关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
SHUT_WR(1):关闭sockfd的写功能,此选项将不允许sockfd进行写操作。
SHUT_RDWR(2):关闭sockfd的读写功能。
*/
if(shutdown(s,1)) { failed++;close(s);continue;}
if(force==0)
{
/* read all available data from socket */
//该循环负责接受服务器返回的数据,如果force为1,则不接受数据
while(1)
{
if(timerexpired) break;
i=read(s,buf,1500);
/* fprintf(stderr,"%d\n",i); */
if(i<0)
{
failed++;
close(s);
goto nexttry;
}
else if(i==0) break;
else
bytes+=i;
}
}
if(close(s)) {failed++;continue;}
speed++;
}
}

参考