php - 搜索存储为整数的部分 IP 地址

标签 php mysql ip-address

我目前有一个包含 IP 地址的 MySQL 数据库。在搜索表单上,客户端想要搜索部分 IP 地址并弹出(可能)许多结果。我目前将 IP 地址作为 unsigned int 存储在 mysql 中。我使用的是 PHP 5.2,因此无法访问 PHP 5.7 及其 INET6_NTOA 函数。

当前数据库有超过 50,000 条记录并且还在继续增长,所以我不想将所有 IP 转换为点分符号,然后进行匹配 - 这似乎有点笨拙。

有没有更好的方法来搜索部分 IP 地址?

最佳答案

实际上,无符号整数列已经是搜索部分 ip 地址匹配项的最有效方法!请不要将您的精力和 CPU 时间浪费在转换回点分符号或对某种字符串列进行 LIKE 搜索上。

有几种记录部分 IP 地址的方法,但最终,它们都归结为带有网络掩码的基本 IP。另外,假设partial是指所有具有共同前缀的IP,那么这也相当于指定了一个IP范围。

无论哪种方式,部分 IP 地址规范最终被描述为两个 32 位无符号整数,编码格式与您的数据库列相同。要么你有一个起始 ip 和结束 ip,要么你有一个基本 ip 和一个掩码。这些整数可以直接在您的 SQL 查询中使用,以有效地获得匹配。更好的是,如果您使用 ip 范围方法,那么引擎将能够利用 ip 列上的有序索引。您不能期望更好。

那么如何建立IP范围呢?我们将首先取决于您的部分地址是如何指定的,但是假设您确实知道网络掩码,那么起始地址等于(基本 ip 和网络掩码),结束地址是((基本 ip & 掩码) | (~netmask)), 其中 &, |和~分别表示按位与、按位或、按位非。

更新

这是应用我描述的策略的示例代码。

自从我上次编写 PHP 代码以来已经有很长时间了,并且从未执行过以下内容,所以请原谅我可能引入的任何错误。我还特意选择“扩展”每个符号场景,以使它们更易于理解,而不是将它们全部压缩在一个非常复杂的正则表达式中。

if (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [/] (\d{1,2}) $/x', $input, $r)) {
    // Four-dotted IP with number of significant bits: 123.45.67.89/24

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = intval($r[5]);

} elseif (preg_match(' /^ (\d{1,3}) (?: [.] [*0] [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with three-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 8 bits)

    $a = intval($r[1]);
    $b = 0;
    $c = 0;
    $d = 0;
    $mask = 8;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with two-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 16 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = 0;
    $d = 0;
    $mask = 16;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with last number missing, or equals to 0 or *:
    // 123.45.67, 123.45.67.0, 123.45.67.*  (assume netmask of 24 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = 0;
    $mask = 24;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) $/x', $input, $r)) {
    // Four-dotted IP: 123.45.67.89 (assume netmask of 32 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = 32;

} else {
    throw new Exception('...');
}

if ($a < 0 || $a > 255) {  throw new Exception('...') };
if ($b < 0 || $b > 255) {  throw new Exception('...') };
if ($c < 0 || $c > 255) {  throw new Exception('...') };
if ($d < 0 || $d > 255) {  throw new Exception('...') };
if ($mask < 1 || $mask > 32) {  throw new Exception('...') };

$baseip = ($a << 24) + ($b << 16) + ($c << 8) + ($d);
$netmask = (1 << (32 - $mask)) - 1;

$startip = $baseip & netmask;
$endip = ($baseip & netmask) | (~netmask);

// ...

doSql( "SELECT ... FROM ... WHERE ipaddress >= ? && ipaddress <= ?", $startip, $endip);

// or

doSql( "SELECT ... FROM ... WHERE ((ipaddress & ?) = ?)", $netmask, $startip);

关于php - 搜索存储为整数的部分 IP 地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26508008/

相关文章:

post - post 方法中未定义索引

php - 使用 PDO 从特定列和行返回数据

在内核模块中将网络字节序IP转换为ip4格式的主机字节序

php - 获取我的IP地址

php - MySQL覆盖或插入

php - 将数据从 c++(opencv 应用程序)传递到 php

PHP 自定义超全局函数

php - 如何使用 PDO 在 PHP 中创建包含分隔符的存储过程?

mysql - SQL Order by 子句不按字母顺序返回

c++ - 如何确定一个字符串是否是 C++ 中的有效 IPv6 地址?