嵌入式linux的网络编程(3)--TCP Client程序设计
CSDN2013年度博客之星评选活动开始,本人有幸入围参加评选,如果博客中的文章对你有所帮助,请为 ce123 投上宝贵一票,非常感谢!
投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/ce123
1.概述
客户端主要需要完成与服务器建立连接,请求数据,应答数据等工作.从代码上来看,客户端程序有很多代码与服务器端程序类似,我们先给出一个简单的客户端源码,随后进行详细的讲解.
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地址和端口号.
2.名字地址转化
通常,人们在使用过程中都不愿意记忆冗长的IP 地址,尤其到IPv6时,地址长度多达128位,那时就更加不可能一次次记忆那么长的IP地址了.因此,使用主机名将会是很好的选择.在Linux中,同样有一些函数可以实现主机名和地址的转化,最为常见的有gethostbyname,gethostbyaddr,getaddrinfo等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化.其中gethostbyname是将主机名转化为IP地址,gethostbyaddr则是逆操作,是将IP地址转化为主机名,另外getaddrinfo还能实现自动识别IPv4地址和IPv6地址.
gethostbyname和gethostbyaddr都涉及到一个hostent的结构体,如下所示:
struct hostent{ char *h_name; /*正式主机名*/ char **h_aliases; /*主机别名*/ int h_addrtype; /*地址类型*/ int h_length; /*地址长度*/ char **h_addr_list; /*指向IPv4或IPv6的地址指针数组*/}
调用该函数后就能返回hostent结构体的相关信息.getaddrinfo函数涉及到一个addrinfo的结构体,如下所示:
struct addrinfo{ int ai_flags; /*AI_PASSIVE,AI_CANONNAME;*/ int ai_family; /*地址族*/ int ai_socktype; /*socket类型*/ int ai_protocol; /*协议类型*/ size_t ai_addrlen; /*地址长度*/ char *ai_canoname; /*主机名*/ struct sockaddr *ai_addr;/*socket结构体*/ struct addrinfo *ai_next;/*下一个指针链表*/}
对hostent结构体而言,addrinfo结构体包含更多的信息.gethostbyname函数语法要点如下:
如果该函数发生错误时,但全局变量errno中不存储错误代码,h_errno中存储的才是错误代码.通过herrno函数访问变量h_errno,该函数的使用与perror的用法一样,如果例子中的29行.
此外,还可以通过gethostname函数获得本地主机的名字,返回的名字可以用于gethostbyname,该函数的声明如下:
#include <unistd.h>int gethostname(char *hostname, size_t size);
hostname: 一个指向将要存放主机名的缓冲区指针;size:缓冲区的长度.
getaddrinfo函数的语法要点如下: