Webbench源码剖析
Webbench介绍
Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。
而其源码也相当的短,只有500行,读起来非常容易。
不过本项目虽然代码量很小,但是最好熟悉unix网络编程,以及linux下C语言多线程的实现(fork()函数),否则可能看起来会比较吃力。
我们将代码拉下来后,可以发现,其只包含两个文件,我们首先分析代码行数较少的socket.c
文件。
socket.c源码剖析
文件说明
这个socket.c
文件里仅有一个函数int Socket(const char *host, int clientPort)
,这是对socket的一层封装,可以解析以字符串传入的url地址
和端口
,并返回一个指向服务器的socket套接字用于连接。
函数工作流程
首先准备好必须的参数并对其进行初始化。
先将主机名转换成ip并存储(包括本身就是ip的主机和通过dns解析主机两部分)
根据主机IP和端口创建socket套接字
执行一次连接,如果连接成功就返回这个套接字,否则返回错误码
源码
这部分源码相对比较简单,主要懂一些linux下网络编程就不难看懂了。
1 |
|
webbench.c源码剖析
文件说明
这个webbench.c
文件是这个项目中最重要的文件,所有的处理操作都在这个文件中执行。
在这个文件头处可以看到可能令人疑惑的地方,在一行有一句#include "socket.c"
,也就是把另一个.c文件给include进来了,但一般我们include都是.h文件。
其实在c语言中#
开头的指令是预处理指令,我们知道,c语言进行编译的第一步就是预处理,而include
指令就是把include后紧跟的文件原封不动的插入进来,而不做改变。而平时我们不这么做的原因就是当多个文件都include一个.c文件时,这个.c文件内的函数会被重定义。但这个项目就两个文件,所以不用考虑那么多。
关于预处理
在linux环境下执行gcc -E webbench.c > result.txt
就能看到预处理结果,当然为了便于观察,可以把除了#include "socket.c"
的所有其他头文件都注释掉。
源码
由于这个文件比较大,所以分函数来进行展示
先来看头文件部分和全局变量部分。
1 |
|
前面这里就是定义了一些用到的宏和全局变量。
接下来我们来分析三个辅助函数,一个是用于设置定时器过期,一个用于显示帮助信息,另一个用于构造请求头。
其中,构造请求头的函数看起来稍微有些复杂,但是其实就是一些逻辑处理,然后根据http协议的定义手动构造出了请求体并保存在全局变量里面,这并不是我们分析的重点。
1 |
|
然后我们来进入main函数,main函数主要是解析选项,并构造url的请求头,然后调用bench函数来正式开始压力测试,
1 |
|
然后我们分析用于作为测试入口的bench函数。
这个函数内是使用fork创建出子进程,同时分配管道用于主进程与子进程之间通信,最后还设定了主进程的逻辑为从管道中不断读取子进程的数据并汇总。
这里我们可以学到一点:所有需要处理的东西全部由子进程去做,主进程只负责汇总或打印报告之类的,这样处理的时候子进程崩了也不影响主进程。
1 |
|
最后是子进程发起请求的函数benchcore函数,这个函数其实逻辑也很简单,就是构造请求并发起,然后看看有没有错误,不过错误种类还挺多。
另外,这个函数还使用了信号量来作为定时器,这部分可以参考,即:在不影响程序执行的前提下进行定时。
1 |
|
总结
流程解析
编程小技巧
getopt_long() : 其函数族成员有getopt、getopt_long、getopt_long_only,可以用来解析命令行参数,很实用。位于getopt.h,可以参阅这篇文章来使用。
pipe() : 管道是两个进程之间的连接,一个进程的标准输出成为另一个进程的标准输入。在UNIX操作系统中,管道用于进程间通信。可以参阅这篇文章来使用。
fork() : 用于创建出子进程,调用一次返回两次,子进程拥有父进程所有的打开的文件与变量。另外,在循环中使用fork会导致子进程继续fork,非常麻烦,为了计数方便可以让子进程在循环中break掉。
fprintf() : printf() == fprintf(stdout,...)这个函数功能更强大,支持重定向。
alarm() : alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号,可以设置对应的信号处理函数去处理这个信号。