在nginx中,master進程與worker進程之間使用了一種全雙工系統通訊方式--socketpair。socketpair 函數成功執行後會建立一對已經建立串連的socket對,兩個相互連信的進程分別使用其中一個socket進行讀寫操作,就能夠實現兩進程間的通訊。
查看nginx源碼,可以看到,下面的函數建立了socketpair
ngx_pid_tngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn){ u_long on; ngx_pid_t pid; ngx_int_t s; /. ......省略...... ./ if (respawn != NGX_PROCESS_DETACHED) { /* Solaris 9 still has no AF_LOCAL */ //建立socketpair if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "socketpair() failed while spawning \"%s\"", name); return NGX_INVALID_PID; } //非阻塞 if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //非阻塞 if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //非同步 on = 1; if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //設定將要在檔案描述詞fd上接收SIGIO 或 SIGURG事件訊號的進程或進程組標識 。 if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //設定close_on_exec,當通過exec函數族建立了新進程後,原進程的該socket會被關閉 if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //設定close_on_exec,當通過exec函數族建立了新進程後,原進程的該socket會被關閉 if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } ngx_channel = ngx_processes[s].channel[1]; } else { ngx_processes[s].channel[0] = -1; ngx_processes[s].channel[1] = -1; } ngx_process_slot = s; pid = fork(); switch (pid) { case -1: ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; case 0: //fork成功,子進程建立,同時相關socket描述符也會被複製一份 ngx_pid = ngx_getpid(); proc(cycle, data); break; default: break; } /. ......省略...... ./ return pid;}
fork成功後,原進程的descriptor也會被複製一份,如果在fork的進程中該描述符不再使用,需要我們及時關閉。
如果我們使用的是 fork->exec函數族 的形式建立新進程的話,我們可以採用更好的辦法來確保原有的descriptor被正常關閉,避免資源的泄漏。也就是上邊代碼中對socket調用fcntl(FD_CLOEXEC)函數,設定該socket的屬性:當exec函數族被調用後,該socket會被自動關閉。使用這種在socket建立後立即設定FD_CLOEXEC屬性的辦法,避免了我們在exec建立進程前手動關閉相關socket的操作,尤其是當有大量的descriptor被建立、管理的時候非常實用。
很多時候我們是使用下邊的方法進行操作的:
#include #include #include #include int main(){ pid_t pid; int fds[2]; int valRead, valWrite; if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { return 0; } pid = fork(); if (0 == pid) { pid = getpid(); printf("[%d]-child process start", pid); close(fds[0]); //read write on fds[1] write(fds[1], &valWrite, sizeof(valWrite)); read(fds[1], &valRead, sizeof(valRead)); } else if (0 < pid) { pid = getpid(); printf("[%d]-parent process continue", pid); close(fds[1]); //read write on fds[0] read(fds[0], &valRead, sizeof(valRead)); write(fds[0], &valWrite, sizeof(valWrite)); } else { printf("%s", "fork failed"); } return 0;}
可以看到,fork前,當前進程建立了一對socket,也就是socketpair。對於這對socket,可以看作一個是伺服器端fds[0],另一個是用戶端fds[1],通過fds[0]與fds[1]之間建立的連結,我們可以完成全雙工系統通訊。
fork執行後,建立了子進程。在子進程中,之前父進程建立的socketpair自然也會被複製一份為fds',存在於子進程中。父進程繼續執行。這個時候,在父進程和子進程中會存在相同socketpair。
試想,我們在主進程中向fds[0]中寫入資料,在子進程中的fds'[1]上就會讀取到該資料,這樣就實現了父進程與子進程間的通訊。當然,在主進程fds[1]上寫資料,在子進程fds'[0]上也會讀到寫入的資料。我們實際使用中,只需要保留一對socket用來通訊就可以了,另外兩個socket就可以分別在父進程和子進程中關閉不用了。
當然了,如果我們能夠把fds中的一個socket通過某種方式傳遞給另一個進程,那麼也可以實現socketpair處理序間通訊了。
以上就介紹了nginx 處理序間通訊-socketpair,包括了方面的內容,希望對PHP教程有興趣的朋友有所協助。