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.
- Webbench源码分析之多进程(三)
- Github Webbench 源码分析+学习(待续)
- webbench源码分析
- webbench源码分析
- webbench源码分析
- webbench源码分析(转)
- webbench源码分析
- Webbench源码分析
- Webbench源码分析
- webbench源码分析
- webbench源码分析
- Webbench 源码分析 阅读笔记
- 网站(Web)压测工具Webbench源码分析
- 网站(Web)压测工具Webbench源码分析
- Webbench源码分析之参数输入getopt_long(一)
- Webbench源码分析之socket及http协议(二)
- Webkit之多进程分析
- webbench源码分析之bench函数分析
- token
- GPS NMEA-0183标准详解(常用的精度以及经纬度坐标)
- 平台架构
- ValueError: Input contains NaN, infinity or a value too large for dtype('float64')问题解决
- IO多路复用之select、poll、epoll详解
- Webbench源码分析之多进程(三)
- Android studio 安装3.0遇到的坑
- Recycleview的adapter封装
- quartz.net 时间表达式----- Cron表达式详解
- 10025---Zookeeper入门:基本概念、5项配置、启动
- 问题 B: 求相邻逆序对个数(函数)
- 2017年12月21ri心得体会
- PHP保留两位小数
- HTTP协议详解