一,如何判斷一個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地址並保存到文本文件
1,downchinaip.sh
#!/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\n", $4, 32-log($5)/log(2)) }' >${ip_txt_path} #parse 2 redis,用php腳本解析,保存到redis echo "begin parse ip\n"; ${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."\n"; 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."\n"; $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.":\n"; if ($is_in == true) { echo "china:\n"; } else { echo "out china:\n"; } //------------------------------------------------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
大家如果更好更成熟的方案可以給我留言,感謝!
