spawn-fcgi原理及源代码分析
spawn-fcgi是一个小程序,作用是管理fast-cgi进程,功能和php-fpm类似,简单小巧,原先是属于lighttpd的一部分。后来因为使用比較广泛。所以就迁移出来作为独立项目了。本文介绍的是这个版本号“spawn-fcgi-1.6.3”。
只是从公布新版本号到眼下已经4年了,代码一直没有变动,需求少,基本满足了。另外php有php-fpm后。码农们再也不操心跑不起FCGI了。
非常久之前看的spawn-fcgi的代码。当时由于须要改一下里面的环境变量。今天翻代码看到了就顺手记录一下。就当沉淀.备忘吧。
用spawn启动FCGI程序的方式为:./spawn-fcgi -a 127.0.0.1 -p 9003 -F ${count} -f ${webroot}/bin/demo.fcgi
这样就会启动count个demo.fcgi程序,他们共同监听同一个listenport9003,从而提供服务。
spawn-fcgi代码不到600行,很简短精炼,从main看起。其功能主要是打开监听port,绑定地址。然后fork-exec创建FCGI进程。退出完毕工作。
老方法,main函数使用getopt解析命令行參数,从而设置全局变量。假设设置了-P參数,须要保存Pid文件,就用open系统调用打开文件。
之后依据是否是root用户启动,假设是root,得做相关的权限设置,比方chroot, chdir, setuid, setgid, setgroups等。
重要的是调用了bind_socket打开绑定本地监听地址,或者sock。再就是调用fcgi_spawn_connection创建FCGI进程,主要就是这2步。
int main(int argc, char **argv)
{
if (!sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode)))
return -1;
/* drop root privs */
if (uid != 0)
{
setuid(uid);
}
else //非root用户启动,打开监听端口,进入listen模式。
{
if (-1 == (fcgi_fd = bind_socket(addr, port, unixsocket, 0, 0, sockmode)))
return -1;
}
if (fcgi_dir && -1 == chdir(fcgi_dir))
{
fprintf(stderr, "spawn-fcgi: chdir('%s') failed: %s\n", fcgi_dir, strerror(errno));
return -1;
}
//fork创建FCGI的进程
return fcgi_spawn_connection(fcgi_app, fcgi_app_argv, fcgi_fd, fork_count, child_count, pid_fd, nofork);
}
bind_socket函数用来创建套接字。绑定监听port。进入listen模式。其參数unixsocket表明须要使用unix sock文件,这里不多介绍。函数代码页挺简单。莫过于通用的sock程序步骤:socket()->setsockopt()->bind()->listen();
static int bind_socket(const char *addr, unsigned short port, const char *unixsocket, uid_t uid, gid_t gid, int mode)
{
//bind_socket函数用来创建套接字。绑定监听端口,进入listen模式
if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0)))
{
fprintf(stderr, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno));
return -1;
}
val = 1;
if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
{
fprintf(stderr, "spawn-fcgi: couldn't set SO_REUSEADDR: %s\n", strerror(errno));
return -1;
}
if (-1 == bind(fcgi_fd, fcgi_addr, servlen))
{
fprintf(stderr, "spawn-fcgi: bind failed: %s\n", strerror(errno));
return -1;
}
if (unixsocket)
{
if (0 != uid || 0 != gid)
{
if (0 == uid) uid = -1;
if (0 == gid) gid = -1;
if (-1 == chown(unixsocket, uid, gid))
{
fprintf(stderr, "spawn-fcgi: couldn't chown socket: %s\n", strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return -1;
}
}
if (-1 != mode && -1 == chmod(unixsocket, mode))
{
fprintf(stderr, "spawn-fcgi: couldn't chmod socket: %s\n", strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return -1;
}
}
if (-1 == listen(fcgi_fd, 1024))
{
fprintf(stderr, "spawn-fcgi: listen failed: %s\n", strerror(errno));
return -1;
}
return fcgi_fd;
}
fcgi_spawn_connection函数的工作是循环一次次创建子进程,然后马上调用execv(appArgv[0], appArgv);替换可执行程序,也就试执行demo.fcgi。
static int fcgi_spawn_connection(char *appPath, char **appArgv, int fcgi_fd, int fork_count, int child_count, int pid_fd,
int nofork)
{
int status, rc = 0;
struct timeval tv = { 0, 100 * 1000 };
pid_t child;
while (fork_count-- > 0)
{
if (!nofork) //正常不会设置nofork的
{
child = fork();
}
else
{
child = 0;
}
switch (child)
{
case 0:
{
//子进程
char cgi_childs[64];
int max_fd = 0;
int i = 0;
if (child_count >= 0)
{
snprintf(cgi_childs, sizeof(cgi_childs), "PHP_FCGI_CHILDREN=%d", child_count);
putenv(cgi_childs);
}
//wuhaiwen:add child id to thread
char bd_children_id[32];
snprintf(bd_children_id, sizeof(bd_children_id), "BD_CHILDREN_ID=%d", fork_count);
putenv(bd_children_id);
if (fcgi_fd != FCGI_LISTENSOCK_FILENO)
{
close(FCGI_LISTENSOCK_FILENO);
dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
close(fcgi_fd);
}
/* loose control terminal */
if (!nofork)
{
setsid();//运行setsid()之后,parent将又一次获得一个新的会话session组id,child将仍持有原有的会话session组,
//这时parent退出之后,将不会影响到child了[luther.gliethttp].
max_fd = open("/dev/null", O_RDWR);
if (-1 != max_fd)
{
if (max_fd != STDOUT_FILENO) dup2(max_fd, STDOUT_FILENO);
if (max_fd != STDERR_FILENO) dup2(max_fd, STDERR_FILENO);
if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO) close(max_fd);
}
else
{
fprintf(stderr, "spawn-fcgi: couldn't open and redirect stdout/stderr to '/dev/null': %s\n", strerror
(errno));
}
} /* we don't need the client socket */
for (i = 3; i < max_fd; i++)
{
if (i != FCGI_LISTENSOCK_FILENO) close(i);
} /* fork and replace shell */
if (appArgv) //假设有外的參数,就用execv运行,否则直接用shell运行
{
execv(appArgv[0], appArgv); }
else
{
char *b = malloc((sizeof("exec ") - 1) + strlen(appPath) + 1);
strcpy(b, "exec ");
strcat(b, appPath); /* exec the cgi */
execl("/bin/sh", "sh", "-c", b, (char *)NULL);
} /* in nofork mode stderr is still open */
fprintf(stderr, "spawn-fcgi: exec failed: %s\n", strerror(errno));
exit(errno); break;
}
}
}
上面是创建子进程的部分代码。基本没啥可说明的。
对于子进程:注意一下dup2函数。由子进程执行,将监听句柄设置为标准输入。输出句柄。比方FCGI_LISTENSOCK_FILENO 0 号在FCGI里面代表标准输入句柄。函数还会关闭其它不必要的socket句柄。
然后调用execv替换可执行程序。执行新的二进制。也就是demo.fcgi的FCGI程序。这样子进程可以继承父进程的全部打开句柄,包含监听socket。这样全部子进程都可以在这个9002port上进行监听新连接。谁拿到了谁就处理之。
对于父进程: 主要须要用select等待一会,然后调用waitpid用WNOHANG參数获取一下子进程的状态而不等待子进程退出。假设失败就打印消息。否则将其PID写入文件。
default:
/* father */ /* wait */
select(0, NULL, NULL, NULL, &tv); switch (waitpid(child, &status, WNOHANG))
{
case 0:
fprintf(stdout, "spawn-fcgi: child spawned successfully: PID: %d\n", child);
/* write pid file */
if (pid_fd != -1)
{
/* assume a 32bit pid_t */
char pidbuf[12];
snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child);
write(pid_fd, pidbuf, strlen(pidbuf));
/* avoid eol for the last one */
if (fork_count != 0)
{
write(pid_fd, "\n", 1);
}
}
break;
基本就是上面的东西了,代码不多,但该有的都有,命令行解析,socket。fork。dup2等。非常久之前看的在这里备忘一下。
spawn-fcgi原理及源代码分析的更多相关文章
- 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源代码分析
http://blog.csdn.net/u011239443/article/details/56843264 在<深入理解Spark 2.1 Core (九):迭代计算和Shuffle的原理 ...
- Spark MLlib LDA 基于GraphX实现原理及源代码分析
LDA背景 LDA(隐含狄利克雷分布)是一个主题聚类模型,是当前主题聚类领域最火.最有力的模型之中的一个,它能通过多轮迭代把特征向量集合按主题分类.眼下,广泛运用在文本主题聚类中. LDA的开源实现有 ...
- google PLDA + 实现原理及源代码分析
LDA背景 LDA(隐含狄利克雷分布)是一个主题聚类模型,是当前主题聚类领域最火.最有力的模型之中的一个,它能通过多轮迭代把特征向量集合按主题分类. 眼下,广泛运用在文本主题聚类中. LDA的开源实现 ...
- Tomcat7.0源代码分析——启动与停止服务原理
前言 熟悉Tomcat的project师们.肯定都知道Tomcat是怎样启动与停止的. 对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处 ...
- android-plugmgr源代码分析
android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...
- hostapd源代码分析(二):hostapd的工作机制
[转]hostapd源代码分析(二):hostapd的工作机制 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004433 在我的上一 ...
- hostapd源代码分析(一):网络接口和BSS的初始化
[转]hostapd源代码分析(一):网络接口和BSS的初始化 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004349 最近在做一 ...
- 【转载】linux环境下tcpdump源代码分析
linux环境下tcpdump源代码分析 原文时间 2013-10-11 13:13:02 CSDN博客 原文链接 http://blog.csdn.net/han_dawei/article/d ...
随机推荐
- jquery获取table指定行和列的数据(当前选中行、列)
//不多说,直接上代码.$("table tr").click(function() {//为表格的行添加点击事件 var tr = $(this);//找到tr原色 var td ...
- mvn sonar:sonar在jenkins步骤的执行位置影响执行结果
1.如图所示,sonar执行可以在build中执行,也可以在步骤Post Steps中执行(mvn sonar:sonar) 2.但是在步骤Post Steps中执行的话,有一个问题,就是假如项目有多 ...
- 数学图形(2.9) Capareda曲线
还是绕在球上的线圈 #http://www.mathcurve.com/courbes3d/capareda/capareda.shtml vertices = t = to (*PI) q = ra ...
- 树莓派安装 Nginx + PHP7.0 + Pi Dashboard
之前我们介绍过树莓派搭建LNMP环境的方法,以及给树莓派装一个仪表盘来监控树莓派运行状态.近期有用户反馈树莓派最新版的系统已经无法找到 PHP5 的软件包了,这是因为新版本已经用 PHP7 替代了 P ...
- [Todo] Java及C++ Exception整理
今天接触到一些Java Exception方面的内容,整理如下: http://developer.51cto.com/art/201111/304649.htm C++中Exception等的整理
- drawable和values的加载规则
Google搞了一大套hdpi.xhdpi之类的这些东西,简单说来,就是为了让我们轻松实现"与设备密度无关的视觉大小一致性",注意这里的"视觉大小一致性",就是 ...
- linux free命令详解和使用实例(查看内存使用率)
转载:http://www.jb51.net/LINUXjishu/152017.html 1.命令格式: free [参数] 2.命令功能: free 命令显示系统使用和空闲的内存情况,包括物理内存 ...
- linux 网卡eth0检测时没有IP地址,怎么回事??
你没有配IP怎么会有,除非你自动获取了,自己动手配置命令: ifconfig eth0 IP地址 netmask 子网掩码自动获取ip地址: dhclient
- RaceWeb介绍(7):由500强公司数据高速生成百度地图——生成坐标字段及坐标数据
接上篇. 一.生成X坐标.Y坐标两个字段. 我们须要为每一个公司建立X坐标和Y坐标字段,用来保存XY坐标. 既然为了突出"快",这一步就有程序来完毕吧. 右键单击"世界5 ...
- 《Go并发编程实战》第2版 紧跟Go的1.8版本号
文章作者:郝林(<Go并发编程实战 (第2版)>作者) 最终来了! 经过出版社的各位编辑.校对.排版伙伴与我的N轮PK和共同努力,<Go并发编程实战>第2版的全部内容最终全然确 ...