• 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
    ", 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
    ", 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
    ", strerror(errno));
            return -1;
        }
        if (-1 == bind(fcgi_fd, fcgi_addr, servlen))
        {
            fprintf(stderr, "spawn-fcgi: bind failed: %s
    ", 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
    ", 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
    ", strerror(errno));
                close(fcgi_fd);
                unlink(unixsocket);
                return -1;
            }
        }
        if (-1 == listen(fcgi_fd, 1024))
        {
            fprintf(stderr, "spawn-fcgi: listen failed: %s
    ", 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
    ", 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
    ", 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
    ", 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, "
    ", 1);
                }
            }
            break;
    


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

  • 相关阅读:
    Linux下Tomcat服务器-maven项目部署
    数据库设计感悟
    数据库设计规范
    从零到一: 代码调试
    Java泛型与反射的综合应用
    Eclipse中,tomcat插件方式启动项目
    集合查询表--Map
    集合线性表--List之LinkedList(队列与栈)
    集合线性表--List之ArrayList
    Java中的日期操作
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6756803.html
Copyright © 2020-2023  润新知