原文轉自:http://chenzhenianqing.cn/articles/936.htmlphp
spawn-fcgi是一個小程序,做用是管理fast-cgi進程,功能和php-fpm相似,簡單小巧,原先是屬於lighttpd的一部分,後來因爲使用比較普遍,因此就遷移出來做爲獨立項目了,本文介紹的是這個版本「spawn-fcgi-1.6.3」。不過從發佈新版本到目前已經4年了,代碼一直沒有變更,需求少,基本知足了。另外php有php-fpm後,碼農們不再擔憂跑不起FCGI了。html
好久以前看的spawn-fcgi的代碼,當時由於須要改一下里面的環境變量。今天翻代碼看到了就順手記錄一下,就當沉澱.備忘吧。web
用spawn啓動FCGI程序的方式爲:./spawn-fcgi -a 127.0.0.1 -p 9003 -F ${count} -f ${webroot}/bin/demo.fcgishell
這樣就會啓動count個demo.fcgi程序,他們共同監聽同一個listen端口9003,從而提供服務。小程序
spawn-fcgi代碼不到600行,很是簡短精煉,從main看起。其功能主要是打開監聽端口,綁定地址,而後fork-exec建立FCGI進程,退出完成工做。session
老方法,main函數使用getopt解析命令行參數,從而設置全局變量。若是設置了-P參數,須要保存Pid文件,就用open系統調用打開文件。以後根據是不是root用戶啓動,若是是root,得作相關的權限設置,好比chroot, chdir, setuid, setgid, setgroups等。app
重要的是調用了bind_socket打開綁定本地監聽地址,或者sock,再就是調用fcgi_spawn_connection建立FCGI進程,主要就是這2步。socket
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\n" , 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\n" , 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\n" , strerror ( errno )); |
12 |
return -1; |
13 |
} |
14 |
15 |
if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) { |
16 |
fprintf (stderr, "spawn-fcgi: bind failed: %s\n" , 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\n" , 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\n" , 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\n" , strerror ( errno )); |
41 |
return -1; |
42 |
} |
43 |
44 |
return fcgi_fd; |
45 |
} |
fcgi_spawn_connection函數的工做是循環一次次建立子進程,而後當即調用execv(appArgv[0], appArgv);替換可執行程序,也就試運行demo.fcgi。php-fpm
1 |
static int fcgi_spawn_connection( char *appPath, char **appArgv, int fcgi_fd, int fork_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\n" , 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\n" , 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\n" , 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, "\n" , 1); |
22 |
} |
23 |
} |
24 |
25 |
break ; |
基本就是上面的東西了,代碼很少,但該有的都有,命令行解析,socket,fork,dup2等。好久以前看的在這裏備忘一下。