sockaddr_in, sockaddr, in_addr区别
网络字节序和主机字节序
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
网络字节序,UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处)。由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的,所以说,网络字节序是大端字节序。
要判断自己的电脑是小端字节序和大端字节序,有很多方法,你可以自己构造一个union或者struct结构体,也可以直接用一个int值就好,只要能判断程序的低字节是存放在主机高字节处还是低字节处就行。下面是一个判断的简单方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> using namespace std; int main(int argc, char **argv) { int test = 0x12345678; char result = char(test); if(result = 0x78) cout << "It is little edian" << endl; else cout << "It is big edian" << endl; return 0; } |
首先还是需要看看这几个结构体的内容,了解清楚这几个结构体是什么,对我们后续了解Socket编程函数集的时候都是有帮助的,起码能知道参数/返回值指的是结构体、二进制IP地址还是字符串什么的。
sockaddr_in和sockaddr
因为sockaddr_in和sockaddr都是16字节的结构体,是可以互相转换的,它们的内容如下:
1 2 3 4 |
struct sockaddr { unsigned short sa_family; //2 char sa_data[14]; //14 }; |
上面是通用的socket地址,具体到Internet socket,用下面的结构,二者可以进行类型转换
1 2 3 4 5 6 |
struct sockaddr_in { short int sin_family; //2 unsigned short int sin_port; //2 struct in_addr sin_addr; //4 unsigned char sin_zero[8]; //8 }; |
sockaddr_in结构体中使用到的struct in_addr就是4字节、32位的IP地址。它的定义有两个,不过编程的时候用的比较多in_addr_t
1 2 3 4 5 6 7 8 |
struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; #define s_addr S_un.S_addr }; |
另一个定义为:
1 2 3 |
struct in_addr { in_addr_t s_addr; }; |
所以,到这里其实对用到的结构体和一些定义名就比较清楚了,sockaddr和sockaddr_in这两个结构体是能够互用的,在要使用sockaddr的地方(比如bind、connect之类的函数)都可以将sockaddr_in结构体进行强制类型转换来使用。其次就是in_addr是一个struct结构体,而in_addr_t一般是一个unsigned int 32位的值,其字节顺序为网络顺序(network byte ordered)。
常用Socket函数集
16位 or 32位整数主机/网络字节序转换
1 2 3 4 5 6 7 8 9 10 11 |
// 将16位主机字符顺序转换成网络字符顺序 unsigned short int htons(unsigned short int hostshort); // 将32位主机字符顺序转换成网络字符顺序 unsigned long int htonl(unsigned long int hostlong); // 将32位网络字符顺序转换成主机字符顺序 unsigned long int ntohl(unsigned long int netlong); // 将16位网络字符顺序转换成主机字符顺序 unsigned short int ntohs(unsigned short int netshort); |
上面这四个函数只是简单的将2字节 or 4字节的数在主机字节序和网络字节序之间转换,暂时用的比较多的应该是htons了,基本都是用这个函数来讲server或者client需要使用的端口号,从主机字节序转换成网络字节序的。
不安全的(不可重入性)inet_xxx()函数族
由于计算机理解的IP都是以二进制形式保存的,那么在网络程序设计中经常会需要字符串IP和二进制IP的转换,linux系统有一组要用于网络地址转换的函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //将点分4段式的IP地址转换为结构in_addr值 int inet_aton(const char *cp, struct in_addr *inp); //将字符串转换为结构in_addr值 in_addr_t inet_addr(const char *cp); //将字符串地址的网络部分转换为结构in_addr值 in_addr_t inet_network(const char *cp); //将结构in_addr转为字符串 char *inet_ntoa(struct in_addr in); //将网络地址和主机地址合成为IP地址,返回值是in_addr值 struct in_addr inet_makeaddr(int net, int host); //获得地址的主机部分 in_addr_t inet_lnaof(struct in_addr in); //获得地址的网络部分 in_addr_t inet_netof(struct in_addr in); |
以上部分函数存在缺陷,例如:
inet_ntoa函数返回值是一个指向字符串的指针,此内存会在每次调用inet_nota函数的时候被覆盖掉,如果不及时拿走数据就会出现不可预料的错误,因此函数是不安全的,存在某种隐患;
inet_addr,inet_network函数的-1返回值是用来表示转换出错的,但是在使用255.255.255.255作为参数进行转换的时候,刚好溢出导致返回值为-1,因此是存在缺陷的,埋下了隐患。
下面用一段代码介绍使用方法和隐患的部分:
<
pre>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char argv[])
{
struct in_addr ip,local,network;
char addr1[]=”192.168.1.1″; / 网络地址字符串 /
char addr2[]=”255.255.255.255″;
char addr3[]=”192.16.1″;
char *str=NULL,str2=NULL;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
int err = 0; /* 测试函数inet_aton */ err = inet_aton(addr1, &ip); if(err){ printf("inet_aton:string %s value is:0x%x\n",addr1, ip.s_addr); }else{ printf("inet_aton:string %s error\n",addr1); } /* inet_addr,先测试192.168.1.1,在测试255.255.255.255 */ // 测试192.168.1.1的时候是没有问题 ip.s_addr = inet_addr(addr1); if(err != -1){ printf("inet_addr:string %s value is:0x%x\n",addr1, ip.s_addr); }else{ printf("inet_addr:string %s error\n",addr1); }; // 测试255.255.255.255的时候转换是没有问题 // 只是在使用255.255.255.255作为参数的时候刚好使返回值为-1导致判断为false ip.s_addr = inet_addr(addr2); if(ip.s_addr != -1){ printf("inet_addr:string %s value is:0x%x\n",addr2, ip.s_addr); }else{ printf("inet_addr:string %s error\n",addr2); }; /* inet_ntoa,先测试192.168.1.1,在测试255.255.255.255 * 证明函数的不可重入性 */ // 函数的返回值应该是放在一个具体的地址的,如果没有及时取走的话,后面的调用会将前面的调用结果覆盖 ip.s_addr = 192<<24|168<<16|1<<8|1; str = inet_ntoa(ip); ip.s_addr = 255<<24|255<<16|255<<8|255; str2 = inet_ntoa(ip); printf("inet_ntoa:ip:0x%x string1 %s,pre is:%s \n",ip.s_addr,str2,str); /* 测试函数inet_addr */ ip.s_addr = inet_addr(addr3); if(err != -1){ printf("inet_addr:string %s value is:0x%x\n",addr3, ip.s_addr); }else{ printf("inet_addr:string %s error\n",addr3); }; str = inet_ntoa(ip); printf("inet_ntoa:string %s ip:0x%x \n",str,ip.s_addr); /* 测试函数inet_lnaof,获得本机地址 */ inet_aton(addr1, &ip); local.s_addr = htonl(ip.s_addr); local.s_addr = inet_lnaof(ip); str = inet_ntoa(local); printf("inet_lnaof:string %s ip:0x%x \n",str,local.s_addr); /* 测试函数inet_netof,获得本机地址 */ network.s_addr = inet_netof(ip); printf("inet_netof:value:0x%x \n",network.s_addr); return 0; |
}
运行结果如下图:
安全的地址转换函数inet_pton(),inet_ntop()
函数inet_pton(),inet_ntop()都是可以重入的,同时支持多种地址类型,包含IPV4和IPV6。
函数介绍如下:
下面用一段代码介绍使用方法:
<
pre>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#define ADDRLEN 16
int main(int argc, char argv[])
{
struct in_addr ip;
char IPSTR[]=”192.168.1.1″; / 网络地址字符串 /
char addr[ADDRLEN]; / 保存转换后的字符串IP地址,16个字节大小 /
const charstr=NULL;
int err = 0; /* 返回值 */
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* 测试函数inet_pton转换192.168.1.1为二进制形式 */ err = inet_pton(AF_INET, IPSTR, &ip); /* 将字符串转换为二进制 */ if(err > 0){ printf("inet_pton:ip,%s value is:0x%x\n",IPSTR,ip.s_addr); } /* 测试函数inet_ntop转换192.168.1.1为字符串 */ ip.s_addr = htonl(192<<24|168<<16|12<<8|255);/*192.168.12.255*/ /*将二进制网络字节序192.168.12.255转换为字符串*/ str = (const char*)inet_ntop(AF_INET, (void*)&ip, (char*)&addr[0], ADDRLEN); if(str){ printf("inet_ntop:ip,0x%x is %s\n",ip.s_addr,str); } return 0; |
}
运行结果:
1 2 |
inet_pton:ip,192.168.1.1 value is:0x101a8c0 inet_ntop:ip,0xff0ca8c0 is 192.168.12.255 |
参考博客(感谢两位博主):
- http://blog.csdn.net/wh_19910525/article/details/54603487
- http://blog.csdn.net/hnlyyk/article/details/47976161