Nginx原始碼分析與實踐---程序間通訊機制(套接字)
在上一篇中,我們看到了nginx共享記憶體方式的程序間通訊。這次我們看下nginx使用套接字的程序間通訊方式。
同樣的幾個問題:
1.什麼時候需要使用套接字方式的程序間通訊機制呢?
舉個栗子:我們知道nginx有master程序和worker程序,那麼master程序是如何向worker程序傳送訊息的呢?worker程序又是如何接收master傳送過來的訊息呢?答案就是使用套接字。注意的是雖然套接字是雙工的,但目前套接字僅用於master程序管理worker程序,而沒用於worker發訊息給master,或者worker程序間通訊(參考:《深入理解Nginx》).
2.nginx的套接字是如何實現的呢?
nginx的套接字是本機套接字,即通過socketpair建立的,不是網路套接字(通過socket建立)。
socketpair:
int socketpair(int family, int type, int protocol, int sockfd[2]);
返回值:成功:0;出錯:-1
socketpair僅適用於unix域套接字,因此,family必須為AF_LOCAL,protocol必須為0。type可以為SOCK_STREAM或SOCK_DGRAM,表示使用TCP還是UDP。sockfd[2]是套接字對,套接字是雙工的。因此,通常都是父程序fork子程序之前呼叫socketpair建立套接字對,接著父程序fork子程序,子程序繼承套接字對。父程序關閉sockfd[1],子程序關閉sockfd[0]。父程序通過sockfd[0]傳送接收訊息,子程序通過sockfd[1]傳送接收訊息。
下面看一個關鍵函式: ngx_spawn_process
.../os/unix/ngx_process.c:
/* 建立套接字 派生子程序 */ ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { ...... if (respawn != NGX_PROCESS_DETACHED) { /* Solaris 9 still has no AF_LOCAL */ /* 建立套接字用於master與worker程序間通訊 */ 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; } ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0, "channel %d:%d", ngx_processes[s].channel[0], ngx_processes[s].channel[1]); /* 套接字設定為非阻塞 */ if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, ngx_nonblocking_n " failed while spawning \"%s\"", name); ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } ...... /* master 建立worker程序 */ pid = fork(); ......
我們看到,在ngx_spawn_process函式內,先通過socketpair建立套接字,建立的套接字放在ngx_processes[s].channel內。而ngx_processes是個結構體陣列,元素型別是ngx_process_t
.../os/unix/ngx_process.h:
typedef struct {
ngx_pid_t pid;
int status;
ngx_socket_t channel[2]; /* socketpair建立的套接字對,用於master與worker間通訊*/
ngx_spawn_proc_pt proc;
void *data;
char *name;
unsigned respawn:1;
unsigned just_spawn:1;
unsigned detached:1;
unsigned exiting:1;
unsigned exited:1;
} ngx_process_t;
socketpair建立的套接字正是放在ngx_process_t結構體的channel成員這。因為nginx一個程序是單執行緒的,每個程序要處理數以萬,以幾十萬計的連線,因此都非常忙,所以關鍵一點就是nginx的程序一定不能阻塞!!!一阻塞,nginx效率就會大幅度下降。因此,可以看到在ngx_spawn_process函式內部,建立完套接字後,接著就是通過ngx_nonblocking將套接字設定為非阻塞,然後才可以通過fork()建立worker程序。
3.master如何通過套接字向worker傳送訊息呢?
上面一節介紹瞭如何建立套接字,下面講如何通過套接字來發送訊息。介紹之前得先知道nginx目前只是將套接字用於master向worker傳送命令,而沒用於worker間通訊。
有了套接字就相當於有了傳送命令的視窗,接下來就是要構造包裝命令的工具,以及傳送接收命令等相配套的方法了。nginx是通過ngx_channel_t(頻道)來包裝命令的。master通過頻道就可以向worker傳送訊息。
下面看ngx_channel_t的原始碼:
.../os/unix/ngx_channel.h
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_CHANNEL_H_INCLUDED_
#define _NGX_CHANNEL_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
/* 頻道定義 */
typedef struct {
ngx_uint_t command; /* 傳遞的命令 */
ngx_pid_t pid; /* 傳送命令方的程序ID */
ngx_int_t slot; /* 傳送命令方在程序陣列ngx_processex中的序號 */
ngx_fd_t fd; /* 套接字控制代碼 */
} ngx_channel_t;
/* 頻道相關的方法 */
ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
ngx_log_t *log);
ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
ngx_log_t *log);
ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
ngx_int_t event, ngx_event_handler_pt handler);
void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);
#endif /* _NGX_CHANNEL_H_INCLUDED_ */
ngx_channel_t的command就是要傳送的命令,型別有:
.../os/unix/ngx_process_cycle.h:
/* 開啟頻道,使用頻道前必須先發送此命令 */
#define NGX_CMD_OPEN_CHANNEL 1
/* 關閉已經開啟的頻道 */
#define NGX_CMD_CLOSE_CHANNEL 2
/* 要求接收方正常退出 */
#define NGX_CMD_QUIT 3
/* 要求接收方強制結束程序 */
#define NGX_CMD_TERMINATE 4
/* 要求接收方重新開啟已經開啟的檔案 */
#define NGX_CMD_REOPEN 5
通過這些命令,master程序就能控制worker程序了。之後是4個頻道配套的方法。
傳送頻道:
ngx_write_channel:s是建立的套接字,ch是頻道,size是頻道大小,log是日誌物件。可以看到函式通過套接字s傳送了頻道ch。
接收頻道:
ngx_read_channel。引數都一樣,注意的是master是用s[0]傳送,worker是用s[1]接收。
新增頻道事件:
ngx_add_channel_event。nginx的worker程序都是非常繁忙的,如何讓worker知道有頻道訊息過來了呢?方法就是將套接字加入到epoll中,使worker程序既能處理使用者請求,又能接收master程序的訊息。當woker從epoll讀取到相應的套接字,就可以呼叫ngx_read_channel讀取頻道訊息了。
cycle:程序的結構體;fd:接收套接字,即s[1];event:epoll事件,EPOLLIN;handler:回撥函式
關閉頻道:
ngx_close_channel。fd:套接字陣列,即s[2]
參考:《深入理解Nginx》