WebBench-1.5源码剖析

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主要的工作原理:

  1. 主函数解析命令行参数,进行必要的准备工作,调用build_request设置HTTP请求报文,进入bench开始测压

  2. bench函数使用fork模拟出多个客户端,调用socket并发请求,每个子进程记录自己的访问数据,并写入管道

  3. 父进程从管道读取子进程的输出信息

  4. 使用alarm函数进行时间控制,到时间后会产生SIGALRM信号,调用信号处理函数使子进程停止

  5. 最后只留下父进程将所有子进程的输出数据汇总计算,输出到屏幕上
    这里写图片描述
    以下是源码的详细剖析
    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;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值