概述:前面我们把参数输入,http协议以及socket客户端编程部分都说了,今天就把多进程这一块内容学习过程记录一下。同时今天学习的这一部分也是webbench的核心部分了。
知识点:
1,多进程的创建。
2,多进程无名管道pipe通信。
3,多进程信号。
还是老样子,在学习源码之前先把用到的知识点搞懂,然后再进行学习,这样就事半功倍了。
1,多进程的创建
说到多进程,那fork()函数是跑不掉了,我这里也就简单说一下啦。fork函数就是一个创建子进程的作用,它的返回值有三种情况。
1,小于0,失败。
2,等于0,表示当前运行进程为子进程,返回值是0。
3,大于0,表示当前运行进程为父进程,返回值是子进程的进程id。
一个简单例子解释一下fork函数。
#include
#include
#include
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
#include
#include
#include
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;iif(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.