C精选开源代码学习2 webbench

Posted 编程圈子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C精选开源代码学习2 webbench相关的知识,希望对你有一定的参考价值。

一、 简介

Web-Bench 是一款优秀的 C语言开发的 性能压力测试工具,最多可以模拟3万个客户,主要返回每秒响应数和带宽。webbench是2004年的软件,已经有近二十年历史了。

二、下载源码

wget  http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
tar -zxvf webbench-1.5.tar.gz
cd webbench-1.5

三、编译使用

make
make install

测试:

webbench -c 1000 -t 60 http://测试的网址

四、源码分析

源文件主要是

  • webbench.c
  • socket.c

1. make文件分析

CFLAGS?=	-Wall -ggdb -W -O
CC?=		gcc
LIBS?=
LDFLAGS?=
PREFIX?=	/usr/local
VERSION=1.5
TMPDIR=/tmp/webbench-$(VERSION)

# 编译所有
all:   webbench tags

# 更新所有目标
tags:  *.c
	-ctags *.c

# 安装
install: webbench
	install -s webbench $(DESTDIR)$(PREFIX)/bin	
	install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1	
	install -d $(DESTDIR)$(PREFIX)/share/doc/webbench
	install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench
	install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench

webbench: webbench.o Makefile
	$(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) 

# 清理编译临时文件
clean:
	-rm -f *.o webbench *~ core *.core tags
	
# 打包到/tmp/目录下
tar:   clean
	-debian/rules clean
	rm -rf $(TMPDIR)
	install -d $(TMPDIR)
	cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR)
	install -d $(TMPDIR)/debian
	-cp -p debian/* $(TMPDIR)/debian
	ln -sf debian/copyright $(TMPDIR)/COPYRIGHT
	ln -sf debian/changelog $(TMPDIR)/ChangeLog
	-cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION)

webbench.o:	webbench.c socket.c Makefile

.PHONY: clean install all tar

2. socket.c文件

/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $
 *
 * This module has been modified by Radim Kolar for OS/2 emx
 */

/***********************************************************************
  module:       socket.c
  program:      popclient
  SCCS ID:      @(#)socket.c    1.5  4/1/94
  programmer:   Virginia Tech Computing Center
  compiler:     DEC RISC C compiler (Ultrix 4.1)
  environment:  DEC Ultrix 4.3 
  description:  UNIX sockets code.
 ***********************************************************************/
 
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

int Socket(const char *host, int clientPort)
{
    int sock;
    unsigned long inaddr;
    // sockaddr_in 结构主要是存储主机、端口
    struct sockaddr_in ad;
    // hostent 记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
    struct hostent *hp;
    
    // 
    memset(&ad, 0, sizeof(ad));
    ad.sin_family = AF_INET;

    inaddr = inet_addr(host);
    // 不要是INADDR_NONE 的广播地址
    if (inaddr != INADDR_NONE)
        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    else
    {
    // 域名解析
        hp = gethostbyname(host);
        if (hp == NULL)
            return -1;
        memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    }
    // htons 是将整型变量从主机字节顺序转变成网络字节顺序
    ad.sin_port = htons(clientPort);
    // 创建 socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
        return sock;
    // 建立连接
    if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
        return -1;
    return sock;
}

这个函数用来创建一个socket连接,参数是主机地址、端口号。

(1) 入口函数

入口函数最重要的事情就是解析命令行参数,给全局变量赋值,


int main(int argc, char *argv[]) {
    int opt = 0;
    int options_index = 0;
    char *tmp = NULL;

    if (argc == 1) {
        usage();
        return 2;
    }
// 下面对参数进行处理,这里的getopt_long是GUN C提供的命令行参数处理函数。
    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 */
                tmp = strrchr(optarg, ':');
                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;
        }
    }

    if (optind == argc) {
        fprintf(stderr, "webbench: Missing URL!\\n");
        usage();
        return 2;
    }

    if (clients == 0) clients = 1;
    if (benchtime == 0) benchtime = 60;
    /* Copyright */
    fprintf(stderr, "Webbench - Simple Web Benchmark "PROGRAM_VERSION"\\n"
                    "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\\n"
    );
    // 调用函数创建请求
    build_request(argv[optind]);
    /* print bench info */
    printf("\\nBenchmarking: ");
    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("\\n");
    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(".\\n");
    // bench是请求的主函数
    return bench();
}

(2) 全局变量

volatile int timerexpired=0;//判断压测时长是否已经到达设定的时间
int speed=0; //记录进程成功得到服务器响应的数量
int failed=0;//记录失败的数量(speed表示成功数,failed表示失败数)
int bytes=0;//记录进程成功读取的字节数
int http10=1;//http版本,0表示http0.9,1表示http1.0,2表示http1.1
int method=METHOD_GET; //请求方式,默认为GET,也支持HEAD、OPTIONS、TRACE
int clients=1;//并发数,默认只有1个进程发请求,通过-c参数设置
int force=0;  //0不等待服务器返回数据(异步),1相反
int force_reload=0;//是否使用缓存,1表示不缓存,0表示可以缓存页面
int proxyport=80; //代理服务器的端口号
char *proxyhost=NULL; //代理服务器地址
int benchtime=30; //压测时间,默认30秒,通过-t参数设置
int mypipe[2];    //管道,父进程和子进程的通信用
char host[MAXHOSTNAMELEN]; //服务器端ip
char request[REQUEST_SIZE]; //所要发送的http请求

(3) bench函数

创建管道、子进程,对http请求测试。

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, "\\nConnect to server failed. Aborting benchmark.\\n");
        return 1;
    }
    close(i);
    /* create pipe */
    // 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;
        }
    }

    if (pid < (pid_t) 0) {
        fprintf(stderr, "problems forking worker no. %d\\n", i);
        perror("fork failed.");
        return 3;
    }

    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\\n",speed,failed); */
        // 写入管道
        fprintf(f, "%d %d %d\\n", 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);
            if (pid < 2) {
                fprintf(stderr, "Some of our childrens died.\\n");
                break;
            }
            speed += i;
            failed += j;
            bytes += k;
            /* fprintf(stderr,"*Knock* %d %d read=%d\\n",speed,failed,pid); */
            if (--clients == 0) break;
        }
        fclose(f);
        // 结果打印
        printf("\\nSpeed=%d pages/min, %d bytes/sec.\\nRequests: %d susceed, %d failed.\\n",
               (int) ((speed + failed) / (benchtime / 60.0f)),
               (int) (bytes / (float) benchtime),
               speed,
               failed);
    }
    return i;
}

(4) 请求核心函数 benchcore


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;
    // 检查或修改与指定信号相关联的处理动作
    // SIGALARM 由setitimer触发的SIGALRM信号
    if (sigaction(SIGALRM, &sa, NULL))
        exit(3);
    alarm(benchtime);

    rlen = strlen(req);
    nexttry:
    while (1) {
        if (timerexpired) {
            if (failed > 0) {
                /* fprintf(stderr,"Correcting failed by signal\\n"); */
                failed--;
            }
            return;
        }
        // 创建连接
        s = Socket(host, port);
        if (s < 0) {
            failed++;
            continue;
        }
        if (rlen != write(s, req, rlen)) {
            failed++;
            close(s);
            continue;
        }
        if (http10 == 0)
            if (shutdown(s, 1)) {
                failed++;
                close(s);
                continue;
            }
        if (force == 0) {
            /* read all available data from socket */
            while (1) {
                if (timerexpired) break;
                i = read(s, buf, 1500);
                /* fprintf(stderr,"%d\\n",i); */
                if (i < 0) {
                    failed++;
                    close(s);
                    goto nexttry;
                } else if (i == 0) break;
                else
                // 读取字节数增加  
                    bytes += i;
            }
        }
        if (close(s)) {
            failed++;
            continue;
        }
        // http测试成功一次,speed加1  
        speed++;
    }
}

(5) build_request

创建URL请求连接 ,请求创建好以后放到全局变量request中

/**************** 
创建URL请求连接 
@url:url地址 
创建好的请求放在全局变量request中 
****************/  
void build_request(const char *url)  
{  
  char tmp[10];  
  int i;  
  
  //请求地址和请求连接清零  
  bzero(host,MAXHOSTNAMELEN);  
  bzero(request,REQUEST_SIZE)值得学习的C/C++开源框架(转)

c开源项目webbench学习

推荐学习c语言的几个开源项目

分享10个适合初学者学习的C开源项目代码

分享10个适合初学者学习的C开源项目代码

经典c开源项目