今天看程序的时候,发现IPv4的地址以一个很奇怪的无符号整型数字存储,极其不直观。
于是觉得很奇怪,考虑这样存储的原因及改进方式。为什么不直接按照4个字节存储呢?
我考虑到的方式有两种:
1.以数组的方式存储,IPv4地址的4个字节分别是这个含有4个元素的数组的元素之一。
2.以结构体的方式存储,IPv4地址的4个字节分别是此结构体的4个1字节成员之一。
这两种方式其实是一样的。那么这就要求我们拿到的是192.168.1.108这样的IP地址,
我们通过strtok这样的函数依次取出这4个字节并存储起来。
这种做法的好处是:直观。
坏处是:需要做依次取出这4个字节的动作,并且不够通用,在某些使用需求,如IP地址查询
的时候效率低下。
后面在网上查找资料,了解到了一些信息,终于也明白为什么TCP/IP里面指明IP地址是一个
32位的无符号整数了。库函数里面有将192.168.1.108这样的IP地址转换为对应的无符号整型
数字的。
在此说下,不能直接以非3个数字位对齐的字符串方式存储IP地址,原因如下:
192168001108 ——> 192 | 168 | 001 | 108 可正确解析
1921681108 ——>具备多义性,解析出来的结果不唯一。
192.168.1.108 : 从计算机的角度来看,应该被转换二进制数字。
108 + 1 * 28 + 168 * 28 * 28 + 192 * 28 * 28 * 28
= 108 + 1 * 256 + 168 * 256 * 256 + 192 * 256 * 256 * 256
于是乎,当我们拿到一个无符号整型数字表示的IP地址的时候,可以考虑使用
库函数转换,也可以通过移位运算的方式。当然,一定要注意你获取的IP地址
的是大端还是小端的。以大端为例,数字A表示上面提到的无符号整数 (IPv4地址)
( (A & 0xff000000) >> 24 ) —— 最低字节 , 对应上面的108的位置
( (A & 0x00ff0000) >> 16 ) —— 次低字节 , 对应上面的1的位置
( (A & 0x0000ff00) >> 8 ) —— 次高字节 , 对应上面的168的位置
( (A & 0x000000ff) >> 0 ) —— 最高字节 , 对应上面的192的位置
现将搜罗到的资料转载如下:
IP地址的三种表示格式简析
转自: http://www.2cto.com/net/201204/127194.html
使用TCP/IP协议进行网络应用开发的朋友首先要面对的就是对IP地址信息的处理。IP地址其实有三种不同的表示格式,关于这一点,如果你还不知道,亦或对相关的知识还有所迷惑,本文对你将会有很大的帮助。
Ascii(网络点分字符串)-
网络地址(32位无符号整形,网络字节序,大头)
主机地址 (主机字节序)
IP地址是IP网络中数据传输的依据,它标识了IP网络中的一个连接,一台主机可以有多个IP地址,IP分组中的IP地址在网络传输中将保持不变。下面具体介绍IP地址的三种不同表示格式。
一、点分10进制表示格式
这是我们最常见的表示格式,比如某机的IP地址可能为“202.101.105.66”。事实上,对于Ipv4(IP版本)来说,IP地址是由一个32位的二进制数所构成,但这样一串数字序列无疑是十分冗长并且
难以阅读和记忆的。为了方便人们的记忆和使用,就将这串数字序列分成4组,每组8位,并改为用 10进制数进行表示,最后用小原点隔开,于是就演变成了“点分10进制表示格式”。
来看看刚才那个IP地址的具体转化过程:
IP地址:11001010011001010110100101000010
分成4组后:11001010 01100101 01101001 01000010
十进制表示:202 101 105 66 www.2cto.com
点分表示:202.101.105.66
二、网络字节顺序格式(NBO,Network Byte Order)
下面我们来谈谈网络字节顺序格式,它和我们后面将要介绍的主机字节顺序格式一样,都只在进行网络开发中才会遇到。因此,在下面的介绍中,我假设读者对Socket
编程知识有一定的基础。
在网络传输中,TCP/IP协议在保存IP地址这个32位二进制数时,协议规定采用在低位存储地址中包含数据的高位字节的存储顺序(大头),这种顺序格式就被称为网络字节顺序格式。在实际网络传输时,数据按照每32位二进制数为一组进行传输,由于存储顺序的影响,实际的字节传输顺序是由高位字节到低位字节的传输顺序。 为了使通信的双方都能够理解数据分组所携带的源地址、目的地址以及分组的长度等二进制信息,无论是主机还是
路由器,在发送每一个分组以前,都必须将二进制信息转换为TCP/IP标准的网络字节顺序格式。网络字节顺序格式的地址不受主机、路由器类型的影响,它的表示是唯一的。
在Socket编程开发中,通过函数inet_addr和inet_ntoa可以实现点分字符串与网络字节顺序格式IP地址之间的转换。
inet_addr函数原型如下:
unsigned long inet_addr(const char FAR * cp)
函数中的参数cp指向网络中标准的点分地址字符串,其中每个以点分开的数字不可以大于255,这些数字可以是十进制、八进制、十六进制或者混合使用。如 “10.23.2.3”、“012.003.002.024”、“0xa.0x3.0x14.0x2”、“10.003.2.0x12”。
我们在前面的socket编程提到server端的代码,连接本地端口:
/* File Name: client.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n,rec_len;
char recvline[4096], sendline[4096];
char buf[MAXLINE]; www.2cto.com
struct sockaddr_in servaddr;
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8000);
//可以使用:inet_pton(AF_INET, "127.0.0.1", servaddr.sin_addr);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将字符串形式的IP地址转换为按网络字节顺序的整形值
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) ;
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
send(sockfd, sendline, strlen(sendline));
irec_len = recv(sockfd, buf, MAXLINE,0);
buf[rec_len] = '\0';
printf("Received : %s ",buf);
close(sockfd); www.2cto.com
}
三、主机字节顺序格式(HBO,Host Byte Order)
主机字节顺序格式顾名思义,其IP地址的格式是和具体主机或者路由器相关的。对于不同的主机,在进行IP地址的存储时有不同的格式,比如对于 Motorola 68k系列主机,其HBO与NBO是相同的。而对于Intel x86系列,HBO与NBO则正好相反。
在Socket编程中,有四个函数来完成主机字节顺序格式和网络字节顺序格式之间的转换,它们是:htonl、htons、ntohl、和ntohs。 htons和ntohs完成16位无符号数的相互转换,htonl和ntohl完成32位无符号数的相互转换。
在实际应用中我们常见到将端口号转换的例子(如上例)。这是因为,如果用户输入一个数字,而且将指定使用这一数字作为端口号,应用程序则必须在使用它建立地址以前,把它从主机字节顺序转换成网络字节顺序(使用htons()函数),以遵守TCP/IP协议规定的存储标准。相应地,如果应用程序希望显示包含于某一地址中的端口号(例如从getpeername()函数中返回的),这一端口号就必须在被显示前从网络顺序转换到主机顺序(使用ntohs()函数)。
那么,对于IP地址,主机字节顺序格式的转换又有哪些应用呢?
应用一,如果想知道从202.156.2.23到202.156.9.65这两个IP之间到底有多少个主机地址怎么办?这时就可以将两个IP地址转换为主机字节顺序的格式然后相减来得到,具体的实现如下:
int GetIPCount(char * ip1,char * ip2) {
long pp;;
long ss;;
pp = ntohl(inet_addr(ip1));;
ss = ntohl(inet_addr(ip2));;
return(ss - pp + 1);;
}
应用二,如果对一个网段进行扫描,比如,当前正在扫描202.156.23.255,怎么让程序知道下一个应扫的IP是202.156.24.0?这时可以将当前IP转换成主机字节顺序格式并加1后,在转换回网络格式
即可,具体实现如下:
char * GetNextIp(char * m_curip) {
struct sockaddr_in in;;
long pp;; www.2cto.com
char * re;;
pp = ntohl(inet_addr(m_curip));;
pp = pp + 1;;
in.sin_addr.s_addr = htonl(pp);;
re = inet_ntoa(in.sin_addr);;
return (re);;
}
总结
本文介绍了IP地址的三种不同表示格式,包括各种格式产生的原因、具体含义以及在Socket编程开发中的一些应用。在实际应用中,必须遵循应用时所应采用的格式标准,同时还应灵活运用格式间的相互转换以及计算技巧。
作者 hguisu
编程中的IP地址格式总结
一般我们常见的网络地址格式是:
192.168.0.1,这样表示很直观,但是我们在编程使用时经常需要转换为32位无符号长整型
的网络字节序。
转换函数:
int inet_aton(const char *cp,struct in_addr *inp) ; //(1)点数格式转换成无符号长整型
char *inet_ntoa(struct in_addr in) ; //(2)无符号长整型转换成点数格式
inet_addr(“a.b.c.d”); //(3)功能和(1)相同,区别是inet_addr不支持255.255.255.255,inet_aton支持。
其中,函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式.
例子:
假设你已经有了一个sockaddr_in结构体ina,你有一个IP地址"132.241.5.10" 要储存在其中,你就要用到函数inet_addr(),将IP地址从 点数格式转换成无符号长整型。
使用方法如下:
ina.sin_addr.s_addr = inet_addr("132.241.5.10"); //赋值,
printf("%s",inet_ntoa(ina.sin_addr)); //显示
注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。
注意,inet_ntoa()将结构体in-addr作为一 个参数,不是长整形。同样需要注意的是它返回的是一个指向一个字符的指针。它是一个由inet_ntoa()控制的静态的固定的指针,所以每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址。假如你需要保存这个IP地址,使用strcopy()函数来指向你自己的字符指针。
主机字节序和网络字节序说明
主机字节顺序:当一个符号用多字节表示的时候,不同的CPU在内存中可能有不同的存储顺序,这就是主机字节序。
网络字节顺序:在网络上这些顺序必须统一起来,因此有了网络字节序。
两种存储模式:
1、小端模式(LE,Little Endian)
这个符合人的思维,也比较好记,就是高地址高位,低地址低位。
2、大端模式(BE,Big Endian)
这个符合编程习惯,因为低地址存储变量名,低地址高位,高地址低位,那么指针按顺序输出时,高位在前低位在后,很直观。举例说明内存中双子0x01020304的存储:
地址:5001 5002 5003 5004
LE: 04 03 02 01
BE: 01 02 03 04
Intel类型的CPU采用小端对齐模式,网络字节序采用大端对齐模式。
转换函数:
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h 代表host, n 代表 network.s 代表short l 代表long 第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多.
参考:
在看公司项目代码时,有涉及到ip地址存储,
使用的是varbinary(4),但没有文档说明
这引发我的思考——缘起
当设计一个数据表时,考虑使用何种列的数据类型对性能有比较大的影响,如存储空间、查询开销等。甚至还影响到一些操作,如ip地址以字符串的形式存储在数据库中,就不可以直接比较大小。还有一点需要考虑,那就是可读性!数据虽然是存储在数据库中,但也要考虑到可读性问题。
本文要探讨的是“IP地址在数据库中,应该使用何种形式存储?”,文章将以实验为基础介绍使用何种形式比较适合。
1、感性认识
大家都知道ip地址分为ipv4、ipv6,这里我以ipv4为例介绍,ipv6原理是一样的。ipv4的小为32bits(或者说是4Bytes),在使用过程中,我们通常是用点分十进制格式,如192.168.120.65。如何把"192.168.120.65"存储到数据库中呢?
我们考虑下面三个因素:
把"192.168.120.65"存储到数据库中有多少中可行方法呢?见下表所示:
数据类型
|
大小
|
注释
|
varchar(15)
|
占7~15字节
|
可读性最好(192.168.120.65),但是最费存储空间
|
bigint
|
8 字节
|
可以将ip地址存储为类似192168120065的格式,这种可读性稍差,也比较费存储空间
|
int
|
4 字节
|
这种可读性很差,会存储为1084782657,由192*16777216+168*65536+120*256+65-2147483648计算所得,占用存储空间少。
|
tinyint
|
4 字节
|
用4个字段来分开存储ip地址,可读性稍差(分别为192, 168, 120, 65),存储空间占用少
|
varbinary(4)
|
4 字节
|
可读性差(0xC0A87841),存储空间占用少
|
从大小来看,依次varchar(15)> bigint> int、tinyint、varbinary(4)。
从可读性来看,依次是varchar(15)> bigint> tinyint> varbinary(4)>int。
从查询效率来看,
综合考虑,似乎tinyint比较好,其次是varbinary(4)。但是tinyint需要占多个表字段,而varbinary只需要占用一个字段即可。正确性还有待下面的实验检查!!!
2、理性认识
本小节通过创建5张表,分别用上述5中数据类型存储ip地址,每张表插入1,000,000条记录。说明为了方便消除差异,这些表中插入的都是192.168.120.65。建表和插入数据的sql语句如下(说明:插入1,000,000条记录要花挺长时间的,如果你要自己实验,可以考虑少插入点数据):
建表和插入数据的sql语句
然后我们执行存储过程sp_spaceused查看空间效率,执行下面的sql语句:
exec sp_spaceused ip_address_varchar
exec sp_spaceused ip_address_bigint
exec sp_spaceused ip_address_int
exec sp_spaceused ip_address_tinyint
exec sp_spaceused ip_address_varbinary
可以得到下面的结果:
说明:上面各个字段的意思如下表所示
列名
|
数据类型
|
说明
|
reserved
|
varchar(18)
|
由数据库中对象分配的空间总量。
|
data
|
varchar(18)
|
数据使用的空间总量。
|
index_size
|
varchar(18)
|
索引使用的空间总量。
|
unused
|
varchar(18)
|
为数据库中的对象保留但尚未使用的空间总量。
|
可以看出,这5张表中的记录都是1000000,ip_address_varchar占空间最大30792 KB;其次是ip_address_bigint和ip_address_varbinary占用16904 KB;最后是ip_address_int和ip_address_tinyint只占用16904 KB。
所以从可读性和空间效率上来看,最理想的是用tinyint的数据类型存储ip地址。其次应该考虑varbinary(4)和bigint。
理论上bigint肯定要比varbinary占用空间多,可是实验得出来是一样的,为什么呢?我查看帮助信息也没有看出什么异常,varbinary(4)的确是占用4个字节、bigint也的确是占用8个字节,如下图
如果有知道的,请告诉我一声!不过让我从这两者之间选(信不过数据结果啊),肯定会选择使用varbinary(4)而不是bigint。如果能够证明数据结果没有错,应该选择bigint,因为他的可读性更好!
3、查询效率
本小节比较上述5中存储ip地址的查询效率。为了比较查询效率,这里重新插入数据,消除每张表中的记录都相同(192.168.120.65),下面编写存储过程像数据表中随机插入1000条记录(但是保证每张表的数据是一样的)。存储过程如下:
随机插入N条ip地址到5张表中
考虑查找在范围192.0.0.0~192.255.255.255之间的ip地址的查询效率问题。说明我忽略了预处理的开销,即将192.0.0.0和192.255.255.255转换为上述的5种类型的时间,代码中我直接使用了这些值,没有给出转换过程,具体代码如下:
查询192.0.0.0~192.255.255.255之间的ip地址
执行得到的消息如下:
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
(5 行受影响)
表 'ip_address_varchar'。扫描计数 1,逻辑读取 6 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
(3 行受影响)
(1 行受影响)
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 113 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
=============================共115毫秒,ip_address_varchar
(5 行受影响)
表 'ip_address_bigint'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
(2 行受影响)
(1 行受影响)
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
===================================共4毫秒,ip_address_bigint
(5 行受影响)
表 'ip_address_int'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
(2 行受影响)
(1 行受影响)
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 146 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
===================================共149毫秒,ip_address_int
(5 行受影响)
表 'ip_address_tinyint'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
(2 行受影响)
(1 行受影响)
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 85 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
=======================================共88毫秒,ip_address_tinyint
(5 行受影响)
表 'ip_address_varbinary'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
(2 行受影响)
(1 行受影响)
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 13 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
===================================共15毫秒,ip_address_varbinary
上述结果只是初略的估计了效率,可能不太精确,但还是具有一定参考价值的!我只看ip_address_varbinary(15毫秒)、ip_address_tinyint(88毫秒)、ip_address_bigint(4毫秒)。
效率差距还是挺大的,综合可读性、存储效率、查询效率,我给这三者排序是:
如果考虑存储效率,tinyint是最好的!其次是bigint,然后是varbinary(4)
如果更多的是考虑查询效率,bigint是最好的!其次是varbinary(4),然后是tinyint
如果让我选择,我会使用varbinary(4)。