webbench源码阅读

[C] webbench源码阅读

毕业以后已经很少系统的去读源码了,个人觉得C语言依然是学习Linux和操作系统非常好的工具,其语法简单,操作内存较为直观,想借着读C源码的机会了解一下网路协议/Linux后台开发的一些东西,我认为这些都是作为一个全栈开发必备的技能。从webbench的源码中我们可以学习以下知识

  • Socket和Tcp连接
  • 命令行参数解析
  • Http协议
  • 管道和进程间通信
  • fork方法
  • 大量的字符串处理方法

webbench的网络操作

webbench使用C原生的socket进行tcp的连接, 文件socket.c,都是一些基础操作

#include 
#include 
#include 
#include 
#include 

int Socket(const char *host, int clientPort)
{
    // 目的: 构造sock
    int sock;
    unsigned long inaddr;
    /*
    struct sockaddr_in {
        __uint8_t       sin_len;
        sa_family_t     sin_family; //必填
        in_port_t       sin_port;   //必填
        struct  in_addr sin_addr;   //必填
        char            sin_zero[8];
    };
    */
    struct sockaddr_in ad; // in.h

    // struct hostent {
    //     char *h_name;    /* official name of host */
    //     char **h_aliases;    /* alias list */
    //     int  h_addrtype; /* host address type */
    //     int  h_length;   /* length of address */
    //     char **h_addr_list;  /* list of addresses from name server */
    // #if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
    // #define  h_addr  h_addr_list[0]  /* address, for backward compatibility */
    // #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
    // };
    struct hostent *hp; // netdb.h 包含了需要解析出来的hostname, 和cname等信息

    memset(&ad, 0, sizeof(ad)); // string.h
    ad.sin_family = AF_INET;

    inaddr = inet_addr(host); // inet.h头文件中,得到host对应的unsigned long整型值,进而判断是不是域名
    // printf("inaddr:%lu\n", inaddr);
    // 非域名
    if (inaddr != INADDR_NONE){
        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    }
    // 域名
    else
    {
        hp = gethostbyname(host);
        // printf("hp{\n\th_name:%s\n\th_aliases:%s\n\th_addr:%s\n}\n", hp->h_name, hp->h_aliases[0], hp->h_addr);
        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;
        // sockaddr_in到sockaddr转换
    if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
        return -1;
    return sock;
}

参数解析

webbench [option]... URL
  -f|--force               Don't wait for reply from server.
  -r|--reload              Send reload request - Pragma: no-cache.
  -t|--time           Run benchmark for  seconds. Default 30.
  -p|--proxy  Use proxy server for request.
  -c|--clients          Run  HTTP clients at once. Default one.
  -9|--http09              Use HTTP/0.9 style requests.
  -1|--http10              Use HTTP/1.0 protocol.
  -2|--http11              Use HTTP/1.1 protocol.
  --get                    Use GET request method.
  --head                   Use HEAD request method.
  --options                Use OPTIONS request method.
  --trace                  Use TRACE request method.
  -?|-h|--help             This information.
  -V|--version             Display program version.

webbench的参数有三类

  1. 域名,应该可以放在任何地方
  2. 后面不需要跟值的一类参数,比如这里的—get等
  3. 后面需要跟一个值的参数,比如-t -p -c
    C有getopt.h用来处理命令行参数,不管是单短线的短参数还是双短线的长参数,处理的方式如下。
static const struct option long_options[]=
{
 // 参数0为长参数,参数NF为短参数
 {"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}
};

此处定义的是长短参数映射的规则,—version对应着-V,第二个参数表示是不是必须的参数,第三个参数表示要赋值给谁,第四个参数,如果第三个参数不是空指针,就把第四个值set给第三个指针,如果第三个参数是空指针,就返回这个值,进行下一步解析,option的定义

struct option {
    /* name of long option */
    const char *name;
    /*
     * one of no_argument, required_argument, and optional_argument:
     * whether option takes an argument
     */
    int has_arg;
    /* if not NULL, set *flag to val when option found */
    int *flag;
    /* if flag not NULL, value to set *flag to; else return value */
    int val;
};

有了长短参数的对应关系,就可以解析这些参数了,getopt_long方法前两个参数分别是参数个数和参数char**列表,第三个参数如果后面不跟:,说明这是一个第二类参数,如果有:则说明这是一个第三类参数,第四个参数是刚才的长短参数对应,第五个参数默认指向一个值为零的位置。

while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
 {
  switch(opt)
  {

   case  0 : break;
   case 'f': force=1;break;
   case 'r': force_reload=1;break;
   case '9': http10=0;break;
   case '1': http10=1;break;
   case '2': http10=2;break;
   case 'V': printf(PROGRAM_VERSION"\n");exit(0);
   case 't': benchtime=atoi(optarg);break;
   case 'p':
         /* proxy server parsing server:port */
        // 解析每个case时,optarg 为当前分支的参数
        // strrchr获取第一次出现:的位置,后面的为port
        printf("optarg: %s\n", optarg);
         tmp=strrchr(optarg,':');
        printf("tmp: %s\n", tmp);
         proxyhost=optarg;
         if(tmp==NULL)
         {
             break;
         }
         if(tmp==optarg)
         {
             fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
             return 2;
         }
         if(tmp==optarg+strlen(optarg)-1)
         {
             fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
             return 2;
         }
         *tmp='\0';
         proxyport=atoi(tmp+1);break;
   case ':':
   case 'h':
   case '?': usage();return 2;break;
   case 'c': clients=atoi(optarg);break;
  }
 }

那么可以放在任何一个位置的参数如何解析呢?
在刚才这段switch-case逻辑完成以后我们就要判断了,我解析到的参数是不是已经到argc到最后一个了,如果到了,那不对啊,还有一个参数没解析,getopt.h中的optint就是记录这个的,如果optint==argc,那么说明少了一个host参数。正常的应该是optint应该比argc少小1,所以剩下那个没有解析的域名参数就应该是argv[optintd]

if(optind==argc) {fprintf(stderr,”webbench: Missing URL!\n”);}

发起Http请求

Http请求是基于Tcp的,只要满足Http协议都可以运行,Http的文本如下(会根据参数的不同略有不同,这里不多说)

Req=GET / HTTP/1.0/
User-Agent: WebBench 1.5/
Host: www.baidu.com/

多进程

Webbench是用多进程来不断进行Http请求的,这其中涉及到一些知识,例如

  • fork方法
  • getpid方法
  • 信号处理::important:: [C] signal信号
  • 进程间通信(管道)::important:: [C] pipe管道
  • 静态变量共享
  • volatile变量
    forkgetpid方法不多介绍,fork返回<=0说明创建子进程失败,getpid如果是0说明自己是子进程。

你可能感兴趣的:(webbench源码阅读)