webbench源码阅读

[C] webbench源码阅读

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

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

webbench的网络操作

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

#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

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 <sec>          Run benchmark for <sec> seconds. Default 30.
  -p|--proxy <server:port> Use proxy server for request.
  -c|--clients <n>         Run <n> 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说明自己是子进程。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容