pupuk / blog

My New Blog. Record & Share. Focus on PHP, MySQL, Javascript and Golang.
MIT License
9 stars 2 forks source link

PHP函数ip2long为什么会出现负数?ip2long如何实现? #10

Open pupuk opened 6 years ago

pupuk commented 6 years ago

PHP的官网,释义:ip2long — 将 IPV4 的字符串互联网协议转换成长整型数字 所以不讨论IPV6 ip2long的函数中long指的是C语言中的long,占4个字节

why

为什么不直接将IP地址如(xxx.xxx.xxx.xxx)直接存入数据库呢?而要将其转成INT的数字形式呢

1、节省储存空间,char形式的ip地址需要15个字节,int形式的ip地址只需要4个字节。如果在这个ip地址字段上建立索引的话,int形式的索引维护开销肯定比char格式的少。

2、便于比较。方便获得相邻的IP地址,方便做IP地址地址库,也方便通过位运算做子网匹配。

背景知识

IPv4的表达方式和范围

{1字节}.{1字节}.{1字节}.{1字节}
{0-255}.{0-255}.{0-255}.{0-255}

IPv4用4个字节,共32位来表达IP地址,所以IPv4地址的最低位是0.0.0.0,最高位地址是:255.255.255.255 当然为了使用方便,IPv4地址被人为地分成了A,B,C,D,E 五类,这个不在今天的探讨范围。 如果用32bit的来表达int,则 unsigned int的范围是 0 - 2^32-1 区分正负号的int是-2^31 - 2^31-1

根本原因

PHP共有8种数据类型(bool,int,float,string,array,object,resource,null)。int类型的都是有符号的,即是: 对于32位的PHP,int的表达范围是 -2^31 - 2^31-1 对于64位的PHP,int的表达范围是 -2^63 - 2^63-1

如果一个IP地址是255.255.255.255 则对应的二进制形式是:{8个1}{8个1}{8个1}{8个1} 而PHP的INT32形式是有符号的,最高位的1,会被当作符号位,1表示负数,这就是的IP地址在32位的PHP下,使用ip2long出现符号的原因。

要解决: 1、使用64位的PHP程序。 2、使用php的函数sprintf,利用无符号来格式化,sprintf('%u', ip2long('255.255.255.255')); 当然sprintf的结果是string类型,但是不影响使用。如果MySQL采用的是unsigned int来存储ip2long的结果,MySQL接收到字符串,在存储的时候,会自动转成schema设计的格式(unsigned int)。

PHP的源码实现

php7.1 /ext/standard/basic_functions.c

/* {{{ proto int ip2long(string ip_address)
   Converts a string containing an (IPv4) Internet Protocol dotted address into a proper address */
PHP_FUNCTION(ip2long)
{
    char *addr;
    size_t addr_len;
#ifdef HAVE_INET_PTON
    struct in_addr ip;
#else
    zend_ulong ip;
#endif

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &addr, &addr_len) == FAILURE) {
        return;
    }

#ifdef HAVE_INET_PTON
    if (addr_len == 0 || inet_pton(AF_INET, addr, &ip) != 1) {
        RETURN_FALSE;
    }
    RETURN_LONG(ntohl(ip.s_addr));
#else
    if (addr_len == 0 || (ip = inet_addr(addr)) == INADDR_NONE) {
        /* The only special case when we should return -1 ourselves,
         * because inet_addr() considers it wrong. We return 0xFFFFFFFF and
         * not -1 or ~0 because of 32/64bit issues. */
        if (addr_len == sizeof("255.255.255.255") - 1 &&
            !memcmp(addr, "255.255.255.255", sizeof("255.255.255.255") - 1)
        ) {
            RETURN_LONG(0xFFFFFFFF);
        }
        RETURN_FALSE;
    }
    RETURN_LONG(ntohl(ip));
#endif
}