• C 网页压力测试器


    引言

      <<独白>> 席慕蓉 节选一

      

           把向你借来的笔还给你吧。

           一切都发生在回首的刹那。

          我的彻悟如果是缘自一种迷乱,那么,我的种种迷乱不也就只是因为一种彻悟?

          在一回首间,才忽然发现,原来,我的一生的种种努力,不过只是为了周遭的人都对我满意而已。

    为了要博得他人的称许与微笑,我战战兢兢地将自己套入所有的模式,所有的桎梏。

    走到中途,才忽然发现,我只剩下一副模糊的面目,和一条不能回头的路。

         把向你借来的笔还给你吧。

    配乐 :

      我在长大 : 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)!='' && c!='/'){
                if(c<='9' && c>='0'){
                    pt = 10*pt + c - '0';
                    continue;
                }
                CERR_EXIT("url is error [%s].", url);        
            }
        }    
        else{
            pt = 80; //默认端口
            //这句话意思 是 将 www.baidu.com/ 其中/之前的地方写入到 ip[]中
            strncpy(__ips.ip, hurl, strcspn(hurl, "/"));    
        }
        __ips.port = pt;    
        
        //将这些内容链接到rqs中
        strcat(rqs, hurl + strcspn(hurl, "/"));
        //采用HTTP1.1 协议
        strcat(rqs, " HTTP/1.1
    ");
        //添加请求方
        strcat(rqs, "User-Agent: Happy is good.
    ");
        //上面这样解析有点慢,最快的是整体for,但是更难维护
        strcat(rqs, "Host: ");
        strcat(rqs, __ips.ip);
        strcat(rqs, "
    ");
        //拼接串最后,连接状态,完毕就关闭
        strcat(rqs, "Connection: close
    ");
        //拼接空行, 上面可以优化
        strcat(rqs, "
    ");
    
        //最终检测,是否字符串太长了
        if(rqs[len-1])
            CERR_EXIT("get http too len!");
    
        return &__ips;            
    }

    主要思路是先检查 参数, 看 是否是 右边这样正则格式的串  "http.://.+/"

    后面拼接结果截图如下:

    更加详细关于HTTP协议解析的,可以参照我前面的连接,HTTP协议简单解析.

    后面介绍的是有了请求串,怎么发送请求和处理请求,代码如下:

    /*
     * 执行的主要方法,结果会通过写管道 wfd写入
     */
    void 
    bcore(struct iport* ips, const char* req, int wfd)
    {
        int srv, len, rlen;
        int speed = 0, failed = 0, bytes = 0;
        FILE* fx;
        char buf[_INT_REQ];    
    
        //先注册信号
        if(SIG_ERR == signal(SIGALRM, __alarm))
            CERR_EXIT("signal SIGALRM error!");
        alarm(_INT_END);
        
        len = strlen(req);    
        for(;;){
            if(v_timer){ //结束之前减一,认为成功过
                if(failed>0)
                    --failed;
                break;
            }        
    
            srv = contweb(ips->ip, ips->port);
            if(srv < 0){
                ++failed;
                continue;    
            }
            //开始写入数据
            if(len != write(srv, req, len)){
                ++failed;
                close(srv);
                continue;
            }
            //下面读取数据,v_timer标志是否超时,超时直接走
            for(;!v_timer;){
                rlen = read(srv, buf, _INT_REQ);                
                if(rlen < 0){ //不考虑软中断了,有问题直接退出
                    ++failed;
                    close(srv);
                    goto __bcore_exit; //退出结算
                }
                if(rlen == 0)//服务端关闭,结束读取
                    break;
                bytes += rlen;
            }
            close(srv);
            ++speed;
        }    
        
    __bcore_exit:
        //管道写入
        fx = fdopen(wfd, "w");
        if(NULL == fx)    
            CERR_EXIT("fdopen wfd:%d error!", wfd);
        fprintf(fx,"%d %d %d
    ", speed, failed, bytes);    
        fclose(fx);
    }

    中间发送了一个alarm延时发送SIGALRM 信号,还有一个信号容易混淆,是SIGCLD信号 子进程退出时候给父进程发送.

    其中 fdopen 是将 Linux文件描述符和 C系统文件操作句柄转换.就是统计数据写入到管道中,管道 是 1进 0出.

    最后还有个 开启多进程的函数代码

    //请求主方法,客户端,返回开启子进程数
    int 
    bench(int cut, const char* url, int wfd)
    {
        int i, rt = 0;//rt 记录正常开启的进程
        pid_t pid;
        char req[_INT_REQ];
        struct iport* ips;
    
        //这里初始化一些环境
        ips = brequst(url, req, sizeof req);    
        puts("***********-----------------------------------------------------------***********");
        puts(req); //打印数据
        puts("***********-----------------------------------------------------------***********");
        
        for(i=0; i<cut; ++i){
            pid = fork(); //狂开进程了
            if(pid <= 0){
                sleep(1);//别让它太快完毕,父进程来不及收尸
                break;//结束子进程继续
            }
            ++rt;
        }        
        if(pid < 0)
            CERR_EXIT("child %d process create error!", i);
        if(pid == 0){ //子进程处理
            bcore(ips, req, wfd);
            exit(EXIT_SUCCESS);//子进程这里就结束了
        }
        //下面是父进程处理
        return rt;                    
    }

    也比较好明白,先拼接请求串,再开启多进程挨个请求处理.

    到这核心函数基本都完成了,总的业务构建请看下面

    第三部分主函数逻辑构建

    // 主函数业务,从这开始
    int main(int argc, char* argv[])
    {
        int cut, len; //开启进程数量 和 读取scanf返回值
        int pfds[2];    
        FILE* fx;
        int speed = 0, failed = 0, bytes = 0;
    
        //简单检测    
        if((argc != 3) || (cut = atoi(argv[1]))<=0 || cut > USHRT_MAX){        
            CERR_EXIT(
                 "
    uage:   ./webtest.out [cut] [url]"
                "
        :=> ./webtest.out 19 http://www.baidu.com/"
            );
        }    
        
        //创建管道 进行测试, 代码比较杂,需要一些Linux基础知识,多练习
        if(pipe(pfds)<-1)
            CERR_EXIT("pipe create is error!");
        
        //结果处理,开启指定多个进程,重新获取进程开启成功数
        cut = bench(cut, argv[2], pfds[1]);    
        
        //这里读取管道信息
        fx = fdopen(pfds[0], "r");
        if(NULL == fx)
            CERR_EXIT("fdopen pfds[0]:%d error.", pfds[0]);
        
        //统计最后数据
        setbuf(fx, NULL);
        while(cut > 0){
            int s, f, b;
            len = fscanf(fx, "%d %d %d", &s, &f, &b);    
            if(len < 3){
                CERR("fscnaf read error! len:%d.", len);
                break;
            }
            speed += s;
            failed += f;
            bytes += b;            
            --cut;        
        }
        fclose(fx);
        close(pfds[0]);
        close(pfds[1]);
    
        //输出统计结果
        puts("***********-----------------------------------------------------------***********");
        printf("Collect %.2f pages/min, %.2f kb/sec. Request %d sucess, %d failed.
    ",
            (speed+failed)*60.0f/_INT_END, bytes/1024.0f/_INT_END, speed, failed);
        puts("***********-----------------------------------------------------------***********");
        
        return 0;
    }

    创建管道,调用多进程处理函数,汇总最后结果.注意一点是开启的管道最后需要自己关闭.(可能这里有坑),回头看一下,代码其实

    还是比较基础的,主要用的就是Linux编程相关的知识点.

    最后输出 每分钟请求的页面数, 每秒请求的bit流,请求成功多少次,请求失败多少次.

    4.看案例结果 

    首选看下面一个操作图,先请求百度试试

    最后统计结果是 每分钟请求了6357个界面左右,开启了23个子进程, 成功2414个,23个错误. 总的而言百度的分流确实不错,负载均衡强.

    再来请求一个普通网站的截图

    上面打印很多connect error,意思是很多进程请求链接失败,短时间大量链接导致对方服务器拒绝链接, 负载均衡差,

    流量差不多,可能和网速有关,请求界面数挺多的这个界面不错,可能是纯静态的. 对于详细查看为什么失败,可以在记录 failed++的时候打印日志看看,

    这里就这样了. 麻雀虽小,能用就好. 完整测试 demo 如下

    webtest.c

    #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);
    
    // 主函数业务,从这开始
    int main(int argc, char* argv[])
    {
        int cut, len; //开启进程数量 和 读取scanf返回值
        int pfds[2];    
        FILE* fx;
        int speed = 0, failed = 0, bytes = 0;
    
        //简单检测    
        if((argc != 3) || (cut = atoi(argv[1]))<=0 || cut > USHRT_MAX){        
            CERR_EXIT(
                 "
    uage:   ./webtest.out [cut] [url]"
                "
        :=> ./webtest.out 19 http://www.baidu.com/"
            );
        }    
        
        //创建管道 进行测试, 代码比较杂,需要一些Linux基础知识,多练习
        if(pipe(pfds)<-1)
            CERR_EXIT("pipe create is error!");
        
        //结果处理,开启指定多个进程,重新获取进程开启成功数
        cut = bench(cut, argv[2], pfds[1]);    
        
        //这里读取管道信息
        fx = fdopen(pfds[0], "r");
        if(NULL == fx)
            CERR_EXIT("fdopen pfds[0]:%d error.", pfds[0]);
        
        //统计最后数据
        setbuf(fx, NULL);
        while(cut > 0){
            int s, f, b;
            len = fscanf(fx, "%d %d %d", &s, &f, &b);    
            if(len < 3){
                CERR("fscnaf read error! len:%d.", len);
                break;
            }
            speed += s;
            failed += f;
            bytes += b;            
            --cut;        
        }
        fclose(fx);
        close(pfds[0]);
        close(pfds[1]);
    
        //输出统计结果
        puts("***********-----------------------------------------------------------***********");
        printf("Collect %.2f pages/min, %.2f kb/sec. Request %d sucess, %d failed.
    ",
            (speed+failed)*60.0f/_INT_END, bytes/1024.0f/_INT_END, speed, failed);
        puts("***********-----------------------------------------------------------***********");
        
        return 0;
    }
    
    /*
     * 请求链接主机,根据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;    
    }
    
    //通过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)!='' && c!='/'){
                if(c<='9' && c>='0'){
                    pt = 10*pt + c - '0';
                    continue;
                }
                CERR_EXIT("url is error [%s].", url);        
            }
        }    
        else{
            pt = 80; //默认端口
            //这句话意思 是 将 www.baidu.com/ 其中/之前的地方写入到 ip[]中
            strncpy(__ips.ip, hurl, strcspn(hurl, "/"));    
        }
        __ips.port = pt;    
        
        //将这些内容链接到rqs中
        strcat(rqs, hurl + strcspn(hurl, "/"));
        //采用HTTP1.1 协议
        strcat(rqs, " HTTP/1.1
    ");
        //添加请求方
        strcat(rqs, "User-Agent: Happy is good.
    ");
        //上面这样解析有点慢,最快的是整体for,但是更难维护
        strcat(rqs, "Host: ");
        strcat(rqs, __ips.ip);
        strcat(rqs, "
    ");
        //拼接串最后,连接状态,完毕就关闭
        strcat(rqs, "Connection: close
    ");
        //拼接空行, 上面可以优化
        strcat(rqs, "
    ");
    
        //最终检测,是否字符串太长了
        if(rqs[len-1])
            CERR_EXIT("get http too len!");
    
        return &__ips;            
    }
    
    /*
     * 执行的主要方法,结果会通过写管道 wfd写入
     */
    void 
    bcore(struct iport* ips, const char* req, int wfd)
    {
        int srv, len, rlen;
        int speed = 0, failed = 0, bytes = 0;
        FILE* fx;
        char buf[_INT_REQ];    
    
        //先注册信号
        if(SIG_ERR == signal(SIGALRM, __alarm))
            CERR_EXIT("signal SIGALRM error!");
        alarm(_INT_END);
        
        len = strlen(req);    
        for(;;){
            if(v_timer){ //结束之前减一,认为成功过
                if(failed>0)
                    --failed;
                break;
            }        
    
            srv = contweb(ips->ip, ips->port);
            if(srv < 0){
                ++failed;
                continue;    
            }
            //开始写入数据
            if(len != write(srv, req, len)){
                ++failed;
                close(srv);
                continue;
            }
            //下面读取数据,v_timer标志是否超时,超时直接走
            for(;!v_timer;){
                rlen = read(srv, buf, _INT_REQ);                
                if(rlen < 0){ //不考虑软中断了,有问题直接退出
                    ++failed;
                    close(srv);
                    goto __bcore_exit; //退出结算
                }
                if(rlen == 0)//服务端关闭,结束读取
                    break;
                bytes += rlen;
            }
            close(srv);
            ++speed;
        }    
        
    __bcore_exit:
        //管道写入
        fx = fdopen(wfd, "w");
        if(NULL == fx)    
            CERR_EXIT("fdopen wfd:%d error!", wfd);
        fprintf(fx,"%d %d %d
    ", speed, failed, bytes);    
        fclose(fx);
    }
    
    //请求主方法,客户端,返回开启子进程数
    int 
    bench(int cut, const char* url, int wfd)
    {
        int i, rt = 0;//rt 记录正常开启的进程
        pid_t pid;
        char req[_INT_REQ];
        struct iport* ips;
    
        //这里初始化一些环境
        ips = brequst(url, req, sizeof req);    
        puts("***********-----------------------------------------------------------***********");
        puts(req); //打印数据
        puts("***********-----------------------------------------------------------***********");
        
        for(i=0; i<cut; ++i){
            pid = fork(); //狂开进程了
            if(pid <= 0){
                sleep(1);//别让它太快完毕,父进程来不及收尸
                break;//结束子进程继续
            }
            ++rt;
        }        
        if(pid < 0)
            CERR_EXIT("child %d process create error!", i);
        if(pid == 0){ //子进程处理
            bcore(ips, req, wfd);
            exit(EXIT_SUCCESS);//子进程这里就结束了
        }
        //下面是父进程处理
        return rt;                    
    }

    编译代码如下

    gcc -Wall -o webtest.out webtest.c

    后面就需要大家尝试了,用的还凑合者.

    光鲜的背后都是平凡.喜欢才是人这台机器的最好能源.

    后记

      错误是难免,欢迎指正交流,共同消磨有趣的时光. ( ^_^ )/~~拜拜

  • 相关阅读:
    新的页面事件的添加
    excel里数字0不显示
    VB6接口、对象比较等
    SQL SERVER服务停止和启动命令行
    如何更改VS2008的字体和大小
    如何部署windows service
    模式应用 - 利用工厂模式制作自己的"小程序测试工厂"
    解决Oracle Temp01.dbf不断变大的问题
    如何配置VS2008让它用外置IIS进行调试
    使用JQuery Autocomplete插件(一)
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5146515.html
Copyright © 2020-2023  润新知