多程序你可能很熟悉,也許有一套自己的使用習慣和方法。這東西沒有什麼權威建議,書上只是給出了基本知識點,至於具體怎麼去用,因人而異。nginx在多程序設計方面有很多值得學習和借鑑的東西,我認為是一套比較好的實現方案。你也許認為這東西很簡單,是老生常談的東西了,但是我這裡要提醒你一下,俗話道酒是陳的香,越經典的東西越值得去琢磨,不要對自己太自信。善於思考的傢伙總是會在一些老的技術上給你許多新鮮的見解,這種牛人你不會沒遇到過吧!扯淡罷了,回到正題。
看函式ngx_spawn_process的骨架:
for (s = 0; s < ngx_last_process; s++)
.../*
* 實現非同步通知的兩個步驟,在nginx事件模型的rtsig機制中會用到
* 參考:
*/if (ioctl(ngx_processes[s].channel[0], fioasync, &on) == -1)
if (fcntl(ngx_processes[s].channel[0], f_setown, ngx_pid) == -1)
...ngx_channel = ngx_processes[s].channel[1];
...// 當前產生的子程序在ngx_processes陣列中的下標
ngx_process_slot = s;
/* * 在fork之後,父程序的ngx_processes陣列,「傳遞」給了子程序,但是這時子程序拿到的陣列是截至建立該程序之前其他程序的資訊。
* 由於子程序是父程序fork得到的,那麼在之後父程序的操作結果在子程序中就不可見了。假設當前誕生的是程序1,用p1表示,當父程序
* 建立p5時,那麼p2-p5的程序資訊在p1中是缺失的,那麼p1需要這些資訊嗎?如果需要的話,該通過什麼手段給它呢?
* 見ngx_start_worker_processes相關分析
*/ngx_pid = fork();
switch (pid)
...ngx_processes[s].pid = pid;
...if (s == ngx_last_process)
/* * 父程序,也就是master程序,依次建立work子程序,為了確保ngx_processes陣列在子程序間同步,每次建立完乙個子程序,
* 就通過ngx_pass_open_channel,做一次廣播,告訴先前已經建立的子程序: "新程序誕生,注意更新程序陣列相關項"
* 那麼這些子程序又是如何更新這些資訊的呢?答案在ngx_worker_process_init中。
*/ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
}
在worker程序的程序資訊陣列中,把當前worker程序資訊結構(即ngx_process_slot對應的位置)中的channel[0]關掉,
同時會把其他程序的channel[1]關掉,而只把他們的channel[0]留著,如下**所示。那麼這樣做的意圖是什麼?
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
if (n == ngx_process_slot)
if (ngx_processes[n].channel[1] == -1)
if (close(ngx_processes[n].channel[1]) == -1)
}if (close(ngx_processes[ngx_process_slot].channel[0]) == -1)
}
在ngx_worker_process_init的最後,呼叫了函式ngx_add_channel_event:
ngx_add_channel_event(cycle, ngx_channel, ngx_read_event,ngx_channel_handler)
主要的目的是向epoll中註冊讀事件。讀資料的fd為變數ngx_channel,處理函式是ngx_channel_handler,那麼ngx_channel是什麼, 這個處理函式又做了些什麼呢?
在ngx_spawn_process函式中,有句:ngx_channel = ngx_processes[s].channel[1],我們知道s是本次用來放置新建立程序資訊的(在ngx_processes中)位置,即也是ngx_process_slot變數,因為這兩個變數,ngx_channel和ngx_process_slot是在fork之前設定的,那麼在隨後子程序的init時,就會用到這兩個剛剛在父程序中設定的值,ngx_add_channel_event中的ngx_channel就是當前子程序的channle[1],所以這裡的意圖也就明了了,該worker程序就是通過監聽channel[1]的讀事件來獲取資訊。好了,然後我們搜遍所有**,並沒有看到在某個channel上監聽寫事件的動作,那麼我們要問了,子程序有沒有需要寫一些資料的時候呢?好吧,我們全文搜尋"channel[0]",看看會發現什麼。我想你肯定發現了ngx_write_channel函式,它的作用就是往channel[0]中去寫資料,那麼是都是誰在寫呢?
我們找到了ngx_pass_open_channel,也就是我上文所說的「廣播」。你要知道的一點就是,父程序中通過向channle[0]裡寫資料,可以在子程序中相應的channel[1]中讀取。好了到這裡我們前面提到的一些疑問基本上都有了答案,父程序通過向各個channel[0]中「廣播」資料,子程序在其自己的channel[1]中讀取相應的資料,他們正是通過這種方式來通訊的。那麼nginx程序間通訊的機制僅僅就是這些嗎?遠不止。。。
我們前面提到父程序寫channel[0],子程序讀channel[1],那麼父程序都傳了些啥?我們最容易看到的載體是ngx_channel_t結構,在ngx_pass_open_channel中,引數ch就是這樣的乙個結構:
/*
* command傳遞的命令
* pid本次產生的新程序pid
* slot新程序在ngx_processes陣列中的位置
* fd新程序中channel[0]
*/typedef struct ngx_channel_t;
對於command,在當前為ngx_cmd_open_channel,也就是告訴其他的子程序,「某個新的程序剛剛誕生,注意同步相關資訊」。那麼子程序得到這個資訊會怎麼做呢?
還記得這個ngx_channel_handler函式嗎,在註冊讀事件時設定的,它裡面會呼叫ngx_read_channel來或者這個channel資料。如果發現是ngx_cmd_open_channel命令,那麼就在ngx_processes的相應位置上更新新誕生程序的資訊:
ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
這個ngx_cmd_open_channel算是最簡單的命令了,其他的處理有什麼特別的地方?
以上我們討論的這些算是基礎設施了,在這之上,nginx做了很多的好東西。目前看到的只是在nginx初始化時做的事情,那麼在實際執行中,程序間又有哪些互動和通訊呢?後面的文章討論。一篇討**章太長的話,大家都蛋疼,你懂得。。。
從nginx角度看伺服器多程序模型設計 二
在ngx master process cycle中要處理眾多的全域性變數,正是通過一些訊號處理函式設定這些變數,才會後面檢測到一些事件的發生。我們來看看都有哪些預定義的事件,以及他們是如何被處理的。多數的事件來自於nginx的使用者,他們可能終止nginx,重啟,重讀配置等等,這些操作則主要依賴於...
多程序伺服器
基於tcp實現多程序伺服器 伺服器端 1 建立套接字 include include int socket int domain,int type,int protocol domain 乙個位址描述。目前僅支援af inet格式,也就是說arpa internet位址格式。type 指定socke...
多程序伺服器
注意 包含了 wrap.c 和 wrap.h 檔案在上篇部落格中 server.c include include include in.h include include include include include include wrap.h define maxline 8192 defi...