最近在设计一种网络服务器架构, 最重要的一点是把耗时操作委托给工作进程(或者线程)来做, 所以考察一下 fastcgi. 大概看了下 lighttpd 的 mod_fastcgi 的源码, 没想到立即被卡住了. 根据我的想法, PHP 等 fastcgi 程序(php-cgi 进程)监听网络, 然后 mod_fastcgi 只需要 connect 这些进程即可, 奇怪的是, 我竟然看到了 listen! -
fcgi_spawn_connection() 函数: fcgi_fd = socket(socket_type, SOCK_STREAM, 0); if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) { close(fcgi_fd); fcgi_fd = socket(socket_type, SOCK_STREAM, 0); /* create socket */ bind(fcgi_fd, fcgi_addr, servlen); listen(fcgi_fd, 1024); switch ((child = fork())) { case 0: { // child process if(fcgi_fd != FCGI_LISTENSOCK_FILENO) { close(FCGI_LISTENSOCK_FILENO); dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO); close(fcgi_fd); } /* exec the cgi */ execve(arg.ptr[0], arg.ptr, env.ptr); exit(errno); } }
我很奇怪, mod_fastcgi 为什么要监听网络连接? 谁会连接它? 问了同事, 同事说可能是 fastcgi 进程要连接它, 可问题是 fastcgi 进程怎么知道连接什么地方, 更不用说, fastcgi 协议里没有提到要 fastcgi 进程主动连接 Web Server 一说.
再看一遍代码, 我注意到了 dup2() 调用, 会不会是 lighttpd 为 php-cgi 进程创建了一个监听 socket? 于是看了下 PHP fastcgi 的代码, 如下解析命令行参数的一段:
" -b <address:port>|<port> Bind Path for external FASTCGI Server mode\n" /* if we're started on command line, check to see if we are being started as an 'external' fastcgi server by accepting a bindpath parameter. */ case 'b': ... if (bindpath) { fcgi_fd = fcgi_listen(bindpath, 128); }
也就是说, 如果指定了 -b 选项, php-cgi 就会创建监听进程, 如果没有的话, fcgi_fd 就是默认的 0(标准输入的文件描述符), 也就是 FCGI_LISTENSOCK_FILENO.
所以, lighttpd mod_fastcgi 的逻辑是这样, 如果需要 spawn php-cgi 进程, 它会创建一个监听的 socket, 然后把该 socket dup2 到 标准输入的 fd, 接着 fork 并用 execve() 函数执行 php-cgi 程序, 这样, php-cgi 程序就可以使用该监听 socket 来接受 mod_fastcgi 的网络连接了.
只要知道了这个逻辑, 细节问题不必深究.