以php檢查ip網段

隨者機器越來越多,將單一IP以字串方式檢測的方式,當網路臨時調整時、或者要檢測的IP數很多時,就有點麻煩了。使用判斷網段的方式,會比較有彈性。

依照tcp/ip的邏輯,利用位元運算先做出了第一版
  1. function matchCIDR($ip$cidr) {  
  2.   list($ip$mask) = explode('/'$cidr);  
  3.   $mask =(32 - $mask);  
  4.   return ((ip2long($ip) >> $mask) == (ip2long($ip) >> $mask));  
  5. }  
想說,是否有更快、更好的的作法?在PHP: ip2long - Manual看到了兩個作法。一個作法,很有效率。另一個卻比較差。

下面這個PHP程式碼,根據實際測試的結果,速度是最快的。不過…程式碼的可讀性比較差,需要想一下才能瞭解其邏輯。
  1. function netMatch($IP$CIDR) {  
  2.   list ($net$mask) = explode('/'$CIDR);  
  3.   return ( ip2long($IP) & ~((1 << (32 - $mask)) - 1) ) == ip2long($net);  
  4. }  

這個寫法,可讀性最好(其實,這樣講也不太對)。但是有個極大的缺點,就是他是以字串的方式做比對,需要額外的使用sprintf()、substr_compare()。導致這樣的程式碼執行速度是三個中最慢的一個。且,在某些情況下,判斷結果有問題。
  1. function ip_in_network($ip$net_addr$net_mask) {  
  2.   if ($net_mask <= 0) {  
  3.     return false;  
  4.   }  
  5.   $ip_binary_string = sprintf("%032b"ip2long($ip));  
  6.   $net_binary_string = sprintf("%032b"ip2long($net_addr));  
  7.   return (substr_compare($ip_binary_string$net_binary_string, 0, $net_mask) === 0);  
  8. }  
為何會舉上面兩個例子?其實,在流量很大的系統中,任何的資源都是珍貴的,須錙銖必較。刻意比較這三個判斷方式,就是要表明這一點。

下面,則是利用vld所呈現的php opcode。藉由opcode來瞭解,為何這兩個作法效能上會有差異?有興趣瞭解者,不妨參考一下opcode、以及PHP: Opcode list - Manual(如何安裝、使用vld?可以參考我之前的文章 - 如何使用vld看php的opcode
  1. <?php  
  2. //方法一,比較慢  
  3. function matchCIDR($ip$cidr) {  
  4.   list($ip$mask) = explode('/'$cidr);  
  5.   $mask =(32 - $mask);  
  6.   return ((ip2long($ip) >> $mask) == (ip2long($ip) >> $mask));  
  7. }  
  8.   
  9. //方法二,最快  
  10. function netMatch($IP$CIDR) {  
  11.   list ($net$mask) = explode('/'$CIDR);  
  12.   return ( ip2long($IP) & ~((1 << (32 - $mask)) - 1) ) == ip2long($net);  
  13. }  

附註:以下資料,非vld呈現的完整資訊。我僅列出重點。
function name:  matchCIDR
number of ops:  21
compiled vars:  !0 = $ip, !1 = $cidr, !2 = $mask
line     # *  op         fetch          ext  return  operands
---------------------------------------------------------------
   3     0  >   RECV                                   1
         1      RECV                                   2
   4     2      SEND_VAL                               '%2F'
         3      SEND_VAR                               !1
         4      DO_FCALL                    2          'explode'
         5      FETCH_DIM_R                    $1      $0, 1
         6      ASSIGN                                 !2, $1
         7      FETCH_DIM_R                    $3      $0, 0
         8      ASSIGN                                 !0, $3
   5     9      SUB                            ~5      32, !2
        10      ASSIGN                                 !2, ~5
   6    11      SEND_VAR                               !0
        12      DO_FCALL                    1          'ip2long'
        13      SR                             ~8      $7, !2
        14      SEND_VAR                               !0
        15      DO_FCALL                    1          'ip2long'
        16      SR                             ~10     $9, !2
        17      IS_EQUAL                       ~11     ~8, ~10
        18    > RETURN                                 ~11
   7    19*     RETURN                                  null
        20*   > ZEND_HANDLE_EXCEPTION


function name:  netMatch
number of ops:  22
compiled vars:  !0 = $IP, !1 = $CIDR, !2 = $net, !3 = $mask
line     # *  op         fetch          ext  return  operands
---------------------------------------------------------------
  10     0  >   RECV                                   1
         1      RECV                                   2
  11     2      SEND_VAL                               '%2F'
         3      SEND_VAR                               !1
         4      DO_FCALL                    2          'explode'
         5      FETCH_DIM_R                    $1      $0, 1
         6      ASSIGN                                 !3, $1
         7      FETCH_DIM_R                    $3      $0, 0
         8      ASSIGN                                 !2, $3
  12     9      SEND_VAR                               !0
        10      DO_FCALL                    1          'ip2long'
        11      SUB                            ~6      32, !3
        12      SL                             ~7      1, ~6
        13      SUB                            ~8      ~7, 1
        14      BW_NOT                         ~9      ~8
        15      BW_AND                         ~10     $5, ~9
        16      SEND_VAR                               !2
        17      DO_FCALL                    1          'ip2long'
        18      IS_EQUAL                       ~12     ~10, $11
        19    > RETURN                                 ~12
  13    20*     RETURN                                  null
        21*   > ZEND_HANDLE_EXCEPTION

留言