嵌入式linux的網絡編程(3)--TCP Client程序設計
客戶端主要需要完成與服務器建立連接,請求數據,應答數據等工作.從代碼上來看,客戶端程序有很多代碼與服務器端程序類似,我們先給出一個簡單的客戶端源碼,隨後進行詳細的講解.
1 /**************************************************************************************/ 2 /*簡介:TCPClient示例。 */ 3 /*************************************************************************************/ 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <errno.h> 7 #include <string.h> 8 #include <netdb.h> 9 #include <sys/types.h> 10 #include <netinet/in.h> 11 #include <sys/socket.h> 12 13 int main(int argc, char *argv[]) 14 { 15 int sockfd; 16 char buffer[1024]; 17 struct sockaddr_in server_addr; 18 struct hostent *host; 19 int portnumber,nbytes; 20 21 if(argc!=3) 22 { 23 printf("Usage:%s hostname portnumber\a\n",argv[0]); 24 exit(1); 25 } 26 27 if((host=gethostbyname(argv[1]))==NULL) 28 { 29 herror ("Get host name error\n"); 30 exit(1); 31 } 32 33 if((portnumber=atoi(argv[2]))<0) 34 { 35 printf("Usage:%s hostname portnumber\a\n",argv[0]); 36 exit(1); 37 } 38 39 /* 客戶程序開始建立 sockfd描述符 */ 40 if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 41 { 42 printf("Socket Error:%s\a\n",strerror(errno)); 43 exit(1); 44 } 45 /* 客戶程序填充服務端的資料 */ 46 bzero(&server_addr,sizeof(server_addr)); 47 server_addr.sin_family=AF_INET; 48 server_addr.sin_port=htons(portnumber); 49 server_addr.sin_addr=*((struct in_addr *)host->h_addr); 50 /* 客戶程序發起連接請求 */ 51 if(connect(sockfd,(struct sockaddr *)(&server_addr),\ 52 sizeof(struct sockaddr))==-1) 53 { 54 printf("Connect Error:%s(%d)\a\n",strerror(errno),errno); 55 exit(1); 56 } 57 /* 連接成功了 */ 58 if((nbytes=read(sockfd,buffer,1024))==-1) 59 { 60 printf("Read Error:%s\n",strerror(errno)); 61 exit(1); 62 } 63 buffer[nbytes]='\0'; 64 printf("I have received:%s\n",buffer); 65 66 /* 結束通訊 */ 67 close(sockfd); 68 exit(0); 69 }客戶端程序首先連接到服務器端,然後才能進行數據交換,在這之前就必須知道服務器的IP地址和端口號.
通常,人們在使用過程中都不願意記憶冗長的IP 地址,尤其到IPv6時,地址長度多達128位,那時就更加不可能一次次記憶那麼長的IP地址了.因此,使用主機名將會是很好的選擇.在Linux中,同樣有一些函數可以實現主機名和地址的轉化,最為常見的有gethostbyname,gethostbyaddr,getaddrinfo等,它們都可以實現IPv4和IPv6的地址和主機名之間的轉化.其中gethostbyname是將主機名轉化為IP地址,gethostbyaddr則是逆操作,是將IP地址轉化為主機名,另外getaddrinfo還能實現自動識別IPv4地址和IPv6地址.
struct hostent { char *h_name; /*正式主機名*/ char **h_aliases; /*主機別名*/ int h_addrtype; /*地址類型*/ int h_length; /*地址長度*/ char **h_addr_list; /*指向IPv4或IPv6的地址指針數組*/ }調用該函數後就能返回hostent結構體的相關信息.getaddrinfo函數涉及到一個addrinfo的結構體,如下所示:
對hostent結構體而言,addrinfo結構體包含更多的信息.
如果該函數發生錯誤時,但全局變量errno中不存儲錯誤代碼,h_errno中存儲的才是錯誤代碼.通過herrno函數訪問變量h_errno,該函數的使用與perror的用法一樣,如果例子中的29行.
此外,還可以通過gethostname函數獲得本地主機的名字,返回的名字可以用於gethostbyname,該函數的聲明如下:
#include <unistd.h> int gethostname(char *hostname, size_t size);hostname: 一個指向將要存放主機名的緩沖區指針;
getaddrinfo函數的語法要點如下:
在調用之前,首先要對hints服務線索進行設置.它是一個addrinfo 結構體,下圖列舉了該結構體常見的選項值.
當客戶端完成創建socket,填充服務器信息結構等工作後,就可以連接服務器了,如例子中的第51行.connect函數的語法要點如下:
當connect函數出錯時,全局變量errno含有下面的值:
EACCES,EPERM 用戶試圖在套接字廣播標志沒有設置的情況下連接廣播地址或由於防火牆策略導致連接失敗.
當connect函數成功返回後,就可以使用sockfd作為與服務器連接的套接字描述符,一些之前提到的I/O函數,都可以用來與服務器進行通信了.
將這兩個程序分別編譯為simple_server和simple_client,通過SecureCRT建立兩個和linux操作系統的連接,分別運行這兩個程序.
注意:localhost是本地循環地址,它代表本機的IP地址,用點分十進制表示為127.0.0.1.這是一個特殊的IP地址,通常用來測試IP協議是否正常.在本地調試網絡程序時會經常用到這個地址.