一、程序说明:
本程序对上述png服务器性能进行测试。(开发于FreeBSD,并可编译运行于Windows Cygwin环境)。
二、使用说明:(类似于ab)
st [options] [http://]hostname/path
-n requests Number of requests to perform
-c concurrency Number of multiple requests to make
-v Print version number and exit
-h Display usage information (this message)
三、源代码:
/******************************************************************************
* Copyright (C) 2004-2005 XiongBin Xiong All rights reserved
* References: Stevens,W.R. 1992. Advanced Programming in the UNIX Environment.
* Addison-Wesley.
* Stevens,W.R. 1998. UNIX Network Programming Volum1.
* Prentice Hall PTR.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#define BUFFSIZE 65535
#define MAXLINE 4096
static int requests=1; /* 在测试会话中所执行的请求个数,默认为一个 */
static int concurrency=1; /* 并发级别,一次产生的请求个数,默认是一次一个 */
static int done; /* 在测试会话中所完成的请求个数 */
static int recvdatas=0; /* 接收的总数据量 */
static char *hostname,*pathname; /* 主机名、请求路径名 */
static struct timeval take_time; /* 完成所有请求所需的时间 */
static int lflag; /* 子进程结束计数标志 */
int parse_url(char *); /* 将URL分解为hostname和pathname */
int test(void); /* 开始进行测试 */
int con_test(int); /* 并发进程测试 */
int fork_do(void); /* 测试程序 */
int record(int); /* 记录测试数据 */
int lock_reg(int,int,int,off_t,int,off_t); /* 使用记录锁对共享文件进行保护 */
static void output_results(void); /* 输出结果 */
static void sig_chld(int); /* 处理子进程SIGCHLD信号 */
static void usage(const char *); /* 提示信息 */
static void err(char *); /* 出错处理函数 */
static void copyright(void); /* 版本信息 */
int
main(int argc, char *argv[])
{
int flag;
char *url;
FILE *fp;
if(argc<2||argc>6){
printf("Invalid input\n");
usage(argv[0]);
exit(1);
}
opterr=0;
while((flag=getopt(argc,argv,"n:c:hv"))!=EOF){
switch(flag){
case 'n':
requests=atoi(optarg); /* 总请求数 */
if(requests<=0)
err("Invalid number of requests");
break;
case 'c':
concurrency=atoi(optarg); /* 并发请求的级别,即每个并发请求中同时发起的请求数 */
if(concurrency<=0)
err("Invalid number of concurrency");
break;
case 'h':
usage(argv[0]); /* 帮助信息 */
break;
case 'v':
copyright(); /* 版本信息 */
exit(0);
case '?':
printf("unrecognized option: -%c\n",optopt); /* 错误的参数 */
usage(argv[0]);
exit(1);
}
}
if(requests<concurrency) /* 并发级别要小于总请求数 */
err("Invalid number of requests or concurrency");
done=requests;
fp=fopen("temp.log","wb");
fprintf(fp,"%d\t%d",done,recvdatas); /* 设置文件初值 */
fclose(fp);
if(optind>=argc)
err("Invalid input");
else
url=argv[optind];
if(parse_url(url)) /* 将URL分解为hostname和pathname */
err("Invalid URL address");
copyright();
write(STDOUT_FILENO,"testing...",10);
test(); /* 开始进行测试 */
output_results(); /* 结果输出 */
unlink("temp.log"); /* 删除临时文件 */
exit(0);
}
/*----------------------------------------------------------------*/
/* 将URL分解为hostname和pathname */
int
parse_url(char *url)
{
char *p,*pp,*urlptr;
urlptr=url;
if(strncmp(urlptr,"http://",7)==0) /* 寻找"http://"处 */
urlptr=urlptr+7;
if((p=strstr(urlptr,"\n"))!=0) /* 寻找"\n"处 */
*p='\0';
p=strchr(urlptr,'/'); /* 寻找'/'处 */
if(p==0)
return(1);
pathname=p+1; /* 获取pathname */
*p='\0';
hostname=urlptr; /* 获取hostname */
return(0);
}
/*-----------------------------------------------------------------*/
/* 开始进行测试 */
int
test(void)
{
int i,n,m;
struct timeval start_time,end_time;
n=requests/concurrency; /* n为并发循环次数 */
m=requests%concurrency; /* 当上面不能整除时,m为最后一次并发请求的并发级别 */
gettimeofday(&start_time,NULL); /* 获取测试开始时间 */
for(i=0;i<n;i++){
con_test(concurrency); /* 并发测试 */
}
con_test(m); /* 剩余并发测试 */
gettimeofday(&end_time,NULL); /* 获取测试结束时间 */
if(end_time.tv_usec<start_time.tv_usec){
take_time.tv_usec=end_time.tv_usec+1000000-start_time.tv_usec;
end_time.tv_sec--;
}else
take_time.tv_usec=end_time.tv_usec-start_time.tv_usec;
take_time.tv_sec=end_time.tv_sec-start_time.tv_sec; /* 测试总耗时 */
return;
}
/*-----------------------------------------------------------------*/
/* 并发进程测试 */
int
con_test(int level)
{
int i,pid;
lflag=level;
signal(SIGCHLD,sig_chld); /* 获取SIGCHLD信号 */
for(i=0;i<level;i++){
if((pid=fork())<0){
printf("con_test(): fork error,\trefork\n");
i--;
}
else if(pid==0){ /* 生成并发子进程 */
fork_do(); /* 测试 */
exit(0);
}
}
while(lflag); /* 等待所有并发子进程结束 */
return(0);
}
/*------------------------------------------------------------------*/
/* 测试程序 */
int
fork_do(void)
{
int i,n,l,k,pid,ppid;
struct sockaddr_in servaddr;
char buf[BUFFSIZE],pngname[MAXLINE],cpid[8],*p,*h,rbuf[MAXLINE]="GET /";
FILE *ffp;
int sockfd;
struct hostent *hp;
struct in_addr **ptr;
hp=gethostbyname(hostname); /* 获取IP地址 */
ptr=(struct in_addr **)hp->h_addr_list;
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr)); /* 初始化套接口地址结构 */
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(80);
memcpy(&servaddr.sin_addr,*ptr,sizeof(struct in_addr));
l=0;
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
printf("connect error\n");
else{ /* 连接成功 */
strcat(rbuf,pathname);
strcat(rbuf," HTTP/1.0"); /* 形成GET请求命令 */
n=strlen(rbuf);
if(write(sockfd,rbuf,n)!=n) /* 发送GET请求至服务器 */
err("fork_do(): write error");
l=recv(sockfd,buf,sizeof(buf),0); /* 接收服务器返回数据 */
if((pid=getpid())<0)
err("fork_do(): getpid error");
sprintf(cpid,"%d",pid);
strcpy(pngname,"clipng");
strcat(pngname,cpid);
strcat(pngname,".png"); /* 形成带有PID的文件名 */
ffp=fopen(pngname,"wb"); /* 创建文件 */
unlink(pngname);
p=strstr(buf,"\n\n");
h=buf;
k=p-h+2; /* 定位在PNG数据区开始处 */
fwrite(&buf[k],sizeof(char),l-k,ffp); /* 写入数据 */
}
record(l); /* 记录本次请求的数据 */
close(sockfd);
fclose(ffp);
return(0);
}
/*------------------------------------------------------------------*/
/* 记录测试数据 */
int
record(int recvn)
{
int fd,a,b;
FILE *fp;
if((fp=fopen("temp.log","r+"))==NULL) /* 读写方式打开文件 */
return(1);
fd=fileno(fp);
while((lock_reg(fd,F_SETLK,F_WRLCK,0,SEEK_SET,0))==-1); /* 给文件加锁 */
fscanf(fp,"%d\t%d",&a,&b); /* 从文件得到当前数据 */
if(recvn)
b+=recvn; /* 如果connect成功,接收数据量累加 */
else
a--; /* connect失败,完成次数减一 */
rewind(fp);
fprintf(fp,"%d\t%d",a,b); /* 写入文件 */
lock_reg(fd,F_SETLK,F_UNLCK,0,SEEK_SET,0); /* 解锁 */
fclose(fp);
return(0);
}
/*-------------------------------------------------------------------*/
/* 使用记录锁对共享文件进行保护 */
int
lock_reg(int fd,int cmd,int type,off_t offset,int whence,off_t len)
{
struct flock lock;
lock.l_type=type; /* F_RDLCK,F_WRLCK,F_UNLCK */
lock.l_start=offset; /* byte offset, 起始处的相对偏移量 */
lock.l_whence=whence; /* SEEK_SET,SEEK_CUR,SEEK_END */
lock.l_len=len; /* 区域的长度(为0表示到最大位置为止) */
return(fcntl(fd,cmd,&lock)); /* 使用记录锁 */
}
/*--------------------------------------------------------------------*/
/* 输出结果 */
static void
output_results(void)
{
float takentime;
FILE *fp;
fp=fopen("temp.log","r");
fscanf(fp,"%d\t%d",&done,&recvdatas); /* 从文件中获取数据 */
takentime=((float)take_time.tv_sec)+((float)take_time.tv_usec)/1000000.0F; /* 格式化为整数+小数形式 */
printf("\n\n");
printf("Server Hostname: %s\n",hostname); /* 主机名 */
printf("Server Port: %hd\n",80); /* 端口 */
printf("Document Path: /%s\n",pathname); /* 请求路径名 */
printf("\n");
printf("Total Requests: %d\n",requests); /* 请求数 */
printf("Concurrency Level: %d\n",concurrency); /* 并发数 */
printf("Time taken for tests: %ld.%03ld seconds\n",take_time.tv_sec,take_time.tv_usec); /* 总耗时 */
printf("Complete requests: %ld\n",done); /* 完成请求数 */
printf("Failed requests: %ld\n",(requests-done)); /* 失败请求数 */
printf("Total transferred: %ld Bytes\n",recvdatas); /* 总接收数据量 */
printf("\n");
if(takentime){
printf("Requests per second: %.2f [#/sec] (mean)\n",(float)(done/takentime)); /* 每秒完成请求数 */
printf("Time per request: %.3f [ms] (mean)\n",(float)(1000*concurrency*takentime/done));/* 完成一个并发请求的时间 */
printf("Time per request: %.3f [ms] (mean, across all concurrent requests)\n",(float)(1000*takentime/done)); /* 完成一个请求的时间 */
printf("Transfer rate: %.2f [Kbytes/sec] received\n",(float)(recvdatas/takentime/1024)); /* 每秒传输数据量 */
}
printf("\n");
}
/*---------------------------------------------------------------------*/
/* 处理子进程SIGCHLD信号 */
static void
sig_chld(int signo)
{
pid_t pid;
int stat;
while((pid=waitpid(-1,&stat,1))>0) /* 处理已结束子进程状态,防止子进程Zombie状态出现 */
lflag--; /* 标志减一 */
return;
}
/*---------------------------------------------------------------------*/
/* 提示信息 */
static void
usage(const char *progname)
{
fprintf(stderr, "Usage: %s [options] [http://]hostname/path\n", progname);
fprintf(stderr, "Options are:\n");
fprintf(stderr, " -n requests Number of requests to perform\n");
fprintf(stderr, " -c concurrency Number of multiple requests to make\n");
fprintf(stderr, " -v Print version number and exit\n");
fprintf(stderr, " -h Display usage information (this message)\n");
exit(0);
}
/*----------------------------------------------------------------------*/
/* 出错处理函数 */
static void
err(char *s)
{
fprintf(stderr, "%s\n", s);
exit(1);
}
/*----------------------------------------------------------------------*/
/* 版本信息 */
static void
copyright(void)
{
printf("This is ServerTest for my pngserver, Version 1.0\n");
printf("Copyright (C) 2004-2005 XiongBin Xiong All rights reserved\n");
printf("hustxxb@hotmail.comn");
printf("\n");
}
/* end all */