通过IP获取地址的原理
IP地址本身是不具备定位功能的,IP地址只是用来决定一台网络主机在网络里的相对位置,这个位置是相对而言,可以通过IP路由到目的主机,并不会关心目的主机的物理位置在哪里。所以理论上是不可能定位到IP地址的物理位置的。所以只能另辟蹊径了。简单介绍两种
1、 那就是通过自治系统(AS)。简单的理解,IP是全球统一的,具有全球唯一性,为了保证唯一性肯定要统一做分配,全球每个需要IP的人都去这个统一分配系统申请,那也不行,这个统一系统肯定忙得要死。所以将这个分配的权力下放,将一批IP分配给某一个组织,这个组织再进行进一步分配给下一级组织……。谁拿走了这批IP地址需要进行登记的,等级的时候随便做个组织地址的登记,咦,暴露了物理地址。所以就可以通过这个登记的信息去获取这批IP地址的大概位置。比如某组织申请了IP地址为1.4.127.0-1.4.127.255,通过查询登记信息,发现这个组织在广东,那么如果一个IP在这个批IP地址里面那么就能知道这个IP归属地的广东。
2、通过不断收集定位信息。虽然IP本身不具备定位功能,但是使用IP的主机具备定位功能,比如某APP获取定位信息的同时获取IP信息,然后一起上传到服务器保存起来,这样就可以建立IP地址的物理位置信息库了。
第一种,登记信息不会开放给大家用的,属于隐私数据了。第二种更加,手机本身就是涉及到用户隐私问题,况且收集困难,用的人少信息就不全。网上还是有很多往网站可以查IP地址的归属地的,百度出来一大堆。因为IP的物理位置相对来说变动不大,所以也有些直接提供所有IP和物理地址映射关系的所有数据。
实现IP归属地显示
调别人的接口
靠谱点,权威点的使用个地图厂商的接口
百度地图接口
高德地图接口
用本地的数据
用别人的接口当然爽,发起请求解析请求搞定,不过也有不好的。收钱,限制调用次数,而且调次接口发起一次IO还很慢。那当然是本地好,主要是快,而且不依赖别人的接口。
例如star很多的项目ip2region
https://gitee.com/lionsoul/ip2region
https://github.com/lionsoul2014/ip2region
1、首先需要获取数据源,去找别人整理好的IP和物理地址对应关系数据或者通过脚本从某个网站爬出来。比如获取到数据如下
2、获取请求的IP地址。
根据IP协议可知,数据包都会携带源地址和目的地址的,所以只要是IP请求肯定能知道源IP地址的,如果隐藏比较好,做了各种代理那就只能取到代理机的IP了。HTTP协议也会在请求头里设置IP地址,所以可以直接通过http请求头进行获取IP地址,Java实现如下。
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
3、拿到IP后直接去步骤1中获取的数据寻找对应的IP物理地址即可。
本地数据查询优化
1、空间优化
我们获取到IP格式是这样的(127.0.0.1),实际上IP地址长度为32二进制,直接字符串保存那也太浪费空间了,而且不好比较所以先将IP地址换成byte数组,就不用自己造轮子了,JDK已经有了,而且IPv4和IPv6都支持。
byte[] ipByte = java.net.InetAddress.getByName("127.0.0.1").getAddress();
2、时间优化
遍历肯定不是最优解,避免遍历的最佳手段就是通过排序后使用二分法进行查询。
在进行空间优化后格式化数据到一个文件里,格式如下
前两位byte为数据头,记录版本号、数据的编码方式、所以字节数、数据长度范围、索引的长度等信息。之后就是索引的地方,所以根据主键进行有序排列的,并且保存索引数据的位置。这样当查询时直接通过二分法直接查询到索引的位置后拿到数据的位置,之后取出数据即可。
代码在github和gitee,CV即用。
https://gitee.com/eedui/location.git
https://github.com/eedui/location.git
号码归属地查询
号码归属地查询也是类似的方法,号码也是有类似的规律,前7位为号段,比如1300000这个号段山东省济南市的,所以拿到一个号码通过号段即可查询到归属地。甚至可以查到是哪个运营商,但是因为可以允许携号转网了,所以运营商更新快估计不准了。用上文数据结构可快速查询。
代码在github和gitee,CV即用。
gitee
github
https://gitee.com/eedui/location.git
https://github.com/eedui/location.git