1. 程式人生 > >如何編寫Linux下的客戶機/伺服器軟體

如何編寫Linux下的客戶機/伺服器軟體

   Linux以其原始碼公開聞名於世,並以其穩定性和可靠性雄霸作業系統領域,在網路應用技術方面使用得更加廣泛。很久以來它就是Windows的重要對手之一。隨著網路時代的來臨,Linux的這種優勢已變得更加突出。本文將論述如何在Linux環境下利用Socket實現客戶機/伺服器通訊。
隨著網路技術的發展,網路結構已從過去的主機/終端型、對等型發展到現在廣為使用的客戶機/伺服器型。客戶機/伺服器模型應用十分廣泛,在Internet上WWW,E-mail,FTP等都是基於這種模型的。在面向連線的通訊模式下,伺服器開啟監聽埠,監聽網路上其它客戶機向該伺服器發出的連線請求,當收到一個請求訊號時與該客戶機建立一個連線,之後兩者進行互動式的通訊。具體步驟可這樣組織:

伺服器:
1.開啟一個已知的監聽埠,如smtp為25、pop3為110、ftp為21、telnet為23等。
2.在監聽埠上監聽客戶機的連線請求,如果有客戶機請求連線則建立一個連線線路。
3.在連線線路上與客戶機通訊。
4.通訊完畢後關閉連線線路並繼續監聽客戶機的連線請求。

客戶機:
1.向指定的伺服器主機及埠發出連線請求。
2.當伺服器建立連線線路後與伺服器進行通訊。
3.通訊完畢後關閉連線線路。

Linux的許多特性都非常有助於網路程式設計:首先Linux擁有POSIX.1標準庫函式,socket()、bind()、listen()這幾個庫函式可以非常方便地實現伺服器/客戶機模型,有關這幾個庫函式的使用說明將在後邊介紹。其次Linux的程序管理也非常符合伺服器的工作原理,所謂程序就是程式在記憶體中執行時的狀態,可以說程序是動態的程式。在執行著Linux作業系統的計算機中,每一個程序都有一個建立它的父程序,而且它也能建立多個子程序。在伺服器端我們可以用父程序去監聽客戶機的連線請求,當有客戶機的連線請求時父程序建立一個子程序去建立連線線路並與客戶機通訊,而它本身可繼續監聽其它客戶機的連線請求,這樣就可避免當有一個客戶機與伺服器建立連線後伺服器就不能再與其它客戶機通訊的問題。Linux的另一個特性是它秉承了UNIX裝置無關性這一優秀特徵,即它通過檔案描述符實現了統一的裝置介面,磁碟、顯示終端、音訊裝置、列印裝置甚至網路通訊都使用統一的I/O呼叫。這三個特性將使Linux下的網路程式設計變得易如反掌。上述三個特性的綜合利用將是這篇文章所要講述的真諦所在。下邊的客戶機/伺服器實現過程可以說明一二,注意與上文所述步驟的不同。

伺服器:
1.開啟一個已知的監聽埠。
2.在監聽埠上監聽客戶機的連線請求,當有一客戶機請求連線時建立連線線路並返回通訊檔案描述符。
4.父程序建立一子程序,父程序關閉通訊檔案描述符並繼續監聽埠上的客戶機連線請求。
3.子程序通過通訊檔案描述符與客戶機進行通訊,通訊結束後終止子程序並關閉通訊檔案描述符。

客戶機:
1.向指定的伺服器主機及埠發出連線請求,請求成功將返回通訊檔案描述符。
2.通過通訊檔案描述符與伺服器進行通訊。
3.通訊完畢後關閉通訊檔案描述符。


Linux的以下幾個庫函式是網路程式設計的核心部分,它們分別是:
(1)socket
呼叫方式:
#include
#include

int socket(int domain,int type,int protocol);

簡要說明:
此函式為通訊建立一個埠,正常呼叫將返回一個檔案描述符,錯誤呼叫將返回-1。domain引數有兩種選擇:AF_UNIX與AF_INET,其中AF_INET為Internet通訊協議。type引數也有兩種選擇:SOCK_STREAM用於TCP,SOCK_DGRAM用於UDP。protocol引數通常為0。可通過下列程式碼為基於TCP協議的Internet通訊建立套介面傳輸埠:

#include
#include
#include
int sock;

if((sock=socket(AF_INET,SOCK_STREAM,0))==-1)
perror("Could not create socket");

(2)bind
呼叫方式:
#include
#include

int bind(int s,const struct sockaddr *address,size_t address_len);

簡要說明:
bind英文含意是關聯,捆綁。其目的就是把socket返回的套介面埠與網路上的物理位置相關聯。
bind正常呼叫返回0,出錯返回-1。此函式有三個引數:其中s為socket呼叫返回的檔案描述符,*address設定了與網路上的物理位置相關的資訊,它的型別是struct sockaddr,但在Internet上它是struct sockaddr_in。在socket.h中struct sockaddr_in定義為:
struct sockaddr_in{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family一般為AF_INET,sin_port為埠號,由於使用不同位元組順序的機器必須作轉換,故應使用巨集命令htons(host to network short)來轉換埠號,sin_addr將置為INADDR_ANY。這三個值設定完成後*address引數才有意義。在編寫程式碼時,應先設定*address引數內部各成員變數的值,再呼叫bind。

(3)listen
呼叫方式:
#include
#include

int listen(int s,int backlog);

簡要說明:
本函式使socket埠能夠接受從客戶機來的連線請求,正常呼叫返回0,出錯返回-1。
s引數為socket產生的檔案描述符,backlog為所能接受客戶機的最大數目。
socket,bind,listen 三個函式的綜合呼叫最終在伺服器上產生一個能接受客戶機請求的監聽檔案描述符s。

(4)accept
呼叫方式:
#include
#include

int accept(int s,struct sockaddr *address,int *address_len);

簡要說明:
當有客戶機發出連線請求時,此函式初始化這個連線。正常呼叫返回與客戶機通訊的通訊檔案描述符,出錯返回-1。引數s為socket呼叫返回的檔案描述符,address將用來儲存客戶機的資訊,此資訊由accept填入,當與客戶機連線時,客戶機的地址與埠將填到此處。address_len是客戶機地址長度的位元組數,也由accept填入。

(5)connect
呼叫方式:
#include
#include

int connect(int s,struct sockaddr *address,size_t address_len);

簡要說明:
客戶機呼叫socket建立傳輸埠後,呼叫connect來建立與遠端伺服器相連的連線線路。
此函式的引數呼叫同bind。

(6)inet_addr
呼叫方式:
#include
#include
#include

in_addr_t inet_addr(const char *addstring);

簡要說明:
此函式將字串addstring表示的網路地址(如192.168.0.1)轉換成32位的網路位元組序二進位制值,若成功返回32位二進位制的網路位元組序地址,若出錯返回 INADDR_NONE。INADDR_NONE是32位均為1的值(即255.255.255.255,它是Internet的有限廣播地址),故如果要轉換的addstring是255.255.255.255,函式呼叫將失敗。

(7)fork
呼叫方式:
#include
#include


pid_t fork(void);

簡要說明:
fork的作用是拷貝父程序的記憶體映象來建立子程序,兩個程序將接著fork後的指令繼續執行。 事實上它返回兩個程序控制號,對於父程序它返回子程序的程序ID,對於子程序它返回0。

可用下邊的程式碼呼叫fork:

pid_t childpid;
if((childpid=fork())=-1){
perror("The fork failed");
exit(1);
}
else if(child==0){
呼叫子程序;
}
else if(child>0){
呼叫父程序;
}


以上介紹了網路程式設計的有關庫函式的呼叫方法,下面舉一個客戶機/伺服器程式的小例子具體說明如何設計網路程式。本例介紹如何檢視伺服器上的時間和日期,由於daytime伺服器的通用埠為13,客戶機程式將通過呼叫13號埠對伺服器上的時間和日期進行操作。
/*timeserve.c*/ 
/*伺服器程式虛擬碼如下: 

開啟daytime監聽埠; 
while(客戶機與伺服器成功連線——成功返回通訊檔案描述符) 

fork() 
子程序: 

讀出當前時間; 
將當前時間寫入通訊檔案描述符; 
關閉通訊檔案描述符; 

父程序: 
關閉通訊檔案描述符; 

*/
 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,char*argv[]) 

int listenfd,communfd; 
struct sockaddr_in servaddr; 
pid_t childpid; 
time_t tick; 
char buf[1024]; 

if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1

perror(
"Could not create socket"); 
exit(
1); 
}
 

servaddr.sin_family
=AF_INET; 
servaddr.sin_addr.s_addr
=INADDR_ANY; 
servaddr.sin_port
=htons(13); 
if(bind(listenfd,(
struct sockaddr *)&servaddr,sizeof(servaddr))==-1

perror(
"bind error"); 
exit(
1); 
}
 
if(listen(listenfd,254)==-1

perror(
"listen error"); 
exit(
1); 
}
 
while(communfd=accept(listenfd,(struct sockaddr*)NULL,NULL)) 

if((childpid=fork())==-1

perror(
"fork error"); 
exit(
1); 
}
 
elseif(childpid==0

tick
=time(NULL); 
snprintf(buf,
sizeof(buf),"%.24s\r\n",ctime(&tick)); 
write(communfd,buf,strlen(buf)); 
close(communfd); 
}
 
elseif(childpid>0
close(communfd); 

}
 
exit(
0); 
}
 


/*timeclient.h*/ 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc,char*argv[]) 

int communfd,n; 
struct sockaddr_in servaddr; 
char recieve[1024],buf[1024]; 

if(argc!=2

perror(
"Usage: client "); 
exit(
1); 
}
 
if((communfd=socket(AF_INET,SOCK_STREAM,0))==-1

perror(
"socket error"); 
exit(
1); 
}
 
servaddr.sin_family
=AF_INET; 
servaddr.sin_port
=htons(13); 
if((servaddr.sin_addr.s_addr=inet_addr(argv[1]))==INADDR_NONE) 

perror(
"inet_addr error"); 
exit(
1); 
}
 
if(connect(communfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1

perror(
"connect error"); 
exit(
1); 
}
 
while((n=read(communfd,recieve,1024))>0

recieve[n]
=0
if(fputs(recieve,stdout)==EOF) 
perror(
"fputs error"); 
}
 
close(communfd); 
exit(
0); 
}
 
用gcc編譯兩個源程式分別取名為server和client,以根使用者身份執行伺服器程式(設伺服器網路地址為192.168.0.1):
server &
然後執行客戶機程式(設伺服器網路地址為192.168.0.1):
client 192.168.0.1
在客戶機上就會反映出伺服器上當前的時間如(Tue Feb 29 21:46:19 2000)。

以上程式程式碼在redhat 6.0上試驗通過。在程式程式碼中有關庫函式snprintf、fputs、read、write、close的用法就不在這裡說明了,如想了解這些庫函式的呼叫方法可到我的網頁http://lzdx.yeah. net/pro_unix.html去查詢。在我的網頁http://lzdx.yeah.net/pro_uici.html中有關於通用Internet介面(UICI)專用庫的介紹,通用Internet介面(UICI)利用Socket庫函式提供了一個簡化的獨立於傳輸的介面,它從整體上簡化了網路程式設計過程。有興趣的人可到那裡去看看。最後祝願我們每個人都能編寫出自己的網路程式。

posted on 2006-04-20 17:19 楊粼波 閱讀(628) 評論(0)  編輯 收藏 引用 所屬分類: 網路程式設計