WebBench压力测试工具(详细源码注释+分析)
本文适合人群:对WebBench实现感兴趣的人
WebBench原理:
Linux下使用的服务器压力测试工具,利用fork建立多个子进程,每个子进程在测试时间内不断发送请求报文,建立多个连接,然后由父进程统计:TCP连接成功次数,TCP连接失败次数,从服务器接收的数据量
WebBench适用于小,中型网站的服务器压力测试(对淘宝,百度这种大型网站不存在测压作用)
WebBench支持的并行连接数:32768
进程号pid是short类型的,short类型最大为32768
所以WebBench最多可以模拟3万多个并发连接去测试网站的负载能力
1.clients参数
//创建子进程进行测试,子进程数量和clients有关 for(i=0; i<clients; i++) { // pid 为 pid_t 类型 表示进程号 pid=fork();//建立子进程 //fork失败 子进程错误 if(pid <= (pid_t) 0) { sleep(1); //当前进程挂起1毫秒,将cpu时间交给其他进程 break; //跳出去,阻止子进程继续fork } }
子进程数量=1+2+3+……+(clients)
关键是的fork函数的理解:fork一个子进程,该子进程将要执行的指令和父进程继续执行的指令是一模一样的
2.benchtime参数
一个子进程在benchtime时间内,不断发送http请求,建立多个连接进行测试,到达benchtime时间则停止测试,返回测试结果(连接成功次数,连接失败次数,服务器响应内容字节数)
针对原版的WebBench所作的改进:
1.弃用了TRACE请求方法:回显服务器收到的请求
因为一般服务器都不支持这个方法,支持这个方法的服务器存在跨站脚本漏洞,攻击者可以此漏洞欺骗合法用户并得到他们的私人信息
2.增加了连接失败类型的统计,结果更加直观
一共两个文件socket.c和webbench.c
加上注释,代码不超过一千行
sorcket.c:
#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> /* sockaddr_in分析: #include <netinet/in.h>和#include <arpa/inet.h>定义的 struct sockaddr { __SOCKADDR_COMMON (sa_); //协议族 char sa_data[14]; //地址+端口号 }; sockaddr缺陷:把目标地址和端口号混在一起了 而sockaddr_in就解决了这一缺陷 将端口号和IP地址分开存储 struct sockaddr_in { sa_family_t sin_family; //地址族 uint16_t sin_port; //16位TCP/UDP端口号 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用,只为了内存对齐 }; */ /* hostent分析: host entry的缩写 记录主机信息包括主机名,别名,地址类型,地址长度和地址列表 struct hostent { char *h_name; //正式主机名 char **h_aliases; //主机别名 int h_addrtype; //主机IP地址类型:IPV4-AF_INET int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位 char **h_addr_list; //主机的IP地址列表 }; #define h_addr h_addr_list[0] //保存的是IP地址 主机的的地址是列表形式的原因: 当一个主机又多个网络接口时,自然有多个地址 */ //host ip地址或者主机名 //clientPort 端口 int Socket(const char *host, int clientPort) { int sock; unsigned long inaddr; struct sockaddr_in ad;//地址信息 struct hostent *hp;//主机信息 /* 因为host可能是ip地址或者主机名 所以当host为主机名的时候需要通过主机名得到IP地址 */ //初始化地址 memset(&ad, 0, sizeof(ad)); //采用TCP/IP协议族 ad.sin_family = AF_INET; //点分十进制IP转化为二进制IP inaddr = inet_addr(host); //输入为IP地址 if (inaddr != INADDR_NONE) //将IP地址复制给ad的sin_addr属性 memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); //输入不是IP地址,是主机名 else { //通过主机名得到主机信息 hp = gethostbyname(host); //没有得到主机信息 if (hp == NULL) return -1; //将IP地址复制给ad的sin_addr属性 memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); } /* 将端口号从主机字节顺序变成网络字节顺序 就是整数在地址空间存储方式变为高字节存放在内存低字节处 网络字节顺序是TCP/IP中规定好的一种数据表示格式,与CPU和操作系统无关 从而可以保证数据在不同主机之间传输时能够被正确解释 网络字节顺序采用大尾顺序:高字节存储在内存低字节处 */ ad.sin_port = htons(clientPort); /* AF_INET: IPV4网络协议 SOCK_STRAM: 提供面向连接的稳定数据传输,即TCP协议 */ //创建一个采用IPV4和TCP的socket sock = socket(AF_INET, SOCK_STREAM, 0); //创建socket失败 if (sock < 0) return sock; //建立连接 连接失败返回-1 if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) return -1; //创建成功 返回socket return sock; }
webbench.c
#include "socket.c" #include <unistd.h> #include<stdio.h> #include <sys/param.h> #include <rpc/types.h> #include <getopt.h> #include <strings.h> #include <time.h> #include <signal.h> #include<string.h> #include<error.h> //用法和各参数的详细意义 static void usage(void) { fprintf(stderr, "webbench [parameter]... URL\n" " -f|--force No waiting for server response \n" " -r|--reload Re-request loading (no caching) \n" " -t|--time <sec> Set run time in seconds, default 30 seconds \n" " -p|--proxy <server:port> Setting the number of proxy servers \n" " -c|--clients <n> How many clients are created, default is 1 \n" " -9|--http09 Using HTTP 0.9 protocol \n" " -1|--http10 Using HTTP 1.0 protocol \n" " -2|--http11 Using HTTP 1.1 protocol \n" " -G|--get Using GET request method \n" " -H|--head Using HEAD request method \n" " -O|--options Using OPTIONS request method \n" " -?|-h|--help Display help information \n" " -V|--version Display program version information \n" ); }; //支持的http请求方法 #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; //默认需要等待服务器响应 int force_reload=0; //失败时重新请求 int proxyport=80; //默认访问服务器端口为80 char *proxyhost=NULL; //默认无代理服务器 int benchtime=30; //默认模拟请求时间为30s //支持的http版本号 int http10=1; /* 0表示http0.9 1表示http1.0 2表示http1.1 */ /* 内部 */ int mypipe[2]; //管道用于父子进程通信 char host[MAXHOSTNAMELEN]; //存储服务器网络地址 #define REQUEST_SIZE 2048 //最大请求次数 char request[REQUEST_SIZE]; //存放http请求报文信息数组 //判断测试时长是否已经到达设定时间 volatile int timeout=0; /* volatile: 类型修饰符,作为指令关键字, 确保本指令不会因为编译器优化而省略 且每次要求重新读值, 编译器在用到这个变量的时候都必须小心的重新读取这个变量的值, 而不是使用保存在寄存器里的备份,保证每次读到的都是最新的 */ //测试结果 int speed=0; //成功得到服务器响应的子进程数量 int failed=0; //没有成功得到服务器响应的子进程数量 int bytes=