Webbench源码分析

    Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行。下载链接:http://home.tiscali.cz/~cz210552/webbench.html

    在分析之前需要理解几个方面的知识:

    1)getopt_long()函数用法:主要是针对命令行参数进行处理。

   http://blog.csdn.net/cashey1991/article/details/7942809

    2)http协议的理解 :

   http请求由三部分组成,分别是:请求行、消息报头、请求正文 , URL!= URI

http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html

    3)linux下的管道内容:理解无名管道在父子进程中如何通讯。

http://www.cnblogs.com/kunhu/p/3608109.html

    4)linux下fork :理解多并发请求及,子进程函数如何处理全局变量(一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。),即子进程之后都拥有各自的全局变量【相互独立的进程】

http://os.chinaunix.net/a2012/0203/1306/000001306508.shtml

    5)linux下信号处理 :理解进程中信息是如何处理,此处为定时是如何退出。

http://blog.chinaunix.net/uid-1877180-id-3011232.html

    6)熟悉index(), strchr(), strspn()相关字符串函数的用法。 

------------------------------------------------------------------------------

    Webbench 源代码只包含二个文件webbench.c (main), socket.c。 以下是针对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 int timerexpired = 0;
int speed = 0;
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;
int force = 0;
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];

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;
	}

	/**
	 * optstring的格式举例说明比较方便,例如:
	 char *optstring = "abcd:";
	 上面这个optstring在传入之后,getopt函数将依次检查命令行是否指定了 -a, -b, -c及 -d(这需要多次调用getopt函数,直到其返回-1),
	 当检查到上面某一个参数被指定时,函数会返回被指定的参数名称(即该字母)
	 最后一个参数d后面带有冒号,: 表示参数d是可以指定值的,如 -d 100 或 -d user。
	 optind表示的是下一个将被处理到的参数在argv中的下标值。
	 *
	 */
	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 */
			tmp = strrchr(optarg, ':');
			proxyhost = optarg;
			if (tmp == NULL)
			{
				break;
			}
			if (tmp == optarg)//:xxxx
			{
				fprintf(stderr, "Error in option --proxy %s: Missing hostname.\n", optarg);
				return 2;
			}
			if (tmp == optarg + strlen(optarg) - 1)//xxxx:
			{
				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;
		case 'c':
			clients = atoi(optarg);
			break;
		}
	}

	if (optind == argc)
	{
		fprintf(stderr, "webbench: Missing URL!\n");
		usage();
		return 2;
	}

	//设置默认值
	if (clients == 0)
		clients = 1;
	if (benchtime == 0)
		benchtime = 60;
	/* Copyright */
	fprintf(stderr, "Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
	"Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n");

	//构建
	build_request(argv[optind]); //此一定是URL

	/* print bench info */
	printf("\nBenchmarking: ");
	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]);//url
	switch (http10)//默认为 HTTP/1.0
	{
	case 0:
		printf(" (using HTTP/0.9)");
		break;
	case 2:
		printf(" (using HTTP/1.1)");
		break;
	}

	printf("\n");
	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(); //真正在开始执行压力测试
}

//构建请求消息结构
void build_request(const char *url)
{
	char tmp[10];
	int i;

	//推荐使用memset替代bzero。
	bzero(host, MAXHOSTNAMELEN);
	bzero(request, 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;

	/*http请求由三部分组成,分别是:请求行、消息报头、请求正文
	 *请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Method Request-URI HTTP-Version CRLF
	 *
	 */
	//方法---------------------------------------------------------------------------
	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, " ");

	//URI Request-URI是一个统一资源标识符-----------------------------------------
	//url =http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html--->uri=www.cnblogs.com
	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 (proxyhost == NULL)//非代理情况下
	{
		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;//定位到://之后的每一个字符 如上面w
	/* printf("%d\n",i); */

	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, '/')) //非80端口时,如:http://localhost:8080/pay 取host & port
		{
			strncpy(host, url + i, strchr(url + i, ':') - url - i);
			bzero(tmp, 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 //host
		{
			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");
	//------------------------------------------------end 请求行

	/*
	 * 请求消息和响应消息都是由开始行(对于请求消息,开始行就是请求行,对于响应消息,开始行就是状态行),
	 * 消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成。
	 * HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。
	 */
	//消息报头

	/*
	 * User-Agent
	 *我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,
	 *这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息。User-Agent请求报头
	 *域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过,这个报头域不是必需的,如果我们自己编写一个浏览器,
	 *不使用User-Agent请求报头域,那么服务器端就无法得知我们的信息了。
	 */
	if (http10 > 0)
		strcat(request, "User-Agent: WebBench "PROGRAM_VERSION"\r\n");

	/**
	 * Host:www.guet.edu.cn 此处使用缺省端口号80,若指定了端口号,则变成:Host:www.guet.edu.cn:指定端口号
	 */
	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");
	}

	//Connection普通报头域允许发送指定连接的选项。例如指定连接是连续,或者指定“close”选项,通知服务器,在响应完成后,关闭连接
	if (http10 > 1)
		strcat(request, "Connection: close\r\n");

	/* add empty line at end */
	if (http10 > 0)
		strcat(request, "\r\n");
	// printf("Req=%s\n",request);
}

/* 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;
		}
		/* 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;
		}
		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;
}

//主要运作
void benchcore(const char *host, const int port, const char *req)
{
	int rlen;
	char buf[1500];
	int s, i;
	struct sigaction sa;

	/* setup alarm signal handler */
	sa.sa_handler = alarm_handler;
	sa.sa_flags = 0;
	if (sigaction(SIGALRM, &sa, NULL))
		exit(3);
	alarm(benchtime);

	rlen = strlen(req);

nexttry:
	while (1) // 足跑benchtime秒
	{
		if (timerexpired)//默认为0
		{
			if (failed > 0)
			{
				/* fprintf(stderr,"Correcting failed by signal\n"); */
				failed--;
			}
			return;
		}

		s = Socket(host, port);
		if (s < 0)
		{
			failed++;
			continue;
		}

		if (rlen != write(s, req, rlen))
		{
			failed++;
			close(s);
			continue;
		}

		if (http10 == 0)
		{
			if (shutdown(s, 1))
			{
				failed++;
				close(s);
				continue;
			}
		}

		if (force == 0)//努力读所有响应数据
		{
			/* read all available data from socket */
			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))//关闭socket
		{
			failed++;
			continue;
		}

		speed++;
	}
}




你可能感兴趣的:(Webbench源码分析)