spawn-fcgi原理及源代码分析

spawn-fcgi原理及源代码分析spawn-fcgi是一个小程序,作用是管理fast-cgi进程,功能和php-fpm类似,简单小巧,原先是属于lighttpd的一部分。后来因为使用比較广泛。所以就迁移出来作为独立项目了。本文介绍的

大家好,又见面了,我是你们的朋友全栈君。

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等。非常久之前看的在这里备忘一下。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154966.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • idea2019开发第一个java程序HelloWorld「建议收藏」

    idea2019开发第一个java程序HelloWorld「建议收藏」用idea2019开发第一个java程序:(马克-towin:idea破解不在本讲义范围之内)新手建议忽略此部分,先把eclipse用熟。技术是一样的。idea缺省配置是黑色的,很晃眼,可以(Files/settings/editor/colorscheme,然后右侧框中选择(Default(白色)或者Darcula(黑色)))底下都一样了。右击src/new/javaclass,名字叫…

  • python使用蒙特卡洛树(MCTS)算法实现黑白棋miniAlphaGo for Reversi[通俗易懂]

    python使用蒙特卡洛树(MCTS)算法实现黑白棋miniAlphaGo for Reversi[通俗易懂]黑白棋(reversi),也叫苹果棋,翻转棋,是一个经典的策略性游戏。一般棋子双面为黑白两色,故称“黑白棋”。因为行棋之时将对方棋子翻转,变为己方棋子,故又称“翻转棋”。棋子双面为红、绿色的成为“苹果棋”。它使用8*8的棋盘,由两人执黑子和白子轮流下棋,最后子多方为胜。规则:(1)黑方先行,双方交替下棋。(2)一步合法的棋步包含:在一个空格新落下一个棋子,并且反转对手一个或多个棋子。(3)新落下的棋子与棋盘上已有的同色棋子间,对方被夹住的所有棋子都要反转过来。可以横着夹,竖着夹,斜着夹。夹住的

  • Tomcat的下载及安装

    Tomcat的下载及安装一、Tomcat下载1、打开Tomcat官网,默认进入官网首页,官网地址为:https://tomcat.apache.org/2、左侧的导航栏可以看到Download(下载),以及最近相关版本的Tomcat(9、8、7…),这里选择的是Tomcat8,点击进入3、进入tomcat8的相关页面后会显示一些该版本的信息,可以忽略,鼠标下滑进行查找4、这里我们可以看到…

  • 数据结构与算法 队列_数据结构中的排序算法

    数据结构与算法 队列_数据结构中的排序算法一、什么是队列队列是一种特殊的线性表。队列元素的进出遵循“先进先出”原则:即只允许在前端(front)也就是队头进行删除操作,而只能在后端(rear)也就是队尾进行插入操作。如图所示:队列的最

  • 全网最全Python项目体系练习500例(附源代码),练完可就业

    全网最全Python项目体系练习500例(附源代码),练完可就业个人公众号yk坤帝后台回复项目四获取整理资源1.有一个jsonline格式的文件file.txt大小约为10K2.补充缺失的代码3.输入日期,判断这一天是这一年的第几天?4.打乱一个排好序的list对象alist?5.现有字典d={‘a’:24,‘g’:52,‘i’:12,‘k’:33}请按value值进行排序?6.字典推导式7.请反转字符串“aStr”?8.将字符串“k:1|k1:2|k2:3|k3:4”,处理成字典{k:1,k1:2,…}9.请按alist中元

  • JS数组对象排序

    JS数组对象排序利用数组api——>sort来进行排序varperson=[{name:”Rom”,age:12},{name:”Bob”,age:22},{name:”Ma”,age:5},{name:”Tony”,age:25}]person.sort((a,b)=>{returna.age-b.age})//升序person.sort((a,b)=>{retu…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号