背景:
笔者最近在自学Linux网络通信编程,相信大家都不会陌生,就是使用套接字理念,运用socket,bind,listen,accept。。。一系列函数进行服务器/客户端的通信问题。
就在刚才,作者解决了一个困扰两天的问题(其实是程序中括号加错了地方!!),可恶的GCC编译器,并没有提示错误。当然,接下来,我总不能记录我如何解决括号加错地方的问题,主要这两天在尝试各种解决方法的问题中,学到了不少知识。现记录下来,以备以后查看,并希望能对正在学习网络编程的同学一些参考。
干货:
一、sockaddr和sockaddr_in
下面首先介绍两个重要的数据类型:sockaddr和sockaddr_in,这两个结构类型都是用来保存socket信息的,如下所示:
struct sockaddr
{
unsigned short sa_family;/*地址族*/
char sa_data[14];/*包含该socket的IP地址和端口号*/
};
struct sockaddr_in
{
short int sa_family;/*地址族*/
unsigned short int sin_port;/*端口号*/
struct int_addr sin_addr;/*IP地址*/
unsigned char sin_zero[8];/*填充0以保持与sockaddr同样大小
};
这两个数据类型是等效的,在这里,我们不涉及具体每项参数的含义(普适整片文章)。只想告诉大家,现在一般我们都使用sockaddr_in结构体来保存socket信息,但是好多函数内的参数是默认为sockaddr数据类型的,那我们使用的时候只需要进行一次强制类型转换就可以了,如下:
struct sockaddr_in serv_addr;/*定义serv_addr结构体*/
int sockfd;
sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建套接字,返回套接字描述符*/
...
...
.../*设置serv_addr结构体中的相关参数*/
connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));/*调用连接函数*/
上述代码是我从一个简单的客户端程序中摘出来的一部分,通过connect函数的第二个参数,我们可以印证上述所讲的内容。
二、地址格式转化
通常呢,我们在表达地址时采用的是点分十进制表示的数值,例如“127.0.0.1”表示本地的IP。然而在socket编程中所使用的则是二进制地址值,这样就需要将两个数值进行转换。
我们这里呢,就讲两个能够兼容IPV4和IPV6两种协议的地址转化函数:inet_pton
函数将点分十进制地址映射为二进制地址;inet_ntop
函数则与之相反。下面我们看一下inet_pton
函数的语法。
/*所需头文件*/
#include <apra/inet.h>
/*函数原型*/
int niet_pton(int family,const char *strptr,void *addrptr)
/*函数参数含义*/
family:协议
strptr:要转化的地址
addrptr:转化后的地址
三、gethostbyname()函数
上一个知识点,我们讲了地址转化函数,那么我们所需要的点分十进制IP地址到底从哪来呢?当然我们可以直接找到服务器端的IP地址,如“192.168.1.1”,然后使用地址转化函数转化成二进制地址。
通常,我们是不愿意记忆冗长的IP地址的,尤其是到了IPV6时,地址长度多达128位,那直接输入IP地址本身就是一件困难的事情了,因此这时使用主机名称将会是很好的选择。gethostbyname就是这样一个可以实现此功能的函数。
/*所需头文件*/
#include <netdb.h>
/*函数原型*/
struct hostent *gethostbyname(const char *hostname)
/*函数参数*/
Hostname:主机名(可以是字符串,也可以是地址)
/*函数返回值*/
成功:hostent类型指针
出错:-1
/*hostent数据类型*/
struct hostent
{
char *h_name;/*正式主机名*/
char **h_aliases;/*主机别名*/
int h_addrtype;/*地址类型*/
int h_length;/*地址长度*/
char **h_addr_list;/*指向IPV4和IPV6的地址指针数组*/
}
四、将IP地址送出去
其实这一小节才是笔者这两天的收获,前面的都是铺垫。第一小节,我们讲到sockaddr_in这么一个数据类型,其中里面有一项成员为:
struct in_addr sin_addr;/*IP地址*/
这个成员变量就是用来保存IP地址的地方,但是我们应该以怎样的方式将地址赋值给sin_addr呢?我们看到它也是一个结构体,我们首先来看下它的成员变量
struct in_addr
{
in_addr_t s_addr;
};
/*in_addr_t用来表示一个32位的IPV4地址*/
(1) 使用inet_pton()函数
我们可以利用inet_pton函数将点分十进制地址转换成二进制地址,然后将地址送给sockaddr_in中的成员sin_addr,代码如下:
void *addrptr;
inet_pton(AF_INET,"127.0.0.1",addrptr);
serv_addr.sin_addr=*((struct in_addr*)addrptr);
有上述代码可以看到,由于sin_addr为in_addr结构体类型,所以必须先将addrptr转换成该类型指针,然后取其值送给sin_addr.
(2)使用gethostbyname函数
使用gethostbyname函数将主机名称转换成二进制地址,送给sin_addr,代码如下:
struct hostent* host;
host=gethostbyname("127.0.0.1/Liu");
...
...
serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
发现了没?原理是一样的,gethostbyname的返回值是个结构体,但是sin_addr需要的只是地址,所以需要将host结构体中的成员h_addr送给sin_addr。