利用纯真数据库根据IP定位地区

来源:互联网 发布:软件企业财务管理制度 编辑:程序博客网 时间:2024/05/21 12:31

        在系统中,查看用户的登录信息是一个很常见的功能。我们往往会记录下用户计算机的IP和地理位置,然而IP地址记录非常容易,但是地理位置相对来说较难。开始,菜鸟是想创建一个IP地址库,根据IP地址库查找相应的地理位置。后来想有没有一个相对完整的IP地址库供菜鸟使用了,于是找到了一个QQwry.dat(纯真数据库)。然而,QQwry.dat是什么呢?以及怎么使用?菜鸟通过各位大神的笔记终于弄懂了,以致写下这篇文章供和我一样的菜鸟学习。

一、纯真数据库(QQwry.dat)

1.基本结构

        QQwry.dat文件在结构上分为文件头、记录区和索引区3部分。我们一般在使用时先从索引区查找记录偏移,根据记录偏移在从记录区中获取。由于记录区是不定长的,且比较多,因此,一般采用二分查找法进行查找。

2.文件头

        QQwry.dat文件的头文件结构非常简单,为8个字节。前4个字节为第一条索引的绝对偏移,后4个字节为最后一条索引的绝对偏移。

3.记录区

        每条IP地址的记录区都由国家和地区组成,但是在这里国家和地区都不太明确,相对而言的。国家可能是指一所学校,地区可能指学校中的某一系。于是我们想着IP地址的记录格式可能为:[IP地址][国家名称][地区名称]。

        国家和地区可能有很多重复,因此我们我可以用重定向来节约空间。其重定向有两种方式:一种是直接用字符串表示国家或地区;另一种是一个4字节的结构,第1个字节表示重定向的模式,后3个字节表示国家名称或地区名称的实际偏移位置。

        重定向的模式分为两种:一种是只有国家,没有地区,也就是说地区记录跟着国家记录走了,在IP地址之后只剩下国家记录的4个字节,后3个字节是一个指针,指向了实际的国家名称,其标识字节为0X01。另一种是既有国家又有地址,即地区记录没有跟着国家记录走。在4个字节的国家记录后还含有地区记录,其标识字节为0X02。

4.索引区

        通过了解“文件头”,我们可以了解到文件头实际上是两个指针,分别指向文件的第一条索引和最后一条索引的绝对偏移。我们可以根据头文件定位到索引区,然后开始查找IP。每条索引区为7个字节,前4个字节表示起始IP地址,后3个字节表示结束IP地址。如222.11.0.1-222.11.0.240,222.11.0.1表示起始IP地址,222.11.0.240表示结束IP地址。若我们要查找的IP地址在这个IP地址的范围内,则根据这条索引区查找国家和地区。

二、实例

1.IP地址实体,包含起始、结束IP、国家名称和地区名称

package com.test.ip.entity;public class IpEntity {private String startIp;// 起始IPprivate String endIp;// 结尾IPprivate String country;// 国家private String area;// 区域// 構造方法:清空数据信息public IpEntity() {super();this.startIp = "";this.endIp = "";this.country = "";this.area = "";}// getter和setter方法提供属性对外访问接口public String getStartIp() {return startIp;}public void setStartIp(String startIp) {this.startIp = startIp;}public String getEndIp() {return endIp;}public void setEndIp(String endIp) {this.endIp = endIp;}public String getCountry() {return country;}public void setCountry(String country) {this.country = country;}public String getArea() {return area;}public void setArea(String area) {this.area = area;}}

2.位置实体

package com.test.ip.entity;/** * 封装IP信息,如国家和地区 *  * @author aleyn * */public class IpLocation {private String country;// 国家private String area;// 地区// 构造方法public IpLocation() {this.country = "";this.area = "";}public IpLocation getCopy() {IpLocation ipLocation = new IpLocation();ipLocation.setCountry(this.getCountry());ipLocation.setArea(this.getArea());return ipLocation;}// getter、setterpublic String getCountry() {return country;}public void setCountry(String country) {this.country = country;}public String getArea() {return area;}public void setArea(String area) {// 若为局域网,纯真IP地址库的地区会显示CZ88.NET,去除if (area.trim().equals("CZ88.NET")) {this.area = "局域网";} else {this.area = area;}}}

3.转换工具类

package com.test.ip.util;import java.util.StringTokenizer;/** * 转换工具 *  * @author aleyn * */public class ConvertUtils {private static StringBuffer sb = new StringBuffer();/** * IP字符串转字节数组 *  * @param ip * @return */public static byte[] getIpArray(String ip) {byte[] buffer = new byte[4];StringTokenizer stringTokenizer = new StringTokenizer(ip, ".");try {buffer[0] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);buffer[1] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);buffer[2] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);buffer[3] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);} catch (Exception e) {e.printStackTrace(); }return buffer;}/** * IP字节数组换字符串 *  * @param ip * @return */public static String getIpString(byte[] ip) {sb.delete(0, sb.length());sb.append(ip[0] & 0xFF);sb.append(".");sb.append(ip[1] & 0xFF);sb.append(".");sb.append(ip[2] & 0xFF);sb.append(".");sb.append(ip[3] & 0xFF);return sb.toString();}/** * 根据某种编码将IP字节数组转为字符串 *  * @param ip * @param offset * @param length * @param encode * @return */public static String getIpString(byte[] ip, int offset, int length,String encode) {try {return new String(ip, offset, length, encode);} catch (Exception e) {return new String(ip, offset, length);}}}

4.读取国家或地区工具类

package com.test.ip.util;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;public class ReadUtils {/** * 从offset未知读取一个4字节为一个long java格式为big-endian *  * @param offset * @return */public static long readLongByFour(RandomAccessFile randomAccessFile,long offset) {long result = 0;try {randomAccessFile.seek(offset);result |= (randomAccessFile.readByte() & 0xFF);result |= ((randomAccessFile.readByte() << 8) & 0xFF00);result |= ((randomAccessFile.readByte() << 16) & 0xFF0000);result |= ((randomAccessFile.readByte() << 24) & 0xFF000000);return result;} catch (Exception e) {return -1;}}/** * 从offset位置开始读取3个字节为一个long *  * @param randomAccessFile * @param offset * @param buffer * @return */public static long readLongByThree(RandomAccessFile randomAccessFile,long offset, byte[] buffer) {long result = 0;try {randomAccessFile.seek(offset);randomAccessFile.readFully(buffer);result |= (buffer[0] & 0xFF);result |= ((buffer[1] << 8) & 0xFF00);result |= ((buffer[2] << 16) & 0xFF0000);return result;} catch (Exception e) {e.printStackTrace();return -1;}}/** * 从当前位置读取3个字节为一个long *  * @param randomAccessFile * @param buffer * @return */public static long readLongByThree(RandomAccessFile randomAccessFile,byte[] buffer) {long result = 0;try {randomAccessFile.readFully(buffer);result |= (buffer[0] & 0xFF);result |= ((buffer[1] << 8) & 0xFF00);result |= ((buffer[2] << 16) & 0xFF0000);return result;} catch (Exception e) {return -1;}}/** * 从内存映射的offset位置,开始3个字节读取一个int *  * @param offset * @return */public static int readIntByThree(MappedByteBuffer mappedByteBuffer,int offset) {mappedByteBuffer.position(offset);return mappedByteBuffer.getInt() & 0x00FFFFFF;}/** * 从内存映射的当前位置,开始3个字节读取一个int *  * @param mappedByteBuffer * @return */public static int readIntByThree(MappedByteBuffer mappedByteBuffer) {return mappedByteBuffer.getInt() & 0x00FFFFFF;}/** * 从offset位置读取4个字节的IP地址放入IP数组中,读取后的IP地址格式为big-endian *  * @param offset * @param ip */public static void readIp(RandomAccessFile randomAccessFile, long offset,byte[] ip) {try {randomAccessFile.seek(offset);randomAccessFile.readFully(ip);byte temp = ip[0];ip[0] = ip[3];ip[3] = temp;temp = ip[1];ip[1] = ip[2];ip[2] = temp;} catch (Exception e) {e.printStackTrace(); }}/** * 从offset位置读取一个以0结束的字符串 *  * @param offset * @return */public static String readString(RandomAccessFile randomAccessFile,long offset, byte[] buffer) {try {randomAccessFile.seek(offset);int i;for (i = 0, buffer[i] = randomAccessFile.readByte(); buffer[i] != 0; buffer[++i] = randomAccessFile.readByte());if (i != 0) {return ConvertUtils.getIpString(buffer, 0, i, "GBK");}} catch (Exception e) {e.printStackTrace(); }return "";}/** * 从offset位置读取一个以0结束的字符串 *  * @param offset * @return */public static String readString(MappedByteBuffer mappedByteBuffer,int offset, byte[] buffer) {try {mappedByteBuffer.position(offset);int i;for (i = 0, buffer[i] = mappedByteBuffer.get(); buffer[i] != 0; buffer[++i] = mappedByteBuffer.get());if (i != 0)return ConvertUtils.getIpString(buffer, 0, i, "GBK");} catch (IllegalArgumentException e) {e.printStackTrace(); }return "";}/** * 从offset位置读取4个字节的IP地址放入IP数组中,读取后的IP地址格式为big-endian *  * @param mappedByteBuffer * @param offset * @param ip */public static void readIp(MappedByteBuffer mappedByteBuffer, int offset,byte[] ip) {try {mappedByteBuffer.position(offset);mappedByteBuffer.get(ip);byte temp = ip[0];ip[0] = ip[3];ip[3] = temp;temp = ip[1];ip[1] = ip[2];ip[2] = temp;} catch (Exception e) {e.printStackTrace(); }}/** * 比较两个字节的大小 *  * @param b1 * @param b2 * @return */private static int compareByte(byte b1, byte b2) {if ((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于return 1;else if ((b1 ^ b2) == 0)// 判断是否相等return 0;elsereturn -1;}/** * 将要查询的IP和起始的IP进行比较 *  * @param ip * @param beginIp * @return */public static int compareIp(byte[] ip, byte[] beginIp) {for (int i = 0; i < 4; i++) {int j = compareByte(ip[i], beginIp[i]);if (j != 0) {return j;}}return 0;}}

5.提示消息

package com.test.ip.util;/** * 提示信息 *  * @author aleyn * */public interface Message {public static final String BAD_IP_FILE = "IP地址库文件错误";public static final String UNKNOW_COUNTRY = "未知国家";public static final String UNKNOW_AREA = "未知区域";}

6.IP查看器

package com.test.ip.viewer;import java.io.File;import java.io.FileNotFoundException;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.ByteOrder;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import com.test.ip.entity.IpEntity;import com.test.ip.entity.IpLocation;import com.test.ip.util.ConvertUtils;import com.test.ip.util.LogFactory;import com.test.ip.util.Message;import com.test.ip.util.ReadUtils;public class IpViewer {private String fileName = "qqwry.dat";// 纯真IP数据库名private String fileDir = null;// 保存的文件夹// 固定常量,如记录长度private static final int IP_RECORD_LENGTH = 7;private static final byte REDIRECT_MODE_ONE = 0x01;private static final byte REDIRECT_MODE_TWO = 0x02;// 缓存已经查找过的IP地址,避免二次查找,加快速度private Map<String, IpLocation> cache;// 随机文件访问类private RandomAccessFile randomAccessFile;// 内存映射文件private MappedByteBuffer mappedByteBuffer;// 起始地区的开始和结束绝对偏移量private long begin, end;// 为提高效率,临时变量private IpLocation ipLocation;private byte[] buffer;private byte[] bufferOne;private byte[] bufferTwo;/** * 初始化 */public IpViewer() {// 获取文件路径和名称String path = IpViewer.class.getResource("").getPath() + fileName;fileName = path;// 初始化变量this.cache = new HashMap<String, IpLocation>();this.ipLocation = new IpLocation();this.buffer = new byte[100];this.bufferOne = new byte[3];this.bufferTwo = new byte[4];try {randomAccessFile = new RandomAccessFile(fileName, "r");} catch (FileNotFoundException e) {// 若文件找不到,在当前目录下重新搜索,并将文件名全部改为小写(有些系统只能识别小写)String name = new File(fileName).getName().toLowerCase();File[] files = new File(fileDir).listFiles();for (int i = 0; i < files.length; i++) {// 判断是否是文件if (files[i].isFile()) {// 判断文件名称if (files[i].getName().toLowerCase().equals(name)) {try {randomAccessFile = new RandomAccessFile(files[i],"r");} catch (FileNotFoundException fileNotFoundException) {e.printStackTrace();fileName = null;}break;}}}}// 若文件打开成功,读取文件头信息if (randomAccessFile != null) {try {begin = ReadUtils.readLongByFour(randomAccessFile, 0);end = ReadUtils.readLongByFour(randomAccessFile, 4);if (begin == -1 || end == -1) {randomAccessFile.close();randomAccessFile = null;}} catch (Exception e) {e.printStackTrace();randomAccessFile = null;}}}/** * 给定一个不完全的地点名称,得到包含该地点的IP范围记录 *  * @param str * @return */public List<IpEntity> getIpEntityDebug(String str) {List<IpEntity> list = new ArrayList<IpEntity>();long endOffset = end + 4;for (long offset = begin + 4; offset < endOffset; offset += IP_RECORD_LENGTH) {// 读取结束IP偏移量long temp = ReadUtils.readLongByThree(randomAccessFile, offset,bufferOne);// 若temp不等于-1,则读取IP信息if (temp != -1) {IpLocation ipLocation = this.getIpLocation(temp);// 判断是否包含改地名,若包含,择添加到listif (ipLocation.getCountry().indexOf(str) != -1|| ipLocation.getArea().indexOf(str) != -1) {IpEntity ipEntity = new IpEntity();ipEntity.setCountry(ipLocation.getCountry());ipEntity.setArea(ipLocation.getArea());// 获取起始IPReadUtils.readIp(randomAccessFile, offset - 4, bufferTwo);ipEntity.setStartIp(ConvertUtils.getIpString(bufferTwo));// 获取结束IPReadUtils.readIp(randomAccessFile, temp, bufferTwo);ipEntity.setEndIp(ConvertUtils.getIpString(bufferTwo));list.add(ipEntity);}}}return list;}public List<IpEntity> getIpEntity(String s) {List<IpEntity> list = new ArrayList<IpEntity>();try {// 映射IP信息文件到内存中if (mappedByteBuffer == null) {FileChannel fc = randomAccessFile.getChannel();mappedByteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0,randomAccessFile.length());mappedByteBuffer.order(ByteOrder.LITTLE_ENDIAN);}int endOffset = (int) end;for (int offset = (int) begin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) {int temp = ReadUtils.readIntByThree(mappedByteBuffer, offset);if (temp != -1) {IpLocation location = this.getIpLocation(temp);// 地点是否包含要查找的地点名称if (location.getCountry().indexOf(s) != -1|| location.getArea().indexOf(s) != -1) {IpEntity entity = new IpEntity();entity.setCountry(location.getCountry());entity.setArea(location.getArea());// 得到起始IPReadUtils.readIp(mappedByteBuffer, offset, bufferTwo);entity.setStartIp(ConvertUtils.getIpString(bufferTwo));// 得到结束IPReadUtils.readIp(mappedByteBuffer, temp, bufferTwo);entity.setEndIp(ConvertUtils.getIpString(bufferTwo));// 添加该记录list.add(entity);}}}} catch (IOException e) {e.printStackTrace(); }return list;}/** * 获取IP所在的地区信息 */public IpLocation getIpLocation(String ip) {IpLocation location = new IpLocation();location.setCountry(this.getCountry(ip));location.setArea(this.getArea(ip));return location;}/** * 根据IP地区偏移量,获取IP信息 *  * @param offset * @return */private IpLocation getIpLocation(long offset) {try {// 跳过4字节IPrandomAccessFile.seek(offset + 4);// 读取第一个字节,判断是否是标识字节byte result = randomAccessFile.readByte();if (result == REDIRECT_MODE_ONE) {// 读取国家偏移量long countryOffset = ReadUtils.readLongByThree(randomAccessFile, bufferOne);// 跳转至偏移处randomAccessFile.seek(countryOffset);// 再次检查标识符result = randomAccessFile.readByte();if (result == REDIRECT_MODE_TWO) {ipLocation.setCountry(ReadUtils.readString(randomAccessFile, ReadUtils.readLongByThree(randomAccessFile, bufferOne), buffer));randomAccessFile.seek(countryOffset + 4);} else {ipLocation.setCountry(ReadUtils.readString(randomAccessFile, countryOffset, buffer));}// 读取地区标识符ipLocation.setArea(this.readArea(randomAccessFile.getFilePointer()));} else if (result == REDIRECT_MODE_TWO) {ipLocation.setCountry(ReadUtils.readString(randomAccessFile,ReadUtils.readLongByThree(randomAccessFile, bufferOne),buffer));ipLocation.setArea(this.readArea(offset + 8));} else {ipLocation.setCountry(ReadUtils.readString(randomAccessFile,randomAccessFile.getFilePointer() - 1, buffer));ipLocation.setArea(this.readArea(randomAccessFile.getFilePointer()));}return ipLocation;} catch (Exception e) {return null;}}/** * 根据IP地区偏移量,获取IP信息 *  * @param offset * @return */private IpLocation getIpLocation(int offset) {// 跳过4字节IPmappedByteBuffer.position(offset + 4);// 读取第一个字节,判断是否是标识字符byte result = mappedByteBuffer.get();if (result == REDIRECT_MODE_ONE) {// 读取国家偏移量int countryOffset = ReadUtils.readIntByThree(mappedByteBuffer);// 跳转至偏移处mappedByteBuffer.position(countryOffset);// 再次检查标识符result = mappedByteBuffer.get();if (result == REDIRECT_MODE_TWO) {ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,ReadUtils.readIntByThree(mappedByteBuffer), buffer));mappedByteBuffer.position(countryOffset + 4);} else {ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,countryOffset, buffer));}// 设置地区标识ipLocation.setArea(this.readArea(mappedByteBuffer.position()));} else if (result == REDIRECT_MODE_TWO) {ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,ReadUtils.readIntByThree(mappedByteBuffer), buffer));ipLocation.setArea(this.readArea(offset + 8));} else {ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,mappedByteBuffer.position() - 1, buffer));ipLocation.setArea(this.readArea(mappedByteBuffer.position()));}return ipLocation;}/** * 获取国家名称字符串 *  * @return */public String getCountry(String ip) {return this.getCountry(ConvertUtils.getIpArray(ip));}/** * 获取国家地区 *  * @param ip * @return */public String getCountry(byte[] ip) {// 检测IP地址文件是否正常if (randomAccessFile == null) {return Message.BAD_IP_FILE;}// 保存IP,转IP字节数组为字符串String ipStr = ConvertUtils.getIpString(ip);// 先从cache中检查ip,若没有在检测文件if (cache.containsKey(ipStr)) {IpLocation ipLocation = cache.get(ipStr);return ipLocation.getCountry();} else {IpLocation ipLocation = this.getIpLocation(ip);cache.put(ipStr, ipLocation.getCopy());return ipLocation.getCountry();}}/** * 根据IP搜索IP文件 *  * @param ip * @return */public IpLocation getIpLocation(byte[] ip) {IpLocation location = null;long offset = this.getLocateIp(ip);if (offset != -1) {location = this.getIpLocation(offset);}if (location == null) {location = new IpLocation();location.setCountry(Message.UNKNOW_COUNTRY);location.setArea(Message.UNKNOW_AREA);}return location;}/** * 根据IP内容,定位IP地址所在的国家,返回一个偏移量 *  * @param ip * @return */public long getLocateIp(byte[] ip) {long m = 0;int n;ReadUtils.readIp(randomAccessFile, begin, bufferTwo);n = ReadUtils.compareIp(ip, bufferTwo);if (n == 0)return begin;else if (n < 0)return -1;// 二分查找法查找IPfor (long i = begin, j = end; i < j;) {m = this.getMiddleOffset(i, j);ReadUtils.readIp(randomAccessFile, m, bufferTwo);n = ReadUtils.compareIp(ip, bufferTwo);if (n > 0) {i = m;} else if (n < 0) {if (m == j) {j -= IP_RECORD_LENGTH;m = j;} else {j = m;}} else {return ReadUtils.readLongByThree(randomAccessFile, m + 4,bufferOne);}}m = ReadUtils.readLongByThree(randomAccessFile, m + 4, bufferOne);ReadUtils.readIp(randomAccessFile, m, bufferTwo);n = ReadUtils.compareIp(ip, bufferTwo);if (n <= 0)return m;elsereturn -1;}/** * 根据IP字节数组获取地区名称 *  * @param ip * @return */private String getArea(byte[] ip) {// 检查IP文件是否正常if (randomAccessFile == null)return Message.BAD_IP_FILE;// 保存IP,转换字节数组IP为字符串String ipStr = ConvertUtils.getIpString(ip);// 现在cache中搜索结果,若没有,再从文件中查找if (cache.containsKey(ipStr)) {IpLocation location = cache.get(ipStr);return location.getArea();} else {IpLocation location = this.getIpLocation(ip);cache.put(ipStr, location.getCopy());return location.getArea();}}/** * 根据IP获取地区名称 *  * @param ip * @return */private String getArea(String ip) {return this.getArea(ConvertUtils.getIpArray(ip));}/** * 从offset位置读取地区信息 *  * @param offset * @return */private String readArea(long offset) {try {randomAccessFile.seek(offset);byte result = randomAccessFile.readByte();if (result == REDIRECT_MODE_ONE || result == REDIRECT_MODE_TWO) {long areaOffset = ReadUtils.readLongByThree(randomAccessFile,offset + 1, bufferOne);if (areaOffset == 0)return Message.UNKNOW_AREA;elsereturn ReadUtils.readString(randomAccessFile, areaOffset,buffer);} else {return ReadUtils.readString(randomAccessFile, offset, buffer);}} catch (Exception e) {e.printStackTrace(); }return "";}private String readArea(int offset) {mappedByteBuffer.position(offset);byte result = mappedByteBuffer.get();if (result == REDIRECT_MODE_ONE || result == REDIRECT_MODE_TWO) {int areaOffset = ReadUtils.readIntByThree(mappedByteBuffer);if (areaOffset == 0)return Message.UNKNOW_AREA;elsereturn ReadUtils.readString(mappedByteBuffer, areaOffset,buffer);} else {return ReadUtils.readString(mappedByteBuffer, offset, buffer);}}/** * 获取begin和end中间偏移量 *  * @param begin * @param end * @return */private long getMiddleOffset(long begin, long end) {long middle = (end - begin) / IP_RECORD_LENGTH;middle >>= 1;if (middle == 0)middle = 1;return begin + middle * IP_RECORD_LENGTH;}}

7.测试程序

package com.test.ip;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.util.Enumeration;import com.test.ip.viewer.IpViewer;public class Test {public static void main(String[] args){String ip = "63.251.90.8";IpViewer ipViewer = new IpViewer();System.out.println(ipViewer.getIpLocation(ip).getCountry()+":"+ipViewer.getIpLocation(ip).getArea());}}  
原创粉丝点击