首頁 >後端開發 >php教程 >nginx 進程間通訊-socketpair

nginx 進程間通訊-socketpair

WBOY
WBOY原創
2016-07-29 08:58:141457瀏覽

    在nginx中,master進程與worker進程之間使用了全雙工通訊方式--socketpair。 socketpair 函數成功執行後會建立一對已經建立連線的socket對,兩個相互通訊的進程分別使用其中一個socket進行讀寫操作,就能夠實現兩個進程間的通訊。

    查看nginx原始碼,可以看到,下面的函數創建了socketpair

ngx_pid_t
ngx_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 <sys/types.h>
#include <sys/socket.h>

#include <stdlib.h>
#include <stdio.h>

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教程有興趣的朋友有所幫助。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn