1、迭代型和并發(fā)型服務(wù)器
對(duì)于使用 socket 的網(wǎng)絡(luò)服務(wù)器程序,有兩種常見的設(shè)計(jì)方式:
迭代型:服務(wù)器每次只處理一個(gè)客戶端,只有當(dāng)完全處理完一個(gè)客戶端的請(qǐng)求后才去處理下一個(gè)客戶端
并發(fā)型:能夠同時(shí)處理多個(gè)客戶端的請(qǐng)求
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è)計(jì)方案
對(duì)于一個(gè)負(fù)載很高的服務(wù)器來說,為每個(gè)客戶端創(chuàng)建一個(gè)新的子進(jìn)程或者線程所帶來的開銷對(duì)服務(wù)器來說是沉重的負(fù)擔(dān)。
可以考慮下面的幾種方案:
在服務(wù)器上預(yù)先創(chuàng)建進(jìn)程或線程
服務(wù)器程序在啟動(dòng)階段(即在任何客戶端請(qǐng)求到來之前)就立刻預(yù)先創(chuàng)建好一定數(shù)量的子進(jìn)程(線程),而不是針對(duì)每個(gè)客戶端來創(chuàng)建一個(gè)新的子進(jìn)程(線程),這些子進(jìn)程(線程)構(gòu)成一個(gè)服務(wù)池
服務(wù)池中每個(gè)子進(jìn)程一次只處理一耳光客戶端,在處理完客戶端請(qǐng)求后,子進(jìn)程并不會(huì)終止,而是獲取下一個(gè)待處理的客戶端繼續(xù)處理
采用上述的服務(wù)池時(shí),在負(fù)載高峰期應(yīng)該動(dòng)態(tài)增加服務(wù)池的大小,在負(fù)載降低時(shí),應(yīng)該相應(yīng)地降低服務(wù)池大小。
在單個(gè)進(jìn)程中處理多個(gè)客戶端
為了實(shí)現(xiàn)這一點(diǎn),必須采用一種允許單個(gè)進(jìn)程同時(shí)監(jiān)視多個(gè)文件描述符 IO 事件的 IO 模型。
必須依靠?jī)?nèi)核來確保每個(gè)服務(wù)進(jìn)程能公平地訪問到服務(wù)器主機(jī)的資源。
采用服務(wù)器集群
用來處理高客戶端負(fù)載的方法還包括使用多個(gè)服務(wù)器系統(tǒng),即服務(wù)器集群。
構(gòu)建服務(wù)器集群最簡(jiǎn)單的方法就是 DNS 輪轉(zhuǎn)負(fù)載共享(DNS round-robin load sharing)或者負(fù)載分發(fā)(load distribution)。一個(gè)地區(qū)的域名權(quán)威服務(wù)器將同一個(gè)域名映射到多個(gè) IP 地址上,后續(xù)對(duì) DNS 服務(wù)器的域名解析請(qǐng)求將以循環(huán)輪轉(zhuǎn)的方式以不同的順序返回這些 IP 地址。
DNS 循環(huán)輪轉(zhuǎn)的優(yōu)勢(shì)是成本低,而且容易實(shí)施。但是也存在一些問題,其中一個(gè)問題是遠(yuǎn)端 DNS 服務(wù)器上所執(zhí)行的緩存操作,這意味著今后位于某個(gè)特定主機(jī)上的客戶端發(fā)出的請(qǐng)求會(huì)繞過循環(huán)輪轉(zhuǎn) DNS 服務(wù)器,并總是由同一個(gè)服務(wù)器來負(fù)責(zé)處理。此外,循環(huán)輪轉(zhuǎn) DNS 并沒有任何內(nèi)建的用來確保到達(dá)良好負(fù)載均衡或者是確保高可用性的機(jī)制。
inetd(Internet 超級(jí)服務(wù)器)守護(hù)進(jìn)程
守護(hù)進(jìn)程 inetd 被設(shè)計(jì)用來消除運(yùn)行大量非常用服務(wù)器進(jìn)程的需要,inetd 可提供兩個(gè)主要的好處:
與其為每個(gè)服務(wù)運(yùn)行一個(gè)單獨(dú)的守護(hù)進(jìn)程,現(xiàn)在只用一個(gè)進(jìn)程 inetd 守護(hù)進(jìn)程,就可以監(jiān)視一組指定的套接字端口,并按照需要啟動(dòng)其他的服務(wù),從而可以降低系統(tǒng)上運(yùn)行的進(jìn)程數(shù)量
inetd 簡(jiǎn)化了啟動(dòng)其他服務(wù)的編程工作,因?yàn)橛?inetd 執(zhí)行的一些步驟通常在所有的網(wǎng)絡(luò)服務(wù)啟動(dòng)時(shí)都會(huì)用到
inetd 守護(hù)進(jìn)程所做的操作
inetd 守護(hù)進(jìn)程通常在系統(tǒng)啟動(dòng)時(shí)運(yùn)行,在成為守護(hù)進(jìn)程后,inetd 執(zhí)行的步驟:
對(duì)于在配置文件?/etc/inetd.conf?中指定的每個(gè)服務(wù),inetd 都會(huì)創(chuàng)建一個(gè)恰當(dāng)類型的套接字,然后綁定到指定的端口上,每個(gè) TCP 都會(huì)通過?listen()?調(diào)用允許客戶端來連接
通過?select()?調(diào)用,inetd 對(duì)前一步中創(chuàng)建的所有套接字進(jìn)行監(jiān)視,看是否有數(shù)據(jù)報(bào)或請(qǐng)求連接發(fā)送過來
select()?調(diào)用進(jìn)入阻塞,直到一個(gè) UDP 套接字上有數(shù)據(jù)報(bào)可讀或者 TCP 套接字上收到了連接請(qǐng)求,在 TCP 連接中,inetd 在進(jìn)入下一個(gè)步驟之前會(huì)先為連接執(zhí)行?accept()
要啟動(dòng)這個(gè)套接字上指定的服務(wù),inetd 調(diào)用?fork()?創(chuàng)建一個(gè)新的進(jìn)程,然后通過?exec()?啟動(dòng)服務(wù)器程序,在執(zhí)行?exec()?之前,子進(jìn)程執(zhí)行如下步驟:
除了用于 UDP 數(shù)據(jù)報(bào)和接受 TCP 連接的文件描述符外,將其他所有從父進(jìn)程繼承而來的文件描述符都關(guān)閉
在文件描述符 0,1,2 上復(fù)制套接字文件描述符,并關(guān)閉套接字文件描述符本身
這一步是可選的,為啟動(dòng)的服務(wù)器進(jìn)程設(shè)定用戶和組 ID,設(shè)定的值可以在?/etc/inetd.conf?中相應(yīng)條目找到
在 TCP 連接上接受一個(gè)連接,inetd 就關(guān)閉這個(gè)套接字
跳回到?select()?步驟繼續(xù)執(zhí)行
/etc/inetd.conf?文件
/etc/inetd.conf?文件中的每一行都描述一種由 inetd 處理的服務(wù),包含以下字段:
服務(wù)名稱
套接字類型
協(xié)議
標(biāo)記,該字段的內(nèi)容要么是?wait,要么是?nowait。表明了由 inetd 啟動(dòng)的服務(wù)器是否會(huì)接管用于該服務(wù)的套接字,如果啟動(dòng)的服務(wù)器需要管理這個(gè)套接字,那么就指定為?wait
登錄名
服務(wù)器程序
服務(wù)器程序參數(shù)
當(dāng)修改了?/etc/inetd.conf?文件之后,需要發(fā)送一個(gè)?SIGHUP?信號(hào)給 inetd,請(qǐng)求其重新讀取配置文件:
kill?-HUP?inted
編輯:黃飛
評(píng)論