• 判断ip地址是属于国内还是国外


    一,如何判断一个ip地址是否属于国内?

         我们以前使用淘宝提供的一个api地址进行判断,但经常出现打不开的报错,

         因为只需要判断是国内或国外,于是考虑自己搞一个简单的。

         分配给国内的ip地址在apnic的官方网站上可以下载到,但不方便直接判断,

         我写了一个demo,可以供大家来参考:

         总体上分为三部分:

         1,定时下载ip地址,保存到文本文件

         2,  解析ip地址列表,保存到redis

         3,  拿到一个ip时,从redis中取出ip地址段进行比较,如果在各个地址段范围内,表示是国内ip,否则是国外ip

         项目地址:  https://github.com/liuhongdi/isipinchina

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: https://github.com/liuhongdi/

     说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,下载ip地址并保存到文本文件

    #!/bin/bash
    #variables,定义用到的变量
    
    ip_txt_path=/data/data/ipdata/china_ip.txt;
    ip_url='http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest';
    php_path=/usr/local/soft/php7/bin/php
    script_path=/data/web/think_cmd/chinaip/putip2redis.php
    
    #mv old txt,每次下载前把旧的ip地址文件改名,删除也可以
    
    cur_time=$(date +"%Y%m%d%H%M%S");
    if [ -f ${ip_txt_path} ];then
           mv ${ip_txt_path} ${ip_txt_path}_${cur_time};
    fi
    
    #download 用curl下载,保存到我们所定义的文本文件中
    
    /usr/bin/curl ${ip_url} | grep ipv4 | grep CN | awk -F| '{ printf("%s/%d
    ", $4, 32-log($5)/log(2)) }' >${ip_txt_path}
    
    #parse 2 redis,用php脚本解析,保存到redis
    
    echo "begin parse ip
    ";
    ${php_path} ${script_path}

    2,变量的配置:

    指定下载后保存到本地的ip地址段文件

    ip_txt_path=/data/data/ipdata/china_ip.txt;

    apnic官网的ip地址段下载url,如果此地址有变化时,需修改ip_url此变量

    ip_url='http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest';

    二进制的php文件的路径,此处应设置为自己服务器上php的安装路径

    php_path=/usr/local/soft/php7/bin/php

    putip2redis.php保存到的路径

    script_path=/data/web/think_cmd/chinaip/putip2redis.php

    三,解析ip地址列表,保存到redis

    1,我们下载到的ip地址段形如:

    36.40.0.0/13
    36.48.0.0/15
    36.51.0.0/16
    36.56.0.0/13
    36.96.0.0/11 

    我们要做两项处理:

    1,转为整数段,形如:

    603979776--603980799

    以方便对接收到ip参数进行比较

    2,整个ip地址段有8千多条

    [root@blog ipdata]$ wc -l china_ip.txt
    8489 china_ip.txt

    每次查询比较8000多次一则没有必要,二则影响效率,

    我们取ip地址的第一段,做为索引,例如;36

    这样可以在查询时先比较第一段,

    如果此索引不存在,则不再继续比较,

    如果存在,取出此索引下面的所有整数段,看ip地址是否存在于这些范围内

    因为只需比较相同的第一段下面的ip地址段,每次比较的数量减少到了平均不到100个,

    对于速度提升有好处

    3,putip2redis.php

    <?php
    /*
    
    解析国内ip地址列表,以ip地址的第一段为索引,
    保存到redis中的一个hash中
    
    by 刘宏缔
    2020.04.02
    
    */
    //------------------------------------------------settings
    ini_set("display_errors","On");
    error_reporting(E_ALL);
    //------------------------------------------------constant
    define("REDIS_SERVER", "127.0.0.1");
    define("REDIS_PORT", "6379");
    define("IP_FILE", "/data/data/ipdata/china_ip.txt");
    define("IP_HASH_NAME", "china_ip_hash");
    //------------------------------------------------link 4 redis
    $redis_link = new Redis();
    $redis_link->connect(REDIS_SERVER,REDIS_PORT);
    
    //------------------------------------------------main
    set_ip_list(IP_FILE);
    //------------------------------------------------function
    //处理所有的ip范围到redis
    function set_ip_list($ip_file) {
        //从文件中得到所有的国内ip
        $arr_all = file($ip_file);
    
        //遍历,得到所有的第一段
        $arr_first = array();
        foreach ($arr_all as $k => $rangeone) {
                  $rangeone = trim($rangeone);
            if ($rangeone == "") {
                continue;
            }
            $first = explode(".", $rangeone);
            if (isset($first[0]) && $first[0]!='') {
                  $arr_first[] = $first[0];
            }
        }
    
        //对所有的第一段去除重复
        $arr_first = array_unique($arr_first);
    
        //得到线上hash的所有key
        $arr_hkeys = hash_keys(IP_HASH_NAME);
    
        //如果一个线上已存在的key不再存在于新ip的第一段的数组中
        //需要从线上hash中删除
        if (is_array($arr_hkeys) && sizeof($arr_hkeys)>0) {
            foreach($arr_hkeys as $k => $hkey_one) {
               if (!in_array($hkey_one, $arr_first)) {
                    echo "will delete :".$hkey_one."
    ";
                    hash_delete_hkey(IP_HASH_NAME,$hkey_one);
                }
            }
        }
    
        //得到每个第一段下面对应的所有ip地址段,保存到redis
        foreach ($arr_first as $k => $first) {
                      add_a_list_by_first($first,$arr_all);
        }
    
    }
    
    
    //把所有的第一段为指定数字的ip,添加到redis
    function add_a_list_by_first($first,$arr) {
    
               $arr_line = array();
         foreach ($arr as $k => $rangeone) {
                    $rangeone = trim($rangeone);
              $first_a = explode(".", $rangeone);
              if (!isset($first_a[0]) || $first_a[0] == "") {
                      continue;
              }
              $cur_first = $first_a[0];
              if ($cur_first == $first) {
    
                  $line = get_line_by_rangeone($rangeone);
                  //echo "line:".$line."
    ";
                  $arr_line[] = $line;
              } else {
                      continue;
              }
          }
    
          if (sizeof($arr_line) >0) {
               $key_name = $first;
               hash_set(IP_HASH_NAME,$key_name,$arr_line);
          }
    }
    
    
    //得到一个ip地址段的起始范围
    function get_line_by_rangeone($networkRange) {
            $s = explode('/', $networkRange);
            $network_start = (double) (sprintf("%u", ip2long($s[0])));
            $network_len = pow(2, 32 - $s[1]);
            $network_end = $network_start + $network_len - 1;
    
            $line = $network_start."--".$network_end;
            return $line;
    }
    
    
    //redis set 一个数组到hash
    function hash_set($hash_name,$key_name,$arr_value){
                global $redis_link;
          $str_value = json_encode($arr_value);
          $b = $redis_link->hset($hash_name, $key_name, $str_value);
    }
    
    //返回redis hash中所有的key,注意只是key,如果value也返回会影响速度
    function hash_keys($hash_name) {
          global $redis_link;
          $arr = $redis_link->hKeys($hash_name);
          return $arr;
    }
    
    //删除一个hash的hkey
    function hash_delete_hkey($hash_name,$key_name) {
          global $redis_link;
          $redis_link->hdel($hash_name, $key_name);
    }
    
    ?>

    说明:需要配置的常量:

    define("REDIS_SERVER", "127.0.0.1");       //redis服务器的ip
    define("REDIS_PORT", "6379");              //redis服务器的port
    define("IP_FILE", "/data/data/ipdata/china_ip.txt");   //下载保存到本地的ip地址段文件,注意和downchinaip.sh中保持一致
    define("IP_HASH_NAME", "china_ip_hash");   //保存到redis中的hash的名字

    四,查询一个ip是否属于国内ip地址

    1,查询时需要把ip转为整数进行比较

    2,isipinchina.php

    <?php
    /*
    判断一个ip是否国内的ip
    需要连接到redis服务器进行判断
    by 刘宏缔
    2020.04.01
    */
    //------------------------------------------------settings
    ini_set("display_errors","On");
    error_reporting(E_ALL);
    //------------------------------------------------constant
    define("REDIS_SERVER", "127.0.0.1");
    define("REDIS_PORT", "6379");
    define("IP_HASH_NAME", "china_ip_hash");
    //------------------------------------------------link 2 redis
    $redis_link = new Redis();
    $redis_link->connect(REDIS_SERVER,REDIS_PORT);
    //------------------------------------------------main
    
    $ip = "203.137.164.152";
    $is_in = is_ip_in_china($ip);
    echo "is_in:".$is_in.":
    ";
    if ($is_in == true) {
        echo "china:
    ";
    } else {
        echo "out china:
    ";
    }
    
    //------------------------------------------------function
    
    //判断一个ip是否属于china
    function is_ip_in_china($ip) {
        $ip = trim($ip);
        $first_a = explode(".", $ip);
        if (!isset($first_a[0]) || $first_a[0] == "") {
            //ip有误,按国外算
            return false;
        }
        $first = $first_a[0];
        
        $arr_range = hash_get(IP_HASH_NAME,$first);
        if (!is_array($arr_range) || sizeof($arr_range) == 0) {
              return false;
        }
        if (is_ip_in_arr_range($ip,$arr_range) == true) {
              return true;
        } else {
              return false;
        }
    }
    
    //判断一个ip是否属于ip的range数组
    function is_ip_in_arr_range($ip,$arr_range) {
        $ip_long = (double) (sprintf("%u", ip2long($ip)));
        foreach ($arr_range as $k => $one) {
            $one = trim($one);
            $arr_one = explode("--", $one);
            if (!isset($arr_one[0]) || !isset($arr_one[1])) {
                 continue;
            }
            $begin = $arr_one[0];
            $end = $arr_one[1];
            if ($ip_long >= $begin && $ip_long <= $end) {
                return true;
            }
        }
        return false;
    }
    
    //得到一个hash中对应key的value
    function hash_get($hash_name,$key_name){
             global $redis_link;
             $str = $redis_link->hget($hash_name, $key_name);
             $arr = json_decode($str,true);
             return $arr;
    }
    
    ?>

    说明:需要配置的常量:

    define("REDIS_SERVER", "127.0.0.1");        //redis服务器的ip
    define("REDIS_PORT", "6379");               //redis服务器的port
    define("IP_HASH_NAME", "china_ip_hash");    //保存到redis中的hash的名字

    3,查询时的功能,大家在使用时应该封装成一个可以添加到项目框架的类来应用

    五,把bash脚本downchinaip.sh添加到crond,每天定时运行

    [root@blog ~]# crontab -l
    30 0 * * * sh /data/web/think_cmd/chinaip/downchinaip.sh >> /data/logs/cronlogs/downchinaiplogs.log 2>&1

    六,php的版本:

    [root@blog chinaip]$ /usr/local/soft/php7/bin/php --version
    PHP 7.4.2 (cli) (built: Mar  5 2020 11:16:38) ( NTS )
    Copyright (c) The PHP Group
    Zend Engine v3.4.0, Copyright (c) Zend Technologies

    七,备注:

       这个方案的执行效率还可以,判断一个ip一次不到半毫秒,

    [root@blog ~]# /usr/local/soft/php7/bin/php /data/web/think_cmd/chinaip/isipinchina.php
    耗时0.00036秒:
    is_in:1:
    china

    大家如果更好更成熟的方案可以给我留言,感谢!

  • 相关阅读:
    【web charting】21个Javascript图表插件程序
    【IOC框架】分析与理解
    【待续】【HTML5】用Canvas标签创建第一张条线图
    【转】大型网站后台架构的演变
    初探Visual C# SQL CLR Database Project
    Js中 关于top、clientTop、scrollTop、offsetTop
    JS的正则表达式
    jquery的extend和fn.extend
    C/C++版数据结构之链表<三>
    C/C++ 一点笔记(2)
  • 原文地址:https://www.cnblogs.com/architectforest/p/12621501.html
Copyright © 2020-2023  润新知