[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的参数有三类
- 域名,应该可以放在任何地方
- 后面不需要跟值的一类参数,比如这里的—get等
- 后面需要跟一个值的参数,比如-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变量
fork
和getpid
方法不多介绍,fork返回<=0说明创建子进程失败,getpid如果是0说明自己是子进程。