1、迭代型和并發(fā)型服務(wù)器
對于使用 socket 的網(wǎng)絡(luò)服務(wù)器程序,有兩種常見的設(shè)計方式:
迭代型:服務(wù)器每次只處理一個客戶端,只有當(dāng)完全處理完一個客戶端的請求后才去處理下一個客戶端
并發(fā)型:能夠同時處理多個客戶端的請求
1.1、代型 UDP echo 服務(wù)器
server
int?main(int?argc,char*?argv[]) { ????int?sfd; ????ssize_t?numRead; ????socklen_t?addrLen,len; ????struct?sockaddr_storage?claddr; ????char?buf[BUF_SIZE]; ????char?addrStr[IS_ADDR_STR_LEN]; ????if(becomeDaemon(0)?==?-1) ????????errExit("becomeDaemon()"); ???? ????sfd?=?inetBind(SERVICE,SOCK_DGRAM,&addrLen); ????if(sfd?==?-1) ????{ ????????syslog(LOG_ERR,"Could?not?create?server?socket?(%s)",strerror(errno)); ????????exit(EXIT_FAILURE); ????} ????for(;;) ????{ ????????len?=?sizeof(struct?sockaddr_storage); ????????numRead?=?recvfrom(sfd,buf,BUF_SIZE,0,(struct?sockaddr*)&claddr,&len); ????????if(numRead?==?-1) ????????????errExit("recvfrom()"); ???????? ????????if(sendto(sfd,buf,numRead,0,(struct?sockaddr*)&claddr,len)?!=?numRead) ????????{ ????????????syslog(LOG_WARNING,"Error?echoing?response?to?%s?(%s)",inetAddressStr((struct?sockaddr*)&claddr,len,addrStr,IS_ADDR_STR_LEN),strerror(errno)); ????????} ????} }
client
int?main(int?argc,char*?argv[]) { ????int?sfd,j; ????size_t?len; ????ssize_t?numRead; ????char?buf[BUF_SIZE]; ????if(argc?2?||?strcmp(argv[1],"--help")?==?0) ????{ ????????printf("%s?host?msg... ",argv[0]); ????????exit(EXIT_SUCCESS); ????} ????sfd?=?inetConnect(argv[1],SERVICE,SOCK_DGRAM); ????if(sfd?==?-1) ????????errExit("Colud?not?connect?to?server?port"); ???? ????for(j?=?2;j?1.2、并發(fā)型 TCP echo 服務(wù)器
static?void?grimReaper(int?sig) { ????int?savedErrno; ????savedErrno?=?errno; ????while(waitpid(-1,NULL,WNOHANG)?>?0) ????????continue; ???? ????errno?=?savedErrno; } static?void?handleRequest(int?cfd) { ????char?buf[BUF_SIZE]; ????ssize_t?numRead; ????while((numRead?=?read(cfd,buf,BUF_SIZE))?>?0) ????{ ????????if(write(cfd,buf,numRead)) ????????{ ????????????syslog(LOG_ERR,"write()?failed?:?%s",strerror(errno)); ????????????exit(EXIT_SUCCESS); ????????} ????} ????if(numRead?==?-1) ????{ ????????syslog(LOG_ERR,"Error?from?read()?:?%s",strerror(errno)); ????????exit(EXIT_SUCCESS); ????} } int?main(int?argc,char*?argv[]) { ????int?lfd,cfd; ????struct?sigaction?sa; ????if(becomeDaemon(0)?==?-1) ????????errExit("becomeDaemon()"); ???? ????sigemptyset(&sa.sa_mask); ????sa.sa_flags?=?SA_RESTART; ????sa.sa_handler?=?grimReaper; ????if(sigaction(SIGCHLD,&sa,NULL)?==?-1) ????{ ????????syslog(LOG_ERR,"Error?from?sigaction()?:?%s",strerror(errno)); ????????exit(EXIT_FAILURE); ????} ????lfd?=?inetListen(SERVICE,10,NULL); ????if(lfd?==?-1) ????{ ????????syslog(LOG_ERR,"Could?not?create?server?socket?:?(%s)",strerror(errno)); ????????exit(EXIT_FAILURE); ????} ????for(;;) ????{ ????????cfd?=?accept(lfd,NULL,NULL); ????????if(cfd?==?-1) ????????{ ????????????syslog(LOG_ERR,"Failure?in?accept?:?(%s)",strerror(errno)); ????????????exit(EXIT_FAILURE); ????????} ???????? ????????switch(fork()) ????????{ ????????????case?-1: ????????????????syslog(LOG_ERR,"Can?not?create?child?:?(%s)",strerror(errno)); ????????????????close(cfd); ????????????????break; ????????????case?0: ????????????????close(lfd); ????????????????handleRequest(cfd); ????????????????_exit(EXIT_SUCCESS); ????????????default: ????????????????close(cfd); ????????????????break; ????????} ????} }1.3、并發(fā)型服務(wù)器的其他設(shè)計方案
對于一個負(fù)載很高的服務(wù)器來說,為每個客戶端創(chuàng)建一個新的子進(jìn)程或者線程所帶來的開銷對服務(wù)器來說是沉重的負(fù)擔(dān)。
可以考慮下面的幾種方案:
在服務(wù)器上預(yù)先創(chuàng)建進(jìn)程或線程
服務(wù)器程序在啟動階段(即在任何客戶端請求到來之前)就立刻預(yù)先創(chuàng)建好一定數(shù)量的子進(jìn)程(線程),而不是針對每個客戶端來創(chuàng)建一個新的子進(jìn)程(線程),這些子進(jìn)程(線程)構(gòu)成一個服務(wù)池
服務(wù)池中每個子進(jìn)程一次只處理一耳光客戶端,在處理完客戶端請求后,子進(jìn)程并不會終止,而是獲取下一個待處理的客戶端繼續(xù)處理
采用上述的服務(wù)池時,在負(fù)載高峰期應(yīng)該動態(tài)增加服務(wù)池的大小,在負(fù)載降低時,應(yīng)該相應(yīng)地降低服務(wù)池大小。
在單個進(jìn)程中處理多個客戶端
為了實現(xiàn)這一點,必須采用一種允許單個進(jìn)程同時監(jiān)視多個文件描述符 IO 事件的 IO 模型。
必須依靠內(nèi)核來確保每個服務(wù)進(jìn)程能公平地訪問到服務(wù)器主機的資源。
采用服務(wù)器集群
用來處理高客戶端負(fù)載的方法還包括使用多個服務(wù)器系統(tǒng),即服務(wù)器集群。
構(gòu)建服務(wù)器集群最簡單的方法就是 DNS 輪轉(zhuǎn)負(fù)載共享(DNS round-robin load sharing)或者負(fù)載分發(fā)(load distribution)。一個地區(qū)的域名權(quán)威服務(wù)器將同一個域名映射到多個 IP 地址上,后續(xù)對 DNS 服務(wù)器的域名解析請求將以循環(huán)輪轉(zhuǎn)的方式以不同的順序返回這些 IP 地址。
DNS 循環(huán)輪轉(zhuǎn)的優(yōu)勢是成本低,而且容易實施。但是也存在一些問題,其中一個問題是遠(yuǎn)端 DNS 服務(wù)器上所執(zhí)行的緩存操作,這意味著今后位于某個特定主機上的客戶端發(fā)出的請求會繞過循環(huán)輪轉(zhuǎn) DNS 服務(wù)器,并總是由同一個服務(wù)器來負(fù)責(zé)處理。此外,循環(huán)輪轉(zhuǎn) DNS 并沒有任何內(nèi)建的用來確保到達(dá)良好負(fù)載均衡或者是確保高可用性的機制。
inetd(Internet 超級服務(wù)器)守護(hù)進(jìn)程
守護(hù)進(jìn)程 inetd 被設(shè)計用來消除運行大量非常用服務(wù)器進(jìn)程的需要,inetd 可提供兩個主要的好處:
與其為每個服務(wù)運行一個單獨的守護(hù)進(jìn)程,現(xiàn)在只用一個進(jìn)程 inetd 守護(hù)進(jìn)程,就可以監(jiān)視一組指定的套接字端口,并按照需要啟動其他的服務(wù),從而可以降低系統(tǒng)上運行的進(jìn)程數(shù)量
inetd 簡化了啟動其他服務(wù)的編程工作,因為由 inetd 執(zhí)行的一些步驟通常在所有的網(wǎng)絡(luò)服務(wù)啟動時都會用到
inetd 守護(hù)進(jìn)程所做的操作
inetd 守護(hù)進(jìn)程通常在系統(tǒng)啟動時運行,在成為守護(hù)進(jìn)程后,inetd 執(zhí)行的步驟:
對于在配置文件?/etc/inetd.conf?中指定的每個服務(wù),inetd 都會創(chuàng)建一個恰當(dāng)類型的套接字,然后綁定到指定的端口上,每個 TCP 都會通過?listen()?調(diào)用允許客戶端來連接
通過?select()?調(diào)用,inetd 對前一步中創(chuàng)建的所有套接字進(jìn)行監(jiān)視,看是否有數(shù)據(jù)報或請求連接發(fā)送過來
select()?調(diào)用進(jìn)入阻塞,直到一個 UDP 套接字上有數(shù)據(jù)報可讀或者 TCP 套接字上收到了連接請求,在 TCP 連接中,inetd 在進(jìn)入下一個步驟之前會先為連接執(zhí)行?accept()
要啟動這個套接字上指定的服務(wù),inetd 調(diào)用?fork()?創(chuàng)建一個新的進(jìn)程,然后通過?exec()?啟動服務(wù)器程序,在執(zhí)行?exec()?之前,子進(jìn)程執(zhí)行如下步驟:
除了用于 UDP 數(shù)據(jù)報和接受 TCP 連接的文件描述符外,將其他所有從父進(jìn)程繼承而來的文件描述符都關(guān)閉
在文件描述符 0,1,2 上復(fù)制套接字文件描述符,并關(guān)閉套接字文件描述符本身
這一步是可選的,為啟動的服務(wù)器進(jìn)程設(shè)定用戶和組 ID,設(shè)定的值可以在?/etc/inetd.conf?中相應(yīng)條目找到
在 TCP 連接上接受一個連接,inetd 就關(guān)閉這個套接字
跳回到?select()?步驟繼續(xù)執(zhí)行
/etc/inetd.conf?文件
/etc/inetd.conf?文件中的每一行都描述一種由 inetd 處理的服務(wù),包含以下字段:
服務(wù)名稱
套接字類型
協(xié)議
標(biāo)記,該字段的內(nèi)容要么是?wait,要么是?nowait。表明了由 inetd 啟動的服務(wù)器是否會接管用于該服務(wù)的套接字,如果啟動的服務(wù)器需要管理這個套接字,那么就指定為?wait
登錄名
服務(wù)器程序
服務(wù)器程序參數(shù)
當(dāng)修改了?/etc/inetd.conf?文件之后,需要發(fā)送一個?SIGHUP?信號給 inetd,請求其重新讀取配置文件:
kill?-HUP?inted
編輯:黃飛
評論