zoukankan      html  css  js  c++  java
  • 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 ", 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 ",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 ",strerror(errno));
    12         return -1;
    13     }
    14  
    15     if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
    16         fprintf(stderr, "spawn-fcgi: bind failed: %s "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 ",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 ",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 "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 "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 "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 ", 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, " ", 1);
    22             }
    23         }
    24  
    25         break;

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

  • 相关阅读:
    vue-cli webpack3扩展多模块打包
    性能对比:aelf智能合约运行环境性能是evm的1000倍
    aelf帮助C#工程师10分钟零门槛搭建DAPP&私有链开发环境
    ASP.NET Core依赖注入最佳实践,提示&技巧
    linux环境安装php fileinfo扩展
    简单的部署nodejs和web应用
    安卓NDK操作sqlite3
    [解决]微信小程序调用wx.login解密之后出现乱码的问题
    windows server 2008 iis 添加ssl证书
    Mac 下修改文件或文件夹的隐藏状态
  • 原文地址:https://www.cnblogs.com/redsmith/p/5467885.html
Copyright © 2011-2022 走看看