spawn-fcgi 代码介绍

原文转自:http://chenzhenianqing.cn/articles/936.html

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程序,他们共同监听同一个listen端口9003,从而提供服务。

spawn-fcgi代码不到600行,非常简短精炼,从main看起。其功能主要是打开监听端口,绑定地址,然后fork-exec创建FCGI进程,退出完成工作。

老方法,main函数使用getopt解析命令行参数,从而设置全局变量。如果设置了-P参数,需要保存Pid文件,就用open系统调用打开文件。之后根据是否是root用户启动,如果是root,得做相关的权限设置,比如chroot, chdir, setuid, setgid, setgroups等。

重要的是调用了bind_socket打开绑定本地监听地址,或者sock,再就是调用fcgi_spawn_connection创建FCGI进程,主要就是这2步。

1 int main(int argc, char **argv) {
2                 if (!sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode)))
3                         return -1;
4  
5                 /* drop root privs */
6                 if (uid != 0) {
7                         setuid(uid);
8                 }
9         else {//非root用户启动,打开监听端口,进入listen模式。
10                 if (-1 == (fcgi_fd = bind_socket(addr, port, unixsocket, 0, 0, sockmode)))
11                         return -1;
12         }
13  
14         if (fcgi_dir && -1 == chdir(fcgi_dir)) {
15                 fprintf(stderr, "spawn-fcgi: chdir(‘%s‘) failed: %s\n", fcgi_dir,strerror(errno));
16                 return -1;
17         }
18  
19     //fork创建FCGI的进程
20         return fcgi_spawn_connection(fcgi_app, fcgi_app_argv, fcgi_fd, fork_count, child_count, pid_fd, nofork);
21 }


bind_socket函数用来创建套接字,绑定监听端口,进入listen模式。其参数unixsocket表明需要使用unix sock文件,这里不多介绍。函数代码页挺简单,莫过于通用的sock程序步骤:socket()->setsockopt()->bind()->listen();

1 static int bind_socket(const char *addr, unsigned short port, const char*unixsocket, uid_t uid, gid_t gid, int mode)
2 {//bind_socket函数用来创建套接字,绑定监听端口,进入listen模式
3 //``````
4     if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
5         fprintf(stderr, "spawn-fcgi: couldn‘t create socket: %s\n",strerror(errno));
6         return -1;
7     }
8  
9     val = 1;
10     if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
11         fprintf(stderr, "spawn-fcgi: couldn‘t set SO_REUSEADDR: %s\n",strerror(errno));
12         return -1;
13     }
14  
15     if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
16         fprintf(stderr, "spawn-fcgi: bind failed: %s\n"strerror(errno));
17         return -1;
18     }
19  
20     if (unixsocket) {
21         if (0 != uid || 0 != gid) {
22             if (0 == uid) uid = -1;
23             if (0 == gid) gid = -1;
24             if (-1 == chown(unixsocket, uid, gid)) {
25                 fprintf(stderr, "spawn-fcgi: couldn‘t chown socket: %s\n",strerror(errno));
26                 close(fcgi_fd);
27                 unlink(unixsocket);
28                 return -1;
29             }
30         }
31  
32         if (-1 != mode && -1 == chmod(unixsocket, mode)) {
33             fprintf(stderr, "spawn-fcgi: couldn‘t chmod socket: %s\n",strerror(errno));
34             close(fcgi_fd);
35             unlink(unixsocket);
36             return -1;
37         }
38     }
39     if (-1 == listen(fcgi_fd, 1024)) {
40         fprintf(stderr, "spawn-fcgi: listen failed: %s\n"strerror(errno));
41         return -1;
42     }
43  
44     return fcgi_fd;
45 }


fcgi_spawn_connection函数的工作是循环一次次创建子进程,然后立即调用execv(appArgv[0], appArgv);替换可执行程序,也就试运行demo.fcgi。

1 static int fcgi_spawn_connection(char *appPath, char **appArgv, int fcgi_fd, intfork_count, int child_count, int pid_fd,
2  int nofork) {
3     int status, rc = 0;
4     struct timeval tv = { 0, 100 * 1000 };
5  
6     pid_t child;
7  
8     while (fork_count-- > 0) {
9  
10         if (!nofork) {//正常不会设置nofork的
11             child = fork();
12         else {
13             child = 0;
14         }
15  
16         switch (child) {
17         case 0: {
18             //子进程
19             char cgi_childs[64];
20             int max_fd = 0;
21  
22             int i = 0;
23             if (child_count >= 0) {
24                 snprintf(cgi_childs, sizeof(cgi_childs), "PHP_FCGI_CHILDREN=%d", child_count);
25                 putenv(cgi_childs);
26             }
27  
28             //wuhaiwen:add child id to thread
29             char bd_children_id[32];
30             snprintf(bd_children_id, sizeof(bd_children_id), "BD_CHILDREN_ID=%d", fork_count);
31             putenv(bd_children_id);
32  
33             if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
34                 close(FCGI_LISTENSOCK_FILENO);
35                 dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
36                 close(fcgi_fd);
37             }
38             /* loose control terminal */
39             if (!nofork) {
40                 setsid();//执行setsid()之后,parent将重新获得一个新的会话session组id,child将仍持有原有的会话session组,
41                 //这时parent退出之后,将不会影响到child了[luther.gliethttp].
42                 max_fd = open("/dev/null", O_RDWR);
43                 if (-1 != max_fd) {
44                     if (max_fd != STDOUT_FILENO) dup2(max_fd, STDOUT_FILENO);
45                     if (max_fd != STDERR_FILENO) dup2(max_fd, STDERR_FILENO);
46                     if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO) close(max_fd);
47                 else {
48                     fprintf(stderr, "spawn-fcgi: couldn‘t open and redirect stdout/stderr to ‘/dev/null‘: %s\n"strerror
49 (errno));
50                 }
51             }
52  
53             /* we don‘t need the client socket */
54             for (i = 3; i < max_fd; i++) {
55                 if (i != FCGI_LISTENSOCK_FILENO) close(i);
56             }
57  
58             /* fork and replace shell */
59             if (appArgv) {//如果有外的参数,就用execv执行,否则直接用shell执行
60                 execv(appArgv[0], appArgv);
61  
62             else {
63                 char *b = malloc((sizeof("exec ") - 1) + strlen(appPath) + 1);
64                 strcpy(b, "exec ");
65                 strcat(b, appPath);
66  
67                 /* exec the cgi */
68                 execl("/bin/sh""sh""-c", b, (char *)NULL);
69             }
70  
71             /* in nofork mode stderr is still open */
72             fprintf(stderr, "spawn-fcgi: exec failed: %s\n"strerror(errno));
73             exit(errno);
74  
75             break;
76         }

上面是创建子进程的部分代码,基本没啥可说明的。
对于子进程:注意一下dup2函数,由子进程运行,将监听句柄设置为标准输入,输出句柄。比如FCGI_LISTENSOCK_FILENO 0 号在FCGI里面代表标准输入句柄。函数还会关闭其他不必要的socket句柄。
然后调用execv替换可执行程序,运行新的二进制,也就是demo.fcgi的FCGI程序。这样子进程能够继承父进程的所有打开句柄,包括监听socket。这样所有子进程都能够在这个9002端口上进行监听新连接,谁拿到了谁就处理之。
对于父进程: 主要需要用select等待一会,然后调用waitpid用WNOHANG参数获取一下子进程的状态而不等待子进程退出,如果失败就打印消息。否则将其PID写入文件。

1 default:
2     /* father */
3  
4     /* wait */
5     select(0, NULL, NULL, NULL, &tv);
6  
7     switch (waitpid(child, &status, WNOHANG)) {
8     case 0:
9         fprintf(stdout, "spawn-fcgi: child spawned successfully: PID: %d\n", child);
10  
11         /* write pid file */
12         if (pid_fd != -1) {
13             /* assume a 32bit pid_t */
14             char pidbuf[12];
15  
16             snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child);
17  
18             write(pid_fd, pidbuf, strlen(pidbuf));
19             /* avoid eol for the last one */
20             if (fork_count != 0) {
21                 write(pid_fd, "\n", 1);
22             }
23         }
24  
25         break;

基本就是上面的东西了,代码不多,但该有的都有,命令行解析,socket,fork,dup2等。很久之前看的在这里备忘一下。

时间: 2024-08-30 07:48:20

spawn-fcgi 代码介绍的相关文章

Android ViewPager实例代码介绍2。

以前写过一篇ViewPager:内容content+指示点的Demo: 这篇文章继续介绍ViewPager:内容content+标题title的Demo. 实现效果图: 源代码: 布局文件:activity_main: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"

Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)

本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作.联合中间表获取对象集合.递归操作.设置单一字段的修改等几种方式. 1.写入中间表操作 一般情况下,我们可以通过执行数据库脚本方式写入. /// <summary> /// 增加用户IP信息 /// </summary> /// <param name="userID"></param&

(四)整合spring cloud云服务架构 - particle-common-framework代码介绍

上一篇我们介绍了spring cloud云服务架构 - particle云架构代码结构,简单的按照几个大的部分去构建代码模块,让我们来回顾一下: 第一部分: 针对于普通服务的基础框架封装(entity.dao.service.controller.api)等 第二部分: spring cloud通用微服务项目,可以监控左右微服务,当然,本身自己也是微服务. 第三部分: 针对于框架内所有组件的封装,可以植入任何的模块项目中. 第四部分: 自身项目的微服务业务,比如:会员模块.消息模块.资金模块.订

Apache Rewrite module 配置开启代码介绍

首先,你必须要确认Apache安装目录下面conf/httpd.conf文件中已经开启: #代码1# LoadModule rewrite_module modules/mod_rewrite.so 然后你要在httpd.conf文件中指定你所想要实现rewrite功能的目录和配置,如下所示: #代码2# <Directory "D:\www\bc"> #指定你需要执行下面rewrite规则的目录     #     # Possible values for the Op

HM代码介绍

我对HM代码结构理解的启蒙文章,转自实验室前辈朱师兄的博客:http://blog.csdn.net/spark19851210/article/details/8964559 1.      环境配置 这个文档描述的版本是HM6.0 运行的方法如下可参考之前的文章: 2.      编码端主函数的调用 主函数中会调用create函数,但是这里面是空函数,所以不做任何操作 encode是非常重要的函数,负责了实际的编码工作,在里面调用m_cTEncTop的encode 函数对每个GOP进行编码,

Particle-Common-Framework代码介绍

第一部分: 针对于普通服务的基础框架封装(entity.dao.service.controller.api)等 第二部分: spring cloud通用微服务项目,可以监控左右微服务,当然,本身自己也是微服务. 第三部分: 针对于框架内所有组件的封装,可以植入任何的模块项目中. 第四部分: 自身项目的微服务业务,比如:会员模块.消息模块.资金模块.订单模块等. 我们针对于以上四个模块分别做详细讲解: 第一部分: 针对于particle-framework模块: 1.  包括阿里巴巴Druid的

linux usb驱动——Gadget代码介绍

一般网上关于介绍USB Gadget的资料都是基于Linux2.6.32或在这之前的版本,作者在关注了Linux2.6.37和Linux3.0.4版本的内核,USB Gadget的一些API已经与Linux2.6.32的不同了.但是那些关键的数据结构还是一样滴. Linux USB Gadget分三层架构: 层次关系从上到下 一层:USB Gadget功能层.BSP/Driver开发者通常是要实现这一层,从而实现一个具体的设备驱动,如Anddroid在此层实现了adb,mtp,mass_stor

PHP设计模式之单例模式简单代码介绍

PHP设计模式之单例模式 单例模式是一种创建型模式,它会限制应用程序,使其只能创建某一特定类类型的一个单一的实例.举例来说,一个web站点将会需要一个数据库连接对象,但是应该有且只能有一个,因此我们通过使用单例模式来实现这种限制. 我们可以使用一个静态属性来保证对于一个特定的类来说只存在一个单一的实例. Class someclass{ Static private $_instance = NULL; } [示例]config类实现了单例模式,以便整个web应用程序可以使用同一个配置对象. <

一段比较有意思的代码——介绍system verilog中的新增幅值语句

system verilog中新加了很多幅值语句,虽然都只适用于阻塞幅值,但是在某些场合中非常实用. 下面是一段有意思的代码,覆盖了一些用法. 1 package definitions; 2 typedef enum logic [2:0] {ADD,SUB,MULT,DIV,SL,SR} opcode_t; 3 typedef enum logic {UNSIGNED, SIGNED} operand_type_t; 4 typedef union packed { 5 logic [23: