WebBench测试源码解析

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

WebBench.c是主要的实现文件
sock.c中是对sock进行了封装,这里就不做过多的讲解。

流程图

下面主要是对Webbench.c的函数进行主要的讲解。
getopt_long(argc,argv,”912Vfrt:p:c:?h”, long_options, &options_index)
函数原型:
int getopt_long(int argc,char* const argv[],const char *optstring,const struct option *longopts,int *longindex)
重点是参数 optstring, 其可以是下列元素:
1、单个字符,表示选项。
2、单个字符后接个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋值给optarg.
3、单个字符后跟两个冒号,表示该选项后可以有参数也可以没有参数,如果有参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋值给optarg.

详细代码

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

    inaddr = inet_addr(host);
    if (inaddr != INADDR_NONE)
        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    else
    {
        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;
}

/*
* (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属性,防止           
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;
}


//用于打印help 使用帮助
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;
    }

    //获取命令行参数
    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, ':');//获取“:”在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;
        case 'c': clients = atoi(optarg); break;
        }
    }
    //man optind 之后出现的原话:
    //optind: the index of the next element to be processed in the argv.  
    //The system initializes it to 1. The caller can reset it to 1 to
    //restart scanning of the same argv or scanning a new argument vector.
    //optind初始化为1 没执行一次getopt_long()后自动+1
    //单个字符,表示选项.
    //单个字符后接一个冒号:表示该选项后必须跟一个参数.
    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]);
    /* 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]);
    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();
}

void build_request(const char *url)
{
    char tmp[10];
    int i;

    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;

    switch (method)
    {
    default:
    case METHOD_GET: strcpy(request, "GET"); break;
    case METHOD_HEAD: strcpy(request, "HEAD"); break;
    case METHOD_OPTIONS: strcpy(request, "OPTIONS"); break;   //http1.1规范增加5个新的方法:OPTIONS,PUT,DELETE,TRACE和CONNEC
    case METHOD_TRACE: strcpy(request, "TRACE"); break;      //所以只有http1.1时才有OPTIONS,TRACE   
    }

    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))//判断url前七为是否为http:// 忽略大小写
    {
        fprintf(stderr, "\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
        exit(2);
    }
    /* protocol/host delimiter */
    i = strstr(url, "://") - url + 3;  //令i等于7 即http://的大小,后用于偏移
    /* 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, '/'))   //判断除http://外是否含有:(是否有端口)
        {
            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
        {
            strncpy(host, url + i, strcspn(url + i, "/"));
        }
        // printf("Host=%s\n",host);
        strcat(request + strlen(request), url + i + strcspn(url + i, "/")); //将request和host拼接
    }
    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("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);       // 连接访问url 子进程进入此函数测试网站,直到benchtime耗完为止
        else                                           // 将测试结果存进speed,failed,bytes中
            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); //将文件内容写入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;          //用于信号安装函数 sigaction/SIGALRM

    /* setup alarm signal handler */
    sa.sa_handler = alarm_handler;   //设置唤醒头 信号唤醒alarm_handler的函数,并设置timerexpired
    sa.sa_flags = 0;
    if (sigaction(SIGALRM, &sa, NULL))  //设置触发闹钟模式
        exit(3);
    alarm(benchtime);           //设置闹钟时间间隔


    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函数 返回一个sock 的文件描述符
            if (s<0) { failed++; continue; }
            if (rlen != write(s, req, rlen)) { failed++; close(s); continue; } //发送请求至目标url
            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)) { failed++; continue; }
            speed++;
}
}

猜你喜欢

转载自blog.csdn.net/energysober/article/details/52822599