webbench-1.5代码阅读笔记

webbench简介

网站压力测试,有四种方式GET,HEAD,OPTIONS,TRACE,通过多进程时间,可通过自由调整参数改变并发数量

int method  --head 发送http报文的方式
int clients --子进程的个数,也就是并发的个数
int force   -f  是否读取服务器返回的数据,默认0--读取,
int force_reload    -r 缓存
int proxyport   端口
char *proxyhost ip地址
char *req_url  url链接地址/*"http://www.baidu.com/"*/

新遇到的函数

strncasecmp
忽略大小写比对字符串
    #include <strings.h>
    int strcasecmp(const char *s1, const char *s2);
    int strncasecmp(const char *s1, const char *s2, size_t n);
index
查找字符串中第一个出现的指定字符的地址
    #include <strings.h>
    char *index(const char *s, int c);
    char *rindex(const char *s, int c);
setvbuf
设置文件流的缓冲区
    #include <stdio.h>
    int setvbuf(FILE *stream, char *buf, int mode, size_t size);
gethostbyname
通过域名"www.baidu.com"得到IP地址,但是已经淘汰
    #include <netdb.h>
    extern int h_errno;
    struct hostent *gethostbyname(const char *name);

    struct hostent {
       char  *h_name;            /* official name of host */
       char **h_aliases;         /* alias list */
       int    h_addrtype;        /* host address type */
       int    h_length;          /* length of address */
       char **h_addr_list;       /* list of addresses */
    }


    hp = gethostbyname(host);
    memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    printf("ip is %s, %d\n", inet_ntop(AF_INET, &ad.sin_addr, buf, sizeof(buf)), clientPort); 

整理后的源代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>

#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 <stdarg.h>


#define PROGRAM_VERSION "1.5"   /* 版本号 */

/* volatile:
 *      -让系统总是从内存读取数据,
 *      -告诉编译器不要做任何优化 
 *      -变量会在程序外被改变*/

volatile int timerexpired = 0;  /* 超时标记,当被设值为1的时候,所有子进程结束退出 */

int speed  = 0; /* 成功请求次数 */
int failed = 0; /* 失败请求次数 */
int bytes  = 0; /* 读取字节总字数 */

/* 支持的HTTP协议版本: 0-0.9, 1-1.0, 2-1.1 */
int http10 = 1;

/* 支持的HTTP方法:GET HEAD OPTIONS TRACE*/
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
int method = METHOD_GET;    /* 默认为GET方法 */

int clients      = 1;   /* 默认启动一个客户端 */
int force        = 0;   /* 是否等待相应数据返回, 0 等待,1 不等待 */
int force_reload = 0;   /* 是否发送 Pragma: no-cache */
int proxyport    = 80;  /* 代理端口 */
char *proxyhost  = NULL;    /* 代理服务器名称 */
char *req_url = NULL;

/*
 * 执行时间,当子进程执行时间到达这个描述之后.
 * 发送SIGALRM信号,将timerexpired设置为1,
 * 让所有子进程退出*/
int benchtime = 30;

int mypipe[2]; /* 管道,子进程给父进程发送数据 */

char host[MAXHOSTNAMELEN];  /* 主机名(64字节) param.h*/    
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE]; /* 请求字符串(HTTP头) */


static void dealarg(int , char**);  /* 处理命令行参数 */

/* 子进程执行执行任务的函数 */
static void benchcore(const char *host, const int port, const char *request);

/* 执行压力测试的主要入口函数 */
static int bench(void);

/* 生成 HTTP 头 */
static void build_request(const char *url);

/* SIGALARM信号捕捉函数 */
static void alarm_handler(int signal) 
{
    timerexpired = 1; /* 将timerexpited设置为1,让所有子进程退出 */
}

/* 打印帮助 */
static void usage(void) 
{
    fprintf(stderr,
            "webbench [option]... URL<\"http://www.baidu.com/\">\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_ip: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[])
{
    if (argc == 1) {
        usage();
        return 2;
    }

    dealarg(argc, argv);

#if 0
    printf("http10 is %d\nmethod is %d\nclients is %d\n"
            "force is %d\nforce_reload is %d\n"
            "proxyport is %d\nproxyhost is %s\n", http10, method, clients,\
            force, force_reload, proxyport, proxyhost);
#endif 


    /* Copyright */
    fprintf(stderr, 
            "Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
            "Copyright (c) Radim Kolar 1997-2004, GPL Open Source software.\n");

    build_request(req_url);    /* 最后一个非选项的参数,被视为URL*/
    printf("url is %s\n", req_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", req_url);

    switch(http10) {
        case 0:
            printf(" (using HTTP/0.9)");
            break;
        case 1:
            printf(" (using HTTP/1.0)");
            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();
}


/* 生成 HTTP 头 */
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) {
        case METHOD_GET:
        default:
            strcpy(request, "GET");
            break;
        case METHOD_OPTIONS:
            strcpy(request, "OPTIONS");
            break;
        case METHOD_HEAD:
            strcpy(request, "HEAD");
            break;
        case METHOD_TRACE:
            strcpy(request, "TRACE");
            break;
    }

    strcat(request, " ");

    if (NULL == strstr(url, "://")) {
        fprintf(stderr, "\n%s: is not a vaild 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;   /* i是"http://", 或者"https://"的长度 */
    /* printf("i is %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://www.baidu.com:8080/"里面的端口号提取出来 */
            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);
            proxyport = atoi(tmp);
            if (proxyport == 0) {
                proxyport=80;
            }
        } else {
            strncpy(host, url+i, strcspn(url+i, "/"));
        }

        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) {
        strcat(request, "Connection: close\r\n");
    }
    /* add empty line at end */
    if (http10 > 0) {
        strcat(request, "\r\n");
    }
    printf("Req:(%s)\n", request);
}

/* 执行压力测试的主要入口函数 */
int bench(void)
{
    int i, j, k;
    pid_t pid = 0;
    FILE *f;

    /* 测试远程主机是否能够连通 */
    i = Socket(proxyhost == NULL ? host : proxyhost, proxyport);
    if (i < 0) {
        fprintf(stderr, "\nConnect to server failed. Aborting benchmark\n");
        return 1;
    }
    printf("i = %d\n", i);

    close(i);

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

    /* 生成子进程 */

    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 faild.");
        return 3;
    }

    if (pid == (pid_t)0) {
        /* I am child */
        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(f, "%d %d %d\n", speed, failed, bytes);
        fclose(f);

        return 0;
    } else { /* this is parent 读管道,打印结果*/
        printf("parent %d\n", getpid());
        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;

            if (--clients == 0) {
                wait(NULL);
                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;

    /* 设置alarm信号捕捉,设置闹钟, 
     * 等时间到了执行捕捉函数,设置timerexpired=1,子进程退出 */
    sa.sa_handler = alarm_handler;
    sa.sa_flags = 0;
    if (sigaction(SIGALRM, &sa, NULL)) {
        exit(3);
    }
    alarm(benchtime);

    rlen = strlen(req);

    /* 死循环,知道接收到SIGALRM信号将timerexpired设置为1 */
nexttry:
    while (1) {
        if (timerexpired) {
            if (failed > 0) {
                failed--;
            }
            return ;
        }

        /* 连接远程服务器 */
        s = Socket(host, port);
        if (s < 0) {
            failed++;
            continue;
        }
        if (http10 == 0) {
            /* 如果是http/0.9 这个关闭socket的写操作 */
            if (shutdown(s, SHUT_WR/* 1 */)) {
                failed++;
                close(s);
                continue;
            }
        }

        /* 如果等待响应数据返回,则读取相应数据,计算传输的字节数 */
        if (force == 0) {
            while (1) {
                if (timerexpired) {
                    break;
                }
                i = read(s, buf, 1500);
                if (i < 0) {
                    failed++;
                    close(s);
                    goto nexttry;
                } else {
                    if (i == 0) {
                        break;
                    } else {
                        bytes += 1;
                    }
                }
            }
        }
        /* 关闭连接 */
        if (close(s)) {
            failed++;
            continue;
        }
        speed++;
    }
}



/* 命令行的选项配置表,细节部分查看man page. man getopt_log */
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}
};

void dealarg(int argc, char **argv)
{
    int opt = 0;
    int options_index = 0;
    char *tmp = NULL;

    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':
                    /* proxyhost server parsing sever:port */
                    tmp = strrchr(optarg, ':');
                    proxyhost = strdup(optarg); /* 放到堆里 */
                    if (tmp == NULL) {
                        break;
                    }
                    if (tmp == optarg) {
                        fprintf(stderr, "Error in option --proxy %s: Missing hostname.\n", optarg);
                        exit(2);
                    }
                    if (tmp == optarg + strlen(optarg) - 1) {
                        fprintf(stderr, "Error in option --proxy %s Port number is missing.\n", optarg);
                        exit(2);
                    }
                    *tmp = '\0';
                    proxyport = atoi(tmp+1);
                    break;
            case ':':
            case 'h':
            case '?':
                    usage();
                    exit(2);
            case 'c':
                    clients = atoi(optarg);
                    break;
        }
    }

    if (optind == argc) {
        fprintf(stderr, "webbench:Missing URL!\n");
        fprintf(stderr, "---------------------------\n");
        usage();
        exit(2);
    }
    req_url = strdup(argv[optind]);

    if (clients == 0) {
        clients = 1;
    }

    if (benchtime == 0) {
        benchtime = 60;
    }
}

int Socket(const char *host, int clientPort)
{
    int sock;
    unsigned long inaddr;
    struct sockaddr_in ad;
    struct hostent *hp;
    char buf[1024];

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

    printf("ip is %s, %d\n", inet_ntop(AF_INET, &ad.sin_addr, buf, sizeof(buf)), 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值