本文适合人群:对WebBench实现感兴趣的人

WebBench原理:

Linux下使用的服务器压力测试工具,利用fork建立多个子进程,每个子进程在测试时间内不断发送请求报文,建立多个连接,然后由父进程统计:TCP连接成功次数,TCP连接失败次数,从服务器接收的数据量

WebBench适用于小,中型网站的服务器压力测试(对淘宝,百度这种大型网站不存在测压作用)

WebBench支持的并行连接数:32768

进程号pid是short类型的,short类型最大为32768

所以WebBench最多可以模拟3万多个并发连接去测试网站的负载能力WebBench源码理解坑点:

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=0; //所有子进程读取到服务器回复的总字节数 int connect_failed=0;
int send_failed=0;
int wclose_failed=0;
int read_failed=0;
int sclose_failed=0; //程序版本号
#define PROGRAM_VERSION "1.5" /* 函数声明 */ //子进程真正相服务器发出请求报文并以其得到此期间的相关数据
static void benchcore(const char* host,const int port, const char *req); //父进程创建子进程,读取子进程测试得到的数据,然后统计处理
static int bench(void); //构造http请求报文
static void build_request(const char *url); //闹钟信号处理函数
static void alarm_handler(int signal)
{
//到达设定的测压时间,则调用闹钟信号处理函数
timeout=1;//timerexpired为1则会在循环中跳出测试
} //构造长选项和短选项的对应
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},
{"version",no_argument,NULL,'V'},
{"proxy",required_argument,NULL,'p'},
{"clients",required_argument,NULL,'c'},
{NULL,0,NULL,0}
}; int main(int argc, char *argv[])
{
//argc表示参数个数
//argv[0]表示自身运行的路径和程序名
//argv[1]指向第1个参数
//argv[n]指向第n个参数 int opt=0;
int options_index=0;
char *tmp=NULL; //进行命令行参数的处理 //1.命令行没有输入参数
if(argc==1)
{
usage();//显示提示信息
return 2;
} //命令行有输入参数则一个个解析
//"frt:p:c:?V912"中一个字符后面加一个冒号代表该命令后面接一个参数
//比如t,p,c命令,后面都要接一个参数
//连续两个冒号则表示参数可有可无
while((opt=getopt_long(argc,argv,"frt:p:c:?V912GHO",long_options,&options_index))!=EOF )
{
switch(opt)
{
case 'f':
force=1;//不等待服务器响应
printf("No waiting for server response\n");
break; case 'r'://重新请求加载(无缓存)
force_reload=1;
printf("Re-request loading (no caching)\n");
break; case '9'://使用http/0.9协议来构造请求
http10=0;
printf("Using HTTP/0.9\n");
break; case '1':
http10=1;//使用http/1.0协议来构造请求
printf("Using HTTP/1.0\n");
break; case '2':
http10=2;//使用http/1.1协议来构造请求
printf("Using HTTP/1.1\n");
break; case 'V':
printf(PROGRAM_VERSION"\n");//显示程序版本信息
exit(0); case 't'://设置运行时间,单位:秒,默认为30秒
benchtime=atoi(optarg);//optarg指向选项后的参数
printf("benchtime=%d\n",benchtime);
break; case 'c'://创建多少个客户端,默认为1个
clients=atoi(optarg);//同上
printf("clients=%d\n",clients);
break; case 'p'://使用代理服务器,则设置其代理网络号和端口号,格式:-p server:port //server:port是一个参数,下面把这个字符串解析成服务器地址和端口两个参数 tmp=strrchr(optarg,':');//在optagr中找到':'最后出现的位置 proxyhost=optarg; if(tmp==NULL)//没有端口号
{
break;
} if(tmp==optarg)//端口号在optarg最开头,说明缺失主机地址
{
fprintf(stderr,"Option parameter error,Proxy server %s: Missing host name ",optarg);
return 2;
}
if(tmp==optarg+strlen(optarg)-1)//':'在最末尾,说明缺失端口号
{
fprintf(stderr,"Option parameter error,Proxy server %s: Missing port number ",optarg);
return 2;
} *tmp='\0';//将optarg从':'开始截断,前面就是主机名,后面是端口号 proxyport=atoi(tmp+1);//设置代理服务器端口号 printf("Using proxy server %s:%d\n",proxyhost,proxyport); break; case 'G':
method=METHOD_GET;
printf("Using GET request method \n");
break;
case 'H':
method=METHOD_HEAD;
printf("Using HEAD request method \n");
break;
case 'O':
method=METHOD_OPTIONS;
printf("Using OPTIONS request method \n");
break;
case '?'://显示帮助信息
usage();
return 2;
break; default://失败也显示帮助信息
usage();
return 2;
break;
}
} //命令参数解析完毕之后,刚好是读到URL,此时argv[optind]指向URL
//URL参数为空
if(optind==argc)
{
fprintf(stderr,"Missing URL\n");
usage();
return 2;
} //设置默认值
if(clients==0)
clients=1;
if(benchtime==0)
benchtime=30; //程序说明
fprintf(stderr,"WebBench: A Lightweight Web Pressure Measuring Tool "PROGRAM_VERSION" covered by YB \nGPL Open Source Software\n"); //构造请求报文
build_request(argv[optind]);//参数为URL //请求报文构造好了,开始测压
printf("\nIn testing :\n"); //选择请求方法
switch(method)
{
case METHOD_OPTIONS:
printf("OPTIONS");
break; case METHOD_HEAD:
printf("HEAD");
break; case METHOD_GET:
printf("GET");
break;
default:
printf("GET");
break; } //打印URL
printf(" %s",argv[optind]); 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"); printf("Operation parameters :\n"); printf("%d Clients",clients); printf(",Testing running %d s",benchtime); if(force)
printf(",Choose to close the connection ahead of time "); if(proxyhost!=NULL)
printf(",Through proxy server %s:%d ",proxyhost,proxyport); if(force_reload)
printf(",Choose no cache "); /*
*换行不能少!库函数是默认行缓冲,子进程会复制整个缓冲区
*若不换行刷新缓冲区,子进程会把缓冲区的也打出来
*而换行后缓冲区就刷新了
*子进程的标准库函数的那块缓冲区就不会有前面这些了
*/
printf(".\n"); //真正开始压力测试!
return bench();
} //父进程创建子进程,读子进程测试到的数据,然后统计处理
static int bench(void)
{
int i,j;
int k;
int c1,c2,c3,c4,c5; pid_t pid=0;//进程号定义 实际上也是int型的
FILE *f;//文件 //先检查一下目标服务器是可用性
i=Socket(proxyhost==NULL?host:proxyhost,proxyport); //目标服务器不可用
if(i<0)
{
fprintf(stderr,"\n Connection server failed, interrupt test \n");
return 3;
} //尝试连接成功了,关闭连接
close(i); //建立父子进程通信的管道
if(pipe(mypipe))
{
perror(" Communication Pipeline Failure ");
return 3;
} /*
父进程创建子进程后,fork函数是让子进程完全拷贝父进程,
包括父进程上下文,什么意思呢?
就是说父进程的EIP(CPU的下一条指令地址)以及变量等等一律拷贝,
也就是说,父进程执行过的代码子进程是不会再执行,
子进程下一条该执行的命令与父进程完全一样!!!
*/
//创建子进程进行测试,子进程数量和clients有关
for(i=0; i<clients; i++)
{
// pid 为 pid_t 类型 表示进程号 pid=fork();//建立子进程 //fork失败 子进程错误
if(pid <= (pid_t) 0)
{
sleep(1); //当前进程挂起1毫秒,将cpu时间交给其他进程
break; //跳出去,阻止子进程继续fork
}
} //处理fork失败情况
if( pid < (pid_t) 0)
{
fprintf(stderr,"The %d Subprocess creation failed ",i);
perror(" Failure to create subprocesses ");
return 3;
} //当前进程是子进程
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(" Pipeline Writer End Failed to Open ");
return 3;
} /*向管道中写入该孩子进程在一定时间内
请求成功的次数
失败次数
读取到服务器回复的总字节数
*/
fprintf(f,"%d %d %d %d %d %d %d %d\n",speed,failed,bytes,connect_failed,send_failed,wclose_failed,read_failed,sclose_failed); //关闭写端
fclose(f); return 0;
}
//当前进程是父进程
else
{
//父进程获得管道读端的文件指针
f=fdopen(mypipe[0],"r"); //管道读端打开失败
if(f==NULL)
{
perror(" Pipeline Reader Failed to Open ");
return 3;
} /*
fopen标准IO函数是自带缓冲区的
我们输入的数据非常短,并且数据要及时
所以没有缓冲是最合适的
我们不需要缓冲区
因此把缓冲类型设置为_IONBF*/
setvbuf(f,NULL,_IONBF,0); speed=0; //连接成功次数,后面除以时间可以得到速度
failed=0; //失败的请求次数
bytes=0; //服务器回复的总字节数 connect_failed=0;
send_failed=0;
wclose_failed=0;
read_failed=0;
sclose_failed=0; //父进程不停的读
while(1)
{
//读入参数以及得到成功得到的参数的个数
pid=fscanf(f,"%d %d %d %d %d %d %d %d",&i,&j,&k,&c1,&c2,&c3,&c4,&c5); //成功得到的参数个数小于8
if(pid<8)
{
fprintf(stderr,"A child process deaid\n");
break;
} //计总数
speed+=i;
failed+=j;
bytes+=k; connect_failed+=c1;
send_failed+=c2;
wclose_failed+=c3;
read_failed+=c4;
sclose_failed+=c5; if(--clients==0)//记录已经读了多少个子进程的数据,读完就退出
break;
} //关闭读端
fclose(f); //统计处理结果
printf("\nSpeed:%d pages/min,%lld bytes/s.\nRequest:%d Success,%d Fail\n",\
(int)((speed+failed)/(benchtime/60.0f)),\
(int)(bytes/(float)benchtime),\
speed,failed); //失败的类型及个数
printf("Reasons for failure:\n");
printf("connect failed:%d\n",connect_failed);
printf("send message failed:%d\n",send_failed);
printf("write-side shutdown failed:%d\n",wclose_failed);
printf("read server message failed:%d\n",read_failed);
printf("socket close failed:%d\n",sclose_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_handler函数为闹钟信号处理函数
sa.sa_handler=alarm_handler;
sa.sa_flags=0; if(sigaction(SIGALRM,&sa,NULL))//超时会产生信号SIGALRM,用sa中指定函数处理
exit(3); alarm(benchtime);//开始计时 rlen=strlen(req);//得到请求报文的长度 nexttry:
while(1)
{
//只有在收到闹钟信号后会使得timeout=1
if(timeout)//超时返回
{
//修正失败信号
if(failed>0)
failed--;
if(connect_failed>0)
connect_failed--;
else if(send_failed>0)
send_failed--;
else if(wclose_failed>0)
wclose_failed--;
else if(read_failed>0)
read_failed--;
else if(sclose_failed>0)
sclose_failed--; return;
} //建立到目的网站的tcp连接,发送http请求
s=Socket(host,port); //连接失败
if(s<0)
{
failed++;//失败次数+1
connect_failed++;
continue;
} //发出请求报文
if(rlen!=write(s,req,rlen))//write函数会返回实际写入的字节数
{
failed++;//实际写入的字节数和请求报文字节数不相同,写失败,发送1失败次数+1
send_failed++;
close(s);//写失败了也不要忘记关闭套接字
continue;
} //http/0.9的特殊处理
/*
*因为http/0.9是在服务器回复后自动断开连接
*在此可以提前先彻底关闭套接字的写的一半,如果失败了那肯定是个不正常的状态
*事实上,关闭写后,服务器没有写完数据也不会再写了,这个就不考虑了
*如果关闭成功则继续往后,因为可能还需要接收服务器回复的内容
*当这个写一定是可以关闭的,因为客户端也不需要写,只需要读
*因此,我们主动破坏套接字的写,但这不是关闭套接字,关闭还是得用close
*/
if(http10==0)
{
if(shutdown(s,1))//1表示关闭写 关闭成功返回0,出错返回-1
{
failed++;//关闭出错,失败次数+1
wclose_failed++;
close(s);//关闭套接字
continue;
}
} //foece=0 默认需要等待服务器回复
if(force==0)
{
//从套接字读取所有服务器回复的数据
while(1)
{
//超时标志为1,不再读取服务器回复的数据
if(timeout)
break; //读取套接字中1500个字节数据到buf数组中
i=read(s,buf,1500);//如果套接字中数据小于要读取的字节数1500会引起阻塞 返回-1 //read返回值: //未读取任何数据 返回 0
//读取成功 返回 已经读取的字节数
//阻塞 返回 -1 //读取阻塞了
if(i<0)
{
failed++; //失败次数+1
read_failed++;
close(s); //关闭套接字,不然失败次数多会严重浪费资源
goto nexttry; //这次失败了那么继续请求下一次连接和发出请求
}
//读取成功
else
{
if(i==0)
break;//没有读取到任何字节数
else
bytes+=i;//从服务器读取到的总字节数增加
}
}
} /* close返回返回值
成功 返回 0
失败 返回 -1 */ //套接字关闭失败
if(close(s))
{
failed++;//没有成功得到服务器响应的子进程数量
sclose_failed++;
continue;
} //套接字关闭成功 成功得到服务器响应的子进程数量+1
speed++;
}
} //构造http报文请求到request数组
/* 典型的http/1.1的get请求如下: 从下一行开始
GET /test.jpg HTTP/1.1 //请求行:请求方法+url+协议版本
User-Agent: WebBench 1.5
Host:192.168.10.1
Pragma: no-cache
Connection: close //从上行结束,最后必须要有一个空行 该函数目的就是根据需求填充出这样一个http请求放到request报文请求数组中
*/
void build_request(const char *url)
{
//存放端口号的中间数组
char tmp[10];
//存放url中主机名开始的位置
int i; //初始化
memset(host,0,MAXHOSTNAMELEN);
memset(request,0,REQUEST_SIZE); //判断应该使用的http协议 //1.缓存和代理都是都是http/1.0以后才有到的
if(force_reload && proxyhost!=NULL && http10<1)
http10=1; //2.head请求是http/1.0后才有的
if(method==METHOD_HEAD && http10<1)
http10=1; //3.options请求和reace请求都是http/1.1才有
if(method==METHOD_OPTIONS && http10<2)
http10=2;
if(method==METHOD_TRACE && http10<2)
http10=2; //开始填写http请求 //填充请求方法到请求行
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," "); //判断url的合法性 //1.url中没有 "://" 字符
if(NULL==strstr(url,"://"))
{
fprintf(stderr,"\n %s:is an illegal URL\n",url);
exit(2);//结束当前进程 2表示是因为url不合法导致进程停止的
}
//2.url过长
if(strlen(url)>1500)
{
fprintf(stderr,"URL too long\n");
exit(2);
} //3.若无代理服务器,则只支持http协议
if(proxyhost==NULL)
{
//忽略字母大小写比较前7位
if (0!=strncasecmp("http://",url,7))
{
fprintf(stderr,"\n URL can't be parsed, need it or not, but don't choose to use proxy server\n");
usage();
exit(2);
}
} //在url中找到主机名开始的地方
//比如:http://baidu.com:80/
//主机名开始的地方为bai....
//i==7
i=strstr(url,"://")-url+3; //4.从主机名开始的地方开始往后找,没有 '/' 则url非法
if(strchr(url+i,'/')==NULL)
{
fprintf(stderr,"\n URL illegal: hostname does not end with'/' \n");
exit(2);
}
//url合法性判断到此结束 //开始填写url到请求行 //无代理时
if(proxyhost==NULL)
{
//存在端口号 比如http://www.baidu.com:80/
if(index(url+i,':')!=NULL && index(url+i,':')<index(url+i,'/'))
{
//填充主机名到host字符数组,比如www.baidu.com
strncpy(host,url+i,strchr(url+i,':')-url-i); //初始化存放端口号的中间数组
memset(tmp,0,10); //切割得到端口号
strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
/* printf("tmp=%s\n",tmp); */ //设置端口号 atoi将字符串转整型
proxyport=atoi(tmp); //避免写了';'却没有写端口号,这种情况下默认设置端口号为80
if(proxyport==0)
proxyport=80;
}
//不存在端口号
else
{
//填充主机名到host字符数组,比如www.baidu.com
strncpy(host,url+i,strcspn(url+i,"/"));
}
// printf("Host=%s\n",host); //将主机名,以及可能存在的端口号以及请求路径填充到请求报文中
//比如url为http://www.baidu.com:80/one.jpg/
//就是将www.baidu.com:80/one.jpg填充到请求报文中
strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
}
//存在代理服务器时就比较简单了,直接填写,不用自己处理
else
{
// printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport); //直接将url填充到请求报文
strcat(request,url);
} //填充http协议版本到请求报文的请求行
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"); //不存在代理服务器且http协议版本为1.0或1.1,填充Host字段
//当存在代理服务器或者http协议版本为0.9时,不需要填充Host字段
//因为http0.9版本没有Host字段,而代理服务器不需要Host字段
if(proxyhost==NULL && http10>0)
{
strcat(request,"Host: ");
strcat(request,host);//Host字段填充的是主机名或者IP
strcat(request,"\r\n");
} /*pragma是http/1.1之前版本的历史遗留问题,仅作为与http的向后兼容而定义
规范定义的唯一形式:
Pragma:no-cache
若选择强制重新加载,则选择无缓存
*/
if(force_reload && proxyhost!=NULL)
{
strcat(request,"Pragma: no-cache\r\n");
} /*我们的目的是构造请求给网站,不需要传输任何内容,所以不必用长连接
http/1.1默认Keep-alive(长连接)
所以需要当http版本为http/1.1时要手动设置为 Connection: close
*/
if(http10>1)
strcat(request,"Connection: close\r\n"); //在末尾填入空行
if(http10>0)
strcat(request,"\r\n"); //fprintf("\nRequest:\n%s\n",request);
}

WebBench压力测试工具(详细源码注释+分析)的更多相关文章

  1. WebBench压力测试工具

    Webbench是有名的网站压力测试工具,它是由 Lionbridge公司(http://www.lionbridge.com)开发. Webbech能测试处在相同硬件上,不同服务的性能以及不同硬件上 ...

  2. 一篇文章看懂TPCx-BB(大数据基准测试工具)源码

    TPCx-BB是大数据基准测试工具,它通过模拟零售商的30个应用场景,执行30个查询来衡量基于Hadoop的大数据系统的包括硬件和软件的性能.其中一些场景还用到了机器学习算法(聚类.线性回归等).为了 ...

  3. Goldeneye.py网站压力测试工具2.1版源码

    Goldeneye压力测试工具的源代码,粗略看了下,代码写的蛮规范和易读的,打算边读边加上了中文注释,但是想来也没太大必要,代码600多行,值得学习的地方还是蛮多的,喜欢Python的同学可以一读 这 ...

  4. linux 下网站压力测试工具webbench

    一直在用webbench ,这个linux下的网站压力测试工具.整理下. 笔记本装的ubuntu,其他linux系统也差不多. webbench 需要先安装 ctags,一个vim的阅读插件,可以直接 ...

  5. Linux下四款Web服务器压力测试工具(http_load、webbench、ab、siege)介绍

    一.http_load程序非常小,解压后也不到100Khttp_load以并行复用的方式运行,用以测试web服务器的吞吐量与负载.但是它不同于大多数压力测试工具,它可以以一个单一的进程运行,一般不会把 ...

  6. (总结)Web性能压力测试工具之WebBench详解

      PS:在运维工作中,压力测试是一项很重要的工作.比如在一个网站上线之前,能承受多大访问量.在大访问量情况下性能怎样,这些数据指标好坏将会直接影响用户体验.但是,在压力测试中存在一个共性,那就是压力 ...

  7. Linux下的压力测试工具:ab、http_load、webbench、siege

    一.ab 1.1 介绍 ab是apache自带的一款功能强大的测试工具.      安装了apache一般就自带了. 1.2 下载 同apache. 1.3 安装 同apache. 1.4 安装结果 ...

  8. Web性能压力测试工具之WebBench

    在运维工作中,压力测试是一项很重要的工作.比如在一个网站上线之前,能承受多大访问量.在大访问量情况下性能怎样,这些数据指标好坏将会直接影响用户体验.但是,在压力测试中存在一个共性,那就是压力测试的结果 ...

  9. LINUX下一款不错的网站压力测试工具webbench

    LINUX下一款不错的网站压力测试工具webbench 分类: Linux 2014-07-03 09:10 220人阅读 评论(0) 收藏 举报 [html] view plaincopy wget ...

随机推荐

  1. How To Do Master Record Mass Maintenance

    How To Do Master Record Mass Maintenance Which master records mass maintenance can be done? What do ...

  2. Elasticsearch Elasticsearch入门指导

    Elasticsearch入门指导 By:授客 QQ:1033553122 1. 开启elasticsearch服务器 1 2. 基本概念 2 <1> 集群(Cluster) 2 < ...

  3. Android为TV端助力 am命令以及hotkey文件的编写

    1.拨打电话:am start -a android.intent.action.CALL -d tel:10086 这里-a表示动作,-d表述传入的数据,还有-t表示传入的类型. 2. 打开一个网页 ...

  4. 测者的性测试手册:SWAP的监控

    swap是什么 swap是磁盘上的一块区域,可以使一个磁盘分区,也可以是一个文件,也可能是一个两种的组合.当物理内存资源紧张的时候,操作系统(Linux)会将一些不常访问的数据放到swap里.为其他常 ...

  5. sql-server的添加数据库文件(日志数据)以及收缩数据库文件(日志数据)

    环境: SSMS sql-server2016 一.为数据库添加数据文件 添加日志数据文件 以下是添加数据文件和日志文件的代码 ALTER DATABASE [joinbest] ADD FILE ( ...

  6. 以太坊 ERC20 与 ERC721 深度解密

    去年11月份的一段时间,Ethereum网络突然变的特别拥堵,原因是兴起了一款以太坊养猫的Dapp游戏,超级可爱的猫形象,再加上配种,繁殖和拍卖等丰富的玩法,风靡了币圈. 一时间币圈大大小小的人都在撸 ...

  7. Linux进程优先级的处理--Linux进程的管理与调度(二十二)

    1. linux优先级的表示 1.1 优先级的内核表示 linux优先级概述 在用户空间通过nice命令设置进程的静态优先级, 这在内部会调用nice系统调用, 进程的nice值在-20~+19之间. ...

  8. UITableView的分割线长短的控制

    UITableView的默认的cell的分割线左边没有顶满,而右边却顶满了.这样显示很难看.我需要让其左右两边都是未顶满状态,距离是20像素 // code1 if ([self.tableView ...

  9. python中装饰器的原理

    装饰器这玩意挺有用,当时感觉各种绕,现在终于绕明白了,俺滴个大爷,还是要慢慢思考才能买明白各种的真谛,没事就来绕一绕 def outer(func): def inner(): print(" ...

  10. Pythonic是什么?

    Python 之禅之中有这样几句话: 优美胜于丑陋(Python 以编写优美的代码为目标) 明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似) 简洁胜于复杂(优美的代码应当是简洁的,不要有复杂 ...