webench源码阅读

简介

webbench是一款用C编写的开源工具,主要用来在Linux下进行网站压力测试。最多可以模拟3万个连接去测试网站的负载能力,并可以设置运行的客户端数、测试时间、使用的http协议版本、请求方法、是否需要等待服务器响应等选项,最后统计每分钟相应请求次数(paga/min)和每秒钟传输数据量(byte/sec),以及请求成功数和失败数,表现测试网站的压力承载能力。

源码及其相关函数分析

webbench源码
getopt_long
sigaction
fdopen

工作流程

	首先解析终端输入的命令,然后根据输入的url构建相应的HTTP请求,开始压力测试后,
创建pipe管道,再创建client个子进程,在子进程中向服务端发送HTTP请求进行压力测试,
然后通过管道将测试信息传送给父进程。父进程将通过管道读取每个子进程的测试结果,
进行汇总统计。

测试

代码与测试相结合,有助于理解
./webbench -c 3000 -t 5 http://baidu.com/

//结果
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://baidu.com/
3000 clients, running 5 sec.

Speed=28164 pages/min, 22326 bytes/sec.
Requests: 1648 susceed, 699 failed.

源码解析

webbench.c

main()

int main(int argc, char *argv[])
{
 int opt=0;
 int options_index=0;
 char *tmp=NULL;
 
 // 如果在终端只输入了./webbench,后面没有跟参数,打印用法,直接退出程序,返回码为2,表示格式错误
 if(argc==1)
 {
	  usage();
          return 2;
 } 
 // ./webbench -c 3000 -t 5 url
 // 循环解析终端输入选项,每次解析一个选项及其后面可能跟的参数
 //该函数主要作用就是解析终端输入的参数,是否与(短选项)“912Vfrt:p:c:?h”或者与(长选项)“long_options”中任一符合
 while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
 {
  switch(opt)
  {
   case  0 : break;
   // 如果选项为'f',那么设置不等待服务器响应,发送请求后直接关闭连接
   case 'f': force=1;break;
   // 如果选项为'r',那么设置强制代理服务器重新发送请求
   case 'r': force_reload=1;break; 
   // 如果选项为'9',那么设置在条件允许范围内使用HTTP/0.9协议
   case '9': http10=0;break;
   // 如果选项为'1',那么设置在条件允许范围内使用HTTP/1.0协议
   case '1': http10=1;break;
   // 如果选项为'2',那么设置在条件允许范围内使用HTTP/1.1协议
   case '2': http10=2;break;
   // 如果选项为'V',那么打印程序版本,然后退出程序
   case 'V': printf(PROGRAM_VERSION"\n");exit(0);
   // 如果选项为't',那么记录其后所跟参数数值到运行基准时间benchtime中
   case 't': benchtime=atoi(optarg);break;
   // 如果选项为'p',那么表示使用代理服务器	     
   case 'p': 
	     /* proxy server parsing server:port */
        // 记录参数中最后出现字符':'的位置及其之后的内容到tmp中
	     tmp=strrchr(optarg,':');
        // 记录参数到代理主机proxyhost中
	     proxyhost=optarg;
        // 如果参数中没有字符':',说明没有端口号,直接退出switch
	     if(tmp==NULL)
	     {
		     break;
	     }
        // 如果参数中只有一个字符':',说明端口号在最前,打印缺失主机名,然后直接返回,返回码为2,表示格式错误
	     if(tmp==optarg)
	     {
		     fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
		     return 2;
	     }
        // 如果参数中最后一个':'之后没有内容,打印缺失端口号,然后直接返回,返回码为2,表示格式错误
	     if(tmp==optarg+strlen(optarg)-1)
	     {
		     fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
		     return 2;
	     }
        // 将proxyhost中的内容从最后一个':'处进行截断,只记录':'之前的内容
	     *tmp='\0';
        // 将最后一个':'之后内容转化为数字并记录在代理服务器端口号proxyport中
	     proxyport=atoi(tmp+1);break;
   // 如果选项为':'、'h'、'?',那么打印用法,并直接退出程序,返回码为2,表示格式错误
   case ':':
   case 'h':
   case '?': usage();return 2;break;
   // 如果选项为'c',那么记录其后所跟参数数值到客户端数量clients中
   case 'c': clients=atoi(optarg);break;
  }
 }
 
 // 如果参数后没有其它内容,打印缺失测试URL,打印用法后直接退出程序,返回码为2,表示格式错误
 if(optind==argc) {
                      fprintf(stderr,"webbench: Missing URL!\n");
		      usage();
		      return 2;
                    }

 // 如果输入客户端数量为0,则设置为默认值1
 if(clients==0) clients=1;
 // 如果输入运行测试的秒数为0,则设置为默认值60
 if(benchtime==0) benchtime=60;
 /* Copyright */
 // 打印版权信息
 fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
	 "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
	 );
 // 将测试URL作为参数传入build_request方法中,构建http请求
 build_request(argv[optind]);
 /* print bench info */
 // 打印Benchmarking、请求方法、测试URL
 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",argv[optind]);
 // 判断使用的http协议类型,如果使用的是默认的HTTP/1.0则不打印
 switch(http10)
 {
    // 如果http10的值为0,打印使用HTTP/0.9
	 case 0: printf(" (using HTTP/0.9)");break;
    // 如果http10的值为2,打印使用HTTP/1.1
	 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();
}

bench

static int bench(void)
{
  int i,j,k;	
  pid_t pid=0;
  FILE *f;

  /* check avaibility of target server */
  // 建立一个TCP连接,检查连接可用性,如果设置了代理服务器,那么连接代理服务器,否则直接连接目标服务器
  i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
  // 连接失败那么打印错误信息,并直接退出,返回码为1,表示基准测试失败(服务器未联机)
  if(i<0) { 
	   fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
           return 1;
         }
  // 关闭连接,这次连接不计入测试
  close(i);
  /* create pipe */
  // 创建管道,如果失败,直接退出程序,返回码为3,表示内部错误
  if(pipe(mypipe))
  {
	  perror("pipe failed.");
	  return 3;
  }

  /* not needed, since we have alarm() in childrens */
  /* wait 4 next system clock tick */
  /*
  cas=time(NULL);
  while(time(NULL)==cas)
        sched_yield();
  */

  /* fork childs */
  // 创建clients个子进程,由子进程进行真正的测试
  for(i=0;i<clients;i++)
  {
	   pid=fork();
      // 如果是子进程或者创建失败,休眠1s后退出循环,让父进程先执行,完成初始化,并且保证子进程中不会再fork出新的子进程
	   if(pid <= (pid_t) 0)
	   {
		   /* child process or error*/
	           sleep(1); /* make childs faster */
		   break;
	   }
  }

  // 如果创建子进程失败,那么打印fork失败,直接退出程序,返回码为3,表示fork失败
  if( pid< (pid_t) 0)
  {
          fprintf(stderr,"problems forking worker no. %d\n",i);
	  perror("fork failed.");
	  return 3;
  }

  // 在子进程中
  if(pid== (pid_t) 0)
  {
    /* I am a child */
    // 如果不使用代理服务器,那么子进程直接对目标服务器发出http请求,否则向代理服务器发出http请求
    if(proxyhost==NULL)
      benchcore(host,proxyport,request);
         else
      benchcore(proxyhost,proxyport,request);

         /* write results to pipe */
    // 获取管道写端的文件指针
	 f=fdopen(mypipe[1],"w");
    // 获取失败,直接退出,返回码为3,表示内部错误
	 if(f==NULL)
	 {
		 perror("open pipe for writing failed.");
		 return 3;
	 }
	 /* fprintf(stderr,"Child - %d %d\n",speed,failed); */
    // 将子进程的传输速率,测试失败数,总传输字节数写入管道,关闭写端
	 fprintf(f,"%d %d %d\n",speed,failed,bytes);
	 fclose(f);
    // 返回0,表示运行成功
	 return 0;
  } else // 父进程中
  {
     // 获取管道读端的指针
	  f=fdopen(mypipe[0],"r");
     // 获取失败,直接退出,返回码为3,表示内部错误
	  if(f==NULL) 
	  {
		  perror("open pipe for reading failed.");
		  return 3;
	  }
     // 设置不使用缓冲。每个I/O操作都被即时写入管道
	  setvbuf(f,NULL,_IONBF,0);
     // 初始化传输速率,测试失败次数,传输总字节数都为0
	  speed=0;
          failed=0;
          bytes=0;

     // 父进程循环读取数据 
	  while(1)
	  {
        // 循环从管道中每3个一组读取子进程的输出数据,并且获取成功读取的参数个数
		  pid=fscanf(f,"%d %d %d",&i,&j,&k);
        // 如果成功读取的个数小于2,说明有子进程中途挂掉,直接退出读取循环
        //把每个子进程输出的三个数据读取,在父进程中统计
		  if(pid<2)
                  {
                       fprintf(stderr,"Some of our childrens died.\n");
                       break;
                  }
        // 否则更新传输速率(测试成功个数),测试失败次数,传输总字节数
       //父进程中speed,filed,bytes三个变量对clients中的数据进行统计综合
       //父子进程遵循读时共享,写时复制,分析时请注意下
		  speed+=i;
		  failed+=j;
		  bytes+=k;
		  /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
        // 客户端数减一后如果等于0,说明没有多的客户端数据读取,直接退出循环
		  if(--clients==0) 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;
}

benchcore

void benchcore(const char *host,const int port,const char *req)
{
 int rlen;
 char buf[1500];
 int s,i;
 struct sigaction sa;

 /* setup alarm signal handler */
 // 设置SIGALRM的信号处理函数
 sa.sa_handler=alarm_handler;
 sa.sa_flags=0;
 if(sigaction(SIGALRM,&sa,NULL))
    exit(3);
 // 设置计时器时间为运行测试的时间,到期后发送SIGALRM信号
 alarm(benchtime);
 
 // 获取请求报文大小
 rlen=strlen(req);
 // 进入循环,每次客户端建立一个连接,计时器时间到期后再退出
 nexttry:while(1)
 {
    // 如果timerexpired等于1,说明收到了SIGALRM信号,表示计时器到期了,直接返回
    if(timerexpired)
    {
       // 如果失败的测试数大于0,那么失败的测试数减一
       if(failed>0)
       {
          /* fprintf(stderr,"Correcting failed by signal\n"); */
          failed--;
       }
       return;
    }
    // 建立与目标服务器的TCP连接
    s=Socket(host,port);
    // 如果连接失败,测试的失败数加一,继续循环                          
    if(s<0) { failed++;continue;} 
    // 如果请求报文写入套接字失败,测试的失败数加一,继续循环
    if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}
    // 如果使用HTTP/0.9协议,因为会在服务器回复后自动断开连接,所以可以先关闭写端
    if(http10==0) 
       // 如果写端关闭失败,那么说明是不正常的连接状态,测试的失败数加一,关闭连接,继续循环
	    if(shutdown(s,1)) { failed++;close(s);continue;}
    // 如果设置需要等待服务器响应,那么还要处理响应数据,否则直接关闭连接
    if(force==0) 
    {
            /* read all available data from socket */
       // 从套接字中读取数据
	    while(1)
	    {
              // 如果计时器到期,结束读取
              if(timerexpired) break; 
         // 将数据读取进buf中
	      i=read(s,buf,1500);
              /* fprintf(stderr,"%d\n",i); */
         // 如果读取失败,测试的失败数加一,关闭连接,继续循环,客户端重新建立连接,直到计时器到期后再退出
	      if(i<0) 
              { 
                 failed++;
                 close(s);
                 goto nexttry;
              }
	       else
             // 如果已经读取到文件末尾,结束读取数据
		       if(i==0) break;
             // 如果读取到了数据,将总共传送的字节数加上读取到的数据的字节数
		       else
			       bytes+=i;
	    }
    }
    // 关闭连接,如果失败,测试失败数加一,继续循环
    if(close(s)) {failed++;continue;}
    // 传输速率(这里用测试成功数表示,后面除以时间得到真正的传输速率)加一
    speed++;
 }
}

socket.c


/* 
 * 建立与目标的TCP连接,返回客户端连接使用的套接字
 * host: 目标域名或主机名
 * clientPort: 目标端口
 */
int Socket(const char *host, int clientPort)
{
    int sock;               // 客户端套接字标识符
    unsigned long inaddr;   // 主机名的IP地址的数字形式
    struct sockaddr_in ad;  // 套接字地址结构
    struct hostent *hp;     // 域名IP地址
    
    // 初始化目标套接字的地址,指定使用的IP协议为IPv4
    memset(&ad, 0, sizeof(ad));
    ad.sin_family = AF_INET;

    // 将目标主机名的IP地址转换为数字
    inaddr = inet_addr(host);
    // 如果返回值不为INADDR_NONE,说明不是无效的IP地址,设置目标套接字的IP地址
    if (inaddr != INADDR_NONE)
        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    // 否则说明是无效的IP地址,host不是主机名而是域名
    else
    {
        // 通过域名获取IP地址
        hp = gethostbyname(host);
        // 如果获取失败。返回-1
        if (hp == NULL)
            return -1;
        // 设置目标套接字的IP地址
        memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    }
    // 设置目标套接字的端口号
    ad.sin_port = htons(clientPort);
    
    // 创建一个使用TCP协议的socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    // 如果创建失败,直接返回
    if (sock < 0)
        return sock;
    // 进行连接,如果连接不成功,返回-1
    if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
        return -1;
    // 如果连接成功,返回sock
    return sock;
}

你可能感兴趣的:(linux,c,进程通信,网络通信)