Webbench源码分析之多进程(三)

来源:互联网 发布:云计算数据中心 编辑:程序博客网 时间:2024/05/18 13:30

概述:前面我们把参数输入,http协议以及socket客户端编程部分都说了,今天就把多进程这一块内容学习过程记录一下。同时今天学习的这一部分也是webbench的核心部分了。

知识点:
1,多进程的创建。
2,多进程无名管道pipe通信。
3,多进程信号。

还是老样子,在学习源码之前先把用到的知识点搞懂,然后再进行学习,这样就事半功倍了。

1,多进程的创建
说到多进程,那fork()函数是跑不掉了,我这里也就简单说一下啦。fork函数就是一个创建子进程的作用,它的返回值有三种情况。
1,小于0,失败。
2,等于0,表示当前运行进程为子进程,返回值是0。
3,大于0,表示当前运行进程为父进程,返回值是子进程的进程id。

一个简单例子解释一下fork函数。

#include <stdio.h>#include <stdlib.h>#include <unistd.h> int value = 100; int main() {     pid_t pid;     printf("start\n");     /*创建子进程*/     pid = fork();     printf("hahaha\n");     /*判断是否创建失败*/     if (pid < 0) {         printf("fork failed\n");         return -1;     }     /*返回值大于0,表示父进程*/     if (pid > 0) {         printf("parent process value = %d\n",value);         sleep(1);     }     /*返回值等于0,表示子进程*/     if (0 == pid) {         value = 99;         printf("child process value = %d\n",value);     }     return 0;  }

我们看一下运行结果:
这里写图片描述

首先我们看到start打印了一次,而hahaha打印了两次。不是说子进程和父进程是完全一样的,为何start只打印出一次,这就要说子进程执行位置了,确实子进程会完全拷贝父进程,但是子进程开始执行的顺序是fork以后,所以hahaha就会打印两次了。其次,我们发现返回值大于0的执行代码运行了,等于0的执行代码也运行了,其实就是父子进程都开始运行fork之后的代码,而父进程的返回值大于0,所以打印出parent process value = 100,子进程也运行相同的代码,但是其返回值为0,所以就打印了child process value = 100.最后,我们看到value是我们之前定义的全局变量,子进程中改变了value值,父进程值还是100,其实这两个值已经不相干了,他们是独立的,不会互相影响,不像多线程需要加锁等同步机制。
关于fork()函数面试题也很多,可以看下这篇博客。

2,多进程无名管道pipe通信。
这里我就用个人理解来说一下pipe()函数。管道通信,我们的pipe()是只能在有亲缘关系的进程间完成通信。比如父子进程,兄弟进程。我把webbench中用到的pipe相关代码抠出来。

int mypipe[2];/* create pipe */if(pipe(mypipe)) {     perror("pipe failed.");     return 3; }

我们看到我们定义了一个int型两个元素的数组mypipe[2];然后通过pipe()函数创建了一个管道。管道创建完成后,其实我们得到了两个文件描述符,一个写端一个读端。mypipe[0]就表示读端,mypipe[1]表示写端。
pipe创建的管道是半双工的,也就是说在同一时间,某进程使用这个管道时只能往里面读或写,但不能同时读写。

那到底是怎么实现进程通信的呢,其实看webbench源码会发现,创建管道是在fork()之前,原因是什么?
就是因为子进程是完全复制父进程的,所以子进程就也有了这两个文件描述符,这样父子进程就可以通过这个管道通信啦,一个在管道一边写,一个在一边读。webbench就是这样使用的,子进程把数据写入管道,父进程一直读数据。

通过man查看pipe函数发现其中说,在使用管道前,首先关闭你不需要的操作,比如你要读,那么就要先close(mypipe[1]),反之就close(mypipe[0]).但是在webbench源码中没有关闭,应该是有一点点小bug吧,个人推测。然后我也具体看了为何要首先关闭管道一端,stackoverflow有这样的答案,有兴趣可以看一下,地址在这:Is it really necessary to close the unused end of the pipe in a process

3,多进程信号。
在源码中我们看到使用了sigaction函数,那么我们先了解一下这个函数。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //函数原型 

稍微解释一下参数,signum:表示要操作的信号值;act:表示新的信号处理方式,oldact表示原来的信号处理方式。这里我们发现结构体struct sigaction.我们看一下这个结构体的定义。

 struct sigaction {  void     (*sa_handler)(int);  void     (*sa_sigaction)(int, siginfo_t *, void *);  sigset_t  sa_mask;  int       sa_flags;  void     (*sa_restorer)(void); };

第一个函数指针就是我们一般用到的,指向我们的信号处理函数。第二个函数指针同样也是指向我们的信号处理函数,不过这个更加详细,需要sa_flags
包含了SA_SIGINFO标志才会以第二个去进行处理,bench中用到的就是第一种 基本用法。详细可以看下这篇博客sigaction 函数。

我这里就写了一个和bench中几乎相同的sigaction的小例子,代码如下:

/*示例功能:实际就是一个定时器的作用,我们设置的时间间隔到了的时候,alarm会发出SIGALRM信号,我们的sigaction函数收到信号,执行我们对应的处理函数alarm_handler(),然后打印出hello world并改变value的值为1,main函数中while循环一直检测value的值是否不为0,不为0程序退出*/#include <stdio.h>#include <signal.h>#include <time.h>#include <unistd.h>int value = 0;/*我们的信号处理函数*/static void alarm_handler(int signal){    printf("hello world\n");        value = 1;}int main(int argc, char *argv[]){    if (2 != argc) {        return -1;      }    /*用户输入定时的时间,单位s*/    int times = atoi(argv[1]);    times = times > 0 ? times : 30;    /*声明变量,赋值*/    struct sigaction sa;    sa.sa_handler = alarm_handler;    sa.sa_flags = 0;    /*信号注册及对应信号处理方式,接收到SIGALRM就会执行我们上面赋值的alarm_handler()函数*/    if (sigaction(SIGALRM, &sa, NULL) < 0) {        return -1;      }    /*达到设置的时间产生信号SIGALRM*/    alarm(times);    while (1) {        if (value) {            printf("%ds time up...\n",times);            return 0;           }           usleep(200000);    }    return 0;}

前面知识点已经说的差不多了,现在就开始看源码吧,这也是webbench最核心的部分了,我就直接在源码上注释了,因为用到的知识点我前面都说了,就不再废话啦。

/*源码*//*SIGLARM信号处理函数,将timerexpired置1*/static void alarm_handler(int signal){    timerexpired=1;}   static int bench(void){    int i,j,k;      pid_t pid=0;    FILE *f;    /*检查服务器是否有效,可否连接*/    i=Socket(proxyhost==NULL?host:proxyhost,proxyport);    if(i<0) {         fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");        return 1;    }    close(i);    /* 创建管道 */    if(pipe(mypipe))    {        perror("pipe failed.");        return 3;    }    /* 创建子进程*/    for(i=0;i<clients;i++)    {        pid=fork();        if(pid <= (pid_t) 0)        {            /* child process or error*/            sleep(1); /* make childs faster */            break;        }    }    /*当pid<0表示fork创建失败,程序退出*/    if( pid < (pid_t) 0)    {        fprintf(stderr,"problems forking worker no. %d\n",i);        perror("fork failed.");        return 3;    }    /*pid == 0 子进程的运行部分*/    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;}/*bench的核心代码*/void benchcore(const char *host,const int port,const char *req){    int rlen;    char buf[1500];    int s,i;    struct sigaction sa;    /*这里就是和我写的那个例子几乎相同,设置了信号SIGALRM的处理函数*/    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\n"); */                failed--;            }            return;        }        /*连接服务器,返回socket文件描述符,用于通信*/        s=Socket(host,port);                                  if(s<0) { failed++;continue;}  /*判断是否连接失败*/        if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} /*判断是否发送数据失败*/        /*当http版本为0.9,是否关闭socket写输入失败*/        if(http10==0)         if(shutdown(s,1)) { failed++;close(s);continue;}        /*force == 0表示等待服务器的数据*/        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;}        speed++;    }}

总结:
Webbench源码到这里也全都看完了,首先学习到了挺多的知识点,包括信号,http协议,多进程的知识。还有就是一个设计方法,webbench是用子进程去处理测试,父进程去统计数据,在我们的代码设计中,有时候也可以考虑这样的方式去做,父进程控总端,子进程去干活,子进程挂了也不会导致父进程死掉,只要父进程在就能不死不灭,哈哈。但是,说到webbench的代码书写,我是觉得不太好的,看着很凌乱,我是不建议这样去写代码。总体来说读源码还是不错的一个学习方式,继续前进吧。Keep & Peace & Love.

原创粉丝点击