糯米文學吧

位置:首頁 > 計算機 > 網絡技術

TCP/IP網絡重複型服務器通信軟件設計

摘要:本文介紹一種新型的基於消息隊列的重複型服務器通信軟件的設計方法,不同於併發型服務器和一般的重複型服務器通信軟件,這種新的軟件具有生成的子進程數少的優點,並且容易對客户機與服務器的連接進行管理,適用於客户機數量較多和隨機數據通信的情況,能夠有效地提高服務器的運行效率。

TCP/IP網絡重複型服務器通信軟件設計

關鍵詞:TCP/IP網絡 重複型服務器通信軟件 套接字 連接 共享內存 消息隊列

  併發服務器與重複服務器的區別

一般TCP/IP服務器通信軟件都是併發型的,即是由一個守護進程負責監聽客户機的連接請求,然後再由守護進程生成一個或多個子進程與客户機具體建立連接以完成通信,其缺點是隨着連接的客户機數量的增多,生成的通信子進程數量會越來越多,在客户機數量較多的應用場合勢必影響服務器的運行效率。一般的重複服務器指的是服務器在接收客户機的連接請求後即與之建立連接,然後要在處理完與客户機的通信任務後才能再去接收另一客户機的請求連接,其優點是不必生成通信子進程,缺點是客户機在每次通信之前都要與服務器建立連接,開銷過大,不能用於隨機的數據通信和繁忙的業務處理。

本文提出的新型的重複型服務器不同於一般的重複服務器,它摒棄了上述兩類服務器的缺點綜合其優點,該服務器通信軟件具有一般重複服務器的特徵但又能處理客户機的隨機訪問,在客户機數量多且業務繁忙的應用場合將發揮其優勢。重複型服務器通信軟件只用三個進程就可完成與所有客户機建立連接,並始終保持這些連接。

重複型服務器通信軟件與客户機建立連接的.方法

  基本思路

當第一台客户機向服務器請求連接時,服務器的守護進程與之建立初始連接(L0),客户機利用L0向服務器發送兩個端口號,守護進程將客户機的IP地址和端口號登記在共享內存的記錄中,然後關閉L0。由守護進程生成的兩個通信子進程從共享內存中獲得客户機IP地址及端口號後,分別向客户機請求連接,建立一個從客户機讀的連接(L1)和一個往客户機寫的連接(L2),並將兩個連接的套接字的句柄記錄在共享內存中。當另一台客户機請求連接時,守護進程不再生成通信子進程,只是將客户機IP地址和端口號同樣登記在共享內存中。通信子進程在一個大循環中先查詢共享內存中是否有新的記錄,如果有則與這一台客户機建立連接,然後輪詢所有已建立的連接的讀套接字,查看是否有數據可讀,有則讀取數據,同時標明該數據是從共享內存中的哪條記錄上的讀套接字中獲得的,再由另一個通信子進程根據這個記錄的編號從共享內存中獲得對應的寫套接字,最後將結果數據往該套接字寫往客户機。

  建立連接

⑴ 服務器通信軟件的初始進程首先建立公用端口上的套接字,並在該套接字上建立監聽隊列,同時生成一個守護進程(Daemon)tcp_s,然後初始進程就退出運行。守護進程在函數accept處堵塞住直到有客户機的連接請求,一有連接請求即調用server函數處理,然後繼續循環等待另一台客户機的請求。因為TCP/IP在連接被拆除後為了避免出現重複連接的現象,一般是將連接放在過時連接表中,連接在拆除後若要避免處於TIME_WAIT狀態(過時連接),可調用setsockopt設置套接字的linger延時標誌,同時將延時時間設置為0。服務器在/etc/services文件中要登記一個全局公認的公用端口號: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); /* 創建監聽套接字 */
myaddr__addr.s_addr=INADDR_ANY;
myaddr__port=sp->s_port; /* 公用端口號 */
bind(ls,&myaddr_in,sizeof(struct sockaddr_in));
listen(ls,5);
qid3=msgget(MSGKEY3,0x1ff); /* 獲得消息隊列的標誌號 */
qid4=msgget(MSGKEY4,0x1ff);
signal(SIGCLD,SIG_IGN); /* 避免子進程在退出後變為僵死進程 */
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:
fprintf(stderr,"初始進程退出,由守護進程監聽客户機的連接請求.n");
}

⑵ 客户機以這樣的形式運行通信程序tcp_c:tcp_c rhostname,rhostname為客户機所要連接的服務器主機名。客户機上的/etc/services文件中也要登記:tcp_server 2000/tcp,公用端口號2000要與服務器一樣。

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中獲取服務器的IP地址 */
qid1=msgget(MSGKEY1,0x1ff);
qid2=msgget(MSGKEY2,0x1ff);
cport1=6000;
s=rresvport(&cport1);
peeraddr__family=hp->h_addrtype;
bcopy(hp->h_addr_list[0],(caddr_t)&peeraddr__addr,hp->h_length);
peeraddr__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置一個整數後調用rresvport函數,該函數先檢查端口號cport1是否已被佔用,如果已被佔用就減一再試,直到找到一個未用的端口號,然後生成一個套接字,將該套接字與端口號相聯形成客户機端的半相關,接下調用connect函數向服務器發出連接請求。客户機在發出連接請求之前,已用函數gethostbyname和getservbyname獲得了服務器的IP地址及其公用端口號,這樣就形成了一個完整的相關,可建立起與服務器的初始連接。接下來再創建兩個套接字s_c1和s_c2,利用初始連接將客户機的兩個套接字的端口號以字符串的形式發送給服務器,這時初始連接的任務已經完成就可將其關閉。以上就完成了與服務器的初始連接,接下來客户機等待服務器的兩次連接請求。