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
編輯:黃飛
電子發(fā)燒友App












評(píng)論