概述:前面我们把参数输入,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);elsebenchcore(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;}elseif(i==0) break;elsebytes+=i;}}if(close(s)) {failed++;continue;}speed++;}
}

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

Webbench源码分析之多进程(三)相关推荐

  1. 网站压测工具 Webbench 源码分析

    介绍 Webbench是一个在Linux下使用的非常简单的网站压测工具.它的源代码只有500多行,挺值得一看的开源项目. 实现原理 只是简单的fork()出多个子进程模拟客户端去访问设定的URL,测试 ...

  2. Spring 源码分析衍生篇三 : lookup-method 和 replaced-method

    文章目录 一.前言 二.基本使用 1. 作用 三.原理实现 1. 预处理 1.1 AbstractBeanDefinition#prepareMethodOverrides 1.2 Autowired ...

  3. SpringMVC之源码分析--ThemeResolver(三)

    概述 上节介绍了SessionThemeResolver解析器,本章分析下CookieThemeResolver,两个解析器实现的功能是一样的,只是使用的主题载体有区别而已,SessionThemeR ...

  4. [原]tornado源码分析系列(三)[网络层 IOLoop类]

    引言:由于都是在工作当中抽出时间看源代码,所以更新速度比较慢,但是还是希望通过对好的源码的分析和探讨,大家相互学习,发现不好的地方共同讨论. 上次讲了IOLoop中的几个重要的方法,inistance ...

  5. Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)

    mybatis是怎么拿sqlSession 在 上一篇的时候,我们的SqlSessionFactoryBuilder已经从xml文件中解析出了Configuration并且返回了sessionFact ...

  6. Spring5源码分析系列(三)Spring5概述

    咕泡学院Tom老师视频讲解第三章,对Spring5进行简要介绍,文章参考自Tom老师视频. Spring是一个开源的轻量级JavaSE(Java标准版本)/JavaEE(Java企业版本)开发应用框架 ...

  7. jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——一些有用的Sizzle API

    说一下Sizzle中零碎的API.这些API有的被jQuery接管,直接使用jQuery.xxx就可以使用,有的没有被接管,如果要在jQuery中使用,使用方法是jQuery.find.xxx. 具体 ...

  8. Android之UiAutomator测试框架源码分析(第三篇:UiDevice功能介绍)

    (注意:本文基于UI Automator测试框架版本为2.2.0) UiDevice类位于androidx.test.uiautomator包中,作为UI Automator测试框架中最重要的类之一, ...

  9. C++Primer Plus (第六版)阅读笔记 + 源码分析【第三章:处理数据】

    第三章:处理数据 简单变量 整型 整型 short .int .long 和 long 无符号类型 选择整型类型 整型字面值 C++如何确定常量的类型 char 类型:字符和小整型 bool类型 co ...

最新文章

  1. Tails 3.13 发布,更新 Intel 微码,改进拼音输入法支持
  2. java月份去0_java – 使用月份解析日期而不是前导0
  3. Spark 系列(一)—— Spark简介
  4. delete 会不会锁表_truncate 和 delete
  5. Please use boost/bind/bind.hpp + using namespace boost::placeholders
  6. 交叉熵【度量两个概率分布间的差异性信息】
  7. oracle分析函数over(Partition by...)及开窗函数详解
  8. qtcreator安装及配置
  9. 云闪付华为P9指纹_华为云闪付app下载-华为云闪付 安卓版v9.0.11.301-PC6安卓网
  10. 全网首发ai绘画小程序基于novelai
  11. 网络机顶盒固件提取、编辑和打包
  12. Docer容器客户端在启动的镜像的时候报错Error invoking remote method ‘docker-start-container‘: Error: (HTTP code 500
  13. 2022这一年:阳了、变轨和逆风
  14. SpringBoot入门系列(二)如何返回统一的数据格式
  15. Linux下安装Oracle 11g详细过程
  16. 【ML05】Feature Scaling 特征缩放
  17. 搭建hadoop3.x报错 Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
  18. 2020程序设计基础c语言答案,2020知到程序设计基础(C语言)入门篇答案全套
  19. 微信公众号/微信小程序获取用户信息以及推送微信模版消息_MQ
  20. GPS Ublox配置

热门文章

  1. AFL学习笔记(下)
  2. ASK、FSK调制解调
  3. Linux下大容量存储,第 2 章 USB Mass Storage大容量存储的基本知识
  4. Intel 快速存储蓝屏
  5. 假定1km长的CSMA/CD网络的数据率为1Gb/s。设信号在网络上的传播速率为200000km/s。求能够使用此协议的最短帧长。
  6. 转载:KOF97坂琦良心得
  7. php 卡路里计算,减肥速度计算器 - 减肥计算器 - 肌肉网
  8. 能力、态度、业绩——绩效考核三要素
  9. 论文笔记—LeGO-LOAM: Lightweight and Ground-Optimized Lidar Odometry and Mapping on Variable Terrain
  10. 首都师范 博弈论 3 1 4纯策略纳什均衡