|
Posted on 2007-12-01 01:27 張旭 閱讀(2216) 評(píng)論(0) 編輯 收藏 引用
TCP/IP編程實(shí)現(xiàn)遠(yuǎn)程文件傳輸 http://www.chinaunix.net 作者:xzh2002 發(fā)表于:2003-04-27 00:08:28
TCP/IP編程實(shí)現(xiàn)遠(yuǎn)程文件傳輸
在TCP/IP網(wǎng)絡(luò)結(jié)構(gòu)中,為了保證網(wǎng)絡(luò)安全,網(wǎng)絡(luò)人員往往需要在路由器上添加防火墻,禁止非法用戶用ftp等安全危害較大的TCP/IP協(xié)議訪問主機(jī)。而有時(shí)系統(tǒng)維護(hù)人員需要用ftp將一些文件從中心機(jī)房主機(jī)傳到前端網(wǎng)點(diǎn)主機(jī)上,比如應(yīng)用程序的替換升級(jí)。如果每次傳輸文件時(shí)都要打開防火墻,未免顯得有些繁瑣,要是在自己的應(yīng)用程序中增加一個(gè)專門的文件傳輸模塊,那將是十分愉快的事情。
UNIX網(wǎng)絡(luò)程序設(shè)計(jì)一般都采用套接字(socket)系統(tǒng)調(diào)用。針對(duì)目前十分流行的客戶/服務(wù)器模式,其程序編寫步驟如下: 1.Socket系統(tǒng)調(diào)用 為了進(jìn)行網(wǎng)絡(luò)I/O,服務(wù)器和客戶機(jī)兩端的UNIX進(jìn)程要做的第一件事是調(diào)用socket()系統(tǒng)調(diào)用,建立軟插座,指明合適的通訊協(xié)議。格式為: #include #include int socket(int family,int type,int protocol) 其中:(1)family指明套節(jié)字族,其值包括: AF_UNIX (UNIX內(nèi)部協(xié)議族) AF_INET (Iternet協(xié)議) AF_NS (XeroxNs協(xié)議,TCP/IP編程取該值) AF_IMPLINK (IMP鏈接層) (2)type 指明套接字類型,取值有: SOCK_STREAM (流套接字) SOCK_DGRAM (數(shù)據(jù)報(bào)套接字) SOCK_RAW (原始套接字) SOCK_SEQPACKET (定序分組套接字) 一般情況下,前兩個(gè)參數(shù)的組合就可以決定所使用的協(xié)議,這時(shí)第三個(gè)參數(shù)被置為0,如果第一個(gè)參數(shù)為AF_INET,第二個(gè)參數(shù)選SOCK_STREAM,則使用的協(xié)議為TCP;第二個(gè)參數(shù)選SOCK_DGRAM,則使用的協(xié)議為UDP;當(dāng)?shù)诙€(gè)參數(shù)選SOCK_RAW時(shí),使用的協(xié)議為IP。值得指出的是并不是所有的族和類型的組合都是合法的,具體請(qǐng)查閱相關(guān)資料。該系統(tǒng)調(diào)用若成功則返回一個(gè)類似文件描述符,成為套節(jié)字描述字,可以像文件描述符那樣用read和write對(duì)其進(jìn)行I/O操作。當(dāng)一個(gè)進(jìn)程使用完該軟插座時(shí),需用close(<描述符>關(guān)閉(具體見后面內(nèi)容)。 2.服務(wù)器端Bind系統(tǒng)調(diào)用 軟插座創(chuàng)建時(shí)并沒有與任何地址相關(guān)聯(lián),必須用bind()系統(tǒng)調(diào)用為其建立地址聯(lián)系。其格式為: #include #include int bind(int socketfd,struct sockaddr_in *localaddr,sizeof(localaddr)); 其中:(1)第一個(gè)參數(shù)socketfd是前步socket()系統(tǒng)調(diào)用返回的套節(jié)字描述符。 (2)第二個(gè)參數(shù)被捆向本地地址的一種結(jié)構(gòu),該結(jié)構(gòu)在sys/netinet/in.h中定義: struct sockaddr_in{ short sin_family;/*socket()系統(tǒng)調(diào)用的協(xié)議族如AF_INET*/ u_short sin_port;/*網(wǎng)絡(luò)字節(jié)次序形式的端口號(hào)碼*/ struct in_addr sin_addr;/*網(wǎng)絡(luò)字節(jié)次序形式的網(wǎng)絡(luò)地址*/ char sin_zero[8]; } 一臺(tái)機(jī)器上的每個(gè)網(wǎng)絡(luò)程序使用一個(gè)各自獨(dú)立的端口號(hào)碼,例如:telnet程序使用端口號(hào)23,而ftp文件傳輸程序使用端口號(hào)21。我們?cè)谠O(shè)計(jì)應(yīng)用程序時(shí),端口號(hào)碼可以由getservbyname()函數(shù)從/etc/services庫文件中獲取,也可以由htons (int portnum)函數(shù)將任意正整數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)次序形式來得到,有些版本的UNIX操作系統(tǒng)則規(guī)定1024以下的端口號(hào)碼只可被超級(jí)用戶使用,普通用戶程序使用的端口號(hào)碼只限于1025到32767之間。網(wǎng)絡(luò)地址可以由gethostbyname(char*hostname)函數(shù)得到(該函數(shù)和getservbyname()一樣都以網(wǎng)絡(luò)字節(jié)次序形式返回所有在他們結(jié)構(gòu)中的數(shù)據(jù)),參數(shù)hostname為/etc/hosts文件中某一網(wǎng)絡(luò)地址所對(duì)應(yīng)的機(jī)器名。該函數(shù)返回一個(gè)類型為hostent的結(jié)構(gòu)指針,hostent結(jié)構(gòu)在netdb.h中定義: struct hostent{ char *h_name; char **h_aliases; int h_addrtype; int h_length; /*地址長度*/ char **h_addr_list; #define h_addr h_addr_list[0];/*地址*/ } (3)第三個(gè)參數(shù)為第二個(gè)結(jié)構(gòu)參數(shù)的長度,如果調(diào)用成功,bind返回0,否則將返回-1并設(shè)置errno。 3.服務(wù)器端系統(tǒng)調(diào)用listen,使服務(wù)器愿意接受連接 格式:int listen(int socketfd,int backlong) 它通常在socket和bind調(diào)用后在accept調(diào)用前執(zhí)行。第二個(gè)參數(shù)指明在等待服務(wù)器執(zhí)行accept調(diào)用時(shí)系統(tǒng)可以排隊(duì)多少個(gè)連接要求。此參數(shù)常指定為5,也是目前允許的最大值。 4.服務(wù)器調(diào)用accept,以等待客戶機(jī)調(diào)用connect進(jìn)行連接。格式如下: int newsocket=(int socketfd,struct sockaddr_in *peer,int*addrlen); 該調(diào)用取得隊(duì)列上的第一個(gè)連接請(qǐng)求并建立一個(gè)具有與sockfd相同特性的套節(jié)字。如果沒有等待的連接請(qǐng)求,此調(diào)用阻塞調(diào)用者直到一連接請(qǐng)求到達(dá)。連接成功后,該調(diào)用將用對(duì)端的地址結(jié)構(gòu)和地址長度填充參數(shù)peer和addlen,如果對(duì)客戶端的地址信息不感興趣,這兩個(gè)參數(shù)用0代替。 5.客戶端調(diào)用connect()與服務(wù)器建立連接。格式為: connect(int socketfd,struct sockaddr_in *servsddr,int addrlen) 客戶端取得套接字描述符后,用該調(diào)用建立與服務(wù)器的連接,參數(shù)socketfd為socket()系統(tǒng)調(diào)用返回的套節(jié)字描述符,第二和第三個(gè)參數(shù)是指向目的地址的結(jié)構(gòu)及以字節(jié)計(jì)量的目的地址的長度(這里目的地址應(yīng)為服務(wù)器地址)。調(diào)用成功返回0,否則將返回-1并設(shè)置errno。 6.通過軟插座發(fā)送數(shù)據(jù) 一旦建立連接,就可以用系統(tǒng)調(diào)用read和write像普通文件那樣向網(wǎng)絡(luò)上發(fā)送和接受數(shù)據(jù)。Read接受三個(gè)參數(shù):一個(gè)是套節(jié)字描述符;一個(gè)為數(shù)據(jù)將被填入的緩沖區(qū),還有一個(gè)整數(shù)指明要讀的字節(jié)數(shù),它返回實(shí)際讀入的字節(jié)數(shù),出錯(cuò)時(shí)返回-1,遇到文件尾則返回0。Write也接受三個(gè)參數(shù):一個(gè)是套節(jié)字描述符;一個(gè)為指向需要發(fā)送數(shù)據(jù)的緩沖區(qū),還有一個(gè)整數(shù)指明要寫入文件的字節(jié)個(gè)數(shù),它返回實(shí)際寫入的字節(jié)數(shù),出錯(cuò)時(shí)返回-1。當(dāng)然,也可以調(diào)用send和recv來對(duì)套節(jié)字進(jìn)行讀寫,其調(diào)用與基本的read和write系統(tǒng)調(diào)用相似,只是多了一個(gè)發(fā)送方式參數(shù)。 7.退出程序時(shí),應(yīng)按正常方式關(guān)閉套節(jié)字。格式如下: int close(socketfd) 前面介紹了UNIX客戶/服務(wù)器模式網(wǎng)絡(luò)編程的基本思路和步驟。值得指出的是socket編程所涉及的系統(tǒng)調(diào)用不屬于基本系統(tǒng)調(diào)用范圍,其函數(shù)原形在libsocket.a文件中,因此,在用cc命令對(duì)原程序進(jìn)行編譯時(shí)需要帶-lsocket選項(xiàng)。 現(xiàn)在,我們可以針對(duì)文章開頭提出的問題著手進(jìn)行編程了。在圖示的網(wǎng)絡(luò)結(jié)構(gòu)中,為使中心機(jī)房的服務(wù)器能和網(wǎng)點(diǎn)上的客戶機(jī)進(jìn)行通信,需在服務(wù)器端添加通過路由器1112到客戶機(jī)的路由,兩臺(tái)客戶機(jī)也必須添加通過路由器2221到服務(wù)器的路由。在服務(wù)器的/etc/hosts文件中應(yīng)該包含下面內(nèi)容: 1.1.1.1 server 2.2.2.2 cli1 2.2.2.3 cli2 客戶機(jī)的/etc/hosts文件中應(yīng)該有本機(jī)地址信息和服務(wù)器的地址信息,如cli1客戶機(jī)的/etc/hosts文件: 2.2.2.2 cli1 1.1.1.1 server 網(wǎng)絡(luò)環(huán)境搭建好后,我們可以在服務(wù)器端編寫fwq.c程序,負(fù)責(zé)接受客戶機(jī)的連接請(qǐng)求,并將從源文件中讀取的數(shù)據(jù)發(fā)送到客戶機(jī)。客戶機(jī)程序khj.c向服務(wù)器發(fā)送連接請(qǐng)求,接收從服務(wù)器端發(fā)來的數(shù)據(jù),并將接收到的數(shù)據(jù)寫入目標(biāo)文件。源程序如下: /*服務(wù)器源程序fwq.c*/
#include
#include
#include
#include
#include
#include
#include
main()
  {
char c,buf[1024],file[30];
int fromlen,source;
register int k,s,ns;
struct sockaddr_in sin;
struct hostent *hp;
system(″clear″);
printf(″\n″);
printf(″\n\n\t\t輸入要傳輸?shù)奈募?#8243;);
scanf(″%s″,file);
 if ((source=open(file,O_RDONLY))<0) {
perror(″源文件打開出錯(cuò)″);
exit(1);
}
printf(″\n\t\t在傳送文件,稍候…″);
hp=gethostbyname(″server″);
 if (hp==NULL) {
perror(″返回主機(jī)地址信息錯(cuò)!!!″);
exit(2);
}
s=socket(AF_INET,SOCK_STREAM,0);
 if(s<0) {
perror(″獲取SOCKET號(hào)失敗!!!″);
exit(3);
}
sin.sin_family=AF_INET;
sin.sin_port=htons(1500);/*使用端口1500*/
bcopy(hp->h_addr,&sin.sin_addr,hp->h_length);
 if(bind(s,&sin,sizeof(sin))<0) {
perror(″不能將服務(wù)器地址捆綁到SOCKET號(hào)上!!!″);
colse(s);
exit(4);
}
 if(listen(s,5)<0 {
perror(″sever:listen″);
exit(5);
}
 while(1) {
 if((ns=accept(s,&sin,&fromlen))<0) {
perror(″sever:accept″);
exit(6);
}
lseek(source,OL,0);/*每次接受客戶機(jī)連接,應(yīng)將用于讀的源文件指針移到文件頭*/
write(ns,file,sizeof(file)); /*發(fā)送文件名*/
while((k=read(source,buf,sizeof(buf)))>0)
write(ns,buf,k);
printf(″\n\n\t\t傳輸完畢!!!\n″);
close(ns);
}
close(source);
exit(0);
}
/*客戶機(jī)源程序khj.c*/
#include
#include
#include
#include
#include
#include
#include
#include
main()
 {
char buf[1024],file[30];
char *strs=″\n\n\t\t正在接收文件″;
int target;
register int k,s;
struct sockaddr_in sin;
struct hostent *hp;
system(″clear″);
printf(″\n″);
hp=gethostbyname(″server″);
 if(hp==NULL) {
perror(″返回服務(wù)器地址信息錯(cuò)!!!″);
exit(1);
}
s=socket(AF_INET,SOCK_STREAM,0);
 if(s<0) {
perror(″獲取SOCKET號(hào)失敗!!!″);
exit(2);
}
sin.sin_family=AF_INET;
sin.sin_port=htons(1500);/*端口號(hào)需與服務(wù)器程序使用的一致*/
bcopy(hp->h_addr,&sin.sin_addr,hp->h_length);
printf(″\n\n\t\t正在與服務(wù)器連接…″);
 if(connect(s,&sin,sizeof(sin),0)<0) {
perror(″不能與服務(wù)器連接!!!″);
exit(3);
}
while((k=read(s,file,sizeof(file)))<=0/*接收文件名*/
 if((target=open(file,o_WRONLY|O_CREAT|O_TRUNC,0644))<0) {
perror(″不能打開目標(biāo)文件!!″);
exit(4);
}
strcat(strs,file);
strcat(strs,″,稍候…″);
write(1,strs,strlen(strs));
while((k=read(s,buf,sizeof(buf)))>0)
write(tatget,buf,k);
printf(″\n\n\t\t接收文件成功!!!\n″);
close(s);
close(target);
}
上述程序在Sco Unix System v3.2及Sco TCP/IP Rumtime環(huán)境下調(diào)試通過。
|