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












評論