函數(shù)connect_to_client(n,type)表示服務(wù)器與第n臺(tái)客戶(hù)機(jī)建立第type次連接。該函數(shù)由兩個(gè)子進(jìn)程同時(shí)調(diào)用,分別從共享內(nèi)存中查出客戶(hù)機(jī)的IP地址和端口號(hào)后與客戶(hù)機(jī)建立連接,建立的連接分別處于各個(gè)子進(jìn)程自己的數(shù)據(jù)空間中,彼此并不相通,所以又要用到共享內(nèi)存,將連接的套接字句柄登記在共享內(nèi)存中,使得與同一臺(tái)客戶(hù)機(jī)建立連接的兩個(gè)套接字形成一一對(duì)應(yīng)的關(guān)系。這樣tcp_s2才可根據(jù)數(shù)據(jù)讀入的套接字去查詢(xún)出對(duì)應(yīng)的寫(xiě)套接字,才能正確地將處理結(jié)果發(fā)送給對(duì)應(yīng)的客戶(hù)機(jī)。tcp_s1以type=1調(diào)用該函數(shù),使用共享內(nèi)存中第n條記錄的cport1和客戶(hù)機(jī)IP地址與客戶(hù)機(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和客戶(hù)機(jī)IP地址與客戶(hù)機(jī)建立第二條連接,同樣也要將這一連接服務(wù)器方的套接字(寫(xiě)套接字)登記在共享內(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è)連接,這是由客戶(hù)機(jī)通信程序決定的,因?yàn)榭蛻?hù)機(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) (寫(xiě)套接字23) │
│ │22├←─────────────────┘
└───┴─┘④
圖1 服務(wù)器和客戶(hù)機(jī)建立連接的過(guò)程
這里必須置套接字的讀取標(biāo)志位O_NDELAY,這樣在讀數(shù)據(jù)時(shí)如果沒(méi)有數(shù)據(jù)可讀read函數(shù)就不會(huì)堵塞住,這是重復(fù)型服務(wù)器能夠?qū)崿F(xiàn)的關(guān)鍵。因?yàn)閁NIX系統(tǒng)將套接字與普通文件等同處理,所以就能夠使用設(shè)置文件標(biāo)志的函數(shù)fcntl來(lái)處理套接字。 來(lái)源:www.examda.com
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,flags|O_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);
②:(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) (寫(xiě)套接字23) │
│ │22├←─────────────────┘
└───┴─┘④
圖1 服務(wù)器和客戶(hù)機(jī)建立連接的過(guò)程
這里必須置套接字的讀取標(biāo)志位O_NDELAY,這樣在讀數(shù)據(jù)時(shí)如果沒(méi)有數(shù)據(jù)可讀read函數(shù)就不會(huì)堵塞住,這是重復(fù)型服務(wù)器能夠?qū)崿F(xiàn)的關(guān)鍵。因?yàn)閁NIX系統(tǒng)將套接字與普通文件等同處理,所以就能夠使用設(shè)置文件標(biāo)志的函數(shù)fcntl來(lái)處理套接字。 來(lái)源:www.examda.com
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,flags|O_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);