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,利用初始連線將客戶機的兩個套接字的埠號以字串的形式傳送給伺服器,這時初始連線的任務已經完成就可將其關閉。以上就完成了與伺服器的初始連線,接下來客戶機等待伺服器的兩次連線請求。