Web网站压力测试工具Webbench:
Webbench是Linux下的一个网站压力测试工具,最多可以模拟3万个并发连接去测试网站的负载能力. 它是由Lionbridge公司开发。
Webbech 能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。
webBech的标准测试可以向我们展示服务器的两项内容:每分钟相应请求次数(paga/min)和每秒钟传输数据量(byte/sec)。
webbench的使用示例:
1、下载
[root@centos tmp]# wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
下载完成ls,就可以看到压缩包
2、解压缩
[root@centos tmp]# tar zxvf webbench-1.5.tar.gz
ls可以看到webbench-1.5目录文件
3、进入webbench-1.5目录文件
[root@centos tmp]# cd webbench-1.5
4、安装
[root@centos tmp]# make
[root@centos tmp]# make install
安装过程中可能会提示一些错误,如:
则需要手动到/usr/local/man 目录下,创建man1文件,再重新安装即可
5、webbench测试网站
[root@centos tmp]# webbench -c 30 -t 20 http://www.baidu.com
30个客户并发访问百度主页,访问时间为20秒
测试结果:
表示:每分钟可以发出45次请求,每秒可传输159749字节数据
在20秒钟30个客户共发出15次请求,全部成功。
webbench主要的工作原理:
主函数解析命令行参数,进行必要的准备工作,调用build_request设置HTTP请求报文,进入bench开始测压
bench函数使用fork模拟出多个客户端,调用socket并发请求,每个子进程记录自己的访问数据,并写入管道
父进程从管道读取子进程的输出信息
使用alarm函数进行时间控制,到时间后会产生SIGALRM信号,调用信号处理函数使子进程停止
最后只留下父进程将所有子进程的输出数据汇总计算,输出到屏幕上
以下是源码的详细剖析
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<stdio.h>
#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>
#include <getopt.h>
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
#define MAXHOSTNAMELEN 20
#define REQUEST_SIZE 2048
/* global values */
volatile int timerexpired = 0; //判断压测时长是否已经到达设定的时间
int speed = 0; //记录进程成功得到服务器响应的数量
int failed = 0; //记录失败的数量(speed表示成功数,failed表示失败数)
int bytes = 0; //记录进程成功读取的字节数
int http10 = 1; //http版本,0表示http0.9,1表示http1.0,2表示http1.1
/* 请求方法: GET, HEAD, OPTIONS, TRACE */
int method = METHOD_GET; //默认为GET请求方法
int clients = 1; //并发数目,默认只有1个进程发请求,通过-c参数设置
int force = 0; //是否需要等待读取从server返回的数据,0表示要等待读取
int force_reload = 0; //是否使用缓存,1表示不缓存,0表示可以缓存页面
int proxyport = 80; //代理服务器的端口
char *proxyhost = NULL; //代理服务器的ip
int benchtime = 30; //压测时间,默认30秒,通过-t参数设置
int mypipe[2];
char host[MAXHOSTNAMELEN]; //服务器端ip
char request[REQUEST_SIZE]; //保存请求报文,(请求行,消息报文,空行,请求正文)
/*struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
struct option结构中的元素解释如下:
1、const char *name:选项名,前面没有短横线。譬如"help"、"verbose"之类。
2、int has_arg:描述长选项是否有选项参数,如果有,是哪种类型的参数,其值见下表 :
符号常量 数值 含义
no_argument 0 选项没有参数
required_argument 1 选项需要参数
optional_argument 2 选项参数是可选的
3、int *flag:
如果该指针为NULL,那么getopt_long返回val字段的值;
如果该指针不为NULL,那么会使得它所指向的结构填入val字段的值,同时getopt_long返回0
4、int val:
如果flag是NULL,那么val通常是个字符常量,如果短选项和长选项一致,那么该字符就应该与optstring中
出现的这个选项的参数相同;*/
//struct option结构体初始化
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);
/*信号处理函数,当测试时间完成是产生SIGALARM信号,调用此函数,将timerexpired置为1
表示子进程需要退出。后面会看到,在程序的while循环中会不断检测此值
只有timerexpired = 1,程序才会跳出while循环并返回。*/
static void alarm_handler(int signal)
{
timerexpired = 1;
}
/*-f | --force 不需要等待服务器响应
- r | --reload 发送重新加载请求
- t | --time <sec> 运行多长时间,单位:秒"
- p | --proxy <server:port> 使用代理服务器来发送请求
- c | --clients <n> 创建多少个客户端,默认1个"
- 9 | --http09 使用 HTTP / 0.9
- 1 | --http10 使用 HTTP / 1.0 协议
- 2 | --http11 使用 HTTP / 1.1 协议
--get 使用 GET请求方法
--head 使用 HEAD请求方
--options 使用 OPTIONS请求方法
--trace 使用 TRACE请求方法
- ? | -h | --help 打印帮助信息
- V | --version 显示版本号
这是教你如何使用webbench的函数,在linux命令行调用webbench方法不对的时候运行,作为提示*/
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;
}
/*int getopt_long_only(int argc, char * const argv[],
const char *optstring, //optsting是选项参数组成的字符串,如果该字符串里任一字母后有冒号,那么这个选项就要求有参数
const struct option *longopts, int *longindex);
返回值:每次调用该函数,它都会分析一个选项,并且返回它的短选项,
如果分析完毕,即已经没有选项了,则会返回 - 1*/
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; //当处理一个带参数的选项时,全局变量optarg会指向它的参数
case 'p':
/* 代理服务器解析 IP :port eg:192.168.1.2:8080 */
tmp = strrchr(optarg, ':'); //tmp = ":8080"
proxyhost = optarg;
if (tmp == NULL)
{
break;
}
if (tmp == optarg) //没有输入主机名/IP地址
{
fprintf(stderr, "Error in option --proxy %s: Missing hostname.\n", optarg);
return 2;
}
if (tmp == optarg + strlen(optarg) - 1) //没有输入端口号 eg:192.168.1.2:
{
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; //getopt_long()在分析选项时,遇到一个没有定义过的选项,则返回值为‘?’
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]); //argv[optind] = URL
/* 以下都是向频幕打印此次测试的相关信息 */
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");
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();
}
//build_request构建HTTP Request请求报文
void build_request(const char *url)
{
char tmp[10];
int i;
//void bzero(void *s, int n);将内存块(字符串s)的前n个字节清零
bzero(host, MAXHOSTNAMELEN);
bzero(request, REQUEST_SIZE);
//设置HTTP版本
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 (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 */
//eg:url = "http://www.baidu.com/" i =4+3=7
i = strstr(url, "://") - url + 3;
//url + i == "www.baidu.com/"
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 */
//The index() function returns a pointer to the first occurrence of the character c in the string s.
if (index(url + i, ':') != NULL &&
index(url + i, ':')<index(url + i, '/'))
{
//eg:http://www.baidu.com:8080/ host = www.baidu.com
strncpy(host, url + i, strchr(url + i, ':') - url - i);
bzero(tmp, 10);
//tmp = 8080
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 //没有端口号 eg:http://www.baidu.com/
{
strncpy(host, url + i, strcspn(url + i, "/"));
}
// printf("Host=%s\n",host);
// why here using request+strlen(request)?
strcat(request + strlen(request), url + i + strcspn(url + i, "/"));
}
else //有代理主机
{
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) //添加Connection: close表示连接为短连接,一次连接即时关闭
strcat(request, "Connection: close\r\n");
/*添加空行*/
if (http10>0) strcat(request, "\r\n"); //空行
}
/* vraci system rc error kod */
static int bench(void)
{
int i, j, k;
pid_t pid = 0;
FILE *f;
/* 调用socket.c文件代码,发起一次TCP连接,返回socket*/
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;
}
/* fork clients个子进程*/
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;
}
/* I am a child */
if (pid == (pid_t)0)
{
if (proxyhost == NULL)
benchcore(host, proxyport, request);
else
benchcore(proxyhost, proxyport, request);
//子进程将测试结果输出到管道
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;
}
//如果是父进程,则从管道读取子进程输出,并作汇总
f = fdopen(mypipe[0], "r");
if (f == NULL)
{
perror("open pipe for reading failed.");
return 3;
}
//设置缓冲区,_IONBUF表示没有缓存
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;
// 这句用于记录已经读了多少个子进程的数据,读完就退出
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;
/* 注册SIGALRM信号的信号处理函数 */
sa.sa_handler = alarm_handler;
sa.sa_flags = 0;
if (sigaction(SIGALRM, &sa, NULL))
exit(3);
alarm(benchtime); //计时,超时产生SIGALRM信号给本函数
rlen = strlen(req); //计算请求报文的大小
nexttry:while (1)
{
if (timerexpired) //一旦超时则返回
{
if (failed>0)
{
/* fprintf(stderr,"Correcting failed by signal\n"); */
failed--;
}
return;
}
s = Socket(host, port); //调用Socket函数建立TCP连接
if (s<0) { failed++; continue; }
//向服务器发出请求
if (rlen != write(s, req, rlen)) { failed++; close(s); continue; }
if (http10 == 0)//针对http0.9做的特殊处理
//调用shutdown使客户端不能再向服务器发送数据,成功-0,失败-(-1)
if (shutdown(s, 1)) { failed++; close(s); continue; }
if (force == 0) //全局变量force表示是否要等待服务器返回的数据
{
while (1)
{
if (timerexpired) break;
i = read(s, buf, 1500); //读取从服务器返回的数据,保存到buf中
if (i<0)
{
failed++;
close(s);
goto nexttry;
}
else if (i == 0) break;
else
bytes += i;
}
}
if (close(s)) { failed++; continue; }
speed++;
}
}
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;
memset(&ad, 0, sizeof(ad));
ad.sin_family = AF_INET;
//inet_addr()将主机名转换成网络传输序列,出错返回INADDR_NONE
inaddr = inet_addr(host);
if (inaddr != INADDR_NONE)
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
else
{
/*gethostbyname()用域名或主机名获取IP地址
返回值,是一个hostent的结构,失败返回NULL
hostent结构体类型
struct hostent
{
char *h_name; 表示的是主机的规范名。例如www.google.com的规范名其实是www.l.google.com。
char **h_aliases;表示的是主机的别名.www.google.com就是google他自己的别名
int h_addrtype; 表示的是主机ip地址的类型,到底是ipv4(AF_INET),还是pv6(AF_INET6)
int h_length; 表示的是主机ip地址的长度
char **h_addr_list; 表示的是主机的ip地址,注意,这个是以网络字节序存储的。千万不要直接用printf带%s参数来打这个东西
#define h_addr h_addr_list[0]
};*/
hp = gethostbyname(host);
if (hp == NULL)
return -1;
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
}
ad.sin_port = htons(clientPort);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return sock;
if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
return -1;
return sock;
}