我有一个像这样的CIDR列表:
192.168.0.1/24
10.0.0.1/32
etc...
名单越来越多 为了检查IP是否适合这些CIDR中的一个,我使用以下函数执行循环:
function cidr_match($ip, $range){
list ($subnet, $bits) = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask; // in case the supplied subnet was not correctly aligned
return ($ip & $mask) == $subnet;
}
由于我的CIDR列表正在增长,我想改进该功能,以避免逐个测试每一行CIDR,直到它返回true。我想摆脱上面函数的 for 循环。
有没有办法对我要检查的IP执行“预检”,以便它不会按顺序运行完整列表(从上到下)?
我想优化,以便我的代码行为方式:将IP提供给函数 - &gt; “排序”列表或“找到”最可能的CIDR的功能种类 - &gt;在IP上检查最可能的CIDR - &gt;尽快返回“true”
我们将不胜感激。
答案 0 :(得分:1)
老实说,除非你的CIDR范围很大并且你在同一个过程中检查了很多IP,否则你可能不会看到很多性能提升的方式。但是,如果这是您正在查看的场景,那么您可以通过预处理范围和IP来尝试挤压一些性能(执行一次ip2long()调用并存储分离的掩码/子网比较)。
例如,这就是你今天做的方式,我假设:
<?php
// Original style
$ranges = array(
"192.168.0.1/32",
"192.168.0.1/26",
"192.168.0.1/24",
"192.168.0.1/16",
"127.0.0.1/24",
"10.0.0.1/32",
"10.0.0.1/24"
);
// Run the check
$start = microtime(true);
find_cidr("10.0.0.42", $ranges);
find_cidr("192.168.0.12", $ranges);
find_cidr("10.0.0.1", $ranges);
$end = microtime(true);
echo "Ran 3 find routines in " . ($end - $start) . " seconds!\n";
function find_cidr($ip, $ranges)
{
foreach($ranges as $range)
{
if(cidr_match($ip, $range))
{
echo "IP {$ip} found in range {$range}!\n";
break;
}
}
}
function cidr_match($ip, $range){
list ($subnet, $bits) = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask; // in case the supplied subnet was not correctly aligned
return ($ip & $mask) == $subnet;
}
在我的机器上,运行时间约为0.0005 - 0.001秒(针对少数几个范围检查3个IP)。
如果我写一些东西来预处理范围:
<?php
// Slightly-optimized style
$ranges = array(
"192.168.0.1/32",
"192.168.0.1/26",
"192.168.0.1/24",
"192.168.0.1/16",
"127.0.0.1/24",
"10.0.0.1/32",
"10.0.0.1/24"
);
$matcher = new BulkCIDRMatch($ranges);
$start = microtime(true);
$matcher->FindCIDR("10.0.0.42");
$matcher->FindCIDR("192.168.0.12");
$matcher->FindCIDR("10.0.0.1");
$end = microtime(true);
echo "Ran 3 find routines in " . ($end - $start) . " seconds!\n";
class BulkCIDRMatch
{
private $_preparedRanges = array();
public function __construct($ranges)
{
foreach($ranges as $range)
{
list ($subnet, $bits) = explode('/', $range);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask; // in case the supplied subnet was not correctly aligned
$this->_preparedRanges[$range] = array($mask,$subnet);
}
}
public function FindCIDR($ip)
{
$result = $this->_FindCIDR(ip2long($ip));
if($result !== null)
{
echo "IP {$ip} found in range {$result}!\n";
}
return $result;
}
private function _FindCIDR($iplong)
{
foreach($this->_preparedRanges as $range => $details)
{
if(($iplong & $details[0]) == $details[1])
{
return $range;
}
}
// No match
return null;
}
}
...然后我看到更快的CHECKING,但是当初始化类并且它处理并存储所有范围时,开头的开销略微增加。因此,如果我在少数范围内仅用3个IP计时OVERALL运行,那么&#34;优化&#34;方式实际上有点慢。但是,如果我针对10,000个CIDR运行1,000个IP,那么&#34;优化&#34;方式将比原始方式有更明显的改进(以额外的内存使用为代价来存储预处理的范围数据)。
所以这一切都取决于音量以及你想要做的事情。
也就是说,如果您担心0.001秒的性能太慢,那么PHP可能不是用于检查的正确语言。或者至少你可能想考虑编写自定义扩展,以便在C中完成更多的处理。
编辑:回答关于找到&#34;可能&#34;的原始问题。要检查的范围(在从字符串形式进行任何类型的转换之前),它可能不是一个非常可靠的尝试。范围可以跨越其初始八位字节,因此如果您开始比较这些值(例如&#34;我看着192.168.1.0,那么我只会查看从192和#34开始的范围; ),您不仅会在每个条目上产生字符串比较的性能开销(这会降低整体查找速度),但您可能会错过有效范围。
答案 1 :(得分:0)
如果您真的关心性能,那么您应该将列表存储在类似于结构的内容中,并以一种并不意味着查找每个条目的方式进行搜索,直到找到匹配为止。
在这种情况下,它是一个排序列表和二进制搜索:
class CidrList {
protected $ranges = [];
public function addRanges($ranges) {
foreach($ranges as $range) {
$this->addRange($range);
}
$this->sortRanges();
}
public function findRangeByIP($ip) {
return $this->_findRangeByIP(ip2long($ip));
}
// simple binary search
protected function _findRangeByIP($ip, $start=NULL, $end=NULL) {
if( $end < $start || $start > $end ) { return false; }
if( is_null($start) ) { $start = 0; }
if( is_null($end) ) { $end = count($this->ranges) -1; }
$mid = (int)floor(($end + $start) / 2);
switch( $this->inRange($ip, $this->ranges[$mid]) ) {
case 0:
return $this->ranges[$mid][2];
case -1:
return $this->_findRangeByIP($ip, $start, $mid-1);
case 1:
return $this->_findRangeByIP($ip, $mid+1, $end);
}
}
// add a single range, protected as the list must be sorted afterwards.
protected function addRange($range) {
list ($subnet, $bits) = explode('/', $range);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$min = $subnet & $mask;
$max = $subnet | ~$mask;
$this->ranges[] = [$min, $max, $range];
}
// sort by start, then by end. aka from narrowest overlapping range to widest
protected function sortRanges() {
usort($this->ranges, function($a, $b) {
$res = $a[0] - $b[0];
switch($res) {
case 0:
return $a[1] - $b[1];
default:
return $res;
}
});
}
protected function inRange($ip, $range) {
list($start, $end, $cidr) = $range;
if( $ip < $start ) { return -1; }
if( $ip > $end ) { return 1; }
return 0;
}
}
用法:
$l = new CidrList();
$l->addRanges(["192.168.0.1/16", "192.168.0.1/24", "127.0.0.1/24", "10.0.0.1/24"]);
var_dump(
$l->findRangeByIP('192.168.0.10'),
$l->findRangeByIP('192.168.1.10'),
$l->findRangeByIP('1.2.3.4')
);
输出:
string(14) "192.168.0.1/24"
string(14) "192.168.0.1/16"
bool(false)
此外,您应该通过缓存整个CidrList
对象或其内部范围集来避免不断重新处理字符串。