skoo's notes

努力记录一些自己觉得有趣的东西...
19 March 2014
by skoo

最近小踩了一下inet_aton的坑。我们把ip数据存储在mysql中,ip的存储不是采用的点分十进制,而是使用inet_aton转换成整数后再存储的;我们用C写的服务器会用一个ip的整数值去查询mysql,查询的结果死活对不上。最后发现C程序转换ip得到的整数和mysql不一样,字节序刚好是相反的。这个时候其实我基本知道是ip转换的时候采用的字节序不一样,一个是网络序,另一个是主机序。在linux上man inet_aton可以看到:

inet_aton() converts the Internet host address cp from the IPv4 numbers-and-dots notation into binary form (in network byte order) and stores it in the structure that inp points to.

说明glibc的inet_aton得到的结果是网络序,不是主机序的。然后,再查一下mysql关于inet_aton的文档:

Given the dotted-quad representation of an IPv4 network address as a string, returns an integer that represents the numeric value of the address in network byte order (big endian).

mysql> SELECT INET_ATON('10.0.5.9');
    -> 167773449
For this example, the return value is calculated as 10×256^3 + 0×256^2 + 5×256 + 9.

我X,mysql的文档说它的inet_aton的结果也是网络序呢。但它举了一例子,这个例子给出的计算方法却是一个主机序的结果哦。事实上,现在mysql的inet_aton得到的结果确实是主机序,根本不是网络序。不知道是文档bug,还是实现bug了。

反正我们需要注意:glibc的inet_aton和mysql的inet_aton的字节许是不同的,在现在的小端机上就是刚好相反。解决方法应该是,将我们程序中的网络序的ip整数通过ntohl函数转换成主机序后,再去查mysql。

最后贴一个Nginx点分十进制ip转整数的实现:

in_addr_t
ngx_inet_addr(u_char *text, size_t len)
{
	u_char      *p, c;
	in_addr_t    addr;
	ngx_uint_t   octet, n;

	addr = 0;
	octet = 0;
	n = 0;

	for (p = text; p < text + len; p++) {

    	c = *p;

    	if (c >= '0' && c <= '9') {
        	octet = octet * 10 + (c - '0');
        	continue;
    	}

    	if (c == '.' && octet < 256) {
        	addr = (addr << 8) + octet;
        	octet = 0;
        	n++;
        	continue;
    	}

    	return INADDR_NONE;
	}

	if (n != 3) {
    	return INADDR_NONE;
	}

	if (octet < 256) {
    	addr = (addr << 8) + octet;
    	return htonl(addr);
	}

	return INADDR_NONE;
}

注意:return htonl(addr),说明最后的结果是是转换成了网络序,和glibc的inet_aton行为是相符的。