一、概述

   現(xiàn)在大家在和Java, PHP, .net寫(xiě)應(yīng)用程序時(shí),都會(huì)用到一些成熟的服務(wù)框架,所以開(kāi)發(fā)效率是比較高的。而在用C/C++寫(xiě)服務(wù)器程序時(shí),用的就五花八門(mén)了,有些人用ACE, 有些人用ICE(號(hào)稱比ACE強(qiáng)許多),等等,這類服務(wù)器框架及庫(kù)比較豐富,但入門(mén)門(mén)檻比較高,所以更多的人是自己直接寫(xiě)服務(wù)器程序,初始寫(xiě)時(shí)覺(jué)得比較簡(jiǎn)單,可時(shí)間久了,便會(huì)覺(jué)得難以擴(kuò)展,性能低,容易出錯(cuò)。其實(shí),Postfix 作者為我們提供了一個(gè)高效、穩(wěn)定、安全的服務(wù)器框架模型,雖然Postfix主要用作郵件系統(tǒng)的 mta,但其框架設(shè)計(jì)卻非常具有通用性。ACL(http://acl.sourceforge.net/) 的作者將Postfix的服務(wù)器框架模型抽取出來(lái),形成了更加通用的服務(wù)器程序開(kāi)發(fā)框架,使程序員在編寫(xiě)服務(wù)器程序時(shí)可以達(dá)到事半功倍的效果。本文主要介紹了ACL中acl_master服務(wù)器程序(基于Postifx服務(wù)器程序框架)的設(shè)計(jì)及功能。

  二、框架設(shè)計(jì)圖

  如下圖所示:

協(xié)作半駐留式服務(wù)器程序開(kāi)發(fā)框架 --- 基于 Postfix 服務(wù)器框架改造

  

  圖1--框架圖

     master主進(jìn)程為控制進(jìn)程,剛啟動(dòng)時(shí)其負(fù)責(zé)監(jiān)聽(tīng)所有端口服務(wù),當(dāng)有新的客戶端連接到達(dá)時(shí),master便會(huì)啟動(dòng)子進(jìn)程進(jìn)行服務(wù),而自己依然監(jiān)控服務(wù)端口,同時(shí)監(jiān)控子進(jìn)程的工作狀態(tài);而提供對(duì)外服務(wù)的子進(jìn)程在master啟動(dòng)時(shí),若沒(méi)有請(qǐng)求任務(wù)則不會(huì)被啟動(dòng),只有當(dāng)有連接或任務(wù)到達(dá)時(shí)才會(huì)被master 啟動(dòng),當(dāng)該服務(wù)子進(jìn)程處理完某個(gè)連接服務(wù)后并不立即退出,而是駐留在系統(tǒng)一段時(shí)間,等待可能的新連接到達(dá),這樣當(dāng)有新的連接到達(dá)時(shí)master就不會(huì)啟動(dòng)新的子進(jìn)程,因?yàn)橐呀?jīng)有處于空閑的子進(jìn)程在等待下一個(gè)連接請(qǐng)求;當(dāng)服務(wù)子進(jìn)程空閑時(shí)間達(dá)一定閥值后,就會(huì)選擇退出,將資源全部歸還操作系統(tǒng)(當(dāng)然,也可以配置成服務(wù)子進(jìn)程永不退出的模式)。因此,可以稱這種服務(wù)器框架為協(xié)作式半駐留式服務(wù)器框架,下面將會(huì)對(duì)協(xié)作式和半駐留作進(jìn)一步介紹。

  三、協(xié)作方式

  Postfix服務(wù)器框架設(shè)計(jì)的非常巧妙,因?yàn)閙aster畢竟屬于用戶空間進(jìn)程,不能象操作系統(tǒng)那樣可以控制每個(gè)進(jìn)程的運(yùn)行時(shí)間片,所以master主進(jìn)程必須與其服務(wù)子進(jìn)程之間協(xié)作好,以處理好以下幾個(gè)過(guò)程:

  1)新連接到達(dá)時(shí),master是該啟動(dòng)新的子進(jìn)程接管該連接還是由空閑子進(jìn)程直接接管

  2)master何時(shí)應(yīng)該啟動(dòng)新的子進(jìn)程

  3)新連接到達(dá),空閑子進(jìn)程池中的子進(jìn)程如何競(jìng)爭(zhēng)接管該連接

  4)子進(jìn)程異常退出時(shí),master如何處理新連接

  5)空閑子進(jìn)程如何選擇退出時(shí)間(空閑時(shí)間或服務(wù)次數(shù)應(yīng)決定子進(jìn)程的退出)

  6)master如何知道各個(gè)子進(jìn)程的工作狀態(tài)(是死了還是活著?)

  7)在不停止服務(wù)的前提下,服務(wù)子進(jìn)程程序如果在線更新、如何添加新的服務(wù)、如何在線更新子進(jìn)程配置

  8)如何減少所有子進(jìn)程與master之間的通訊次數(shù)從而降低master的負(fù)載

  四、流程圖

  1)  master主進(jìn)程流程圖

協(xié)作半駐留式服務(wù)器程序開(kāi)發(fā)框架 --- 基于 Postfix 服務(wù)器框架改造

  

  圖2--master進(jìn)程流程圖

  Postifx 中的 master 主進(jìn)程與各個(gè)子進(jìn)程之間的IPC通訊方式為管道,所以管道的個(gè)數(shù)與子進(jìn)程數(shù)是成正比的。如果管道中斷,則 master 認(rèn)為該管道所對(duì)應(yīng)的子進(jìn)程已經(jīng)退出,如果是異常退出,master還需要標(biāo)記該服務(wù)類子進(jìn)程池以防止該類子進(jìn)程異常退出頻繁而啟動(dòng)也異常頻繁(如果子進(jìn)程啟動(dòng)過(guò)于頻繁則會(huì)給操作系統(tǒng)造成巨大負(fù)載);另外,如果某類服務(wù)的子進(jìn)程在服務(wù)第一個(gè)連接時(shí)就異常退出,則master認(rèn)為該服務(wù)有可能是不可用的,所以當(dāng)有新的連接再到達(dá)時(shí)就會(huì)延遲啟動(dòng)該服務(wù)子進(jìn)程。

  當(dāng)服務(wù)子進(jìn)程池中有空閑子進(jìn)程時(shí),master便會(huì)把該服務(wù)端口的監(jiān)聽(tīng)權(quán)讓出,從而該服務(wù)的空閑子進(jìn)程在該服務(wù)端口上接收新的連接。當(dāng)某個(gè)子進(jìn)程獲得新的連接后便會(huì)立即通知master其已經(jīng)處于忙狀態(tài),master便立即查找該服務(wù)的子進(jìn)程進(jìn)程池還有無(wú)空閑子進(jìn)程,如果有則master依然不會(huì)接管該服務(wù)端口的監(jiān)聽(tīng)任務(wù);如果沒(méi)有了,則master立即接管該服務(wù)端口的監(jiān)聽(tīng)任務(wù),當(dāng)有新的連接到達(dá)時(shí),master先檢查有沒(méi)有該服務(wù)的空閑進(jìn)程,若有便讓出該服務(wù)端口的監(jiān)聽(tīng)權(quán),若沒(méi)有便會(huì)啟動(dòng)新的子進(jìn)程,然后讓出監(jiān)聽(tīng)權(quán)。

  2)  服務(wù)子進(jìn)程流程圖

協(xié)作半駐留式服務(wù)器程序開(kāi)發(fā)框架 --- 基于 Postfix 服務(wù)器框架改造

  

  圖3--子進(jìn)程流程圖

  在master主進(jìn)程剛啟動(dòng)時(shí),因?yàn)闆](méi)有任何服務(wù)請(qǐng)求,所以子進(jìn)程是不隨master一起啟動(dòng)的,此時(shí)所有服務(wù)端口的監(jiān)控工作是由master統(tǒng)一負(fù)責(zé),當(dāng)有客戶端連接到達(dá)時(shí),服務(wù)子進(jìn)程才由master啟動(dòng),進(jìn)而接收該新連接,在進(jìn)一步處理客戶端請(qǐng)求前,子進(jìn)程必須讓master進(jìn)程知道它已經(jīng)開(kāi)始" 忙"了,好由master來(lái)決定是否再次接管該服務(wù)端口的監(jiān)控任務(wù),所以子進(jìn)程首先向master發(fā)送“忙”消息,然后才開(kāi)始接收并處理該客戶端請(qǐng)求,當(dāng)子進(jìn)程完成了對(duì)該客戶端的請(qǐng)求任務(wù)后,需向master發(fā)送“空閑”消息,以表明自己又可以繼續(xù)處理新的客戶端連接任務(wù)了。這一“忙”一“閑”兩個(gè)消息,便體現(xiàn)了服務(wù)子進(jìn)程與master主進(jìn)程的協(xié)作特點(diǎn)。

  當(dāng)然,服務(wù)子進(jìn)程可以選擇合適的退出時(shí)機(jī):如果自己的服務(wù)次數(shù)達(dá)到配置的閥值,或自己空閑時(shí)間達(dá)到閥值,或與master主進(jìn)程之間的IPC管道中斷(一般是由master停止服務(wù)要求所有服務(wù)子進(jìn)程退出時(shí)或master要求所有服務(wù)子進(jìn)程重讀配置時(shí)而引起的),則服務(wù)子進(jìn)程便應(yīng)該結(jié)束運(yùn)行了。這個(gè)停止過(guò)程,一方面體現(xiàn)了子進(jìn)程與master主進(jìn)程之間的協(xié)作特點(diǎn),另一方面也體現(xiàn)了子進(jìn)程半駐留的特點(diǎn),從而體現(xiàn)子進(jìn)程進(jìn)程池的半駐留特性,這一特性的最大好處就是:按需分配,當(dāng)請(qǐng)求連接比較多時(shí),所啟動(dòng)運(yùn)行的子進(jìn)程就多,當(dāng)請(qǐng)求連接任務(wù)較少時(shí),只有少數(shù)的子進(jìn)程在運(yùn)行,其它的都退出了。

  3) 小結(jié)

  以上從整體上介紹了Postfix服務(wù)器框架模型中master主進(jìn)程與服務(wù)子進(jìn)程的邏輯流程圖,當(dāng)然,其內(nèi)部實(shí)現(xiàn)要點(diǎn)與細(xì)節(jié)遠(yuǎn)比上面介紹的要復(fù)雜,只是這些復(fù)雜層面別人已經(jīng)幫我們屏蔽了,我們所要做的是在此基礎(chǔ)上寫(xiě)出自己的應(yīng)用服務(wù)來(lái),下面簡(jiǎn)要介紹了基于Postfix的服務(wù)器框架改造抽象的ACL庫(kù)中服務(wù)器程序的開(kāi)發(fā)方式,以使大家比較容易上手。

  五、五種服務(wù)器框架模板簡(jiǎn)介

  要想使用ACL服務(wù)器框架庫(kù)開(kāi)發(fā)服務(wù)器程序,首先介紹兩個(gè)個(gè)小名詞:acl_master服務(wù)器主進(jìn)程與服務(wù)器模板。acl_master服務(wù)器主進(jìn)程與 Postfix中的master主進(jìn)程功能相似,它的主要作用是啟動(dòng)并控制所有的服務(wù)子進(jìn)程,這個(gè)程序不用我們寫(xiě),是ACL服務(wù)器框架中已經(jīng)寫(xiě)好的;所謂服務(wù)器模板,就是我們需要根據(jù)自己的需要從ACL服務(wù)器框架中選擇一種服務(wù)器模型(即服務(wù)器模板),然后在編寫(xiě)服務(wù)器時(shí)按該服務(wù)器模板的方式寫(xiě)即可。為什么還要選擇合適的服務(wù)器框架模板?這是因?yàn)镻ostfix本身就提供了三類服務(wù)器應(yīng)用形式(單進(jìn)程單連接進(jìn)程池、單進(jìn)程多連接進(jìn)程池、觸發(fā)器進(jìn)程池),這三類應(yīng)用形式便分別使用了Postfix中的三種服務(wù)器模板,此外,ACL中又增加了兩種服務(wù)器應(yīng)用形式(多線程進(jìn)程池、單進(jìn)程非阻塞進(jìn)程池)。下面分別就這五種服務(wù)器框架模板一一做簡(jiǎn)介:

  1) 單進(jìn)程單連接進(jìn)程池:由 acl_master 主進(jìn)程啟動(dòng)多個(gè)進(jìn)程組成進(jìn)程池提供某類服務(wù),但每個(gè)進(jìn)程每次只能處理一個(gè)客戶端連接請(qǐng)求

  2) 單進(jìn)程多連接進(jìn)程池:由 acl_master 主進(jìn)程啟動(dòng)多個(gè)進(jìn)程組成進(jìn)程池提供某類服務(wù),而每個(gè)進(jìn)程可以同時(shí)處理多個(gè)客戶端連接請(qǐng)求

  3) 觸發(fā)器進(jìn)程池:由 acl_master 主進(jìn)程啟動(dòng)多個(gè)進(jìn)程組成進(jìn)程池提供定時(shí)器類服務(wù)(類似于UNIX中的cron),當(dāng)某個(gè)定時(shí)器時(shí)間到達(dá)時(shí),便由一個(gè)進(jìn)程開(kāi)始運(yùn)行處理任務(wù)

  4) 多線程進(jìn)程池:由 acl_master 主進(jìn)程啟動(dòng)多個(gè)進(jìn)程組成進(jìn)程池提供某類服務(wù),而每個(gè)進(jìn)程是由多個(gè)線程組成,每個(gè)線程處理一個(gè)客戶端連接請(qǐng)求

  5) 單進(jìn)程非阻塞進(jìn)程池:由 acl_master 主進(jìn)程啟動(dòng)多個(gè)進(jìn)程組成進(jìn)程池提供某類服務(wù),每個(gè)進(jìn)程可以并發(fā)處理多個(gè)連接(類似于Nginx, Lighttpd, Squid, Ircd),由于采用非阻塞技術(shù),該模型服務(wù)器的并發(fā)處理能力大大提高,同時(shí)系統(tǒng)資源消耗也最小;當(dāng)然,該模型與單進(jìn)程多連接進(jìn)程池采用的技術(shù)都是非阻塞技術(shù),但該模型進(jìn)行更多的應(yīng)用封裝與高級(jí)處理,使編寫(xiě)非阻塞程序更加容易

  以上五種服務(wù)器方式中,由于可以根據(jù)需要配置成多個(gè)進(jìn)程實(shí)例,所以可以充分地利用多核的系統(tǒng)。其中,第5種的運(yùn)行效率是最高的,當(dāng)然其編程的復(fù)雜度要比其它的高,而第1種是執(zhí)行效率最低的,其實(shí)它也是最安全的(在Postfix中的smtpd/smtp 等進(jìn)程就屬于這一類),而相對(duì)來(lái)說(shuō),第4種在運(yùn)行效率與編寫(xiě)復(fù)雜度方面是一個(gè)比較好的折衷,所以在寫(xiě)一般性服務(wù)器時(shí),該服務(wù)器模型是作者推薦的方案,此外,第4種方案還有一個(gè)好處,可以做到對(duì)于空連接不必占用線程,這樣也大大提供了并發(fā)度(即線程數(shù)可以遠(yuǎn)小于連接數(shù))。

  六、使用簡(jiǎn)介

  本節(jié)暫不介紹具體的編程過(guò)程,只是介紹一些配置使用過(guò)程。假設(shè)已經(jīng)寫(xiě)好了服務(wù)器程序:echo_server, 該程序可以采用上面的 1), 2), 4), 5) 中的任一服務(wù)器模型來(lái)寫(xiě),假設(shè)采用了第4)種;另外,還假設(shè):acl_master等所有文件安裝的根目錄為 /opt/acl/, 主進(jìn)程程序 acl_master 及 echo_server 安裝在 /opt/acl/libexec/,  acl_master 主程序配置文件安裝在 /opt/acl/conf/,echo_server 配置文件安裝在 /opt/acl/conf/service/, 日志文件目錄為 /opt/acl/var/log/, 進(jìn)程號(hào)文件目錄為 /opt/acl/var/pid/。比如,你讓 echo_server 的服務(wù)端口為 6601,服務(wù)地址為 127.0.0.1, 它只是提供簡(jiǎn)單的 echo 服務(wù)。

  你可以運(yùn)行 /opt/acl/sh/start.sh 腳本來(lái)啟動(dòng) acl_master 主進(jìn)程(用 ps -ef|grep acl_master 會(huì)看到 acl_master 進(jìn)程存在 ,但 ps -ef|grep echo_server 卻沒(méi)有發(fā)現(xiàn)它的存在),然后你在一個(gè)Unix終端上 telnet 127.0.0.1 6601, 在另一個(gè)終端上 ps -ef|grep echo_server 就會(huì)發(fā)現(xiàn)有一個(gè) echo_server子進(jìn)程了,然后在 telnet 6601 的終端上隨便輸入一些字符串,便會(huì)立即得到回復(fù),關(guān)閉該 telnet 連接,用 ps -ef|grep echo_server 會(huì)發(fā)現(xiàn)該進(jìn)程依然存在,當(dāng)再次 telnet 127.0.0.1 6601 時(shí),該echo_server進(jìn)程又繼續(xù)為新連接提供服務(wù)了。可以試著多開(kāi)幾個(gè)終端同時(shí) telnet 127.0.0.1 6601,看看運(yùn)行效果如何?

  注意,服務(wù)子進(jìn)程的配置文件格式需要與其所采用的模板類型一致;進(jìn)程池中最大進(jìn)程個(gè)數(shù)、進(jìn)程中線程池最大線程個(gè)數(shù)、進(jìn)程最大服務(wù)次數(shù)、空閑時(shí)間等都是可以以配置文件中指定的。