首页 > 代码库 > c开源项目webbench学习

c开源项目webbench学习

    webbench是用c语言来实现的网站压力测试工具,可以并发产生3万个链接测试网站。在学习webbench时候最好先简单了解一下http协议,推荐小日本的一本书《图解http》,适合入门,图文并茂,先简单介绍下wenbench的使用方法,在ubuntu14.04上安装完后,执行:

root@zhuzhu:webbench-1.5# webbench 
webbench [option]... URL
  -f|--force               Don‘t wait for reply from server.
  -r|--reload              Send reload request - Pragma: no-cache.
  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.
  -p|--proxy <server:port> Use proxy server for request.
  -c|--clients <n>         Run <n> HTTP clients at once. Default one.
  -9|--http09              Use HTTP/0.9 style requests.
  -1|--http10              Use HTTP/1.0 protocol.
  -2|--http11              Use HTTP/1.1 protocol.
  --get                    Use GET request method.
  --head                   Use HEAD request method.
  --options                Use OPTIONS request method.
  --trace                  Use TRACE request method.
  -?|-h|--help             This information.
  -V|--version             Display program version.
root@zhuzhu:webbench-1.5#

可以看到全部的参数,常用的参数 -c 表示模拟的用户数,-t 表示持续发起连接的时间:

    webbench -c 5 -t 10 http://www.baidu.com/  

其结果如下:

root@zhuzhu:webbench-1.5# ./webbench -c 5 -t 10 http://www.baidu.com/
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://www.baidu.com/
5 clients, running 10 sec.

Speed=594 pages/min, 1036972 bytes/sec.
Requests: 99 susceed, 0 failed.

下面开始介绍下源码,对webbench做了一些简单的修改,但基本内容和组织没有变。全部源码见:

https://github.com/zhukunbo/webbench


1、软件流程

    

技术分享


2、代码详解

#include "socket.c"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>

#include "webbench.h"

static int mypipe[2];
static int force;
static int force_reload;
static int http10 = 1;
static int bench_time;
static int proxy_port = HTTP_DEF_PORT;
static int clients;
static int speed;
static int failed;
static int bytes;
static int time_out;
static char *proxy_host;
static int method = METHOD_GET;
static char host[MAXHOSTNAMELEN]; 
static char request[REQUEST_SIZE];
int debug_open = 1;

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

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

static void build_requst(const char *url)
{
	int i;
	char tmp[16];

	bzero(host, MAXHOSTNAMELEN);
        bzero(request, REQUEST_SIZE);

	if (force_reload && (proxy_host != 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;
	}

	/* 客户端发起的请求类型,默认为GET,请求资源 */
	switch (method){
		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 (strstr(url, "://") == NULL) {
		fprintf(stderr, "\n %s is no a vaild url. \n",url);
		exit(EXIT_FAILURE);
	}
	
	if (strlen(url) > 1500) {
		fprintf(stderr, "\n URL is too long \n");
		exit(EXIT_FAILURE);		
	}

	if (proxy_host == NULL) {
		if (strncasecmp("http://", url, 7) != 0) {
			fprintf(stderr, "\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
			exit(EXIT_FAILURE);
		}
	}

	i = strstr(url, "://") - url + 3;
	if (strstr(url + i, "/") == NULL) {
		fprintf(stderr,"\nInvalid URL syntax - hostname don‘t ends with ‘/‘.\n");
		exit(EXIT_FAILURE);
	}

	if (proxy_host == NULL) {
		if ((index(url + i, ‘:‘) != NULL) && 
			(index(url + i, ‘:‘) < index(url + i, ‘/‘))) {
			strncpy(host, url + i, strchr(url + i, ‘:‘) - url - i);	
			bzero(tmp, sizeof(tmp));
			strncpy(tmp, index(url + i, ‘:‘) + 1, 
			strchr(url + i, ‘/‘) - index(url + i, ‘:‘) - 1);
			proxy_port = atoi(tmp);
			if (proxy_port == 0) {
				proxy_port = HTTP_DEF_PORT;
			}
	   } else {
			strncpy(host, url + i, strcspn(url + i, "/"));
	   }
	   PRINT_DEG("host is %s, proxy_port = %d", host, proxy_port);
	   strcat(request + strlen(request), url + i + strcspn(url + i, "/"));	   	   
	} else {
       strcat(request, url);
	}
	PRINT_DEG("the requst is %s \n", request);

	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 "PRG_VERSION"\r\n");
	}
	if ((proxy_host == NULL) && (http10 > 0)) {		
		strcat(request,"Host: ");
		strcat(request,host);
		strcat(request,"\r\n");
	}
	
	if (force_reload && (proxy_host != NULL)) {
		strcat(request,"Pragma: no-cache\r\n");
	}
	if (http10 > 1) {
		strcat(request,"Connection: close\r\n");
	}
	/* add empty line in the end */
	if (http10 > 0) {
		strcat(request,"\r\n"); 
	}   
}

static void alarm_handler(int signal)
{
	PRINT_DEG("it is time out");
	time_out = 1;
}

static void bench_calc(const char *host,const int port,const char *req)
{
	int len, n;
	int sock_id;
	char buff[1500];
	struct sigaction sa;

	/* setup alarm signal handler */
	sa.sa_handler = alarm_handler;
	sa.sa_flags=0;

	if (sigaction(SIGALRM, &sa, NULL)) {
		exit(3);
	}

	alarm(bench_time);

	len = strlen(req);

re_try:
	while (1) {
		if (time_out) {
			if (failed > 0) {
				--failed;				
			}
			return;
		}

		sock_id = create_socket_info(host, port);
		if (sock_id < 0) {
			failed++;
			continue;
		}		
		if (len != write(sock_id, req, len)) {
			failed++;
			close(sock_id);
			continue;
		}
		if (http10 == 0) {
			if (shutdown(sock_id, 1)) {
				failed++;
				close(sock_id);
				continue;
			}
		}
			
		if (force == 0) {    /* 若是强制则不要等待服务器相应 */
			while (1) {
				if (time_out) {
					break;
				}
				n = read(sock_id, buff, 1500);
				if (n < 0) {
					failed++;
					close(sock_id);
					goto re_try;
				} else if (n == 0) {
					break;
				} else {
					bytes += n;
				}
			}			
		}

		if (close(sock_id)) {
			failed++;
			continue;
		}
		speed++;
	}
}

static int core_process(void)
{
	int i;
	int n, m, k;
	pid_t pid;
	FILE *pipe_fd;

	/* 创建管道 */
	if (pipe(mypipe)) {
		perror("pipe failed.");
		return -1;
	}

	/* fork process number of clients */
	for (i = 0; i < clients; i++) {    /* 创建线程 */
		pid = fork();
		if (pid <= (pid_t)0) {			
			sleep(1);  /* 让父进程先运行,防止子进程先运行后,写管道失败 */
			break;
		}
	}

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

	if (pid == 0) {		
		/* this is a child process */
		bench_calc(((proxy_host == NULL) ? host: proxy_host), proxy_port, request);

		/* 向管道中写统计好的数据 */
		pipe_fd = fdopen(mypipe[1], "w");
		if (pipe_fd == NULL) {
			perror("open pipe for writing failed.");
		 	return -1;
		}
		
		PRINT_DEG("speed, failed, bytes= %d %d %d", speed, failed, bytes);
		fprintf(pipe_fd, "%d %d %d\n", speed, failed, bytes);
		fclose(pipe_fd);

		return 0;
	} else {
		PRINT_DEG("this is father");
		pipe_fd = fdopen(mypipe[0], "r");
		if (pipe_fd == NULL) {
			perror("open pipe for reading failed.");
			return -1;
		}		
		setvbuf(pipe_fd, NULL, _IONBF, 0);
		speed  = 0;
      	failed = 0;
     	bytes  = 0;

		PRINT_DEG("speed, failed, bytes= %d %d %d", speed, failed, bytes);

		while (1) {
		        /* 读管道 */
			pid = fscanf(pipe_fd, "%d %d %d", &m, &n, &k);
			if (pid < 2) {
				fprintf(stderr,"Some of our childrens died.\n");
				break;
			}
			speed  += m;
			failed += n;
			bytes  += k;

			if (--clients == 0) {
				break;
			}			
			PRINT_DEG("clients = %d", clients);	
		}
		fclose(pipe_fd);

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

	return speed;
}

int main(int argc, char *argv[])
{
    int opt;
    int options_index=0;
	int socket_fd;
    char *tmp=NULL;
    
    /*1、参数检查 */
    if (argc == 1) {
        usage();
        exit(EXIT_FAILURE);
    }
    /*2、参数解析 */
    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(PRG_VERSION);
                exit(EXIT_SUCCESS);
            case ‘t‘:
                bench_time = atoi(optarg);
                break;
            case ‘p‘:
                /* 获取代理服务器端口号,strrchr,返回从左边开始最后一个比配的字符以后的字符串 */
                tmp = strrchr(optarg, ‘:‘);
                proxy_host = optarg;
                if (tmp == NULL) {
                    break;
                }
                if (tmp == optarg) {
                    fprintf(stderr, "Error in option --proxy %s: Missing hostname.\n", optarg);
                    exit(EXIT_FAILURE);
                }
                if (tmp == (optarg + strlen(optarg) - 1)) {
                    fprintf(stderr, "Error in option --proxy %s Port number is missing.\n", optarg);
                    exit(EXIT_FAILURE);
                }
                proxy_port = atoi(tmp + 1);
                break;
            case ‘ ‘ :
            case ‘?‘:
            case ‘h‘:
                usage();
                exit(EXIT_FAILURE);
            case ‘c‘:
                clients = atoi(optarg);
                break;
            default :
                usage();
                exit(EXIT_FAILURE);
        }
}
    if (optind == argc) {
        fprintf(stderr, "webbench: Missing URL!\n");
        usage();
        exit(EXIT_FAILURE);
    }
    /* 设置默认的参数 */
    if (clients == 0) {
        clients = 1;     /* 默认模拟一个用户 */
    } 
    if (bench_time == 0) {
        bench_time = 30;     /* 默认发起连接时间为30秒 */
    }

	/********************** info print *************************/
    fprintf(stderr,
		"Webbench - Simple Web Benchmark "PRG_VERSION"\n"
		"Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
	);
	build_requst(argv[optind]);  /* http协议参数相关参数设置 */
	
	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]);
	switch (http10) {
		case 0: 
			printf(" (using HTTP/0.9)");
			break;
		case 2: 
			printf(" (using HTTP/1.1)");
			break;
	}
	printf("\n");

	printf("%d clients \n", clients);
	printf("running %d sec", bench_time);	
	if (force) {
		printf(", early socket close");
	}
	if (proxy_host != NULL) {
		printf(", via proxy server %s:%d",proxy_host,proxy_port);
	}
	if (force_reload) {
		printf(", forcing reload");
	}
 	printf(".\n");
	/********************** info print end*************************/
	/*3、check avaibility of target server  */
	socket_fd = create_socket_info((proxy_host == NULL) ? host : proxy_host, proxy_port);  /* 创建套接字,主要用来测试服务器可用 */
	if (socket_fd < 0) {
		fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
        exit(EXIT_FAILURE);
	}
	close(socket_fd);
	
    /*4、核心处理 */
	return core_process();
}
/*
* host :the host addr
* client_port :the dest port num 
*
*/
int create_socket_info(const char *host, int cli_port)
{
	int sock;
	unsigned long inaddr;
	struct hostent *hp;
	struct sockaddr_in addr;
	
	memset(&addr, 0, sizeof(addr));
	inaddr = inet_addr(host);
	if (inaddr != INADDR_NONE) {
		memcpy(&addr.sin_addr, &inaddr, sizeof(inaddr));
	} else {
		hp = gethostbyname(host);
		if (hp == NULL) {
			return -1;
		}
		memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
	}
	addr.sin_port = htons(cli_port);
	addr.sin_family = AF_INET;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		return sock;
	}
	/* connect to the server */
	if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		return -1;
	}
    
	return sock;
}




本文出自 “12128867” 博客,请务必保留此出处http://12138867.blog.51cto.com/12128867/1918475

c开源项目webbench学习