zoukankan      html  css  js  c++  java
  • 跟着webbench学习C++网络编程(二)

    跟着webbench学习C++网络编程(二)

    ​ webbench.c是最主要的文件,里面的结构大概如下:

    我会用在代码中注释的方式来记录学习的过程:

    /*
     * (C) Radim Kolar 1997-2004
     * This is free software, see GNU Public License version 2 for
     * details.
     *
     * Simple forking WWW Server benchmark:
     *
     * Usage:
     *   webbench --help
     *
     * Return codes:
     *    0 - sucess 成功
     *    1 - benchmark failed (server is not on-line)
     *    2 - bad param  参数错误
     *    3 - internal error, fork failed   内部错误,创建进程失败
     *
     */
    #include "socket.c"
    #include <unistd.h>         
    #include <sys/param.h>
    #include <rpc/types.h>
    #include <getopt.h>             // getopt_long
    #include <strings.h>
    #include <time.h>
    #include <signal.h>
    
     /* values */
    volatile int timerexpired = 0;
    int speed = 0;
    int failed = 0;
    int bytes = 0;
    
    /* 请求方式 */
    /* Allow: GET, HEAD, OPTIONS, TRACE */
    #define METHOD_GET 0
    #define METHOD_HEAD 1
    #define METHOD_OPTIONS 2
    #define METHOD_TRACE 3
    #define PROGRAM_VERSION "1.5"
    
    /* 相关参数及其默认设置 */
    int http10 = 1;                     /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
    int method = METHOD_GET;            /* GET 方式 */
    int clients = 1;                    /* 只模拟一个客户端 */
    int force = 0;                      /* 等待响应 */
    int force_reload = 0;               /* 失败时重新请求 */
    int proxyport = 80;                 /* 默认访问端口 */
    char* proxyhost = NULL;             /* 代理服务器 */
    int benchtime = 30;                 /* 模拟请求时间 */
    
    /* internal */
    int mypipe[2]; /* 管道 */
    char host[MAXHOSTNAMELEN]; /* 网络地址 */
    #define REQUEST_SIZE 2048
    char request[REQUEST_SIZE]; /* 请求 */
    
    static const struct option long_options[] =
    {
        {"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}
    };
    
    /* prototypes */
    static void benchcore(const char* host, const int port, const char* request);
    static int bench(void);
    static void build_request(const char* url);
    
    static void alarm_handler(int signal)
    {
        timerexpired = 1;
    }
    
    /* 静态函数:输出 help 信息 */
    static void usage(void)
    {
        fprintf(stderr,
            "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.
    "
        );
    };
    
    int main(int argc, char* argv[])
    {
        int opt = 0;
        int options_index = 0;
        char* tmp = NULL;
    
        /* 不带参数时直接输出 help 信息 */
        if (argc == 1)
        {
            usage();
            return 2;
        }
    
        /* getopt_long 为命令行参数解析的库函数,可通过 man 3 getopt_long 查看 */
        /* getopt_long 详解可见https://blog.csdn.net/cashey1991/article/details/7942809 */
        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"
    ");
                exit(0);
            case 't':
                benchtime = atoi(optarg);
                break;
            case 'p':
                /* proxy server parsing server:port */
                /*strrchr 在参数所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置*/
                tmp = strrchr(optarg, ':');
                proxyhost = optarg;
                /* 参数错误,不符合 域名:端口号 的格式*/
                if (tmp == NULL)
                {
                    break;
                }
                /* 没有输入域名参数 */
                if (tmp == optarg)
                {
                    fprintf(stderr, "Error in option --proxy %s: Missing hostname.
    ", optarg);
                    return 2;
                }
                /* 没有输入端口参数 */
                if (tmp == optarg + strlen(optarg) - 1)
                {
                    fprintf(stderr, "Error in option --proxy %s Port number is missing.
    ", optarg);
                    return 2;
                }
                *tmp = '';
                proxyport = atoi(tmp + 1);
                break;
            case ':':
            case 'h':
            case '?': usage(); return 2; break;
            case 'c': clients = atoi(optarg); break;
            }
        }
    
        /* optind 被 getopt_long 设置为命令行参数中未读取的下一个元素下标值 */
        /* agrc表示参数的个数,但其值是比实际参数的个数多一个的,因为argv[0]表示程序运行的全路径名。
        如果未读取的下一个参数为空,则说明没有输入url*/
        if (optind == argc)
        {
            fprintf(stderr, "webbench: Missing URL!
    ");
            usage();
            return 2;
        }
    
        /* 不能指定客户端数和请求时间为 0 */
        if (clients == 0) clients = 1;
        if (benchtime == 0) benchtime = 60;
    
        /* Copyright */
        fprintf(stderr, "Webbench - Simple Web Benchmark "PROGRAM_VERSION"
    "
            "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
    "
        );
    
        /* 构造 HTTP 请求到 request 数组 */
        build_request(argv[optind]);
    
        /* 以下到函数结束为输出提示信息 */
        /* print bench info */
        printf("
    Benchmarking: ");
        switch (method)
        {
        case METHOD_GET:
        default:
            printf("GET"); break;
        case METHOD_OPTIONS:
            printf("OPTIONS"); break;
        case METHOD_HEAD:
            printf("HEAD"); break;
        case METHOD_TRACE:
            printf("TRACE"); break;
        }
    
        printf(" %s", argv[optind]);
        switch (http10)
        {
        case 0: printf(" (using HTTP/0.9)"); break;
        case 2: printf(" (using HTTP/1.1)"); break;
        }
    
        printf("
    ");
    
        if (clients == 1) printf("1 client");
        else
            printf("%d clients", clients);
    
        printf(", running %d sec", benchtime);
        if (force) printf(", early socket close");
        if (proxyhost != NULL) printf(", via proxy server %s:%d", proxyhost, proxyport);
        if (force_reload) printf(", forcing reload");
        printf(".
    ");
    
        /* 开始压力测试,返回 bench 函数执行结果 */
        return bench();
    }
    
    
    //检查url参数,构造请求头
    void build_request(const char* url)
    {
        char tmp[10];       //后面用来暂时存放端口号
        int i;              //存储主机名开始的位置
    
        /* 初始化 */
        /* 此处学习bzero和memset()的区别 */
        bzero(host, MAXHOSTNAMELEN);
        bzero(request, REQUEST_SIZE);
    
        /* 判断应该使用的 HTTP 协议 */
        // HTTP/0.9     GET
        // HTTP/1.0     GET POST HEAD
        // HTTP/1.1     GET POST HEAD PUT PATCH OPTIONS DELETE
        if (force_reload && proxyhost != NULL && http10 < 1) http10 = 1;
        if (method == METHOD_HEAD && http10 < 1) http10 = 1;
        if (method == METHOD_OPTIONS && http10 < 2) http10 = 2;
        if (method == METHOD_TRACE && http10 < 2) http10 = 2;
    
        /*填写 method 方法 */
        switch (method)
        {
        default:
        case METHOD_GET: strcpy(request, "GET"); break;
        case METHOD_HEAD: strcpy(request, "HEAD"); break;
        case METHOD_OPTIONS: strcpy(request, "OPTIONS"); break;
        case METHOD_TRACE: strcpy(request, "TRACE"); break;
        }
    
        strcat(request, " ");
    
        /* URL 合法性判断 */
        /* strstr 查找传入字符串中第一次出现指定子字符串的位置,不包括终止符'' */
        if (NULL == strstr(url, "://"))
        {
            fprintf(stderr, "
    %s: is not a valid URL.
    ", url);
            exit(2);
        }
    
        if (strlen(url) > 1500)
        {
            fprintf(stderr, "URL is too long.
    ");
            exit(2);
        }
    
        if (proxyhost == NULL)
        /*
        表头文件:#include <string.h>
        函数 定义:int strncasecmp(const char *s1, const char *s2, size_t n)
        函数 说明:strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。
        返回值 :若参数s1和s2字符串相同,则返回0; 若s1大于s2,则返回大于0的值; 若s1小于s2,则返回小于0的值
        */ 
            if (0 != strncasecmp("http://", url, 7))
            {
                /* 只支持 HTTP 地址 */
                fprintf(stderr, "
    Only HTTP protocol is directly supported, set --proxy for others.
    ");
                exit(2);
            }
    
    
        /* 找到主机名开始的地方 */
        /* protocol/host delimiter */
        i = strstr(url, "://") - url + 3;
    
        /* url必须以 / 结束*/
        /*  char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 */
        if (strchr(url + i, '/') == NULL)
        {
            fprintf(stderr, "
    Invalid URL syntax - hostname don't ends with '/'.
    ");
            exit(2);
        }
    
        if (proxyhost == NULL)
        {
            /* get port from hostname */
            /* char *index(const char *s, int c) 找出参数s字符串中第一个出现参数c的地址,然后将该字符串出现的地址返回。字符串结束字符(NULL)也视为字符串的一部分。
                如果找到指定的字符,则返回字符所在地址,否则返回NULL*/
            if (index(url + i, ':') != NULL && index(url + i, ':') < index(url + i, '/'))
            {
                strncpy(host, url + i, strchr(url + i, ':') - url - i);
                /* 端口 */
                bzero(tmp, 10);
                strncpy(tmp, index(url + i, ':') + 1, strchr(url + i, '/') - index(url + i, ':') - 1);
                /* 设置端口 */
                proxyport = atoi(tmp);
                if (proxyport == 0)
                    proxyport = 80;
            }
    
            //域名后没有接端口号的情况,只提取域名
            else {
            /* size_t strcspn(const char *str1, const char *str2) 检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。*/
                strncpy(host, url + i, strcspn(url + i, "/"));
            }
    
            //printf("host:%s	port:%d
    ",host,proxyport);
            //这一步是在request头中加上具体路径,比如http://www.baidu.com/test/ 就是 /test/
            strcat(request + strlen(request), url + i + strcspn(url + i, "/"));
    
        }
        else {
            // printf("ProxyHost=%s
    ProxyPort=%d
    ",proxyhost,proxyport);
            strcat(request, url);
        }
    
        if (http10 == 1)
            strcat(request, " HTTP/1.0");
        else if (http10 == 2)
            strcat(request, " HTTP/1.1");
        strcat(request, "
    ");
    
        if (http10 > 0)
            strcat(request, "User-Agent: WebBench "PROGRAM_VERSION"
    ");
    
        if (proxyhost == NULL && http10 > 0)
        {
            strcat(request, "Host: ");
            strcat(request, host);
            strcat(request, "
    ");
        }
    
        if (force_reload && proxyhost != NULL)
        {
            strcat(request, "Pragma: no-cache
    ");
        }
    
        if (http10 > 1)
            strcat(request, "Connection: close
    ");
    
        /* add empty line at end */
        if (http10 > 0) strcat(request, "
    ");
    
        printf("Req=%s
    ",request);
    }
    
    /* vraci system rc error kod */
    static int bench(void)
    {
        int i, j, k;
        pid_t pid = 0;
        FILE* f;
    
        /* 作为测试地址是否合法 */
        /* check avaibility of target server */
        i = Socket(proxyhost == NULL ? host : proxyhost, proxyport);
        if (i < 0) {
            fprintf(stderr, "
    Connect to server failed. Aborting benchmark.
    ");
            return 1;
        }
        close(i);
    
        /* 建立管道 */
        /* create pipe */
        if (pipe(mypipe))
        {
            perror("pipe failed.");
            return 3;
        }
    
        /* not needed, since we have alarm() in childrens */
        /* wait 4 next system clock tick */
        /*
         * cas=time(NULL);
         * while(time(NULL)==cas)
         * sched_yield();
         * */
    
         /* 派生子进程 */
         /* fork childs */
        for (i = 0; i < clients; i++)
        {
            pid = fork();
            if (pid <= (pid_t)0)
            {
                /* child process or error*/
                sleep(1); /* make childs faster */
                break; /* 子进程立刻跳出循环,要不就子进程继续 fork 了 */
            }
        }
    
        if (pid < (pid_t)0)
        {
            fprintf(stderr, "problems forking worker no. %d
    ", i);
            perror("fork failed.");
            return 3;
        }
    /*
    void perror(const char *s)
    perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
    在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
    */
        if (pid == (pid_t)0)
        {
            /* 子进程发出实际请求 */
            /* I am a child */
            if (proxyhost == NULL)
                benchcore(host, proxyport, request);
            else
                benchcore(proxyhost, proxyport, request);
    
            /* 打开管道写 */
            /* write results to pipe */
            f = fdopen(mypipe[1], "w");
            if (f == NULL)
            {
                perror("open pipe for writing failed.");
                return 3;
            }
    
            /* fprintf(stderr,"Child - %d %d
    ",speed,failed); */
            fprintf(f, "%d %d %d
    ", speed, failed, bytes);
            fclose(f);
    
            return 0;
    
        }
        else {
            /* 父进程打开管道读 */
            f = fdopen(mypipe[0], "r");
            if (f == NULL)
            {
                perror("open pipe for reading failed.");
                return 3;
            }
    
            setvbuf(f, NULL, _IONBF, 0);
            speed = 0; /* 传输速度 */
            failed = 0; /* 失败请求数 */
            bytes = 0; /* 传输字节数 */
    
            while (1)
            {
                pid = fscanf(f, "%d %d %d", &i, &j, &k);
                /* 如果成功,函数fscanf返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。 */
                if (pid < 2)
                {
                    fprintf(stderr, "Some of our childrens died.
    ");
                    break;
                }
                speed += i;
                failed += j;
                bytes += k;
                /* fprintf(stderr,"*Knock* %d %d read=%d
    ",speed,failed,pid); */
                /* 子进程是否读取完 */
                if (--clients == 0) break;
            }
            fclose(f);
    
            /* 结果计算 */
            printf("
    Speed=%d pages/min, %d bytes/sec.
    Requests: %d susceed, %d failed.
    ",
                (int)((speed + failed) / (benchtime / 60.0f)),
                (int)(bytes / (float)benchtime),
                speed,
                failed);
        }
        return i;
    }
    
    void benchcore(const char* host, const int port, const char* req)
    {
        int rlen;
        char buf[1500];
        int s, i;
        struct sigaction sa;
    
        /*安装信号 */
        /* setup alarm signal handler */
        sa.sa_handler = alarm_handler;
        sa.sa_flags = 0;
        if (sigaction(SIGALRM, &sa, NULL))
            exit(3);
    
        /* 设置闹钟函数 */
        alarm(benchtime);
    
        rlen = strlen(req);
    
    nexttry:
        while (1) {
            /* 收到信号则 timerexpired = 1 */
            if (timerexpired)
            {
                if (failed > 0)
                {
                    /* fprintf(stderr,"Correcting failed by signal
    "); */
                    failed--;
                }
                return;
            }
            /* 建立 socket, 进行 HTTP 请求 */
            s = Socket(host, port);
            if (s < 0)
            {
                failed++;
                continue;
            }
            /* write返回读取到的字节数 */
            if (rlen != write(s, req, rlen))
            {
                failed++;
                close(s);
                continue;
            }
            /* HTTP 0.9 的处理 */
            if (http10 == 0)
                /* 如果关闭不成功 */
                if (shutdown(s, 1))
                {
                    failed++;
                    close(s);
                    continue;
                }
    
            /* -f 选项时不读取服务器回复 */
            if (force == 0)
            {
                /* read all available data from socket */
                while (1)
                {
                    if (timerexpired) break;
                    i = read(s, buf, 1500);
                    /* fprintf(stderr,"%d
    ",i); */
                    if (i < 0)
                    {
                        failed++;
                        close(s);
                        goto nexttry;
                    }
                    else
                        if (i == 0) break;
                        else bytes += i;
                }
            }
            if (close(s))
            {
                failed++;
                continue;
            }
            speed++;
        }
    }
    
  • 相关阅读:
    文档视图
    引入缓冲池技术
    数据库访问与查询
    OnInitialUpdate函数
    显示股票视图的全局函数
    切换视图的核心代码
    GuiEdit的使用
    操作方法
    SQL 使用 解析
    调用API 实现 窗体 拖动
  • 原文地址:https://www.cnblogs.com/honernan/p/14201970.html
Copyright © 2011-2022 走看看