纯真IP地址数据库qqwry.dat解析

ip地址数据库,在现在互联网时代非常有用,比如大型网站的用户安全保护系统,就常常会根据ip反查的信息,甄别账号的一些不安全登录行为,比如跨区域登录问题等。ip其实关联了一些有信息,比如区域,所在运营商,一些收录全的,甚至包括具体经纬度,像百度的IP定位api就比较全。下面来介绍一下“ 纯真IP地址数据库qqwry”的格式以及解析

以下是“ 纯真IP地址数据库qqwry”官网对其的介绍。

纯真版IP地址数据库是当前网络上最权威、地址最精确、IP记录以及网吧数据最多的IP地址数据库。收集了包括中国电信、中国移动、中国联通、铁通、长城宽带等各 ISP 的最新准确 IP 地址数据。通过大家的共同努力打造一个没有未知数据,没有错误数据的QQ IP。IP数据库每5天更新一次,请大家定期更新最新的IP数据库!

格式

+———-+
| 文件头 | (8字节)
+———-+
| 记录区 | (不定长)
+———-+
| 索引区 | (大小由文件头决定)
+———-+

使用java语言解析的两种思路:

  • 使用内存映射文件方式读取,使用java的MappedByteBuffer 将原数据文件映射到MappedByteBuffer对象中,然后通过MappedByteBuffer 提供的字节读取方式实现ip的查找。搜索是在索引区使用二分法

  • 使用byte数组读取,及将二进制的数据库信息全都按顺序读入到一个数组中,由于数据是有格式的,我们便可计算根据索引区和记录区在数组中的位置,当查询ip时,从数组中的索引区开始通过二分查找方式找到IP地址对应的国家和区域的位置,然后从数组中取出地区信息。

热升级思路:

使用一个可调度的单线程的线程池,线程定时检测qqwry.dat文件是否修改,若修改则重新将数据重新载入,载入过程可使用可重入锁ReentrantLock来锁住资源,避免在更新的过程中脏查询

两种解析方式的实现源码如下:
方式一(MappedByteBuffer ):

package com.difeng.qqwry1;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @Description:ip定位查找工具(使用内存映射文件方式读取,线程安全)
 * @author:difeng
 * @date:2016年12月11日
 */
public class IPLocation {

    private static final int IP_RECORD_LENGTH = 7;

    private static final byte REDIRECT_MODE_1 = 0x01;

    private static final byte REDIRECT_MODE_2 = 0x02;

    private MappedByteBuffer mbbFile;

    private static Long lastModifyTime = 0L;

    public static boolean enableFileWatch = false;

    private static ReentrantLock lock = new ReentrantLock();

    private File qqwryFile;

    private long firstIndexOffset;

    private long lastIndexOffset;

    private long totalIndexCount;

    public IPLocation(String filePath) throws Exception {
        this.qqwryFile = new File(filePath);
        load();
        if (enableFileWatch) {
            watch();
        }
    }

    private void watch(){
        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                long time = qqwryFile.lastModified();
                if (time > lastModifyTime) {
                    lastModifyTime = time;
                    try {
                        load();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, 1000L, 30000L, TimeUnit.MILLISECONDS);
    }

    public long read4ByteAsLong(long pos) {
        mbbFile.position((int)pos);
        return 0xFFFFFFFFL & mbbFile.getInt();
    }

    public long read3ByteAsLong(long pos){
        mbbFile.position((int)pos);
        return 0xFFFFFFL & mbbFile.getInt();
    }


    @SuppressWarnings("resource")
    private void load() throws Exception {
        lastModifyTime = qqwryFile.lastModified();
        lock.lock();
        try {
            mbbFile =  new RandomAccessFile(qqwryFile, "r")
                    .getChannel()
                    .map(FileChannel.MapMode.READ_ONLY, 0, qqwryFile.length());
            mbbFile.order(ByteOrder.LITTLE_ENDIAN);
            firstIndexOffset = read4ByteAsLong(0);
            lastIndexOffset = read4ByteAsLong(4);
            totalIndexCount = (lastIndexOffset - firstIndexOffset) / IP_RECORD_LENGTH + 1;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * @Description:将“.”号分隔的字符串转换为long类型的数字,字节序例如:
     *   ip:182.92.240.48  16进制表示(B6.5C.F0.30)
     *   转换后ip的16进制表示:0xB65CF030
     * @param ipStr
     * @return:long
     */
    private static long inet_pton(String ipStr) {
        if(ipStr == null){
            throw new NullPointerException("ip不能为空");
        }
        String [] arr = ipStr.split("\\.");
        long ip = (Long.parseLong(arr[0])  & 0xFFL) << 24 & 0xFF000000L;
        ip |=  (Long.parseLong(arr[1])  & 0xFFL) << 16 & 0xFF0000L;
        ip |=  (Long.parseLong(arr[2])  & 0xFFL) << 8 & 0xFF00L;
        ip |=  (Long.parseLong(arr[3])  & 0xFFL);
        return ip;
    }

    private long search(long ip) {
        long low = 0;
        long high = totalIndexCount;
        long mid = 0;
        while(low <= high) {
            mid = (low + high) >>> 1 ;
            long indexIP = read4ByteAsLong(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH);
            long nextIndexIP =  read4ByteAsLong(firstIndexOffset + mid * IP_RECORD_LENGTH);
            if(indexIP <= ip && ip < nextIndexIP) {
                return read3ByteAsLong(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH + 4);
            } else {
                if(ip > indexIP) {
                    low = mid + 1;
                } else if(ip < indexIP) {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

    private Location readIPLocation(long offset) {
        try {
            mbbFile.position((int)offset + 4);
            Location loc = new Location();
            byte redirectMode = mbbFile.get();
            if (redirectMode == REDIRECT_MODE_1) {
                long countryOffset = read3ByteAsLong((int)offset + 5);
                mbbFile.position((int)countryOffset);
                redirectMode = mbbFile.get();
                if (redirectMode == REDIRECT_MODE_2) {
                    loc.country = readString(read3ByteAsLong(countryOffset + 1));
                    mbbFile.position((int)countryOffset + 4);
                } else {
                    loc.country = readString(countryOffset);
                }
                loc.area = readArea(mbbFile.position());
            } else if (redirectMode == REDIRECT_MODE_2) {
                loc.country = readString(read3ByteAsLong((int)offset + 5));
                loc.area = readArea((int)offset + 8);
            } else {
                loc.country = readString(mbbFile.position() - 1);
                loc.area = readArea(mbbFile.position());
            }
            return loc;
        } catch (Exception e) {
            return null;
        }
    }

    private String readArea(int offset) {
        mbbFile.position(offset);
        byte redirectMode = mbbFile.get();
        if (redirectMode == REDIRECT_MODE_1 || redirectMode == REDIRECT_MODE_2) {
            long areaOffset = read3ByteAsLong((int)offset + 1);
            if (areaOffset == 0){
                return "";
            } else {
                return readString(areaOffset);
            }
        } else {
            return readString(offset);
        }
    }

    private String readString(long offset) {
        try {
            mbbFile.position((int)offset);
            byte[] buf = new byte[128];
            int i;
            for (i = 0, buf[i] = mbbFile.get(); buf[i] != 0; buf[++i] = mbbFile.get());
            
            if (i != 0){
                return new String(buf, 0, i, "GBK");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    public  Location fetchIPLocation(String ip) {
        lock.lock();
        try {
            long offset = search(inet_pton(ip));
            if(offset != -1){
                return readIPLocation(offset);
            }
        } finally {
            lock.unlock();
        }
        return null;
    }
}

方式二(数组方式):

package com.difeng.qqwry2;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @Description:ip定位(使用byte数据方式读取)
 * @author:difeng
 * @date:2016年12月13日
 */
public class IPLocation {
    
    private  byte[] data;
    
    private  long firstIndexOffset;
    
    private  long lastIndexOffset;
    
    private  long totalIndexCount;
    
    private static final byte REDIRECT_MODE_1 = 0x01;
    
    private static final byte REDIRECT_MODE_2 = 0x02;
    
    static   final long IP_RECORD_LENGTH = 7;
    
    private static ReentrantLock lock = new ReentrantLock();
    
    private static Long lastModifyTime = 0L;

    public static boolean enableFileWatch = false;
    
    private File qqwryFile;
    
    public IPLocation(String  filePath) throws Exception {
        this.qqwryFile = new File(filePath);
        load();
        if(enableFileWatch){
            watch();
        }
    }
    
    private void watch() {
        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                long time = qqwryFile.lastModified();
                if (time > lastModifyTime) {
                    lastModifyTime = time;
                    try {
                        load();
                        System.out.println("reload");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, 1000L, 5000L, TimeUnit.MILLISECONDS);
    }
    
    private void load() throws Exception {
        lastModifyTime = qqwryFile.lastModified();
        ByteArrayOutputStream out = null;
        FileInputStream in = null;
        lock.lock();
        try {
            out = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            in = new FileInputStream(qqwryFile);
            while(in.read(b) != -1){
                out.write(b);
            }
            data = out.toByteArray();
            firstIndexOffset = read4ByteAsLong(0);
            lastIndexOffset = read4ByteAsLong(4);
            totalIndexCount = (lastIndexOffset - firstIndexOffset) / IP_RECORD_LENGTH + 1;
            in.close();
            out.close();
        } finally {
            try {
                if(out != null) {
                    out.close();
                }
                if(in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    }
    
    private long read4ByteAsLong(final int  offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8L) & 0xFF00L;
        val |= (data[offset + 2] << 16L) & 0xFF0000L;
        val |= (data[offset + 3] << 24L) & 0xFF000000L;
        return val;
    }

    private long read3ByteAsLong(final int offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8) & 0xFF00;
        val |= (data[offset + 2] << 16) & 0xFF0000;
        return val;
    }
    
    private long search(long ip) {
        long low = 0;
        long high = totalIndexCount;
        long mid = 0;
        while(low <= high){
            mid = (low + high) >>> 1 ;
            long indexIP = read4ByteAsLong((int)(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH));
            long indexIPNext = read4ByteAsLong((int)(firstIndexOffset + mid * IP_RECORD_LENGTH));
            if(indexIP <= ip && ip < indexIPNext) {
                return read3ByteAsLong((int)(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH + 4));
            } else {
                if(ip > indexIP) {
                    low = mid + 1;
                } else if (ip < indexIP) {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }
    
    public Location fetchIPLocation(String ip) {
        long numericIp = inet_pton(ip);
        lock.lock();
        long offset = search(numericIp);
        try{
            if(offset != -1) {
                return readIPLocation((int)offset);
            }
        } finally {
            lock.unlock();
        }
        return null;
    }
    
    private Location readIPLocation(final int offset) {
        final Location loc = new Location();
        try {
            byte redirectMode = data[offset + 4];
            if (redirectMode == REDIRECT_MODE_1) {
                long countryOffset = read3ByteAsLong((int)offset + 5);
                redirectMode = data[(int)countryOffset];
                if (redirectMode == REDIRECT_MODE_2) {
                    final QQwryString country = readString((int)read3ByteAsLong((int)countryOffset + 1));
                    loc.country = country.string;
                    countryOffset = countryOffset + 4;
                } else {
                    final QQwryString country = readString((int)countryOffset);
                    loc.country = country.string;
                    countryOffset += country.byteCountWithEnd;
                }
                loc.area = readArea((int)countryOffset);
            } else if (redirectMode == REDIRECT_MODE_2) {
                loc.country = readString((int)read3ByteAsLong((int)offset + 5)).string;
                loc.area = readArea((int)offset + 8);
            } else {
                final QQwryString country = readString((int)offset + 4);
                loc.country = country.string;
                loc.area = readArea((int)offset + 4 + country.byteCountWithEnd);
            }
            return loc;
        } catch (Exception e) {
            return null;
        }
    }

    private String readArea(final int offset) {
        byte redirectMode = data[offset];
        if (redirectMode == REDIRECT_MODE_1 || redirectMode == REDIRECT_MODE_2) {
            long areaOffset = read3ByteAsLong((int)offset + 1);
            if (areaOffset == 0) {
                return "";
            } else {
                return readString((int)areaOffset).string;
            }
        } else {
            return readString(offset).string;
        }
    }
    
    private QQwryString readString(int offset) {
        int pos = offset;
        final byte[] b = new byte[128];
        int i;
        for (i = 0, b[i] = data[pos++]; b[i] != 0; b[++i] = data[pos++]);
        try{
               return new QQwryString(new String(b,0,i,"GBK"),i + 1);
        } catch(UnsupportedEncodingException e) {
            return new QQwryString("",0);
        }
    }
    
     /**
     * @Description:“.”号分隔的字符串转换为long类型的数字
     * @param ipStr 
     * @return:long
     */
    private static long inet_pton(String ipStr) {
        if(ipStr == null){
            throw new NullPointerException("ip不能为空");
        }
        String [] arr = ipStr.split("\\.");
        long ip = (Long.parseLong(arr[0])  & 0xFFL) << 24 & 0xFF000000L;
        ip |=  (Long.parseLong(arr[1])  & 0xFFL) << 16 & 0xFF0000L;
        ip |=  (Long.parseLong(arr[2])  & 0xFFL) << 8 & 0xFF00L;
        ip |=  (Long.parseLong(arr[3])  & 0xFFL);
        return ip;
    }
    
    private class QQwryString{
        
        public final String string;
        
        public final int byteCountWithEnd;
        
        public QQwryString(final String string,final int byteCountWithEnd) {
            this.string = string;
            this.byteCountWithEnd = byteCountWithEnd;
        }
        
        @Override
        public String toString() {
            return string;
        }
        
    }
}

以上为主要代码,获取全部代码请点击全部代码

使用

final IPLocation ipLocation = new IPLocation(filePath);
Location loc = ipl.fetchIPLocation("182.92.240.50");
System.out.printf("%s %s",loc.country,loc.area);

格式改进

由于原格式中读取地区记录时采用重定向,有些繁琐。去掉之后格式更简单,国家和地区单独存放,索引里分别记录的国家和地区的地址。
新格式如下:

+----------+
| 文件头 | (8字节)
+----------+
| 记录区 | (不定长)
+----------+
| 索引区 | (大小由文件头决定)
+----------+
文件头:
+------------------------------+-----------------------------+
| first index position(4 bytes)|last index position(4 bytes) |
+------------------------------+-----------------------------+
记录区:
+------------------+----------+------------------+----------+-----
| country1(n bytes)|\0(1 byte)| country2(n bytes)|\0(1 byte)|...
+------------------+----------+------------------+----------+-----
+------------------+----------+------------------+----------+-----
| area1(n bytes) |\0(1 byte)| area2(n bytes) |\0(1 byte)|...
+------------------+----------+------------------+----------+-----
索引区:
+------------+-------------------------+------------------------+
|ip1(4 bytes)|country position(3 bytes)| area position(3 bytes) |...
+------------+-------------------------+------------------------+
转换方法:

final IPFileConvertor convertor = new 
IPFileConvertor(IPFileConvertor.class.getResource("/qqwry.dat").getPath(),"./qqwry.dat");
convertor.convert();
新格式使用方法和之前的一致,使用com.difeng.convert包下的解析类IPLocation解析即可。

相关连接:
qqwry下载: qqwry
全球ip地址库(收费):IPLocation

-------------------------------------------------------------------------------------------------------------------------------

纯真 IP 数据库自动更新文件教程

 

 

 相传纯真数据库 qqwry.dat 已经有很久远的历史了,相信也依旧有非常多网站使用该库来查询 IP,其中在两年前也开始正式的用到了,同样更庆幸的是 qqwry.dat 纯真数据库依旧保持着更新,并且免费。相信很多人对于查询 IP 可能都是使用的第三方接口,需求较大的可能还会使用付费的 IP 查询接口,然而对于像这样追求简单极致,并且不想付费的人来说,qqwry.dat 你可能就不会陌生了。然而纯真网络官网提供的数据库需要本地安装软件才能获得最新的 IP 地址库,然后在通过软件安装目录找到 qqwry.dat 文件再去使用,手动更新就显得不太方便了,所以刚刚才更新用户中心的 IP 地址库,也算是懒到极致了。不过还好,发现了一个 PHP 自动更新纯真数据库 qqwry.dat 文件的方法,通过以下代码的实现,你应该可以实现纯真数据库自动更新更能

<?php
// PHP 纯真 IP 地址数据库自动更新功能
$copywrite = file_get_contents("http://update.cz88.net/ip/copywrite.rar");
$qqwry = file_get_contents("http://update.cz88.net/ip/qqwry.rar");
$key = unpack("V6", $copywrite)[6];
for($i=0; $i<0x200; $i++){
    $key *= 0x805;
    $key ++;
    $key = $key & 0xFF;
    $qqwry[$i] = chr( ord($qqwry[$i]) ^ $key );
}
$qqwry = gzuncompress($qqwry);
$fp = fopen("qqwry.dat", "wb");
if($fp){
    fwrite($fp, $qqwry);
    fclose($fp);
}
?>

这还是在 github 上发现作者 shuax 分享的示例,特别感谢!纯真数据库自动更新原理:以上代码使用 php 实现,从 copywrite.rar 中读取解密需要的一个 key,然后解密 qqwry.rar 头 0x200 字节数据,随后使用 zlib 解压数据即可得到 qqwry.dat 文件。qqwry.dat 收集了包括中国电信、中国移动、中国联通、长城宽带、聚友宽带等 ISP 的 IP 地址数据,包括网吧数据,没有错误数据的 QQ IP,IP 数据库每 5 天更新一次。

转载:https://www.2kss.com/40035.html

 

纯真ip下载:https://files.cnblogs.com/files/yehuisir/qqwry.zip

posted @ 2019-07-02 10:43  搬砖小伙子  阅读(2955)  评论(0编辑  收藏  举报