首页 > 代码库 > lighttpd+fastcgi模块分析
lighttpd+fastcgi模块分析
一开始不怎么明白fastcgi和cgi的区别,查了资料说,fastcgi多了一个进程池,不要每次都fork和退出
这个不是重点,还是对着代码看吧
怎样在lighttpd运行php呢,需要下面这样配置
把fastcig模块的前面井号去掉,然后在下面加上这个相关的配置就可以
fastcgi.debug = 1
fastcgi.server = ( ".php" =>
( "localhost" =>
(
"host"=>"127.0.0.1",
"port"=>4000,
#"socket" => "/tmp/php.socket",
"bin-path" => "/usr/bin/php-cgi"
)
)
)
重启lighttpd,然后就可以访问了
用命令看一下cgi进程
[root@fire-16-168 ~]# ps aux|grep cgi
root 21614 0.0 0.1 471616 18276 ? Ss 15:29 0:00 /usr/bin/php-cgi
root 21615 0.0 0.1 471616 18280 ? Ss 15:29 0:00 /usr/bin/php-cgi
root 21616 0.0 0.1 471616 18276 ? Ss 15:29 0:00 /usr/bin/php-cgi
root 21617 0.0 0.1 471616 18280 ? Ss 15:29 0:00 /usr/bin/php-cgi
root 21619 0.0 0.0 471616 6092 ? S 15:29 0:00 /usr/bin/php-cgi
root 21620 0.0 0.0 471616 6088 ? S 15:29 0:00 /usr/bin/php-cgi
root 21625 0.0 0.0 471616 6088 ? S 15:29 0:00 /usr/bin/php-cgi
root 21628 0.0 0.0 471616 6088 ? S 15:29 0:00 /usr/bin/php-cgi
呵呵,总共有8个进程,
S Interruptible sleep (waiting for an event to complete)
s is a session leader
仔细观察,你会发现这些php-cgi的状态不尽相同,有的是Ss,有的是S,通过man ps你能找到这些状态的含义:
也就是说,Ss状态的进程都是主进程(max-procs代表的那些进程),而S状态的进程都是子进程(PHP_FCGI_CHILDREN代表的那些进程)。如果不相信,你可以使用命令核实一下数量:
再看看网络连接是怎样的
[root@fire-16-168 ~]# netstat -anp|grep cgi
tcp 0 0 127.0.0.1:4000 0.0.0.0:* LISTEN 21614/php-cgi
tcp 0 0 127.0.0.1:4001 0.0.0.0:* LISTEN 21615/php-cgi
tcp 0 0 127.0.0.1:4002 0.0.0.0:* LISTEN 21616/php-cgi
tcp 0 0 127.0.0.1:4003 0.0.0.0:* LISTEN 21617/php-cgi
其中只有4个进程在监听,为什么会这样,还是看代码吧
for (n = 0; n < da_ext->value->used; n++) { data_array *da_host = (data_array *)da_ext->value->data[n]; fcgi_extension_host *host; config_values_t fcv[] = { { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 主机*/ { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 端口*/ { "mode", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 可以用unix套接字域去连接*/ { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 cgi的位置*/ { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ { "min-procs-not-working", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 this is broken for now */ { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 最大cgi进程数*/ { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 这里是可以传给php-cgi的参数 */ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ { "broken-scriptfilename", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ { "allow-x-send-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ { "strip-request-uri", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ { "kill-signal", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ { "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; if (da_host->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "ssSBS", "unexpected type for key:", "fastcgi.server", "[", da_host->key, "](string)"); return HANDLER_ERROR; } #初始化host结构 host = fastcgi_host_init(); buffer_copy_string_buffer(host->id, da_host->key); #host默认值 host->check_local = 1; host->min_procs = 4; host->max_procs = 4; host->max_load_per_proc = 1; host->idle_timeout = 60; host->mode = FCGI_RESPONDER; host->disable_time = 60; host->break_scriptfilename_for_php = 0; host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ host->kill_signal = SIGTERM; host->fix_root_path_name = 0; fcv[0].destination = host->host; fcv[1].destination = host->docroot; fcv[2].destination = fcgi_mode; fcv[3].destination = host->unixsocket; fcv[4].destination = host->bin_path; fcv[5].destination = &(host->check_local); fcv[6].destination = &(host->port); fcv[7].destination = &(host->min_procs); fcv[8].destination = &(host->max_procs); fcv[9].destination = &(host->max_load_per_proc); fcv[10].destination = &(host->idle_timeout); fcv[11].destination = &(host->disable_time); fcv[12].destination = host->bin_env; fcv[13].destination = host->bin_env_copy; fcv[14].destination = &(host->break_scriptfilename_for_php); fcv[15].destination = &(host->allow_xsendfile); fcv[16].destination = host->strip_request_uri; fcv[17].destination = &(host->kill_signal); fcv[18].destination = &(host->fix_root_path_name); #进行配置文件替换 if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { return HANDLER_ERROR; } #判断有没有设置端口和socket if ((!buffer_is_empty(host->host) || host->port) && !buffer_is_empty(host->unixsocket)) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "either host/port or socket have to be set in:", da->key, "= (", da_ext->key, " => (", da_host->key, " ( ..."); return HANDLER_ERROR; } #使用unix domain socket if (!buffer_is_empty(host->unixsocket)) { /* unix domain socket */ struct sockaddr_un un; if (host->unixsocket->used > sizeof(un.sun_path) - 2) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "unixsocket is too long in:", da->key, "= (", da_ext->key, " => (", da_host->key, " ( ..."); return HANDLER_ERROR; } } else { /* tcp/ip */ if (buffer_is_empty(host->host) && buffer_is_empty(host->bin_path)) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "host or binpath have to be set in:", da->key, "= (", da_ext->key, " => (", da_host->key, " ( ..."); return HANDLER_ERROR; } else if (host->port == 0) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "port has to be set in:", da->key, "= (", da_ext->key, " => (", da_host->key, " ( ..."); return HANDLER_ERROR; } } if (!buffer_is_empty(host->bin_path)) { /* a local socket + self spawning */ size_t pno; /* HACK: just to make sure the adaptive spawing is disabled */ host->min_procs = host->max_procs; if (host->min_procs > host->max_procs) host->max_procs = host->min_procs; if (host->max_load_per_proc < 1) host->max_load_per_proc = 0; if (s->debug) { log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd", "--- fastcgi spawning local", "\n\tproc:", host->bin_path, "\n\tport:", host->port, "\n\tsocket", host->unixsocket, "\n\tmin-procs:", host->min_procs, "\n\tmax-procs:", host->max_procs); } #开始创建进程 for (pno = 0; pno < host->min_procs; pno++) { fcgi_proc *proc; proc = fastcgi_process_init(); proc->id = host->num_procs++; host->max_id++; if (buffer_is_empty(host->unixsocket)) { proc->port = host->port + pno; } else { buffer_copy_string_buffer(proc->unixsocket, host->unixsocket); buffer_append_string_len(proc->unixsocket, CONST_STR_LEN("-")); buffer_append_long(proc->unixsocket, pno); } if (s->debug) { log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", "--- fastcgi spawning", "\n\tport:", host->port, "\n\tsocket", host->unixsocket, "\n\tcurrent:", pno, "/", host->min_procs); } #这个开始创建套接字 if (fcgi_spawn_connection(srv, p, host, proc)) { log_error_write(srv, __FILE__, __LINE__, "s", "[ERROR]: spawning fcgi failed."); return HANDLER_ERROR; } fastcgi_status_init(srv, p->statuskey, host, proc); proc->next = host->first; if (host->first) host->first->prev = proc; host->first = proc; } } else { fcgi_proc *proc; proc = fastcgi_process_init(); proc->id = host->num_procs++; host->max_id++; host->active_procs++; proc->state = PROC_STATE_RUNNING; if (buffer_is_empty(host->unixsocket)) { proc->port = host->port; } else { buffer_copy_string_buffer(proc->unixsocket, host->unixsocket); } fastcgi_status_init(srv, p->statuskey, host, proc); host->first = proc; host->min_procs = 1; host->max_procs = 1; } if (!buffer_is_empty(fcgi_mode)) { if (strcmp(fcgi_mode->ptr, "responder") == 0) { host->mode = FCGI_RESPONDER; } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) { host->mode = FCGI_AUTHORIZER; if (buffer_is_empty(host->docroot)) { log_error_write(srv, __FILE__, __LINE__, "s", "ERROR: docroot is required for authorizer mode."); return HANDLER_ERROR; } } else { log_error_write(srv, __FILE__, __LINE__, "sbs", "WARNING: unknown fastcgi mode:", fcgi_mode, "(ignored, mode set to responder)"); } } /* if extension already exists, take it */ fastcgi_extension_insert(s->exts, da_ext->key, host); }
ok,分析一下流程
1,读取配置文件
2,设置一下默认参数(如果我们某些属性没有设置的话,由系统参数去配置)例如 min-proc 系统默认为4个啊
3,创建套接字 根据fcgi_spawn_connection 这个函数
我们开始分析这个函数
1 static int fcgi_spawn_connection(server *srv, 2 plugin_data *p, 3 fcgi_extension_host *host, 4 fcgi_proc *proc) { 5 int fcgi_fd; 6 int socket_type, status; 7 struct timeval tv = { 0, 100 * 1000 }; 8 #ifdef HAVE_SYS_UN_H 9 struct sockaddr_un fcgi_addr_un; 10 #endif 11 struct sockaddr_in fcgi_addr_in; 12 struct sockaddr *fcgi_addr; 13 14 socklen_t servlen; 15 16 #ifndef HAVE_FORK 17 return -1; 18 #endif 19 20 if (p->conf.debug) { 21 log_error_write(srv, __FILE__, __LINE__, "sdb", 22 "new proc, socket:", proc->port, proc->unixsocket); 23 } 24 #如果unixsocket套接字不为空,也就是有设置啦 25 if (!buffer_is_empty(proc->unixsocket)) { 26 memset(&fcgi_addr, 0, sizeof(fcgi_addr)); 27 28 #ifdef HAVE_SYS_UN_H 29 fcgi_addr_un.sun_family = AF_UNIX; 30 strcpy(fcgi_addr_un.sun_path, proc->unixsocket->ptr); 31 32 #ifdef SUN_LEN 33 servlen = SUN_LEN(&fcgi_addr_un); 34 #else 35 /* stevens says: */ 36 servlen = proc->unixsocket->used + sizeof(fcgi_addr_un.sun_family); 37 #endif 38 socket_type = AF_UNIX; 39 fcgi_addr = (struct sockaddr *) &fcgi_addr_un; 40 41 buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("unix:")); 42 buffer_append_string_buffer(proc->connection_name, proc->unixsocket); 43 44 #else 45 log_error_write(srv, __FILE__, __LINE__, "s", 46 "ERROR: Unix Domain sockets are not supported."); 47 return -1; 48 #endif 49 } else { #这里就是走tcp模式 50 fcgi_addr_in.sin_family = AF_INET; 51 52 if (buffer_is_empty(host->host)) { 53 fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 54 } else { 55 struct hostent *he; 56 57 /* set a useful default */ 58 fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 59 60 #获取主机的真实地址 61 if (NULL == (he = gethostbyname(host->host->ptr))) { 62 log_error_write(srv, __FILE__, __LINE__, 63 "sdb", "gethostbyname failed: ", 64 h_errno, host->host); 65 return -1; 66 } 67 68 if (he->h_addrtype != AF_INET) { 69 log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype); 70 return -1; 71 } 72 73 if (he->h_length != sizeof(struct in_addr)) { 74 log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length); 75 return -1; 76 } 77 78 memcpy(&(fcgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length); 79 80 }
#绑定端口 81 fcgi_addr_in.sin_port = htons(proc->port); 82 servlen = sizeof(fcgi_addr_in); 83 84 socket_type = AF_INET; 85 fcgi_addr = (struct sockaddr *) &fcgi_addr_in; 86 87 buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("tcp:")); 88 if (!buffer_is_empty(host->host)) { 89 buffer_append_string_buffer(proc->connection_name, host->host); 90 } else { 91 buffer_append_string_len(proc->connection_name, CONST_STR_LEN("localhost")); 92 } 93 buffer_append_string_len(proc->connection_name, CONST_STR_LEN(":")); 94 buffer_append_long(proc->connection_name, proc->port); 95 } 96 #建立socket 97 if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { 98 log_error_write(srv, __FILE__, __LINE__, "ss", 99 "failed:", strerror(errno));100 return -1;101 }102 #连接socket103 if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {104 /* server is not up, spawn it 连接失败,则说明服务器还没有起来 */105 pid_t child;106 int val;107 108 if (errno != ENOENT &&109 !buffer_is_empty(proc->unixsocket)) {110 unlink(proc->unixsocket->ptr);111 }112 113 close(fcgi_fd);114 115 /* reopen socket 重新打开socket */116 if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {117 log_error_write(srv, __FILE__, __LINE__, "ss",118 "socket failed:", strerror(errno));119 return -1;120 }121 122 val = 1;123 if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {124 log_error_write(srv, __FILE__, __LINE__, "ss",125 "socketsockopt failed:", strerror(errno));126 return -1;127 }128 129 /* create socket */130 if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {131 log_error_write(srv, __FILE__, __LINE__, "sbs",132 "bind failed for:",133 proc->connection_name,134 strerror(errno));135 return -1;136 }137 #监听socket138 if (-1 == listen(fcgi_fd, 1024)) {139 log_error_write(srv, __FILE__, __LINE__, "ss",140 "listen failed:", strerror(errno));141 return -1;142 }143 144 #ifdef HAVE_FORK 开始生产进程145 switch ((child = fork())) {146 case 0: {147 size_t i = 0;148 char *c;149 char_array env;150 char_array arg;151 152 /* create environment */153 env.ptr = NULL;154 env.size = 0;155 env.used = 0;156 157 arg.ptr = NULL;158 arg.size = 0;159 arg.used = 0;160 161 if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {162 close(FCGI_LISTENSOCK_FILENO);163 dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);164 close(fcgi_fd);165 }166 167 openDevNull(STDERR_FILENO);168 169 /* we don‘t need the client socket */170 for (i = 3; i < 256; i++) {171 close(i);172 }173 174 /* build clean environment */175 if (host->bin_env_copy->used) {176 for (i = 0; i < host->bin_env_copy->used; i++) {177 data_string *ds = (data_string *)host->bin_env_copy->data[i];178 char *ge;179 180 if (NULL != (ge = getenv(ds->value->ptr))) {181 env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge));182 }183 }184 } else {185 for (i = 0; environ[i]; i++) {186 char *eq;187 188 if (NULL != (eq = strchr(environ[i], ‘=‘))) {189 env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1));190 }191 }192 }193 194 /* create environment */195 for (i = 0; i < host->bin_env->used; i++) {196 data_string *ds = (data_string *)host->bin_env->data[i];197 198 env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));199 }200 201 for (i = 0; i < env.used; i++) {202 /* search for PHP_FCGI_CHILDREN */203 if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=", sizeof("PHP_FCGI_CHILDREN=") - 1)) break;204 }205 206 /* not found, add a default */207 if (i == env.used) {208 env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1"));209 }210 211 env.ptr[env.used] = NULL;212 #处理cgi程序名字213 parse_binpath(&arg, host->bin_path);214 215 /* chdir into the base of the bin-path,216 * search for the last / */217 if (NULL != (c = strrchr(arg.ptr[0], ‘/‘))) {218 *c = ‘\0‘;219 220 /* change to the physical directory */221 if (-1 == chdir(arg.ptr[0])) {222 *c = ‘/‘;223 log_error_write(srv, __FILE__, __LINE__, "sss", "chdir failed:", strerror(errno), arg.ptr[0]);224 }225 *c = ‘/‘;226 }227 228 229 /* exec the cgi 开始执行cgi */230 execve(arg.ptr[0], arg.ptr, env.ptr);231 232 /* log_error_write(srv, __FILE__, __LINE__, "sbs",233 "execve failed for:", host->bin_path, strerror(errno)); */234 235 exit(errno);236 237 break;238 }239 case -1:240 /* error */241 break;242 default:243 /* father */244 245 /* wait */246 select(0, NULL, NULL, NULL, &tv);247 248 switch (waitpid(child, &status, WNOHANG)) {249 case 0:250 /* child still running after timeout, good */251 break;252 case -1:253 /* no PID found ? should never happen */254 log_error_write(srv, __FILE__, __LINE__, "ss",255 "pid not found:", strerror(errno));256 return -1;257 default:258 log_error_write(srv, __FILE__, __LINE__, "sbs",259 "the fastcgi-backend", host->bin_path, "failed to start:");260 /* the child should not terminate at all */261 if (WIFEXITED(status)) {262 log_error_write(srv, __FILE__, __LINE__, "sdb",263 "child exited with status",264 WEXITSTATUS(status), host->bin_path);265 log_error_write(srv, __FILE__, __LINE__, "s",266 "If you‘re trying to run PHP as a FastCGI backend, make sure you‘re using the FastCGI-enabled version.\n"267 "You can find out if it is the right one by executing ‘php -v‘ and it should display ‘(cgi-fcgi)‘ "268 "in the output, NOT ‘(cgi)‘ NOR ‘(cli)‘.\n"269 "For more information, check http://trac.lighttpd.net/trac/wiki/Docs%3AModFastCGI#preparing-php-as-a-fastcgi-program"270 "If this is PHP on Gentoo, add ‘fastcgi‘ to the USE flags.");271 } else if (WIFSIGNALED(status)) {272 log_error_write(srv, __FILE__, __LINE__, "sd",273 "terminated by signal:",274 WTERMSIG(status));275 276 if (WTERMSIG(status) == 11) {277 log_error_write(srv, __FILE__, __LINE__, "s",278 "to be exact: it segfaulted, crashed, died, ... you get the idea." );279 log_error_write(srv, __FILE__, __LINE__, "s",280 "If this is PHP, try removing the bytecode caches for now and try again.");281 }282 } else {283 log_error_write(srv, __FILE__, __LINE__, "sd",284 "child died somehow:",285 status);286 }287 return -1;288 }289 290 /* register process */291 proc->pid = child;292 proc->last_used = srv->cur_ts;293 proc->is_local = 1;294 295 break;296 }297 #endif298 } else {299 proc->is_local = 0;300 proc->pid = 0;301 302 if (p->conf.debug) {303 log_error_write(srv, __FILE__, __LINE__, "sb",304 "(debug) socket is already used; won‘t spawn:",305 proc->connection_name);306 }307 }308 309 proc->state = PROC_STATE_RUNNING;310 host->active_procs++;311 312 close(fcgi_fd);313 314 return 0;315 }
上面的流程是这样的
1根据配置文件判断用哪一种方式通讯,有tcp,有unix自身套接字(这个有个局限性,要cgi在同一台机器才能用的
2,连接服务端,如果失败的话则为服务端创建套接字
if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
close(FCGI_LISTENSOCK_FILENO);
dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
close(fcgi_fd);
}
FCGI_LISTENSOCK_FILENO 这个是什么来的
根据fast-cgi协议是这样说的
当应用开始执行时,Web服务器留下一个打开的文件描述符,FCGI_LISTENSOCK_FILENO。该描述符引用Web服务器创建的一个正在监听的socket。
FCGI_LISTENSOCK_FILENO等于STDIN_FILENO。当应用开始执行时,标准的描述符STDOUT_FILENO和STDERR_FILENO被关闭。一个用于应用确定它是用CGI调用的还是用FastCGI调用的可靠方法是调用getpeername(FCGI_LISTENSOCK_FILENO),对于FastCGI应用,它返回-1,并设置errno为ENOTCONN。
Web服务器对于可靠传输的选择,Unix流式管道(AF_UNIX)或TCP/IP(AF_INET),是内含于FCGI_LISTENSOCK_FILENO socket的内部状态中的
呵呵
我们可以看到, 代码把fcgi_fd重定向到FCGI_LISTENSOCK_FILENO 这个身上,这个值为0,巧妙的地方就在这里了
我一直很奇怪在这里找不到accept,实在太笨啦,lighttpd相对于cgi只不过是一个客户端而已,不可能在lighttpd自己身上accept
所以唯一的办法就是把刚刚的套接字传递给cgi
为了证明这个东西,我们翻看了php-cgi的代码
1 int fcgi_fd = 0; 2 3 fastcgi = fcgi_is_fastcgi(); 4 if (bindpath) { 5 fcgi_fd = fcgi_listen(bindpath, 128); 6 if (fcgi_fd < 0) { 7 fprintf(stderr, "Couldn‘t create FastCGI listen socket on port %s\n", bindpath); 8 #ifdef ZTS 9 tsrm_shutdown();10 #endif11 return FAILURE;12 }13 fastcgi = fcgi_is_fastcgi();14 }15 if (fastcgi) {16 /* How many times to run PHP scripts before dying */17 if (getenv("PHP_FCGI_MAX_REQUESTS")) {18 max_requests = atoi(getenv("PHP_FCGI_MAX_REQUESTS"));19 if (max_requests < 0) {20 fprintf(stderr, "PHP_FCGI_MAX_REQUESTS is not valid\n");21 return FAILURE;22 }23 }24 25 /* make php call us to get _ENV vars */26 php_php_import_environment_variables = php_import_environment_variables;27 php_import_environment_variables = cgi_php_import_environment_variables;28 29 /* library is already initialized, now init our request */30 request = fcgi_init_request(fcgi_fd);
ok,bindpath是何方神圣,通常我们是这样启动cgi的
php-cgi -b 127.0.0.1:9000,我们在上面根本没有传递这个参数给php-cgi,所以判断不成立
在看看fastcgi这个变量是怎样判断的,我们看看这个函数
1 int fcgi_is_fastcgi(void)2 {3 if (!is_initialized) {4 return fcgi_init();5 } else {6 return is_fastcgi;7 }8 }
1 int fcgi_init(void) 2 { 3 if (!is_initialized) { 4 #ifndef _WIN32 5 sa_t sa; 6 socklen_t len = sizeof(sa); 7 #endif 8 zend_hash_init(&fcgi_mgmt_vars, 0, NULL, fcgi_free_mgmt_var_cb, 1); 9 fcgi_set_mgmt_var("FCGI_MPXS_CONNS", sizeof("FCGI_MPXS_CONNS")-1, "0", sizeof("0")-1);10 11 is_initialized = 1;12 #ifdef _WIN3213 # if 014 /* TODO: Support for TCP sockets */15 WSADATA wsaData;16 17 if (WSAStartup(MAKEWORD(2,0), &wsaData)) {18 fprintf(stderr, "Error starting Windows Sockets. Error: %d", WSAGetLastError());19 return 0;20 }21 # endif22 if ((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) &&23 (GetStdHandle(STD_ERROR_HANDLE) == INVALID_HANDLE_VALUE) &&24 (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE)) {25 char *str;26 DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_WAIT;27 HANDLE pipe = GetStdHandle(STD_INPUT_HANDLE);28 29 SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL);30 31 str = getenv("_FCGI_SHUTDOWN_EVENT_");32 if (str != NULL) {33 HANDLE shutdown_event = (HANDLE) atoi(str);34 if (!CreateThread(NULL, 0, fcgi_shutdown_thread,35 shutdown_event, 0, NULL)) {36 return -1;37 }38 }39 str = getenv("_FCGI_MUTEX_");40 if (str != NULL) {41 fcgi_accept_mutex = (HANDLE) atoi(str);42 }43 return is_fastcgi = 1;44 } else {45 return is_fastcgi = 0;46 }47 #else48 errno = 0;49 if (getpeername(0, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) {50 fcgi_setup_signals();51 return is_fastcgi = 1;52 } else {53 return is_fastcgi = 0;54 }55 #endif56 }57 return is_fastcgi;58 }
呵呵,注意49行用getpeername这个函数,传进去的是套接字是0,还记得我们刚才执行cgi的时候dup2吗,呵呵巧妙就在这里
也就是说如果我们fastcgi的话就直接采用fcgi_fd=0 这个套接字,谜底解开了吧
我们再次看看php-cgi是怎样accept的
1 #ifndef PHP_WIN32 2 /* Pre-fork, if required */ 3 if (getenv("PHP_FCGI_CHILDREN")) { 4 char * children_str = getenv("PHP_FCGI_CHILDREN"); 5 children = atoi(children_str); 6 if (children < 0) { 7 fprintf(stderr, "PHP_FCGI_CHILDREN is not valid\n"); 8 return FAILURE; 9 } 10 fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, children_str, strlen(children_str)); 11 /* This is the number of concurrent requests, equals FCGI_MAX_CONNS */ 12 fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, children_str, strlen(children_str)); 13 } else { 14 fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, "1", sizeof("1")-1); 15 fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, "1", sizeof("1")-1); 16 } 17 18 if (children) { 19 int running = 0; 20 pid_t pid; 21 22 /* Create a process group for ourself & children */ 23 setsid(); 24 pgroup = getpgrp(); 25 #ifdef DEBUG_FASTCGI 26 fprintf(stderr, "Process group %d\n", pgroup); 27 #endif 28 29 /* Set up handler to kill children upon exit */ 30 act.sa_flags = 0; 31 act.sa_handler = fastcgi_cleanup; 32 if (sigaction(SIGTERM, &act, &old_term) || 33 sigaction(SIGINT, &act, &old_int) || 34 sigaction(SIGQUIT, &act, &old_quit) 35 ) { 36 perror("Can‘t set signals"); 37 exit(1); 38 } 39 40 if (fcgi_in_shutdown()) { 41 goto parent_out; 42 } 43 44 while (parent) { 45 do { 46 #ifdef DEBUG_FASTCGI 47 fprintf(stderr, "Forking, %d running\n", running); 48 #endif 49 pid = fork(); 50 switch (pid) { 51 case 0: 52 /* One of the children. 53 * Make sure we don‘t go round the 54 * fork loop any more 55 */ 56 parent = 0; 57 58 /* don‘t catch our signals */ 59 sigaction(SIGTERM, &old_term, 0); 60 sigaction(SIGQUIT, &old_quit, 0); 61 sigaction(SIGINT, &old_int, 0); 62 break; 63 case -1: 64 perror("php (pre-forking)"); 65 exit(1); 66 break; 67 default: 68 /* Fine */ 69 running++; 70 break; 71 } 72 } while (parent && (running < children)); 73 74 if (parent) { 75 #ifdef DEBUG_FASTCGI 76 fprintf(stderr, "Wait for kids, pid %d\n", getpid()); 77 #endif 78 parent_waiting = 1; 79 while (1) { 80 if (wait(&status) >= 0) { 81 running--; 82 break; 83 } else if (exit_signal) { 84 break; 85 } 86 } 87 if (exit_signal) { 88 #if 0 89 while (running > 0) { 90 while (wait(&status) < 0) { 91 } 92 running--; 93 } 94 #endif 95 goto parent_out; 96 } 97 } 98 } 99 } else {100 parent = 0;101 }102 103 #endif /* WIN32 */104 }
呵呵cgi会先根据获取回来的 PHP_FCGI_CHILDREN 的设置去生成子进程,
/* not found, add a default */
if (i == env.used) {
env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1"));
}
lighttpd会默认设置一个啦
所谓的pre-fork模式啦
关于上次怎样区别父进程执行那些代码,子进程执行那些代码相信读者会很容易发现
父进程进行wait,然后做一个管理子进程的多小
子进程就开始accept啦
1 while (!fastcgi || fcgi_accept_request(request) >= 0) { 2 SG(server_context) = fastcgi ? (void *) request : (void *) 1; 3 init_request_info(request TSRMLS_CC); 4 CG(interactive) = 0; 5 6 if (!cgi && !fastcgi) { 7 while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { 8 switch (c) { 9 10 case ‘a‘: /* interactive mode */ 11 printf("Interactive mode enabled\n\n"); 12 CG(interactive) = 1; 13 break; 14 15 case ‘C‘: /* don‘t chdir to the script directory */ 16 SG(options) |= SAPI_OPTION_NO_CHDIR; 17 break; 18 19 case ‘e‘: /* enable extended info output */ 20 CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; 21 break; 22 23 case ‘f‘: /* parse file */ 24 if (script_file) { 25 efree(script_file); 26 } 27 script_file = estrdup(php_optarg); 28 no_headers = 1; 29 break; 30 31 case ‘i‘: /* php info & quit */ 32 if (script_file) { 33 efree(script_file); 34 } 35 if (php_request_startup(TSRMLS_C) == FAILURE) { 36 SG(server_context) = NULL; 37 php_module_shutdown(TSRMLS_C); 38 return FAILURE; 39 } 40 if (no_headers) { 41 SG(headers_sent) = 1; 42 SG(request_info).no_headers = 1; 43 } 44 php_print_info(0xFFFFFFFF TSRMLS_CC); 45 php_request_shutdown((void *) 0); 46 fcgi_shutdown(); 47 exit_status = 0; 48 goto out; 49 50 case ‘l‘: /* syntax check mode */ 51 no_headers = 1; 52 behavior = PHP_MODE_LINT; 53 break; 54 55 case ‘m‘: /* list compiled in modules */ 56 if (script_file) { 57 efree(script_file); 58 } 59 SG(headers_sent) = 1; 60 php_printf("[PHP Modules]\n"); 61 print_modules(TSRMLS_C); 62 php_printf("\n[Zend Modules]\n"); 63 print_extensions(TSRMLS_C); 64 php_printf("\n"); 65 php_output_end_all(TSRMLS_C); 66 fcgi_shutdown(); 67 exit_status = 0; 68 goto out; 69 70 #if 0 /* not yet operational, see also below ... */ 71 case ‘‘: /* generate indented source mode*/ 72 behavior=PHP_MODE_INDENT; 73 break; 74 #endif 75 76 case ‘q‘: /* do not generate HTTP headers */ 77 no_headers = 1; 78 break; 79 80 case ‘v‘: /* show php version & quit */ 81 if (script_file) { 82 efree(script_file); 83 } 84 no_headers = 1; 85 if (php_request_startup(TSRMLS_C) == FAILURE) { 86 SG(server_context) = NULL; 87 php_module_shutdown(TSRMLS_C); 88 return FAILURE; 89 } 90 if (no_headers) { 91 SG(headers_sent) = 1; 92 SG(request_info).no_headers = 1; 93 } 94 #if ZEND_DEBUG 95 php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); 96 #else 97 php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); 98 #endif 99 php_request_shutdown((void *) 0);100 fcgi_shutdown();101 exit_status = 0;102 goto out;103 104 case ‘w‘:105 behavior = PHP_MODE_STRIP;106 break;107 108 case ‘z‘: /* load extension file */109 zend_load_extension(php_optarg);110 break;111 112 default:113 break;114 }115 }116 117 if (script_file) {118 /* override path_translated if -f on command line */119 STR_FREE(SG(request_info).path_translated);120 SG(request_info).path_translated = script_file;121 /* before registering argv to module exchange the *new* argv[0] */122 /* we can achieve this without allocating more memory */123 SG(request_info).argc = argc - (php_optind - 1);124 SG(request_info).argv = &argv[php_optind - 1];125 SG(request_info).argv[0] = script_file;126 } else if (argc > php_optind) {127 /* file is on command line, but not in -f opt */128 STR_FREE(SG(request_info).path_translated);129 SG(request_info).path_translated = estrdup(argv[php_optind]);130 /* arguments after the file are considered script args */131 SG(request_info).argc = argc - php_optind;132 SG(request_info).argv = &argv[php_optind];133 }134 135 if (no_headers) {136 SG(headers_sent) = 1;137 SG(request_info).no_headers = 1;138 }139 140 /* all remaining arguments are part of the query string141 * this section of code concatenates all remaining arguments142 * into a single string, seperating args with a &143 * this allows command lines like:144 *145 * test.php v1=test v2=hello+world!146 * test.php "v1=test&v2=hello world!"147 * test.php v1=test "v2=hello world!"148 */149 if (!SG(request_info).query_string && argc > php_optind) {150 int slen = strlen(PG(arg_separator).input);151 len = 0;152 for (i = php_optind; i < argc; i++) {153 if (i < (argc - 1)) {154 len += strlen(argv[i]) + slen;155 } else {156 len += strlen(argv[i]);157 }158 }159 160 len += 2;161 s = malloc(len);162 *s = ‘\0‘; /* we are pretending it came from the environment */163 for (i = php_optind; i < argc; i++) {164 strlcat(s, argv[i], len);165 if (i < (argc - 1)) {166 strlcat(s, PG(arg_separator).input, len);167 }168 }169 SG(request_info).query_string = s;170 free_query_string = 1;171 }172 } /* end !cgi && !fastcgi */173 174 /*175 we never take stdin if we‘re (f)cgi, always176 rely on the web server giving us the info177 we need in the environment.178 */179 if (SG(request_info).path_translated || cgi || fastcgi) {180 file_handle.type = ZEND_HANDLE_FILENAME;181 file_handle.filename = SG(request_info).path_translated;182 file_handle.handle.fp = NULL;183 } else {184 file_handle.filename = "-";185 file_handle.type = ZEND_HANDLE_FP;186 file_handle.handle.fp = stdin;187 }188 189 file_handle.opened_path = NULL;190 file_handle.free_filename = 0;191 192 /* request startup only after we‘ve done all we can to193 * get path_translated */194 if (php_request_startup(TSRMLS_C) == FAILURE) {195 if (fastcgi) {196 fcgi_finish_request(request, 1);197 }198 SG(server_context) = NULL;199 php_module_shutdown(TSRMLS_C);200 return FAILURE;201 }202 if (no_headers) {203 SG(headers_sent) = 1;204 SG(request_info).no_headers = 1;205 }206 207 /*208 at this point path_translated will be set if:209 1. we are running from shell and got filename was there210 2. we are running as cgi or fastcgi211 */212 if (cgi || fastcgi || SG(request_info).path_translated) {213 if (php_fopen_primary_script(&file_handle TSRMLS_CC) == FAILURE) {214 zend_try {215 if (errno == EACCES) {216 SG(sapi_headers).http_response_code = 403;217 PUTS("Access denied.\n");218 } else {219 SG(sapi_headers).http_response_code = 404;220 PUTS("No input file specified.\n");221 }222 } zend_catch {223 } zend_end_try();224 /* we want to serve more requests if this is fastcgi225 * so cleanup and continue, request shutdown is226 * handled later */227 if (fastcgi) {228 goto fastcgi_request_done;229 }230 231 STR_FREE(SG(request_info).path_translated);232 233 if (free_query_string && SG(request_info).query_string) {234 free(SG(request_info).query_string);235 SG(request_info).query_string = NULL;236 }237 238 php_request_shutdown((void *) 0);239 SG(server_context) = NULL;240 php_module_shutdown(TSRMLS_C);241 sapi_shutdown();242 #ifdef ZTS243 tsrm_shutdown();244 #endif245 return FAILURE;246 }247 }248 249 if (CGIG(check_shebang_line)) {250 /* #!php support */251 switch (file_handle.type) {252 case ZEND_HANDLE_FD:253 if (file_handle.handle.fd < 0) {254 break;255 }256 file_handle.type = ZEND_HANDLE_FP;257 file_handle.handle.fp = fdopen(file_handle.handle.fd, "rb");258 /* break missing intentionally */259 case ZEND_HANDLE_FP:260 if (!file_handle.handle.fp ||261 (file_handle.handle.fp == stdin)) {262 break;263 }264 c = fgetc(file_handle.handle.fp);265 if (c == ‘#‘) {266 while (c != ‘\n‘ && c != ‘\r‘ && c != EOF) {267 c = fgetc(file_handle.handle.fp); /* skip to end of line */268 }269 /* handle situations where line is terminated by \r\n */270 if (c == ‘\r‘) {271 if (fgetc(file_handle.handle.fp) != ‘\n‘) {272 long pos = ftell(file_handle.handle.fp);273 fseek(file_handle.handle.fp, pos - 1, SEEK_SET);274 }275 }276 CG(start_lineno) = 2;277 } else {278 rewind(file_handle.handle.fp);279 }280 break;281 case ZEND_HANDLE_STREAM:282 c = php_stream_getc((php_stream*)file_handle.handle.stream.handle);283 if (c == ‘#‘) {284 while (c != ‘\n‘ && c != ‘\r‘ && c != EOF) {285 c = php_stream_getc((php_stream*)file_handle.handle.stream.handle); /* skip to end of line */286 }287 /* handle situations where line is terminated by \r\n */288 if (c == ‘\r‘) {289 if (php_stream_getc((php_stream*)file_handle.handle.stream.handle) != ‘\n‘) {290 long pos = php_stream_tell((php_stream*)file_handle.handle.stream.handle);291 php_stream_seek((php_stream*)file_handle.handle.stream.handle, pos - 1, SEEK_SET);292 }293 }294 CG(start_lineno) = 2;295 } else {296 php_stream_rewind((php_stream*)file_handle.handle.stream.handle);297 }298 break;299 case ZEND_HANDLE_MAPPED:300 if (file_handle.handle.stream.mmap.buf[0] == ‘#‘) {301 int i = 1;302 303 c = file_handle.handle.stream.mmap.buf[i++];304 while (c != ‘\n‘ && c != ‘\r‘ && c != EOF) {305 c = file_handle.handle.stream.mmap.buf[i++];306 }307 if (c == ‘\r‘) {308 if (file_handle.handle.stream.mmap.buf[i] == ‘\n‘) {309 i++;310 }311 }312 file_handle.handle.stream.mmap.buf += i;313 file_handle.handle.stream.mmap.len -= i;314 }315 break;316 default:317 break;318 }319 }320 321 switch (behavior) {322 case PHP_MODE_STANDARD:323 php_execute_script(&file_handle TSRMLS_CC);324 break;325 case PHP_MODE_LINT:326 PG(during_request_startup) = 0;327 exit_status = php_lint_script(&file_handle TSRMLS_CC);328 if (exit_status == SUCCESS) {329 zend_printf("No syntax errors detected in %s\n", file_handle.filename);330 } else {331 zend_printf("Errors parsing %s\n", file_handle.filename);332 }333 break;334 case PHP_MODE_STRIP:335 if (open_file_for_scanning(&file_handle TSRMLS_CC) == SUCCESS) {336 zend_strip(TSRMLS_C);337 zend_file_handle_dtor(&file_handle TSRMLS_CC);338 php_output_teardown();339 }340 return SUCCESS;341 break;342 case PHP_MODE_HIGHLIGHT:343 {344 zend_syntax_highlighter_ini syntax_highlighter_ini;345 346 if (open_file_for_scanning(&file_handle TSRMLS_CC) == SUCCESS) {347 php_get_highlight_struct(&syntax_highlighter_ini);348 zend_highlight(&syntax_highlighter_ini TSRMLS_CC);349 if (fastcgi) {350 goto fastcgi_request_done;351 }352 zend_file_handle_dtor(&file_handle TSRMLS_CC);353 php_output_teardown();354 }355 return SUCCESS;356 }357 break;358 #if 0359 /* Zeev might want to do something with this one day */360 case PHP_MODE_INDENT:361 open_file_for_scanning(&file_handle TSRMLS_CC);362 zend_indent();363 zend_file_handle_dtor(&file_handle TSRMLS_CC);364 php_output_teardown();365 return SUCCESS;366 break;367 #endif368 }369 370 fastcgi_request_done:371 {372 STR_FREE(SG(request_info).path_translated);373 374 php_request_shutdown((void *) 0);375 376 if (exit_status == 0) {377 exit_status = EG(exit_status);378 }379 380 if (free_query_string && SG(request_info).query_string) {381 free(SG(request_info).query_string);382 SG(request_info).query_string = NULL;383 }384 }385 386 if (!fastcgi) {387 if (benchmark) {388 repeats--;389 if (repeats > 0) {390 script_file = NULL;391 php_optind = orig_optind;392 php_optarg = orig_optarg;393 continue;394 }395 }396 break;397 }398 399 /* only fastcgi will get here */400 requests++;401 if (max_requests && (requests == max_requests)) {402 fcgi_finish_request(request, 1);403 if (bindpath) {404 free(bindpath);405 }406 if (max_requests != 1) {407 /* no need to return exit_status of the last request */408 exit_status = 0;409 }410 break;411 }412 /* end of fastcgi loop */413 }
最终会通过 php_execute_script(&file_handle TSRMLS_CC); 这个函数执行php脚本,到了这里我们可以回答我们上面的那个问题
num-procs = max-procs * ( 1 + PHP_FCGI_CHILDREN ) = 8个
本文玩,大家有什么疑问可以留言啊
参考文章 http://www.360doc.com/content/12/1027/22/834950_244161021.shtml
http://www.cppblog.com/woaidongmao/archive/2011/06/21/149101.html fast-cgi协议
http://blog.csdn.net/springfieldking/article/details/8204210 fast-cgi工作原理
lighttpd+fastcgi模块分析