引言
<<独白>> 席慕蓉 节选一
把向你借来的笔还给你吧。
一切都发生在回首的刹那。
我的彻悟如果是缘自一种迷乱,那么,我的种种迷乱不也就只是因为一种彻悟?
在一回首间,才忽然发现,原来,我的一生的种种努力,不过只是为了周遭的人都对我满意而已。
为了要博得他人的称许与微笑,我战战兢兢地将自己套入所有的模式,所有的桎梏。
走到中途,才忽然发现,我只剩下一副模糊的面目,和一条不能回头的路。
把向你借来的笔还给你吧。
配乐 :
我在长大 : http://music.163.com/#/song?id=155977
不知疲倦的整夜码代码 像块石头一样滚来滚去
可是找不到家的方向
我在长大 / 我在长大 / 我在长大 / 我在长大
前言
我们先说一下,参照的东西和需要掌握的东西
具体参照
1. HTTP协议简单介绍 http://www.cnblogs.com/biyeymyhjob/archive/2012/07/28/2612910.html
这里需要掌握简单的Linux 操作,这里分享一个自己总结的socket入门基础文档.
2. socket 简单入门doc http://download.csdn.net/detail/wangzhione/9412538
到这里, 基本准备工作就完成了,那就搞起吧, 看看那些说起来很玄乎的工具是怎么简单实现的.顺带扯一点,
下面代码自己多看几遍写一遍,可以帮助你理解Linux中很多基础概念,例如 信号,多进程, 管道,socket,中断,
协议等.
正文
1.简单说一点格式
这里我们先看下面一段代码格式,来了解C接口设计中一些潜规则.
/* * 这是一个对外的接口函数声明, *相当于public */ extern int isbig(void); /* * 这个接口也具备对外能力,但是省略了extern *意图就是构建整个 *.h文件的时候需要用它,但是不希望用户用. *属于 low level api, 内部接口集用,外部不推荐用,有点像protected */ int heoo(void); /* * 这个是个内部声明或定义, 只能在当前接口集的内部使用,外部无法 *访问,相当于pirate */ static void* __run(void* arg);
定义部分简单说明一下.以前博文中说过,这里再简单说一次,编程和打游戏一样,要有好节奏,
要有好套路,这样打起来才顺.等同于实践可以总结出理论基础.前任要感谢.
/* * 定义加声明,放在一块 */ static int __run(void) { static union { unsigned short _s; unsigned char _cs[sizeof(unsigned short)]; } __ut = { 1 }; return __ut._cs[0] == 0; } /* * 声明和定义分开 */ extern void start(void); //下面是定义部分 void start(void) { puts("你好!"); }
套路很多,都是历代编程王者,开创的各大武功的起手式,各大定式都可以就看你想拜入那个门派,开始这个编程网游之旅.
2.说思路
经过上面简单格式介绍,这里就说一下思路. 关于 这篇博文,C 对网页的压力测试构建的思路.具体如下:
a. 拼接请求报文 , 解析
b. 发送请求报文 , 请求
c. 处理请求报文, 处理
d.总结请求报文, 输出统计
上面就是这次处理的主要流程, 多次请求采用的fork多个子进程
for(i=0; i<cut; ++i){ pid = fork(); //狂开进程了 if(pid <= 0){ sleep(1);//别让它太快完毕,父进程来不及收尸 break;//结束子进程继续 } ++rt; }
关于父子进程通信采用 管道
//创建管道 进行测试, 代码比较杂,需要一些Linux基础知识,多练习 if(pipe(pfds)<-1) CERR_EXIT("pipe create is error!");
还有一个是通过信号处理超时问题
//默认全局标识, 控制开关 volatile int v_timer = 0; //闹钟信号,当它启动那一刻就表示可以退下了. static void __alarm(int sig) { v_timer = 1; }
后面我们就通过代码来说了,最好你写一遍,就明白容易了.扯一点,关于volatile 可以声明是为了方式编译器优化,导致代码死循环.
3.逐个实现
第一部分头文件,声明解析
首先观察下面前戏代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <stdarg.h> #include <limits.h> #include <unistd.h> #include <fcntl.h> #include <netdb.h> #include <signal.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //控制台打印错误信息, fmt必须是双引号括起来的宏 #ifndef CERR #define CERR(fmt, ...) fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt " ", __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) //检测并退出的宏 #define CERR_EXIT(fmt, ...) CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE) #endif/* !CERR */ // url长度限制 和 请求串长度限制 #define _INT_URL (1024) #define _INT_REQ (2048) //主机地址的最大长度 #define _INT_PORT (255) //等待时间,在60s后就不在处理了 #define _INT_END (23) //简单结构 struct iport{ short port; char ip[_INT_PORT]; }; //默认全局标识, 控制开关 volatile int v_timer = 0; //闹钟信号,当它启动那一刻就表示可以退下了. static void __alarm(int sig) { v_timer = 1; } /* * 执行的主要方法,结果会通过写管道 wfd写入 */ void bcore(struct iport* ips, const char* req, int wfd); /* * 只接受完整请求 * 例如 * brequest("http://192.168.1.162:80/", ...) 或 http://www.baidu,com * 通过url拼接请求串,并返回访问结构 */ struct iport* brequst(const char* url, char rqs[], int len); //请求主方法,客户端,返回开启的子进程数 int bench(int cut, const char* url, int wfd); /* * 请求链接主机,根据ip或url * * host : url地址 * port : 请求的端口号 */ int contweb(const char* host, int port);
CERR系列是一个帮助宏,简单打印错误信息. 技巧值得推荐使用.其中 _INT_END 表示每个子进程请求的时间长度(s).
感觉注释也挺详细的关于
//请求主方法,客户端,返回开启的子进程数 int bench(int cut, const char* url, int wfd);
主要开启多进程的,依次调用 breques解析url 和 调用 bcore 处理socket .
其中 contweb 是一个可以根据ip或主机地址url解析,链接主机.具体实现如下
/* * 请求链接主机,根据ip或url * * host : url地址 * port : 请求的端口号 */ int contweb(const char* host, int port) { int sd; in_addr_t ia; struct sockaddr_in sa = { AF_INET }; struct hostent *hp; ia = inet_addr(host); if(ia != INADDR_NONE) memcpy(&sa.sin_addr, &ia, sizeof(ia)); else { hp = gethostbyname(host); if(hp < 0){ CERR("gethostbyname %s error!", host); return -1; } memcpy(&sa.sin_addr, hp->h_addr, hp->h_length); } if((sd=socket(PF_INET, SOCK_STREAM, 0))<0){ CERR("socket SOCK_STREAM"); return sd; } //连接主机 sa.sin_port = htons(port); if(connect(sd, (struct sockaddr*)&sa, sizeof sa)<0){ CERR("connect sd:%d error!", sd); return -1; } return sd; }
没有什么花哨的东西就是同步链接HTTP服务器, 如果传入的的host是url那么就调用 gethostbyname 获取到 请求
主机的sin_addr地址量.
扯一点,socket编程大部分也等同于业务代码,固定的套路固定的模式,写写也都会写了.但也挺麻烦了,随便一个简单的
完整的socket demo都4-5百行代码,写的手疼.封装也很麻烦.但是你理解了它的思路或套路,还是很容易上马提枪的.
第二部分各个,定义解析
首先代码可以优化,我写好后没优化了,就直接分享了.先看http协议解析代码如下
//通过url拼接请求串 struct iport* brequst(const char* url, char rqs[], int len) { static struct iport __ips; //请求的主机, 中间用的url, 临时数据 const char *hurl, *tmp; char c; int pt = 0;//临时用的int变量 //简单检查参数检查,检查数组大小,url是否合法 if(len<_INT_REQ || !url || !*url || strlen(url)>_INT_URL) CERR_EXIT("params url:%s, len[>=%d];%d error.", url, _INT_REQ, len); //检测url是否是http请求的url,也比较简单,复杂检查要用正则表达式 hurl = strstr(url, "://"); if(!hurl || strncasecmp(url, "http", 4)) CERR_EXIT("url is err [%s]!", url); //简单初始化 memset(rqs, 0, len); memset(&__ips, 0, sizeof __ips); //这种代码可以优化,用指针替代数组索引 strcat(rqs, "GET "); hurl += 3; //跳过 "://"指向下一个字符 //解析url 上是否有端口信息 tmp = index(hurl, ':'); if(tmp && tmp < index(hurl, '/')){ strncpy(__ips.ip, hurl, tmp - hurl); //没有做安全检查 while((c=*++tmp)!='