IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Webbench源码剖析

    armsword发表于 2014-11-22 16:27:25
    love 0

    我们知道知名的Web网站压力测试工具有Webbench、ab、http_load、siege等等,这种工具的源码都不是太长,所以,我用了一下午和晚上时间仔细了分析了Webbench的源码,并且写下这篇博客记录下。

    我们先看下一般Webbench是怎么做压力测试的吧,方法很简单,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #模拟200次请求,持续时间5秒的压力测试 -c 后为并发数, -t 后为持续时间
    [imlinuxer@imlinuxer webbench-1.5]# webbench -c 200 -t 5 http://localhost/index.php
    Webbench - Simple Web Benchmark 1.5
    Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
    Benchmarking: GET http://localhost/index.php
    200 clients, running 5 sec.
    Speed=156804 pages/min, 128496336 bytes/sec.
    Requests: 13067 susceed, 0 failed.

    那Webbench的原理是怎么样的?其实也是很简单的,就是根据提供的参数构造HTTP请求Header,然后使用fork,创建指定大小(webbench提供的参数-c 后的数字,上文为200)个子进程,每个子进程利用socket创建TCP连接到URL,然后通过管道向父进程发送数据,父进程通过管道读取子进程的数据,并作累计,输出即可。其简单流程图如下图所示:


    下面简单说下源码吧,源码很短,不到600行,两个文件,分别为socket.c和webbench.c。先说下socket.c,代码为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
    // 将字符串转换为32位二进制网络字节序的IPv4地址
    inaddr = inet_addr(host);
    if (inaddr != INADDR_NONE)
    memcpy(&ad.sin;_addr, &inaddr;, sizeof(inaddr));
    else
    {
    // 使用域名或主机名获取ip地址
    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;
    }

    自己封装了个socket模块,只需要注意的就是URL可能不是域名,也可能是IP地址。

    然后就是webbench.c文件,咱们从main函数说起,因为需要对命令行做处理,所以使用了getopt_long函数,没使用这个函数的同学可以man getopt_long或者查看在线文件man 。注意下此结构体为getopt_long的参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 长选项,getopt_long的参数
    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}
    };

    之后是buildr_equest()函数,主要功能是构造HTTP头请求,构造成下面类似情况,具体可以参考代码:

    1
    2
    3
    4
    GET / HTTP/1.1
    Host: www.baidu.com
    Cache-Control: max-age=0
    Pragma: no-cache

    之后便是打印压力测试的一些信息,没啥可说的,很容易读懂,之后到了return bench()处,bench函数是压力测试的核心代码。
    bench里根据并发数,使用fork()创建子进程,子进程调用benchcore()函数,此函数里使用alarm和sigaction信号控制定时时间,alarm函数设置了多少时间之后产生SIGALRM信号,一旦产生此信号,运行alarm_handler函数,使得timerexpired等于1,这样可以通过判断timerexpired值来退出程序。然后子进程将数据写入管道。同时父进程读取管道数据,将数据进行累加,当全部读取完子进程后,父进程输出信息退出。

    总体来说,Webbench代码还是很好读懂的,当然此代码也存在一些问题,比如不支持POST请求方式,只能模拟单个IP测试等等。
    宿舍马上要熄灯了,写的比较着急,可能逻辑混乱些。我Github里对此源码做了非常详细的源码剖析。感兴趣的同学请查看详细的源码剖析吧。

    链接地址:
    https://github.com/armsword/Source/tree/master/webbench-1.5



沪ICP备19023445号-2号
友情链接