第七講 守護進程與其他網(wǎng)絡服務器編程技術_第1頁
第七講 守護進程與其他網(wǎng)絡服務器編程技術_第2頁
第七講 守護進程與其他網(wǎng)絡服務器編程技術_第3頁
第七講 守護進程與其他網(wǎng)絡服務器編程技術_第4頁
第七講 守護進程與其他網(wǎng)絡服務器編程技術_第5頁
已閱讀5頁,還剩78頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

第七講

守護進程與其他網(wǎng)絡服務器編程技術任立勇電子科技大學計算機學院2/5/20231目錄守護進程和inetd超級服務器概述syslogd守護進程和syslog函數(shù)創(chuàng)建守護進程inetd守護進程幾種服務器技術的比較;tcp并發(fā)服務器-每個客戶一個子進程;tcp預先派生子進程服務器程序,accept無上鎖保護;tcp預先派生子進程服務器程序,accept使用文件鎖保護;tcp預先派生子進程服務器程序,accept使用線程互斥鎖保護;tcp預先派生子進程服務器程序,傳遞描述字;tcp并發(fā)服務器程序,每個客戶一個線程;tcp預先創(chuàng)建線程服務器程序,每個線程各自accept;tcp預先創(chuàng)建線程服務器程序,主線程統(tǒng)一accept;2Networkprogramming‘03守護進程概述守護進程是在后臺運行不受終端控制的進程(如輸入、輸出等),一般的網(wǎng)絡服務都是以守護進程的方式運行。守護進程脫離終端的主要原因有兩點:用來啟動守護進程的終端在啟動守護進程之后,需要執(zhí)行其他任務。(如其他用戶登錄該終端后,以前的守護進程的錯誤信息不應出現(xiàn))由終端上的一些鍵所產(chǎn)生的信號(如中斷信號),不應對以前從該終端上啟動的任何守護進程造成影響。要注意守護進程與后臺運行程序(即加&啟動的程序)的區(qū)別。3Networkprogramming‘03啟動守護進程的方法在系統(tǒng)啟動是由系統(tǒng)初始化腳本啟動,這些腳本一般在/etc或/etc/rc開頭的目錄。如inet超級服務器,web服務器等;許多網(wǎng)絡服務器是由inet超級服務器啟動的,如Telnetd、FTPd等;cron守護進程按一定的規(guī)則執(zhí)行一些程序,由它啟動的程序也以守護進程的方式運行。守護進程可以在用戶終端上啟動,這是測試守護進程或重新啟動守護進程常用的方法。4Networkprogramming‘03用戶守護進程登記出錯信息創(chuàng)建一個Unix域數(shù)據(jù)報套接口,并向syslogd守護進程綁定的路徑名發(fā)送我們的消息,我們就能從自己的守護進程向syslogd發(fā)送登記信息??梢詣?chuàng)建一個UDP套接口,將日志消息發(fā)到回饋地址及端口號514;更簡單的方法是利用syslog函數(shù);5Networkprogramming‘03syslogd守護進程syslogd是一個系統(tǒng)守護進程,它主要負責接收系統(tǒng)或用戶守護進程的輸出消息,并根據(jù)配置信息作出相應處理。syslogd在啟動時執(zhí)行以下操作:讀入配置文件,通常是/etc/syslogd.conf,它設定守護進程對接收的各種登記消息如何處理。這些消息可能被寫入一個文件(一種特殊文件是/dev/console,這將把消息寫到控制臺上)?;虬l(fā)給指定的用戶,或轉發(fā)給另一臺主機上的syslogd進程。創(chuàng)建一個Unix域套接字,給它捆綁路徑名/var/run/log創(chuàng)建一個udp套接字,給它捆綁端口514打開路徑名/dev/klog,內(nèi)核中的所有出錯消息作為這個設備的輸入出現(xiàn);在此之后syslogd進程運行一個無限循環(huán),循環(huán)中調用select,等待三個描述字(以上2、3、4創(chuàng)建)之一變?yōu)榭勺x,并按配置文件對消息進行處理6Networkprogramming‘03openlog函數(shù)voidopenlog(constchar*ident,intoption,intfacility);voidcloselog(void);openlog函數(shù)在第一次調用syslog函數(shù)之前調用,當不再需要發(fā)生登記消息時可調用closelog函數(shù);ident是一個字符串,它將被加到每條登記消息前面;option參數(shù)由下頁圖中的值組合而成。facility參數(shù)為后面沒有設置設施的syslog調用設置一個缺省值。7Networkprogramming‘03openlog的選項選項(options) 描述LOG_CONS 如果不能發(fā)往syslogd守護進程,則登記到 控制臺上LOG_NDELAY 不延遲打開,立即創(chuàng)建套接口LOG_PERROR 既發(fā)往syslogd守護進程,又登記到 標準錯誤輸出LOG_PID 登記每條消息的進程ID8Networkprogramming‘03syslog函數(shù)#include<syslog.h>voidsyslog(intpriority,constchar*message,…);參數(shù)message與printf所用的格式化字符串類似,同時增加了%m,它將由對應的當前errno值的出錯消息所取代;參數(shù)priority是級別(level)和設施(facility)的組合。設施和級別的目的是,允許在/etc/syslog.conf文件中進行配置,使得對相同設施的消息得到同樣的處理,或使相同級別的消息得到同樣的處理。9Networkprogramming‘03登記消息的級別級別(level) 值描述LOG_EMERG 0系統(tǒng)不可用(優(yōu)先級最高)LOG_ALERT 1必須立即進行處理LOG_CRIT 2危險情況LOG_ERR 3出錯情況LOG_WARNING 4警告性情況LOG_NOTICE 5常見但值得注意的情況(缺省)LOG_INFO 6通告消息LOG_DEBUG 7調試消息(優(yōu)先級最低)10Networkprogramming‘03登記消息的設施設施(facility) 描述 設施(facility)描述LOG_AUTH 安全/授權消息 LOG_LOCAL6 本地使用LOG_AUTHPRIV 安全/授權消息(私有) LOG_LOCAL7 本地使用LOG_CRON cron守護進程 LOG_LPR 行式打印機系統(tǒng)LOG_DAEMON 系統(tǒng)守護進程 LOG_MAIL 郵件系統(tǒng)LOG_FTP FTP守護進程 LOG_NEWS 網(wǎng)絡新聞系統(tǒng)LOG_KERN 內(nèi)核消息 LOG_SYSLOG 由syslogd內(nèi)部消息LOG_LOCAL0 本地使用 LOG_USER任意的用戶消息(缺省LOG_LOCAL1 本地使用 LOG_UUCP UUCP消息LOG_LOCAL2 本地使用LOG_LOCAL3 本地使用LOG_LOCAL4 本地使用LOG_LOCAL5 本地使用11Networkprogramming‘03syslog函數(shù)的例子當調用rename函數(shù)失敗時,守護進程可能會調用:if(rename(file1,file2)==-1)syslog(LOG_INFO|LOG_LOCAL2,”rename(%s,%s):%m”,file1,file2);如果配置文件中有以下兩行:kern* /dev/consolelocal2.debug /var/log/cisco.log /var/log/info.log則指定內(nèi)核的所有消息登記到控制臺上,所有設施為local2的調試消息將添加到/var/log/cisco文件的末尾;12Networkprogramming‘03setsid()函數(shù)#include<sys/types.h>#include<unistd.h>pid_tsetsid(void);返回值:若成功則為進程組ID,出錯則為-1該函數(shù)是實現(xiàn)守護進程必須調用和非常重要的函數(shù)。如果調用進程不是一個進程組的組長,則此函數(shù)創(chuàng)建一個新的會話:此進程變成該會話的首進程,同時是該會話的唯一進程;此進程成為一個新進程組的組長進程。新進程組ID是此調用進程的進程ID;此進程沒有控制終端,如果在調用setsid之前此進程有一個控制終端,那么這種聯(lián)系也被解除。如果調用進程已經(jīng)是一個進程組長,則函數(shù)出錯。為了保證不處于這種情況,通常首先調用fork,然后使父進程終止。13Networkprogramming‘03創(chuàng)建守護進程#include <syslog.h>#define MAXFD 64externint daemon_proc; /*definedinerror.c*/voiddaemon_init(constchar*pname,intfacility){ int i; pid_t pid; if((pid=Fork())!=0) exit(0); /*parentterminates*/ /*1stchildcontinues*/

setsid(); /*becomesessionleader*/調試目的14Networkprogramming‘03創(chuàng)建守護進程(續(xù)) Signal(SIGHUP,SIG_IGN); if((pid=Fork())!=0) exit(0); /*1stchildterminates*/ /*2ndchildcontinues*/ daemon_proc=1; /*forourerr_XXX()functions*/ chdir("/"); /*changeworkingdirectory*/ umask(0); /*clearourfilemodecreationmask*/ for(i=0;i<MAXFD;i++) close(i); openlog(pname,LOG_PID,facility);}15Networkprogramming‘03程序說明第一次調用fork的目的是保證調用setsid的調用進程不是進程組長。(而setsid函數(shù)是實現(xiàn)與控制終端脫離的唯一方法);setsid函數(shù)使進程成為新會話的會話頭和進程組長,并與控制終端斷開連接;第二次調用fork的目的是:即使守護進程將來打開一個終端設備,也不會自動獲得控制終端。(因為在SVR4中,當沒有控制終端的會話頭進程打開終端設備時,如果這個終端不是其他會話的控制終端,該終端將自動成為這個會話的控制終端),這樣可以保證這次生成的進程不再是一個會話頭。忽略SIGHUP信號的原因是,當?shù)谝淮紊傻淖舆M程(會話頭)終止時,該會話中的所有進程(第二次生成的子進程)都會收到該信號;16Networkprogramming‘03守護進程方式運行的時間服務器#include <time.h>intmain(intargc,char**argv){ int listenfd,connfd; socklen_t addrlen,len; structsockaddr *cliaddr; char buff[MAXLINE]; time_t ticks; daemon_init(argv[0],0); if(argc==2) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); 17Networkprogramming‘03時間服務器 else err_quit("usage:daytimetcpsrv2[<host>]<serviceorport>"); cliaddr=Malloc(addrlen); for(;;){ len=addrlen; connfd=Accept(listenfd,cliaddr,&len); err_msg("connectionfrom%s",Sock_ntop(cliaddr,len)); ticks=time(NULL); snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks)); Write(connfd,buff,strlen(buff)); Close(connfd); }}18Networkprogramming‘03編寫守護進程的注意事項當程序開始時,盡快調用daemon_init,使之變成守護進程,否則容易受控制終端影響;守護進程必須避免調用printf和fprintf函數(shù),而調用syslog函數(shù)。19Networkprogramming‘03配置守護進程由于守護進程沒有控制終端,因而無法通過用戶輸入來進行相應配置,在編程時通常采用以下幾種方法來配置守護進程:配置文件:將所有的配置參數(shù)存入一個配置文件,當守護進程啟動時可以自動讀取配置信息進行配置;環(huán)境變量:守護進程通過讀取環(huán)境變量而獲得配置信息同樣,因為守護進程運行時沒有控制終端,所以它不會收到來自內(nèi)核的SIGHUP信號,因此很多守護進程將該信號作為管理員通知其配置文件已修改之用。守護進程收到該信號后應重新讀入配置文件。另外兩個守護進程不應收到的信號是SIGINT和SIGWINCH,這些信號也可以作為通知用。20Networkprogramming‘03inetd:超級網(wǎng)絡服務器4.3BSD以前的版本中的每個網(wǎng)絡服務有一個與之對應的進程,它們的啟動幾乎完全一樣(如創(chuàng)建套接字,綁定地址,監(jiān)聽端口…),這種模型存在以下問題:這些守護進程有幾乎相同的代碼,首先是創(chuàng)建套接字,還要考慮變成守護進程;每個守護進程在進程表中要占一項,但它們在大多數(shù)時間處于睡眠狀態(tài);21Networkprogramming‘03inetd:超級網(wǎng)絡服務器(續(xù))4.3BSD通過一個網(wǎng)絡超級服務器inet守護進程簡化了上述問題:大部分啟動時要做的工作由inetd處理,所有守護進程的編寫得到簡化。這避免了每個服務器程序都要調用daemon_init函數(shù);單個進程(inetd)能為多個服務等待客戶的請求,取代了每個服務一個進程的方式,這樣減少了系統(tǒng)中的進程數(shù);22Networkprogramming‘03inetd守護進程的工作流程啟動時讀取/etc/inetd.conf文件并給文件中指定的所有服務創(chuàng)建一個相應類型的套接字,inetd能處理的服務器數(shù)目依賴于它最多能創(chuàng)建的描述字的數(shù)目。每個創(chuàng)建的套接字都被加入到select調用的描述字集中;為每個套接字調用bind,給它們捆綁服務器的眾所周知端口和通配地址。對tcp套接字調用listen,以接受外來的連接請求;所有套接字建立后,調用select等待這些套接字變?yōu)榭勺x;Select返回一個可讀的套接字后,如果是一個tcp套接字,就調用accept接受這個新的連接;inetd守護進程fork,由子進程處理服務請求。子進程關閉除連接套接字以外的所有描述字;如果是tcp套接字,父進程必須關閉連接套接字。23Networkprogramming‘03inetd守護進程的工作流程socket()bind()listen()如果是tcp套接字select()等待可讀條件accept()如果是tcp套接字fork()close已連接套接字(如果是tcp)close已連接套接字之外的所有描述字將套接字描述字dup到描述字0、1、2,然后close原來的套接字setgid()setuid()(如果不是root)exec()服務程序父進程子進程對每個在/etc/inetd.conf文件中列出的每個服務24Networkprogramming‘03inetd的配置文件ftpstreamtcpnowaitroot/usr/bin/ftpdftpd–ltelnetstreamtcpnowaitroot/usr/bin/telnetdtelnetdloginstreamtcpnowaitroot/usr/bin/rlogindrlogind–stftpstreamudpwaitroot/usr/bin/tftpdtftpd–s/tftpboot25Networkprogramming‘03inetd的等待方式對tcp服務器設置nowait方式,意味著inetd在接受請求同一服務的其他連接之前不需要等待該服務的子進程終止。給數(shù)據(jù)報服務設置wait標志,需要對父進程的操作步驟作一定的修改。即inetd在該UDP套接字上再次選擇之前,必須等待在該套接字上服務的子進程終止:父進程中的fork返回時,記錄子進程的進程號,以便父進程可以用waitpid等待它終止;父進程用FD_CLR宏關閉select使用的描述字集中與該這個套接字相關的位。當子進程終止時,父進程收到一個SIGCHLD信號,父進程的信號處理程序得到終止子進程的進程號,父進程通過打開描述字集中相應的位恢復對該套接字的select。父進程需要等待數(shù)據(jù)報服務的子進程終止是因為:一個數(shù)據(jù)報服務只有一個套接字,如果不關閉相應的位,則隨后到來的數(shù)據(jù)將會使select返回可讀條件,從而fork一個錯誤的子進程。26Networkprogramming‘03常見的網(wǎng)絡服務器模型最簡單的迭代服務器模型并發(fā)服務器(服務器為每個客戶創(chuàng)建一個新進程)并發(fā)服務器(服務器為每個客戶創(chuàng)建一個新線程)在一個進程內(nèi)select多個客戶本講將介紹兩種新的并發(fā)服務器程序設計方法預先派生子進程預先派生線程27Networkprogramming‘03存在的問題對于預先創(chuàng)建進程或線程的服務器技術,客戶程序一般不作特殊處理,因為很少有進程控制問題。但對服務器而言,則存在許多新的問題,如:如果池中的進程或線程不夠怎么辦?過多又怎么辦?父子進程、父子線程,以及進程之間、線程之間如何同步?28Networkprogramming‘03試驗說明我們針對每個服務器運行同一客戶程序的多個實例,測量服務固定數(shù)目的客戶請求所需的CPU時間,而迭代服務器是我們的基準,我們把它從實際的CPU時間中減去就得到用于進程控制那部分CPU時間,因為迭代服務器沒有進程控制開銷。所有數(shù)據(jù)都是在與服務器主機處于同一子網(wǎng)的兩臺不同主機上運行同一客戶程序。每個客戶派生5個子進程,對服務器開5個連接,因此服務器在任意時刻最多有10個連接。每個客戶請求從服務器返回4000個字節(jié)的數(shù)據(jù)量。預先派生子進程或線程個數(shù)為15個。29Networkprogramming‘03各種類型的服務器的耗時比較編 服務器描述 進程控制CPU時間(與基準之差)號 solarisDUinxBSD/OS0 迭代服務器(測量基準,無進程控制) 0.0 0.00.01 簡單并發(fā)服務器,為每個客戶請求fork一個進程504.2168.929.62 預先派生子進程,每個子進程調用accept 6.2 1.83 預先派生子進程,用文件上鎖方式保護accept25.210.02.74 預先派生子進程,用線程互斥鎖保護accept 21,5 預先派生子進程,由父進程向子進程傳遞套接36.710.96.1

字描述字6 并發(fā)服務器,為每個客戶請求創(chuàng)建一個線程 18.7 4.77 預先派生子線程,用互斥鎖上鎖方式保護accept8.63.58 預先派生子線程,由主線程調用accept 14.55.030Networkprogramming‘03#define MAXN 16384 /*max#bytestorequestfromserver*/intmain(intargc,char**argv){ int i,j,fd,nchildren,nloops,nbytes; pid_t pid; ssize_t n; char request[MAXLINE],reply[MAXN]; if(argc!=6) err_quit("usage:client<hostnameorIPaddr><port><#children>" "<#loops/child><#bytes/request>"); nchildren=atoi(argv[3]); nloops=atoi(argv[4]); nbytes=atoi(argv[5]); snprintf(request,sizeof(request),"%d\n",nbytes);/*newlineatend*/ for(i=0;i<nchildren;i++){ if((pid=Fork())==0){ /*child*/TCP測試用客戶程序31Networkprogramming‘03TCP測試用客戶程序(續(xù)) for(j=0;j<nloops;j++){ fd=Tcp_connect(argv[1],argv[2]); Write(fd,request,strlen(request)); if((n=Readn(fd,reply,nbytes))!=nbytes) err_quit("serverreturned%dbytes",n); Close(fd);/*TIME_WAITonclient,notserver*/ } printf("child%ddone\n",i); exit(0); } /*parentloopsaroundtofork()again*/ } while(wait(NULL)>0) /*nowparentwaitsforallchildren*/ ; if(errno!=ECHILD) err_sys("waiterror"); exit(0);}32Networkprogramming‘03TCP并發(fā)服務器:

每個客戶一個子進程intmain(intargc,char**argv){ int listenfd,connfd; pid_t childpid; void sig_chld(int),sig_int(int),web_child(int); socklen_t clilen,addrlen; structsockaddr *cliaddr; if(argc==2) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); else err_quit("usage:serv01[<host>]<port#>"); cliaddr=Malloc(addrlen); Signal(SIGCHLD,sig_chld); Signal(SIGINT,sig_int);33Networkprogramming‘03TCP并發(fā)服務器:

每個客戶一個子進程(續(xù)) for(;;){ clilen=addrlen; if((connfd=accept(listenfd,cliaddr,&clilen))<0){ if(errno==EINTR) continue; /*backtofor()*/ else err_sys("accepterror"); } if((childpid=Fork())==0){ /*childprocess*/ Close(listenfd); /*closelisteningsocket*/ web_child(connfd); /*processtherequest*/ exit(0); } Close(connfd); /*parentclosesconnectedsocket*/ }}34Networkprogramming‘03TCP并發(fā)服務器:

每個客戶一個子進程(續(xù))#define MAXN 16384 /*max#bytesthataclientcanrequest*/voidweb_child(intsockfd){ int ntowrite; ssize_t nread; char line[MAXLINE],result[MAXN]; for(;;){ if((nread=Readline(sockfd,line,MAXLINE))==0) return; /*connectionclosedbyotherend*/ /*4linefromclientspecifies#bytestowriteback*/ ntowrite=atol(line); if((ntowrite<=0)||(ntowrite>MAXN)) err_quit("clientrequestfor%dbytes",ntowrite); Writen(sockfd,result,ntowrite); }}35Networkprogramming‘03TCP并發(fā)服務器:

每個客戶一個子進程(續(xù))voidsig_int(intsigno){ void pr_cpu_time(void); pr_cpu_time(); exit(0);}#include <sys/resource.h>#ifndef HAVE_GETRUSAGE_PROTOint getrusage(int,structrusage*);#endifvoidpr_cpu_time(void){ double user,sys; structrusage myusage,childusage;36Networkprogramming‘03TCP并發(fā)服務器:

每個客戶一個子進程(續(xù)) if(getrusage(RUSAGE_SELF,&myusage)<0) err_sys("getrusageerror"); if(getrusage(RUSAGE_CHILDREN,&childusage)<0) err_sys("getrusageerror"); user=(double)myusage.ru_utime.tv_sec+ myusage.ru_utime.tv_usec/1000000.0; user+=(double)childusage.ru_utime.tv_sec+ childusage.ru_utime.tv_usec/1000000.0; sys=(double)myusage.ru_stime.tv_sec+ myusage.ru_stime.tv_usec/1000000.0; sys+=(double)childusage.ru_stime.tv_sec+ childusage.ru_stime.tv_usec/1000000.0; printf("\nusertime=%g,systime=%g\n",user,sys);}37Networkprogramming‘03TCP預先派生子進程服務器程序客戶1客戶2子進程1子進程2父進程子進程3…子進程N可用子進程池forkforkforkfork38Networkprogramming‘03TCP預先派生子進程服務器程序這種技術的優(yōu)點在于:不需要引入父進程執(zhí)行fork的開銷,新客戶就可以得到處理。而缺點在于,每次啟動服務器時,父進程必須猜測到底需要預先派生多少個子進程。除此以外,如不考慮再派生子進程,一旦所有子進程都為客戶請求所占用,此時新的請求將被暫時忽略,直到有一個新的進程可用??蛻舻母杏X就是服務器的響應變慢。解決上述問題的辦法是:由父進程監(jiān)視可用子進程個數(shù),一旦低于某個閾值,再派生額外的子進程,反之,則終止部分新派生的進程。39Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護staticint nchildren;staticpid_t *pids;intmain(intargc,char**argv){ int listenfd,i; socklen_t addrlen; void sig_int(int); pid_t child_make(int,int,int); if(argc==3) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==4) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); else err_quit("usage:serv02[<host>]<port#><#children>");40Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù)) nchildren=atoi(argv[argc-1]); pids=Calloc(nchildren,sizeof(pid_t)); for(i=0;i<nchildren;i++) pids[i]=child_make(i,listenfd,addrlen); /*parentreturns*/ Signal(SIGINT,sig_int); for(;;) pause(); /*everythingdonebychildren*/}41Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù))pid_tchild_make(inti,intlistenfd,intaddrlen){ pid_t pid; void child_main(int,int,int); if((pid=Fork())>0) return(pid); /*parent*/ child_main(i,listenfd,addrlen); /*neverreturns*/}/*endchild_make*/42Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù))voidchild_main(inti,intlistenfd,intaddrlen){ int connfd; void web_child(int); socklen_t clilen; structsockaddr *cliaddr; cliaddr=Malloc(addrlen); printf("child%ldstarting\n",(long)getpid()); for(;;){ clilen=addrlen; connfd=Accept(listenfd,cliaddr,&clilen); web_child(connfd); /*processtherequest*/ Close(connfd); }}43Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù))voidsig_int(intsigno){ int i; void pr_cpu_time(void); /*4terminateallchildren*/ for(i=0;i<nchildren;i++) kill(pids[i],SIGTERM); while(wait(NULL)>0) /*waitforallchildren*/ ; if(errno!=ECHILD) err_sys("waiterror"); pr_cpu_time(); exit(0);}44Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù))父進程listenfd子進程listenfd子進程listenfd子進程listenfd…...file{}socket{}45Networkprogramming‘03TCP預先派生子進程服務器程序:

accept無上鎖保護(續(xù))當程序啟動后,N個子進程被派生,它們分別調用accept并由內(nèi)核置入睡眠狀態(tài)。當一個客戶連接請求到達時,N個睡眠進程均被喚醒,但只有最先被調度執(zhí)行的進程才能獲得客戶連接,而其他N-1個進程再次睡眠。這種情況稱之為“驚群”問題,它將會導致系統(tǒng)服務性能下降。(測試“驚群”現(xiàn)象)為了解決上述問題,可以增加一個監(jiān)控進程,監(jiān)視空閑子進程的數(shù)量。46Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護源自BSD的內(nèi)核允許多個進程在同一監(jiān)聽描述字上調用accept,但對于基于SVR4(如Solaris2.5)則會在客戶連接到該服務器后不久,某個子進程的accept就會返回EPROTO錯誤,表示協(xié)議錯。解決上述問題的方法就是讓應用進程在調用accept前后設置某種形式的鎖,以保證只有一個子進程阻塞在accept調用,而其他子進程則阻塞在試圖獲取提供調用accept權力的鎖上。實現(xiàn)這種鎖的方式有很多,如文件鎖,信號量,互斥鎖等等。本節(jié)主要介紹文件鎖保護accept的方式47Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護(續(xù))在main函數(shù)中的唯一改動是在子進程的循環(huán)前增加一個函數(shù)調用:my_lock_init:

my_lock_init(“/tmp/lock.XXXX”); for(i=0;i<nchildren;i++) pids[i]=child_make(i,listenfd,addrlen);child_make函數(shù)不變,child_main函數(shù)的唯一改動是在調用accept前獲取文件鎖,在accept返回后釋放文件鎖。 for(;;){ clilen=addrlen;

my_lock_wait(); connfd=accept(listenfd,cliaddr,&clilen);

my_lock_release(); close(connfd); }48Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護(續(xù))staticstructflock lock_it,unlock_it;staticint lock_fd=-1; /*fcntl()willfailifmy_lock_init()notcalled*/voidmy_lock_init(char*pathname){char lock_file[1024]; /*4mustcopycaller'sstring,incaseit'saconstant*/strncpy(lock_file,pathname,sizeof(lock_file));Mktemp(lock_file);lock_fd=Open(lock_file,O_CREAT|O_WRONLY,FILE_MODE);Unlink(lock_file); /*butlock_fdremainsopen*/49Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護(續(xù)) lock_it.l_type=F_WRLCK; lock_it.l_whence=SEEK_SET; lock_it.l_start=0; lock_it.l_len=0; unlock_it.l_type=F_UNLCK; unlock_it.l_whence=SEEK_SET; unlock_it.l_start=0; unlock_it.l_len=0;}/*endmy_lock_init*/50Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護(續(xù))voidmy_lock_wait(){int rc;while((rc=fcntl(lock_fd,F_SETLKW,&lock_it))<0){ if(errno==EINTR) continue; else err_sys("fcntlerrorformy_lock_wait"); }}voidmy_lock_release(){if(fcntl(lock_fd,F_SETLKW,&unlock_it)<0) err_sys("fcntlerrorformy_lock_release");}51Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用文件鎖保護(續(xù))從本節(jié)的第1章表中可以看出,文件上鎖增加了服務器的進程控制CPU時間。同時,文件上鎖方式涉及到文件系統(tǒng)操作,這是很耗時的。但加鎖機制卻是SVR4系統(tǒng)中多子進程accept同一監(jiān)聽套接字的唯一方法。ApacheWeb服務器程序1.1版利用了以上兩節(jié)的技術。預先派生子進程后,如果實現(xiàn)允許所有子進程都阻塞在accept調用上,那么使用不加鎖機制,否則就使用本節(jié)介紹的文件鎖技術。52Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用線程互斥鎖保護本節(jié)介紹的線程互斥鎖不僅適用于同一進程內(nèi)各線程間的上鎖,同時也適用于不同進程間的上鎖。為使用線程鎖,main,child_make,child_main函數(shù)都不用改動,只需要修改3個上鎖函數(shù)。為了在多個進程之間使用線程鎖,應該作到:(1)互斥鎖變量必須存儲在為所有進程共享的內(nèi)存中;(2)必須通知線程函數(shù)庫互斥鎖是在不同進程間共享的。(這同樣要求線程庫支持PTHREAD_PROCESS_SHARED屬性)53Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用線程互斥鎖保護(續(xù))#include "unpthread.h"#include <sys/mman.h>staticpthread_mutex_t *mptr; /*actualmutexwillbeinsharedmemory*/voidmy_lock_init(char*pathname){ int fd; pthread_mutexattr_t mattr; fd=Open("/dev/zero",O_RDWR,0); mptr=Mmap(0,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE, MAP_SHARED,fd,0); Close(fd);

Pthread_mutexattr_init(&mattr); Pthread_mutexattr_setpshared(&mattr,PTHREAD_PROCESS_SHARED); Pthread_mutex_init(mptr,&mattr);}54Networkprogramming‘03TCP預先派生子進程服務器程序:

accept使用線程互斥鎖保護(續(xù))voidmy_lock_wait(){ Pthread_mutex_lock(mptr);}voidmy_lock_release(){ Pthread_mutex_unlock(mptr);}55Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字對預先派生子進程服務器程序的最后一種改動就是由父進程調用accept,然后再將所接收的連接描述字傳遞給子進程。這樣就繞過了子進程中調用accept可能需要上鎖保護的問題。但為了實現(xiàn)給子進程傳遞描述字,父進程必須跟蹤所有子進程的忙閑狀態(tài),以便給空閑子進程傳遞新的描述字。為此,需要為每個子進程維護一個信息結構,用來管理子進程。56Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))typedefstruct{ pid_t child_pid; /*processID*/ int child_pipefd;/*parent'sstreampipeto/fromchild*/ int child_status; /*0=ready*/ long child_count; /*#connectionshandled*/}Child;Child *cptr; /*arrayofChildstructures;calloc'ed*/57Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))pid_tchild_make(inti,intlistenfd,intaddrlen){ int sockfd[2]; pid_t pid; void child_main(int,int,int); Socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd); if((pid=Fork())>0){ Close(sockfd[1]); cptr[i].child_pid=pid; cptr[i].child_pipefd=sockfd[0]; cptr[i].child_status=0; return(pid); /*parent*/ }58Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù)) Dup2(sockfd[1],STDERR_FILENO);/*child'sstreampipeto parent*/ Close(sockfd[0]); Close(sockfd[1]); Close(listenfd); /*childdoesnotneedthisopen*/ child_main(i,listenfd,addrlen); /*neverreturns*/}59Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))voidchild_main(inti,intlistenfd,intaddrlen){ char c; int connfd; ssize_t n; void web_child(int); printf("child%ldstarting\n",(long)getpid()); for(;;){ if((n=Read_fd(STDERR_FILENO,&c,1,&connfd))==0) err_quit("read_fdreturned0"); if(connfd<0) err_quit("nodescriptorfromread_fd"); web_child(connfd); /*processtherequest*/ Close(connfd); Write(STDERR_FILENO,"",1); /*tellparentwe'rereadyagain*/ }}60Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))staticint nchildren;intmain(intargc,char**argv){ int listenfd,i,navail,maxfd,nsel,connfd,rc; void sig_int(int); pid_t child_make(int,int,int); ssize_t n; fd_set rset,masterset; socklen_t addrlen,clilen; structsockaddr *cliaddr; if(argc==3) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==4) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); else err_quit("usage:serv05[<host>]<port#><#children>");61Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù)) FD_ZERO(&masterset); FD_SET(listenfd,&masterset); maxfd=listenfd; cliaddr=Malloc(addrlen); nchildren=atoi(argv[argc-1]); navail=nchildren; cptr=Calloc(nchildren,sizeof(Child)); /*preforkallthechildren*/ for(i=0;i<nchildren;i++){ child_make(i,listenfd,addrlen); /*parentreturns*/ FD_SET(cptr[i].child_pipefd,&masterset); maxfd=max(maxfd,cptr[i].child_pipefd); } Signal(SIGINT,sig_int);62Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù)) for(;;){ rset=masterset; if(navail<=0) FD_CLR(listenfd,&rset); /*turnoffifnoavailablechildren*/ nsel=Select(maxfd,&rset,NULL,NULL,NULL); /*4checkfornewconnections*/ if(FD_ISSET(listenfd,&rset)){ clilen=addrlen; connfd=Accept(listenfd,cliaddr,&clilen); for(i=0;i<nchildren;i++) if(cptr[i].child_status==0) break; /*available*/ if(i==nchildren) err_quit("noavailablechildren"); cptr[i].child_status=1; /*markchildasbusy*/ cptr[i].child_count++; navail--;63Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù)) n=Write_fd(cptr[i].child_pipefd,"",1,connfd); Close(connfd); if(--nsel==0) continue; /*alldonewithselect()results*/ } /*4findanynewly-availablechildren*/ for(i=0;i<nchildren;i++){ if(FD_ISSET(cptr[i].child_pipefd,&rset)){ if((n=Read(cptr[i].child_pipefd,&rc,1))==0) err_quit("child%dterminatedunexpectedly",i); cptr[i].child_status=0; navail++; if(--nsel==0) break; /*alldonewithselect()results*/ } } }}64Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))voidsig_int(intsigno){ int i; void pr_cpu_time(void); /*4terminateallchildren*/ for(i=0;i<nchildren;i++) kill(cptr[i].child_pid,SIGTERM); while(wait(NULL)>0) /*waitforallchildren*/ ; if(errno!=ECHILD) err_sys("waiterror"); pr_cpu_time(); for(i=0;i<nchildren;i++) printf("child%d,%ldconnections\n",i,cptr[i].child_count); exit(0);}65Networkprogramming‘03TCP預先派生子進程服務器程序:

傳遞描述字(續(xù))通過字節(jié)流管道傳遞描述字給每個子進程,由子進程通過字節(jié)流管道寫回1字節(jié)的數(shù)據(jù)以表示可用,要比無論是共享內(nèi)存中的互斥鎖還是文件鎖的上鎖和解鎖更費時;通過統(tǒng)計各個子進程的連接數(shù),發(fā)現(xiàn)越是排在前頭的子進程所處理的客戶請求就越多,出現(xiàn)不公平現(xiàn)象。這與以前幾種技術靠內(nèi)核來調度子進程接收連接是公平的情況不一樣。66Networkprogramming‘03TCP并發(fā)服務器程序:

每個客戶一個線程intmain(intargc,char**argv){ int listenfd,connfd; void sig_int(int); void *doit(void*); pthread_t tid; socklen_t clilen,addrlen; structsockaddr *cliaddr; if(argc==2) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); else err_quit("usage:serv06[<host>]<port#>"); cliaddr=Malloc(addrlen);67Networkprogramming‘03TCP并發(fā)服務器程序:

每個客戶一個線程(續(xù)) Signal(SIGINT,sig_int); for(;;){ clilen=addrlen; connfd=Accept(listenfd,cliaddr,&clilen); Pthread_create(&tid,NULL,&doit,(void*)connfd); }}void*doit(void*arg){ void web_child(int); Pthread_detach(pthread_self()); web_child((int)arg); Close((int)arg); return(NULL);}68Networkprogramming‘03TCP并發(fā)服務器程序:

每個客戶一個線程(續(xù))voidsig_int(intsigno){ void pr_cpu_time(void); pr_cpu_time(); exit(0);}69Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept我們不是讓每個線程都阻塞在accept調用上,而是直接使用互斥鎖來保證線程間互斥地調用accept,此處我們直接使用線程鎖(因為它們是屬于同一進程)。為維護線程的信息,定義如下結構:typedefstruct{ pthread_t thread_tid; /*threadID*/ long thread_count; /*#connectionshandled*/}Thread;Thread *tptr; /*arrayofThreadstructures;calloc'ed*/int listenfd,nthreads;socklen_t addrlen;pthread_mutex_t mlock;70Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept(續(xù))#include "unpthread.h"#include "pthread07.h"pthread_mutex_t mlock=PTHREAD_MUTEX_INITIALIZER;intmain(intargc,char**argv){ int i; void sig_int(int),thread_make(int); if(argc==3) listenfd=Tcp_listen(NULL,argv[1],&addrlen); elseif(argc==4) listenfd=Tcp_listen(argv[1],argv[2],&addrlen); else err_quit("usage:serv07[<host>]<port#><#threads>");71Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept(續(xù)) nthreads=atoi(argv[argc-1]); tptr=Calloc(nthreads,sizeof(Thread)); for(i=0;i<nthreads;i++) thread_make(i); /*onlymainthreadreturns*/ Signal(SIGINT,sig_int); for(;;) pause(); /*everythingdonebythreads*/}72Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept(續(xù))voidsig_int(intsigno){ int i; void pr_cpu_time(void); pr_cpu_time(); for(i=0;i<nthreads;i++) printf("thread%d,%ldconnections\n",i,tptr[i].thread_count); exit(0);}73Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept(續(xù))voidthread_make(inti){ void *thread_main(void*); Pthread_create(&tptr[i].thread_tid,NULL,&thread_main,(void*)i); return; /*mainthreadreturns*/}74Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

每個線程各自accept(續(xù))void*thread_main(void*arg){ int connfd; void web_child(int); socklen_t clilen; structsockaddr *cliaddr; cliaddr=Malloc(addrlen); printf("thread%dstarting\n",(int)arg); for(;;){ clilen=addrlen; Pthread_mutex_lock(&mlock); connfd=Accept(listenfd,cliaddr,&clilen); Pthread_mutex_unlock(&mlock); tptr[(int)arg].thread_count++; web_child(connfd); /*processtherequest*/ Close(connfd); }}75Networkprogramming‘03tcp預先創(chuàng)建線程服務器程序:

主線程統(tǒng)一accept主要的設計問題是如何將已連接的描述字傳遞給線程池中的空閑線程。我們可以用以前的

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論