spawn-fcgi 代碼介紹

原文轉自: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, intfork_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等。好久以前看的在這裏備忘一下。

相關文章
相關標籤/搜索