国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

TCP/IP網(wǎng)絡(luò)重復(fù)型服務(wù)器通信軟件的設(shè)計(jì)

2019-11-17 05:01:57
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
本文介紹一種新型的基于消息隊(duì)列的重復(fù)型服務(wù)器通信軟件的設(shè)計(jì)方法,不同于并發(fā)型服務(wù)器和一般的重復(fù)型服務(wù)器通信軟件,這種新的軟件具有生成的子進(jìn)程數(shù)少的優(yōu)點(diǎn),并且輕易對(duì)客戶機(jī)與服務(wù)器的連接進(jìn)行治理,適用于客戶機(jī)數(shù)量較多和隨機(jī)數(shù)據(jù)通信的情況,能夠有效地提高服務(wù)器的運(yùn)行效率。

  并發(fā)服務(wù)器與重復(fù)服務(wù)器的區(qū)別   一般TCP/ip服務(wù)器通信軟件都是并發(fā)型的,即是由一個(gè)守護(hù)進(jìn)程負(fù)責(zé)監(jiān)聽(tīng)客戶機(jī)的連接請(qǐng)求,然后再由守護(hù)進(jìn)程生成一個(gè)或多個(gè)子進(jìn)程與客戶機(jī)具體建立連接以完成通信,其缺點(diǎn)是隨著連接的客戶機(jī)數(shù)量的增多,生成的通信子進(jìn)程數(shù)量會(huì)越來(lái)越多,在客戶機(jī)數(shù)量較多的應(yīng)用場(chǎng)合勢(shì)必影響服務(wù)器的運(yùn)行效率。一般的重復(fù)服務(wù)器指的是服務(wù)器在接收客戶機(jī)的連接請(qǐng)求后即與之建立連接,然后要在處理完與客戶機(jī)的通信任務(wù)后才能再去接收另一客戶機(jī)的請(qǐng)求連接,其優(yōu)點(diǎn)是不必生成通信子進(jìn)程,缺點(diǎn)是客戶機(jī)在每次通信之前都要與服務(wù)器建立連接,開(kāi)銷過(guò)大,不能用于隨機(jī)的數(shù)據(jù)通信和繁忙的業(yè)務(wù)處理。  本文提出的新型的重復(fù)型服務(wù)器不同于一般的重復(fù)服務(wù)器,它摒棄了上述兩類服務(wù)器的缺點(diǎn)綜合其優(yōu)點(diǎn),該服務(wù)器通信軟件具有一般重復(fù)服務(wù)器的特征但又能處理客戶機(jī)的隨機(jī)訪問(wèn),在客戶機(jī)數(shù)量多且業(yè)務(wù)繁忙的應(yīng)用場(chǎng)合將發(fā)揮其優(yōu)勢(shì)。重復(fù)型服務(wù)器通信軟件只用三個(gè)進(jìn)程就可完成與所有客戶機(jī)建立連接,并始終保持這些連接。  重復(fù)型服務(wù)器通信軟件與客戶機(jī)建立連接的方法  基本思路  當(dāng)?shù)谝慌_(tái)客戶機(jī)向服務(wù)器請(qǐng)求連接時(shí),服務(wù)器的守護(hù)進(jìn)程與之建立初始連接(L0),客戶機(jī)利用L0向服務(wù)器發(fā)送兩個(gè)端口號(hào),守護(hù)進(jìn)程將客戶機(jī)的IP地址和端口號(hào)登記在共享內(nèi)存的記錄中,然后關(guān)閉L0。由守護(hù)進(jìn)程生成的兩個(gè)通信子進(jìn)程從共享內(nèi)存中獲得客戶機(jī)IP地址及端口號(hào)后,分別向客戶機(jī)請(qǐng)求連接,建立一個(gè)從客戶機(jī)讀的連接(L1)和一個(gè)往客戶機(jī)寫的連接(L2),并將兩個(gè)連接的套接字的句柄記錄在共享內(nèi)存中。當(dāng)另一臺(tái)客戶機(jī)請(qǐng)求連接時(shí),守護(hù)進(jìn)程不再生成通信子進(jìn)程,只是將客戶機(jī)IP地址和端口號(hào)同樣登記在共享內(nèi)存中。通信子進(jìn)程在一個(gè)大循環(huán)中先查詢共享內(nèi)存中是否有新的記錄,假如有則與這一臺(tái)客戶機(jī)建立連接,然后輪詢所有已建立的連接的讀套接字,查看是否有數(shù)據(jù)可讀,有則讀取數(shù)據(jù),同時(shí)標(biāo)明該數(shù)據(jù)是從共享內(nèi)存中的哪條記錄上的讀套接字中獲得的,再由另一個(gè)通信子進(jìn)程根據(jù)這個(gè)記錄的編號(hào)從共享內(nèi)存中獲得對(duì)應(yīng)的寫套接字,最后將結(jié)果數(shù)據(jù)往該套接字寫往客戶機(jī)。 2.2 建立連接  ⑴ 服務(wù)器通信軟件的初始進(jìn)程首先建立公用端口上的套接字,并在該套接字上建立監(jiān)聽(tīng)隊(duì)列,同時(shí)生成一個(gè)守護(hù)進(jìn)程(Daemon)tcp_s,然后初始進(jìn)程就退出運(yùn)行。守護(hù)進(jìn)程在函數(shù)accept處堵塞住直到有客戶機(jī)的連接請(qǐng)求,一有連接請(qǐng)求即調(diào)用server函數(shù)處理,然后繼續(xù)循環(huán)等待另一臺(tái)客戶機(jī)的請(qǐng)求。因?yàn)門CP/IP在連接被拆除后為了避免出現(xiàn)重復(fù)連接的現(xiàn)象,一般是將連接放在過(guò)時(shí)連接表中,連接在拆除后若要避免處于TIME_WAIT狀態(tài)(過(guò)時(shí)連接),可調(diào)用setsockopt設(shè)置套接字的linger延時(shí)標(biāo)志,同時(shí)將延時(shí)時(shí)間設(shè)置為0。服務(wù)器在/etc/services文件中要登記一個(gè)全局公認(rèn)的公用端口號(hào):tcp_server 2000/tcp。
strUCt servent *sp;
struct sockaddr_in peeraddr_in,myaddr_in;
linkf=0;
sp=getservbyname("tcp_server","tcp");
ls=socket(AF_INET,SOCK_STREAM,0); /* 創(chuàng)建監(jiān)聽(tīng)套接字 */
myaddr_in.sin_addr.s_addr=INADDR_ANY;
myaddr_in.sin_port=sp->s_port; /* 公用端口號(hào) */
bind(ls,&myaddr_in,sizeof(struct sockaddr_in));
listen(ls,5);
qid3=msgget(MSGKEY3,0x1ff); /* 獲得消息隊(duì)列的標(biāo)志號(hào) */
qid4=msgget(MSGKEY4,0x1ff);
signal(SIGCLD,SIG_IGN); /* 避免子進(jìn)程在退出后變?yōu)榻┧肋M(jìn)程 */
addrlen=sizeof(struct sockaddr_in);
lingerlen=sizeof(struct linger);
linger.l_onoff=1;
linger.l_linger=0;
setpgrp();
switch(fork()){ /* 生成Daemon */
case -1:exit(1);
case 0: /* Daemon */
for(;;){
s=accept(ls,&peeraddr_in,&addrlen);
setsockopt(s,SOL_SOCKET,SO_LINGER,&linger,lingerlen);
server();
close(s);
}
default:
f}   ⑵ 客戶機(jī)以這樣的形式運(yùn)行通信程序tcp_c:tcp_c rhostname,rhostname為客戶機(jī)所要連接的服務(wù)器主機(jī)名。客戶機(jī)上的/etc/services文件中也要登記:tcp_server 2000/tcp,公用端口號(hào)2000要與服務(wù)器一樣。
int qid1,qid2,s_c1,s_c2,cport1,cport2;
struct servent *sp;
struct hostent *hp;
memset((char *)&myaddr_in,0,sizeof(struct sockaddr_in));
memset((char *)&peeraddr_in,0,sizeof(struct sockaddr_in));
addrlen=sizeof(struct sockaddr_in);
sp=getservbyname("tcp_server","tcp");

hp=gethostbyname(argv[1]); /* 從/etc/hosts中獲取服務(wù)器的IP地址 */
qid1=msgget(MSGKEY1,0x1ff);
qid2=msgget(MSGKEY2,0x1ff);
cport1=6000;
s=rresvport(&cport1);
peeraddr_in.sin_family=hp->h_addrtype;
bcopy(hp->h_addr_list[0],(caddr_t)&peeraddr_in.sin_addr,hp->h_length);
peeraddr_in.sin_port=sp->s_port;
connect(s,(struct sockaddr *)&peeraddr_in,sizeof(peeraddr_in));
cport1--;
s_c1=rresvport(&cport1);
cport2=cport1;
s_c2=rresvport(&cport2);
sprintf(cportstr,"%dx%d",cport1,cport2);
write(s,cportstr,strlen(cportstr)+1);
close(s);   先給變量cport1置一個(gè)整數(shù)后調(diào)用rresvport函數(shù),該函數(shù)先檢查端口號(hào)cport1是否已被占用,假如已被占用就減一再試,直到找到一個(gè)未用的端口號(hào),然后生成一個(gè)套接字,將該套接字與端口號(hào)相聯(lián)形成客戶機(jī)端的半相關(guān),接下調(diào)用connect函數(shù)向服務(wù)器發(fā)出連接請(qǐng)求。客戶機(jī)在發(fā)出連接請(qǐng)求之前,已用函數(shù)gethostbyname和getservbyname獲得了服務(wù)器的IP地址及其公用端口號(hào),這樣就形成了一個(gè)完整的相關(guān),可建立起與服務(wù)器的初始連接。接下來(lái)再創(chuàng)建兩個(gè)套接字s_c1和s_c2,利用初始連接將客戶機(jī)的兩個(gè)套接字的端口號(hào)以字符串的形式發(fā)送給服務(wù)器,這時(shí)初始連接的任務(wù)已經(jīng)完成就可將其關(guān)閉。以上就完成了與服務(wù)器的初始連接,接下來(lái)客戶機(jī)等待服務(wù)器的兩次連接請(qǐng)求。
QQRead.com 推出數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)故障解析 常用數(shù)據(jù)恢復(fù)方案 硬盤數(shù)據(jù)恢復(fù)教程 數(shù)據(jù)保護(hù)方法 數(shù)據(jù)恢復(fù)軟件 專業(yè)數(shù)據(jù)恢復(fù)服務(wù)指南   ⑶ tcp_s的監(jiān)聽(tīng)隊(duì)列在收到客戶機(jī)發(fā)來(lái)的連接請(qǐng)求后,由server函數(shù)讀出客戶機(jī)發(fā)送來(lái)的兩個(gè)端口號(hào),并在第一次調(diào)用時(shí)生成兩個(gè)通信子進(jìn)程tcp_s1和tcp_s2,以后就不再生成,這是與并發(fā)服務(wù)器最大的不同。tcp_s進(jìn)程將客戶機(jī)的兩個(gè)端口號(hào)和IP 地址以記錄的形式登記在共享內(nèi)存最后一條記錄中,子進(jìn)程通過(guò)共享內(nèi)存獲得這兩個(gè)端口號(hào),然后再分別與客戶機(jī)建立連接。tcp_s繼續(xù)處于監(jiān)聽(tīng)狀態(tài),以便響應(yīng)其他客戶機(jī)的連接請(qǐng)求。兩個(gè)子進(jìn)程都應(yīng)該關(guān)閉從父進(jìn)程繼續(xù)來(lái)的但又沒(méi)有使用的套接字s。
server(){
int f;char c;
cport1=cport2=f=0;
for(;;){
read(s,&c,1);
if(c==0) break;
if(c=='x'){
f=1;continue;
}
if(f) cport2=(cport2*10)+(c-'0');
else cport1=(cport1*10)+(c-'0');
}
/* 在共享內(nèi)存中登記客戶機(jī)端口號(hào)和IP地址 */
shm_login(cport1,cport2,peeraddr_in.sin_addr.s_addr);
if(linkf==0){ /* 只生成兩個(gè)子進(jìn)程 */
if(fork()==0){ /* 子進(jìn)程tcp_s2 */
close(s);Server_Send();
}else
if(fork()==0){ /* 子進(jìn)程tcp_s1 */
close(s);Server_Receive();
}
}
linkf=1;
}  共享內(nèi)存的結(jié)構(gòu)如下,通信子進(jìn)程tcp_s1從s_socket1讀,tcp_s2往對(duì)應(yīng)的s_socket2寫。
struct s_linkinfo{
int id; /* 連接的標(biāo)志號(hào),從1開(kāi)始順序編號(hào) */
int s_socket1; /* 服務(wù)器的讀套接字 */
int linkf1; /* 與客戶機(jī)的cport1連接標(biāo)志,0:未建立連接,1:已經(jīng)連接 */
int cport1; /* 客戶機(jī)的第一個(gè)端口號(hào) */
int s_socket2; /* 服務(wù)器的寫套接字 */
int linkf2; /* 與客戶機(jī)的cport2連接標(biāo)志 */
int cport2; /* 客戶機(jī)的第二個(gè)端口號(hào) */
u_long client_addr; /* 客戶機(jī)IP地址 */
char flag; /* 共享內(nèi)存占用標(biāo)志,'i':已占用,'o':未占用 */
};  ⑷ tcp_c用listen(s_c1,5)在套接字s_c1上建立客戶機(jī)的第一個(gè)監(jiān)聽(tīng)隊(duì)列,等待服務(wù)器的連接請(qǐng)求。在與服務(wù)器建立第一個(gè)連接后,再用listen(s_c2,5)建立第二個(gè)監(jiān)聽(tīng)隊(duì)列,與服務(wù)器建立第二個(gè)連接。
listen(s_c1,5);
s_w=accept(s_c1,&peeraddr_in,&addrlen);
close(s_c1); /*只答應(yīng)接收一次連接請(qǐng)求*/
linger.l_onoff=1;linger.l_linger=0;
setsockopt(s_w,SOL_SOCKET,SO_LINGER,&linger,sizeof(struct linger));
listen(s_c2,5);
s_r=accept(s_c2,&peeraddr_in,&addrlen);
close(s_c2);
setsockopt(s_r,SOL_SOCKET,SO_LINGER,&linger,sizeof(struct linger));   ⑸ 進(jìn)程tcp_s1調(diào)用函數(shù)Server_Receive在一個(gè)循環(huán)中不斷查詢是否又有新的客戶機(jī)登記在共享內(nèi)存中,方法是判定共享內(nèi)存中最后一條記錄的linkf1標(biāo)志是否為0,假如為0就調(diào)函數(shù)connect_to_client與客戶機(jī)建立第一個(gè)連接,然后輪詢所有的讀套接字,有數(shù)據(jù)則讀,沒(méi)有數(shù)據(jù)則讀下一個(gè)讀套接字。

Server_Receive(){
int s1,len,i,linkn,linkf1,n;
struct msg_buf *buf,mbuf;
buf=&mbuf;
for(;;){
linkn=shm_info(0,GETLINKN);
linkf1=shm_info(linkn,GETLINKF1);
if(linkf1==0){
if((i=connect_to_client(linkn,1))<0){
shm_logout(linkn);continue;
}
}
for(n=1;n<=linkn;n++){
s1=shm_info(n,GETS1);
i=read(s1,buf,MSGSIZE);
if(i==0){
fprintf(stderr,"A client exit!/n");
shutdown(s1,1);close(s1);
shm_logout(n);
linkn--;continue;
}
if(i==-1) continue;
buf->mtype=MSGTYPE;buf->sid=n;
len=strlen(buf->mdata);
fprintf(stderr,"mdata=%s/n",buf->mdata);
i=msgsnd(qid3,buf,len+BUFCTLSIZE+1,0);
}
}
}  由于已將讀套接字的讀取標(biāo)志設(shè)為O_NDELAY,所以沒(méi)有數(shù)據(jù)可讀時(shí)read函數(shù)就返回-1不會(huì)堵塞住。這樣我們才能接收到客戶機(jī)隨機(jī)的數(shù)據(jù)發(fā)送同時(shí)也才能及時(shí)響應(yīng)新的客戶機(jī)的連接請(qǐng)求,這是重復(fù)服務(wù)器得以實(shí)現(xiàn)的要害所在。假如read函數(shù)返回0則表示客戶機(jī)通信程序已退出或者別的原因,比如客戶機(jī)關(guān)機(jī)或網(wǎng)絡(luò)通信故障等,此時(shí)就要從共享內(nèi)存中清除相應(yīng)客戶機(jī)的記錄。在建立連接時(shí)假如出現(xiàn)上述故障也要從共享內(nèi)存中清除相應(yīng)客戶機(jī)的記錄。在有數(shù)據(jù)可讀時(shí)就將sid標(biāo)志設(shè)置為n,表示數(shù)據(jù)是從第n臺(tái)客戶機(jī)讀取的,這樣子進(jìn)程tcp_s2才可根據(jù)消息的sid標(biāo)志往第n臺(tái)客戶機(jī)寫數(shù)據(jù)。  ⑹ 進(jìn)程tcp_s2調(diào)用函數(shù)Server_Send,在一個(gè)循環(huán)中不斷查詢是否又有新的客戶機(jī)連接登記在共享內(nèi)存中,方法是判定共享內(nèi)存中最后一條記錄的linkf2標(biāo)志是否為0,假如為0就調(diào)用函數(shù)connect_to_client與客戶機(jī)建立第二個(gè)連接,然后再?gòu)南㈥?duì)列中讀數(shù)據(jù)。因?yàn)橹挥幸粋€(gè)tcp_s2進(jìn)程在讀消息隊(duì)列,所以就不必對(duì)消息進(jìn)行區(qū)別,有數(shù)據(jù)則讀。再按照消息的sid標(biāo)志從共享內(nèi)存中查出寫套接字,然后將數(shù)據(jù)往該套接字寫。由于該寫套接字是在進(jìn)程tcp_s2內(nèi)創(chuàng)建的,所以只要簡(jiǎn)單地使用套接字的句柄即可訪問(wèn)該套接字。函數(shù)msgrcv要設(shè)置IPC_NOWAIT標(biāo)志以免在沒(méi)有數(shù)據(jù)時(shí)堵塞住,這樣才能繼續(xù)執(zhí)行下面的程序以便及時(shí)地與下一臺(tái)客戶機(jī)建立連接,這也是一個(gè)要害的地方。tcp_s2調(diào)用函數(shù)Server_Send用于數(shù)據(jù)發(fā)送,tcp_s1則調(diào)用函數(shù)Server_Recvice用于數(shù)據(jù)接收。
Server_Send(){
int s2,linkn,linkf2,i;
struct msg_buf *buf,mbuf;
buf=&mbuf;
for(;;){
linkn=shm_info(0,GETLINKN);
linkf2=shm_info(linkn,GETLINKF2);
if(linkf2==0){
if((i=connect_to_client(linkn,2))<0){
shm_logout(linkn);continue;
}
}
i=msgrcv(qid4,buf,MSGSIZE,MSGTYPE,0x1ffIPC_NOWAIT);
if(i==-1) continue;
s2=shm_info(buf->sid,GETS2);
if(write(s2,buf,i+1)!=i+1){
perror("write");close(s2);
}
}
}  函數(shù)connect_to_client(n,type)表示服務(wù)器與第n臺(tái)客戶機(jī)建立第type次連接。該函數(shù)由兩個(gè)子進(jìn)程同時(shí)調(diào)用,分別從共享內(nèi)存中查出客戶機(jī)的IP地址和端口號(hào)后與客戶機(jī)建立連接,建立的連接分別處于各個(gè)子進(jìn)程自己的數(shù)據(jù)空間中,彼此并不相通,所以又要用到共享內(nèi)存,將連接的套接字句柄登記在共享內(nèi)存中,使得與同一臺(tái)客戶機(jī)建立連接的兩個(gè)套接字形成一一對(duì)應(yīng)的關(guān)系。這樣tcp_s2才可根據(jù)數(shù)據(jù)讀入的套接字去查詢出對(duì)應(yīng)的寫套接字,才能正確地將處理結(jié)果發(fā)送給對(duì)應(yīng)的客戶機(jī)。tcp_s1以type=1調(diào)用該函數(shù),使用共享內(nèi)存中第n條記錄的cport1和客戶機(jī)IP地址與客戶機(jī)建立第一個(gè)連接,同時(shí)將這一連接服務(wù)器方的套接字(讀套接字)登記在共享內(nèi)存第n條記錄的s_socket1中,同時(shí)將連接標(biāo)志linkf1置1。tcp_s2以type=2調(diào)用該函數(shù),使用共享內(nèi)存中第n條記錄的cport2和客戶機(jī)IP地址與客戶機(jī)建立第二條連接,同樣也要將這一連接服務(wù)器方的套接字(寫套接字)登記在共享內(nèi)存第n條記錄的s_socket2中,將連接標(biāo)志linkf2置1。因?yàn)樵摵瘮?shù)由兩個(gè)子進(jìn)程同時(shí)調(diào)用,為了保持進(jìn)程間同步,當(dāng)type=2時(shí)必需等到第n條記錄的linkf1為1時(shí)才能繼續(xù)執(zhí)行,即必須先建立第一個(gè)連接才能再建立第二個(gè)連接,這是由客戶機(jī)通信程序決定的,因?yàn)榭蛻魴C(jī)通信程序是先監(jiān)聽(tīng)并建立起第一個(gè)連接后再監(jiān)聽(tīng)并建立第二個(gè)連接。子進(jìn)程tcp_s1和tcp_s2通過(guò)共享內(nèi)存實(shí)現(xiàn)進(jìn)程間通信,在實(shí)際應(yīng)用中總是使用共享內(nèi)存的最后一條記錄。
②:(5991,5990,168.1.1.71) ┌─────┐①:(5991,5990) 168.1.1.21
┌─────────────┤ 守護(hù)進(jìn)程 ├←─────────┐┌─────┐
│ │ tcp_s │ 初始連接L0 ││ Client 1 │
│ 共享內(nèi)存 └─────┘ │├──┬──┤
│ id s1 linkf1 cport1 s2 linkf2 cport2 IP_Address flag ││5999│5998│
│ ┌─┬──┬──┬──┬──┬──┬──┬─────┬─┐│└──┴──┘
│ │1 │ 12 │ 1 │5999│ 13 │ 1 │5998│168.1.1.21│i ││ 168.1.1.22
│ ├─┼──┼──┼──┼──┼──┼──┼─────┼─┤│┌─────┐
│ │2 │ 14 │ 1 │5995│ 17 │ 1 │5994│168.1.1.22│i │││ Clinet 2 │
│ ├─┼──┼──┼──┼──┼──┼──┼─────┼─┤│├──┬──┤

└→┤3 │0/22│0/1 │5991│0/23│0/1 │5990│168.1.1.71│i│││5995│5994│
└─┴──┼──┴┬─┴──┼──┴┬─┴─────┴─┘│──┴──┘
⑤:(22,1)↑ │ ↑ ↓⑥:(5990,168.1.1.71)│ 168.1.1.71
│ │ │ └─────┐ │┌─────┐
│ │ │⑧:(23,1) ┌──┴┬─┐ └┤ Client 3 │
│ │ └──────┤ │13│ ├──┬──┤
│ ↓③:(5991,168.1.1.71) │通信 ├─┤ │5991│5990│
│┌──┴┬─┐ │子進(jìn)程│17│ └┬─┴─┬┘
└┤ │12│ │tcp_s2├─┤ │ L2↑⑦
│通信 ├─┤ │ │23├───┼───┘
│子進(jìn)程│14│ └───┴─┘ │
│tcp_s1├─┤L1 (讀套接字22) (寫套接字23) │
│ │22├←─────────────────┘
└───┴─┘④
圖1 服務(wù)器和客戶機(jī)建立連接的過(guò)程  這里必須置套接字的讀取標(biāo)志位O_NDELAY,這樣在讀數(shù)據(jù)時(shí)假如沒(méi)有數(shù)據(jù)可讀read函數(shù)就不會(huì)堵塞住,這是重復(fù)型服務(wù)器能夠?qū)崿F(xiàn)的要害。因?yàn)閁NIX系統(tǒng)將套接字與普通文件等同處理,所以就能夠使用設(shè)置文件標(biāo)志的函數(shù)fcntl來(lái)處理套接字。
int connect_to_client(n,type){
u_long client_addr; /* type=1,2 */
int s2,cport,sport,i;
if(type==2){
for(;;) if(shm_info(n,GETLINKF1)==1) break;
}
sport=6000-1;s2=rresvport(&sport);
cport=shm_info(n,GETCPORT1+type-1);
client_addr=shm_info(n,GETCADDR);
peeraddr_in.sin_port=htons((short)cport);
peeraddr_in.sin_addr.s_addr=client_addr;
connect(s2,(struct sockaddr *)&peeraddr_in,sizeof(peeraddr_in));
flags=fcntl(s2,F_GETFL,0);
fcntl(s2,F_SETFL,flagsO_NDELAY);
if(type==1) i=shm_update(n,s2,0,1,0);
if(type==2) i=shm_update(n,0,s2,0,1);
return(i);
}  ⑺ tcp_c在接收到服務(wù)器的兩個(gè)連接后,生成子進(jìn)程tcp_c1調(diào)用函數(shù)Client_Receive用于接收數(shù)據(jù),tcp_c則調(diào)用函數(shù)Client_Send用于發(fā)送數(shù)據(jù)。假如函數(shù)Client_Receive從循環(huán)中退出,就說(shuō)明服務(wù)器通信軟件已退出,于是子進(jìn)程在退出之前要先殺掉父進(jìn)程。
cpid=getpid(); /* 父進(jìn)程的進(jìn)程號(hào) */
if(fork()==0){ /* tcp_c1 */
close(s_w);
Client_Receive();
sprintf(cmdline,"kill -9 %d",cpid);
system(cmdline);
}else{
close(s_r);
Client_Send();
}QQRead.com 推出數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)故障解析 常用數(shù)據(jù)恢復(fù)方案 硬盤數(shù)據(jù)恢復(fù)教程 數(shù)據(jù)保護(hù)方法 數(shù)據(jù)恢復(fù)軟件 專業(yè)數(shù)據(jù)恢復(fù)服務(wù)指南   客戶機(jī)服務(wù)器接收和發(fā)送數(shù)據(jù)的方法

  數(shù)據(jù)的傳送過(guò)程   硬件劃分:
├←─── 服務(wù)器 ───→┼← 網(wǎng)絡(luò) →┼←── 客戶機(jī) ──→┤
┌──┐⑥┌──┐⑦┌──┐
┌→┤qid4├→┤ L2 ├→┤qid2├─┐
⑤│ └──┘ └──┘ └──┘ ↓⑧
┌──┐ ┌──┴──┐ ┌──→ ┌──┴──┐ ┌────┐
│ DB ├←→┤s_process │ │ │c_process ├←→┤終端用戶│
└──┘ └──┬──┘ └─── └──┬──┘ └────┘
④↑ ┌──┐ ┌──┐ ┌──┐ │①
└─┤qid3├←┤ L1 ├←┤qid1├←┘
軟件劃分: └──┘③└──┘②└──┘
├←─ s_process ──→┼←tcp_s→┼←tcp_c→┼← c_process →┤
圖2 數(shù)據(jù)在客戶機(jī)服務(wù)器之間傳遞的全過(guò)程  其中s_process和c_process是分別運(yùn)行在服務(wù)器上的服務(wù)器業(yè)務(wù)程序和運(yùn)行在客戶機(jī)上的客戶業(yè)務(wù)進(jìn)程。qid3,qid4和qid1,qid2是分別存在于服務(wù)器及客戶機(jī)上的消息隊(duì)列。  tcp_s和tcp_c是分別運(yùn)行在服務(wù)器和客戶機(jī)上的通信軟件。在客戶機(jī)和服務(wù)器之間建立的兩條連接是L1和L2,其中L1專用于客戶機(jī)至服務(wù)器,L2專用于服務(wù)器至客戶機(jī)。  下面敘述圖2中所示的數(shù)據(jù)傳遞過(guò)程,同時(shí)介紹用于數(shù)據(jù)接收和發(fā)送的四個(gè)函數(shù)。因?yàn)闃I(yè)務(wù)程序不知何時(shí)可以接收或發(fā)送消息,所以這四個(gè)函數(shù)都存在一個(gè)循環(huán)不斷地試圖接收或發(fā)送數(shù)據(jù)。表示消息的數(shù)據(jù)結(jié)構(gòu)是sg_buf,消息由消息類別mtype及正文段mdata組成。  正文段中存放的數(shù)據(jù)是無(wú)結(jié)構(gòu)的,必須定義一種數(shù)據(jù)結(jié)構(gòu)(struct),用結(jié)構(gòu)中的各變量對(duì)mdata進(jìn)行劃分,從而使mdata中的數(shù)據(jù)可以被理解和使用。還可將mdata前面的一部分區(qū)域劃出來(lái)重新命名用作其他用途。消息在整個(gè)數(shù)據(jù)傳遞的過(guò)程中起類似“載體”的作用。
#define MSGSIZE 200
struct msg_buf{
long mtype; /* 消息類別 */
long cpid; /* 客戶業(yè)務(wù)進(jìn)程標(biāo)識(shí)號(hào) */
long sid; /* 共享內(nèi)存記錄編號(hào) */
long msgid; /* 消息編號(hào) */
char mdata[MSGSIZE-16]; /* 數(shù)據(jù)區(qū) */

}  ① 客戶業(yè)務(wù)程序c_process從終端用戶接收數(shù)據(jù),先存放在一個(gè)結(jié)構(gòu)中,然后將該結(jié)構(gòu)的內(nèi)容依照一定的格式拷入buf->mdata中,然后將buf以消息的形式放入消息隊(duì)列qid1中。
pidc=getpid();/* c_process的進(jìn)程號(hào) */
buf->mtype=1; /* 消息類別都為1 */
buf->sid=0; /* sid在客戶機(jī)沒(méi)用 */
buf->msgid=++msgid;
buf->cpid=pidc;
msgsnd(qid1,buf,MSGSIZE,0);  ② 進(jìn)程tcp_c調(diào)用函數(shù)Client_Send從qid1中取得消息,然后往L1寫給服務(wù)器。從qid1中取消息時(shí)對(duì)消息并不予于區(qū)別,凡在qid1中的消息都要由進(jìn)程tcp_c來(lái)發(fā)送。
for(;;){ /* 取mtype=1的消息 */
msgrcv(qid1,buf,MSGSIZE,1,0);
write(s_w,buf,i+1);
}  ③ 進(jìn)程tcp_s1調(diào)用函數(shù)Server_Receive從L1讀數(shù)據(jù)至buf中,將buf作為消息放入qid3中。
for(n=1;n<=linkn;n++){
s1=shm_info(n,GETS1);
i=read(s1,buf,MSGSIZE);
if(i==-1) continue;
if(i==0) ... /* 判定出客戶機(jī)已退出 */
/* n是s1在共享內(nèi)存登記項(xiàng)的編號(hào) */
buf->sid=n;
msgsnd(qid3,buf,MSGSIZE,0);
}  ④ 服務(wù)器業(yè)務(wù)程序s_process從消息隊(duì)列qid3中接收消息到buf,然后將buf->mdata轉(zhuǎn)成結(jié)構(gòu),根據(jù)結(jié)構(gòu)的內(nèi)容對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。s_process處在一個(gè)循環(huán)中,一有消息就取走去作消息所要求的操作,對(duì)消息并不加以區(qū)別。假如沒(méi)有消息函數(shù)msgrcv就處于堵塞狀態(tài)。  ⑤ s_process根據(jù)消息的內(nèi)容訪問(wèn)數(shù)據(jù)庫(kù)后將結(jié)果放在一個(gè)結(jié)構(gòu)中,然后將該結(jié)構(gòu)的內(nèi)容拷到buf->mdata中,再將緩沖區(qū)buf以消息的形式放于消息隊(duì)列qid4中,最后s_process又要繼續(xù)循環(huán)再去接收新的消息。
for(;;){
msgrcv(qid3,buf,MSGSIZE,1,0);
... ...
/* 解釋buf->mdata的內(nèi)容,對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作后再將結(jié)果存放在buf->mdata中 */
buf->mtype=1;
msgsnd(qid4,buf,MSGSIZE,0);
}  ⑥ 進(jìn)程tcp_s2調(diào)用Server_Send從qid4中取走mtype=1的第一個(gè)消息,往L2寫回客戶機(jī)。
for(;;){
i=msgrcv(qid4,buf,MSGSIZE,1,0);
if(i==-1) continue;
s2=shm_info(buf->sid,GETS2);
write(s2,buf,i+1);
}  ⑦ 進(jìn)程tcp_c1調(diào)用函數(shù)Client_Receive從L2讀數(shù)據(jù)到buf中,將buf作為消息放入qid2中。假如函數(shù)read返回0則表示服務(wù)器通信程序已經(jīng)退出,于是就中斷循環(huán)。這里必須將消息的類別mtype設(shè)置為客戶業(yè)務(wù)進(jìn)程的進(jìn)程號(hào)cpid,便于客戶業(yè)務(wù)程序識(shí)別。
for(;;){
i=read(s_r,buf,MSGSIZE);
if(i==0){
close(s_r);return(1);
}
buf->mtype=buf->cpid;
msgsnd(qid2,buf,i+1,0);
}  ⑧ 客戶業(yè)務(wù)程序c_process從消息隊(duì)列qid2中取走mtype=pidc(自身進(jìn)程號(hào))的第一個(gè)消息放入緩沖區(qū)buf中,再將buf->mdata中的數(shù)據(jù)劃分為結(jié)構(gòu),對(duì)該結(jié)構(gòu)作處理后將最終結(jié)果顯示給用戶。 在①中c_process將數(shù)據(jù)發(fā)出后要在什么時(shí)候到qid2中去拿結(jié)果呢? 方法是一就消息發(fā)送出去后客戶業(yè)務(wù)程序馬上就到qid2中去拿結(jié)果,若沒(méi)有給自己的消息則堵塞住直到消息到來(lái)。這里程序設(shè)計(jì)成在堵塞20秒后發(fā)出時(shí)鐘警報(bào),調(diào)用函數(shù)overtime作出超時(shí)反應(yīng)。當(dāng)時(shí)鐘警報(bào)時(shí)假如函數(shù)msgrcv正處于堵塞狀態(tài)也會(huì)退出并返回-1。  這里就又存在一個(gè)問(wèn)題,c_process在發(fā)送一個(gè)新消息后可能先接收到上一個(gè)因超時(shí)而未能被接收到的消息,解決這一問(wèn)題最簡(jiǎn)單的方法就是發(fā)送消息之前給每個(gè)消息編號(hào),假如接收到的消息的編號(hào)與發(fā)送的消息的編號(hào)不同則將消息從消息隊(duì)列中刪除,或者將消息取出后放在某一地方另行處理,然后繼續(xù)等待接收正確編號(hào)的消息。刪除消息的方法很簡(jiǎn)單,只要從消息隊(duì)列中將消息取出就可以了。假如進(jìn)程c_process被殺則遲到的消息由于其mtype表示的c_process已經(jīng)不在運(yùn)行,所以將會(huì)始終存在于消息隊(duì)列中,直到客戶機(jī)關(guān)機(jī),因此在必要時(shí)也要對(duì)這些無(wú)主的消息作善后處理。
alarm(20);
signal(SIGALRM,overtime);
for(;;){
i=msgrcv(qid2,buf,MSGSIZE,pidc,0);
if(i==-1) break;
if(buf->msgid==msgid) break;
}
alarm(0);
printf("%s/n",buf->mdata);overtime(int sig){
strcpy(buf->mdata,"overtime");
}QQRead.com 推出數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)指南教程 數(shù)據(jù)恢復(fù)故障解析 常用數(shù)據(jù)恢復(fù)方案 硬盤數(shù)據(jù)恢復(fù)教程 數(shù)據(jù)保護(hù)方法 數(shù)據(jù)恢復(fù)軟件 專業(yè)數(shù)據(jù)恢復(fù)服務(wù)指南   兩個(gè)要害問(wèn)題的解決方法

  通常一臺(tái)服務(wù)器要連接多臺(tái)客戶機(jī),而每臺(tái)客戶機(jī)由于支持多用戶方式就會(huì)同時(shí)運(yùn)行多個(gè)c_process進(jìn)程。服務(wù)器如何準(zhǔn)確地將消息送給哪一臺(tái)客戶機(jī)? 另外一臺(tái)客戶機(jī)上運(yùn)行的每一個(gè)c_process進(jìn)程如何正確地獲取發(fā)送給自己的消息? 這是兩個(gè)要害的問(wèn)題。 第一個(gè)問(wèn)題在前面已經(jīng)講述過(guò),主要是通過(guò)消息的sid標(biāo)志來(lái)區(qū)別的。第二個(gè)問(wèn)題是這樣解決,在第①步時(shí)c_process進(jìn)程先將自身的進(jìn)程號(hào)pidc放在buf->cpid中,該值在以后的傳輸過(guò)程中保持不變,在第⑦步再將cpid賦值給消息類別mtype。這樣在第⑧時(shí)c_process進(jìn)程就從消息隊(duì)列qid2中取走消息類別mtype等于其自身進(jìn)程號(hào)pidc的消息,而不會(huì)錯(cuò)將送給同一客戶機(jī)別的c_process進(jìn)程的消息拿走。(圖3)
┌──────────────┐ ┌────────────┐

│Server ┌───┤ ├───┐ ┌─────┐│
│ │tcp_s │ ┌────┤tcp_c ├┐│c_process2││
│ ┌─────┐ └─┬─┤ │ ├───┤│└─────┘│
│ │s_process │┌───┴┐│ │ ┌─→┤tcp_c1││┌─────┐│
│ │服務(wù)程序 ││共享內(nèi)存││ │ │ L2├─┬─┘││c_process1││
│ └─┬─┬─┘└───┬┘│ │ │ │ ↓⑦ │└───┬┬┘│
│ ⑤↓ ↑④ ┌─┴─┤L1 │ │ │ │ └─┐ │↑⑧│
│┌──┘ │ ┌─┤tcp_s1├←──┘ │ │ │ ②↑ ││ │
││┌──┬┼┐③│ │ ├←┐L1' │ │ │┌──┬┼┐①││ │
│││qid3│ ├←┘ ├───┤ │ │ │ ││qid1│ ├←┘│ │
││├──┼─┤ ┌┤tcp_s2├─┼───┘ │ │├──┼─┤ │ │
│││qid4│ ┼→─┘│ ├┐│┌────┐│ ││qid2│ ┼──┘ │
││└──┴┬┘⑥ └───┤│└┤ ││ │└──┴┬┘ │
│└────┘ │└→┤Client2 ││ └────┘ Client1 │
└──────────────┘ L2'└────┘└──────────┘
圖3 消息在服務(wù)器和客戶機(jī)內(nèi)傳送的過(guò)程  消息隊(duì)列與共享內(nèi)存

  在運(yùn)行服務(wù)器通信軟件之前應(yīng)先創(chuàng)建共享內(nèi)存和消息隊(duì)列,創(chuàng)建共享內(nèi)存的方法見(jiàn)文獻(xiàn)[3]。本文共用到四個(gè)共享內(nèi)存操作函數(shù):shm_login(cport1,cport2,client_addr)在共享內(nèi)存中申請(qǐng)一條記錄將三個(gè)參數(shù)登記其中,并將flag標(biāo)志設(shè)為'i'表示已經(jīng)占用,同時(shí)根據(jù)記錄的位置賦值給記錄編號(hào)id。shm_logout(id)將共享內(nèi)存中第id條記錄刪除,并將后面的記錄前移,重新計(jì)算各條記錄的編號(hào)。shm_info(id,type)根據(jù)type查詢第id條記錄的內(nèi)容,比如type為GETS1時(shí)表示要查詢s_socket1的值,當(dāng)type等于GETLINKN時(shí)統(tǒng)計(jì)共享內(nèi)存的記錄總數(shù)。shm_update(id,s_socket1,s_socket2,linkf1,linkf2)修改第id條記錄的內(nèi)容,假如某個(gè)參數(shù)為零則不修改這個(gè)參數(shù),如shm_update(n,s2,0,1,0)只修改s_socket1和linkf1的值,其余內(nèi)容不作修改。在業(yè)務(wù)繁忙的情況下,有必要擴(kuò)大消息隊(duì)列的存儲(chǔ)容量,下面的例子將消息隊(duì)列qid3的容量擴(kuò)大兩倍。
struct msqid_ds sbuf1,*sbuf;int qid3;
sbuf=&sbuf1;
qid3=msgget(MSGKEY3,02000);
msgctl(qid1,IPC_STAT,sbuf);
sbuf->msg_qbytes*=2;
msgctl(qid3,IPC_SET,sbuf);  其他問(wèn)題的討論

  由于將服務(wù)器與客戶機(jī)的連接登記在共享內(nèi)存中,所以可以控制服務(wù)器與客戶機(jī)的連接次數(shù),在服務(wù)器接收到客戶機(jī)的連接請(qǐng)求后可以先查詢共享內(nèi)存,假如與同一臺(tái)客戶機(jī)建立的連接次數(shù)已達(dá)到限定的數(shù)量時(shí),服務(wù)器的守護(hù)進(jìn)程就可以關(guān)閉掉已與客戶機(jī)建立起來(lái)的初始連接,同時(shí)不再將客戶機(jī)的端口號(hào)和IP地址登記在共享內(nèi)存中,這樣子進(jìn)程也將不會(huì)再與客戶機(jī)建立連接了。

  另外這種重復(fù)型服務(wù)器通信軟件使用一個(gè)只讀的套接字和一個(gè)只寫的套接字,由于一個(gè)套接字都有獨(dú)立的讀緩沖區(qū)和寫緩沖區(qū),長(zhǎng)度都是24k。于是只讀的套接字就不會(huì)用到寫緩沖區(qū),只寫的套接字就不會(huì)用到讀緩沖區(qū),為了節(jié)省系統(tǒng)資源有必要將套接字設(shè)置成只有一個(gè)緩沖區(qū),比如將只讀套接字的寫緩沖區(qū)長(zhǎng)度設(shè)置為0。
int i,bufsize;
i=sizeof(int);
getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i);
fprintf(stderr,"size=%d/n",bufsize);
bufsize=0;
setsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,i);
getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i);
fprintf(stderr,"size=%d/n",bufsize);  在圖2所示的僅是應(yīng)用模式中的一種,本文提到的重復(fù)型服務(wù)器通信軟件還可用于更復(fù)雜的情況。比如當(dāng)客戶機(jī)要與另一臺(tái)客戶機(jī)通信時(shí)就可用服務(wù)器作為中轉(zhuǎn)站,從而不必在客戶機(jī)之間建立連接。 比如通信子進(jìn)程tcp_s1查詢出目的客戶機(jī)登記在共享內(nèi)存第x條記錄中,就將接收到的消息的sid置為x,這樣子進(jìn)程tcp_s2就可將消息送往第x臺(tái)客戶機(jī),當(dāng)然源客戶機(jī)在發(fā)送的消息中應(yīng)指明目的客戶機(jī)的IP地址。這在客戶機(jī)之間通信并不頻繁的情況下很有用,因?yàn)檫@樣就可減少所有的客戶機(jī)都要相互建立連接的系統(tǒng)開(kāi)銷,有利于提高整個(gè)網(wǎng)絡(luò)的運(yùn)行效率。在某種特定的應(yīng)用場(chǎng)合服務(wù)器在收到客戶機(jī)的服務(wù)請(qǐng)求后,但因某種原因暫不能處理,于是就將消息存放起來(lái),要等到條件成熟時(shí)服務(wù)器才能處理客戶請(qǐng)求并將結(jié)果返回給客戶機(jī),此時(shí)客戶機(jī)就不能認(rèn)為這也是一個(gè)遲到的消息,應(yīng)另行處理。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 济源市| 许昌县| 平武县| 略阳县| 罗平县| 抚宁县| 石棉县| 乌鲁木齐县| 沙雅县| 宁国市| 济阳县| 南城县| 邹平县| 盱眙县| 苏尼特左旗| 彭水| 晴隆县| 沁阳市| 漠河县| 白银市| 灵山县| 仙居县| 嘉定区| 铜川市| 惠安县| 酉阳| 邢台市| 资兴市| 古浪县| 新昌县| 厦门市| 平度市| 翁源县| 望谟县| 铜梁县| 田阳县| 景洪市| 上饶县| 陇西县| 常宁市| 普兰店市|