spawn-fcgi原理及源码分析

转自:http://blog.csdn.net/nyist327/article/details/41277883

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步。

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

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

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

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

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

上面是创建子进程的部分代码,基本没啥可说明的。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值