希望这对某人有所帮助。鉴于您没有具体询问正则表达式,并且由于主题是关于匹配 IP 地址,我想我会把它放在那里,以便它可以帮助遇到类似问题的人。
服务器软件通常力求尽可能快速和高效;匹配 IP 地址通常以算术方式完成。话虽如此,在讨论可能的替代方案之前,我将介绍一种快速的方法来完全按照您的要求进行操作。
如果您只是对 IP 地址字符串执行通配符匹配,我建议您使用此方法。它已根据您的用例量身定制,但我将自己包含简单的匹配功能。
相比之下,与使用 PHP 的 RegEx 函数(这是更复杂的方法模式匹配)
注意事项:
- 我将此处引用的函数用于非常特定的目的。它们适用于这种情况,因为 IP 地址中没有“*”字符。正如它们所写的那样,如果您正在测试的变量中包含“*”字符,它只会与通配符匹配,因此存在丢失信息的可能性很小。
- 如果您正在编写命令行守护程序,或者您的进程将在其生命周期内使用相同的 IP 列表进行多次检查,那么使用 RegEx 库是有益的。只有在必须初始加载和准备 IP 列表 RegEx 以供首次使用时,才能在此处使用我的方法带来的小幅速度优势。
- 您可以将代码从“wildcard_match()”移动到“match_ip()”内部以获得更多好处,避免另一个函数调用的开销。
代码(可以复制粘贴):
<?php
/**
* This function compares a string ("$test") to see if it is
* equal to another string ("$wild"). An '*' in "$wild" will
* match any characters in "$test".
*
* @param string $wild - The string to compare against. This may
* be either an exact character string to match, or a string
* with a wild card ('*') character that will match any character(s)
* found in "$test".
*
* @param string $test - A character string we're comparing against
* "$wild" to determine if there is a match.
*
* @return bool Returns TRUE if "$test" is either an exact match to
* "$wild", or it fits the bill taking any wild card characters into
* consideration.
*
**/
function wildcard_match( $pattern, $test ) {
$p = 0;
$a_name = explode("*", $pattern);
$segs = count($a_name);
$max_seg = ($segs-1);
$plen = 0;
$test_len = strlen($test);
for ($i = 0; $i < $segs; $i++) {
$part = $a_name[$i];
$plen = strlen($part);
if ($plen === 0) {
if ($i === $max_seg) return true;
continue;
}
$p = strpos($test, $part, $p);
if ($p === false) {
return false;
}
$p+=$plen;
}
if ($p===$test_len) {
return true;
}
return false;
}
/**
* Function to quickly traverse an array of whole, or
* wild card IPv4 addresses given in "$whitelist" and
* determine if they match the given whole IPv4
* address in "$test".
*
* @param array $whitelist - An array of IPv4 addresses, either
* whole, or containing an '*' character wherein any character(s)
* in "$test" will match.
*
* @param string $test - A complete string (dot-decimal) IPv4
* address to compare against the contents of the array given in
* parameter one ("$whitelist").
*
* @return bool Returns TRUE, if the IPv4 address given in "$test" was
* matched to an IPv4 address or IPv4 wild card pattern in the array
* given in parameter one ("$whitelist").
*
**/
function match_ip( $whitelist, $test ) {
foreach ($whitelist as $w) {
if (wildcard_match($w, $test)) return true;
}
return false;
}
/* The array of IP addresses we're going to validate */
$check_array = array("50.245.1.9", "35.125.25.255", "202.16.15.25");
/* The array as given in your example (minus the extra ' at the end) */
$whitelist1=array('50*','202.16*','123.168*');
/* An array for RegEx matching */
$whitelist2=array('50\..*','202\.16\..*','123\.168\..*');
microtime(true); /* Execute this once to make sure its module is loaded */
echo "Giving PHP a second to get its ducks in a row...\n";
usleep(1000000); /** Give PHP a second to load and prepare */
$st = microtime(true);
foreach ($check_array as $c) {
if (match_ip($whitelist1, $c)) {
echo "$c....Match!\n";
} else {
echo "$c....No match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n\n\n";
$st = microtime(true);
foreach ($check_array as $c) {
if(preg_match('/^(' . implode('|', $whitelist2) . ')/', $c)){
echo "$c....Match!\n";
} else {
echo "$c....No Match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n";
由此产生的输出是:
Giving PHP a second to get its ducks in a row...
50.245.1.9....Match!
35.125.25.255....No match!
202.16.15.25....Match!
Time 1: 0.00027704238891602
50.245.1.9....Match!
35.125.25.255....No Match!
202.16.15.25....Match!
Time 2: 0.00040698051452637
第一个结果集来自函数“match_ip()”,第二个结果集来自启动 RegEx 库。
现在,对于通配符匹配 IP,一个可能更好的解决方案是在您的数组中使用 CIDR 表示法的 IP 地址数组。通常,您希望允许来自特定网络或 IP 地址范围的 IP 流量。
这里有很多假设,但例如(使用您的“$whitelist”数组):
'50*' 可能被解释为“我希望允许来自 50.xxx.xxx.xxx 的所有 IP 地址访问。
在这种情况下,您需要指定格式“50.0.0.0/8”。 (“50”后面的“0”可以是任意数字。因为“/8”,它们将被完全忽略。)
xxx.xxx.xxx.xxx
| | | |
8 16 24 32
IPv4 地址在计算上是 32 位,所以在上面,您说您只关心前 8 位匹配。
“123.168*”将是“123.168.0.0/16”
“101.23.54.0/24”将允许所有以“101.23.54”开头的IP地址访问。
“44.32.240.10/32”将仅允许 IP 地址“44.32.240.10”访问。没有范围。
所以你可以这样做:
<?php
/**
* Determines if the two given IPv4 addresses
* are equal, or are on the same network using
* the given number of "$mask" bits.
*
* @param string $ip1 - The first string dot-decimal IPv4
* address.
*
* @param string $ip1 - The second string dot-decimal IPv4
* address.
*
* @param int $mask - The number of bits in the mask.
*
* @return bool Returns TRUE if they match after being
* masked, or FALSE if not.
*
*/
function ip_match( $ip1, $ip2, $mask) {
$mask = (int)$mask;
$ip1 = ip2long($ip1);
$ip2 = ip2long($ip2);
if ($ip1 === false || $ip2 === false) return false;
if ($mask < 1 || $mask > 32) return false;
$mask = (0x00000000FFFFFFFF & 0x00000000FFFFFFFF << (32-$mask));
if ( ($ip1 & $mask) === ($ip2 & $mask) ) {
return true;
}
return false;
}
/**
* Takes an array of string (CIDR) network representations and
* sorts them into an array used later for checking against IP
* addresses.
*
* @param array $cidr_array - An array of IP addressess in
* CIDR notation e.g. 192.168.1.1/24
*
* @return array Returns an array of objects with the following
* properties:
* 'ip' - The string (dot-decimal) IP address that
* has been numerically verified for use
* in comparisons later.
*
* 'mask' - The number of bits used for creating
* the subnet mask against which IP
* addresses will be compared.
*
**/
function make_whitelist( $cidr_array ) {
$wl = array();
$lip = 0;
$bm = 0;
$spos = 0;
if (!is_array($cidr_array)) return false;
foreach ($cidr_array as $ip) {
$spos = strpos($ip, "/");
if ($spos === false) {
$bm = 32; /* If there's no "/", assume
* that we want an EXACT IP
* address. Hence the 32 bit
* mask
**/
} else {
$bm = (int)substr($ip, ($spos+1));
$ip = substr($ip, 0, $spos++);
}
$lip = ip2long($ip); /* Using this here to check IP validity
* before storing it in the array...
* We use ip2long() later for comparisons.
*
* You can store it this way - as a long -
* instead of as a string (I do) to
* use less memory if you wish.
*
**/
if ($bm === 0) continue; /* A bit mask of ZERO will block
* ALL IP addresses, skip it
* for the example.
**/
if ($lip === false) continue; /* If it's an invalid IP, skip it,
* you could optionally try to
* resolve it as a hostname using
* gethostbyname() or gethostbynamel()
* here...
**/
array_push($wl, (object)array('ip'=>$ip, 'mask'=>$bm));
}
return $wl;
}
$whitelist = make_whitelist(array("50.0.0.0/8", "202.16.0.0/16", "123.168.0.0/16", "1.1.1.1"));
$ips_to_check = array("50.1.174.41", "42.123.100.23", "123.168.4.79", "1.1.1.2", "1.1.1.1");
foreach ($ips_to_check as $ip) {
foreach ($whitelist as $w) {
if (ip_match($ip, $w->ip, $w->mask)) {
echo "\"$ip\" is allowed!\n";
continue 2;
}
}
echo "\"$ip\" is NOT allowed!\n";
}
我知道这很多,但这里有很多可供人们思考、在搜索时查找,并希望能够用来让他们的生活更轻松!