Java实现IP地址定位地址信息

在做网站的时候,参考网上一下相关的文章和说明,上班偷闲之际整理了IP定位的部分,方便以后使用的时候查询,具体参考如下:

1. 我用到了QQWry.dat,当然首先是引入该文件。

2. 封装通过IP获取的信息,包括国家,地区等。

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2. /**
  3. *
  4. * 功能描述:封装IP地址信息POJO
  5. *          一条IP范围记录,不仅包括国家和区域,也包括起始IP和结束IP
  6. *
  7. * @author  Administrator
  8. *
  9. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  10. */ 
  11. public class IPEntry { 
  12.      
  13.     // 起始IP 
  14.     public String beginIp; 
  15.     // 结束IP 
  16.     public String endIp; 
  17.     // 归属国家 
  18.     public String country; 
  19.     // 归属地区 
  20.     public String area; 
  21.      
  22.     /**
  23.      *
  24.      * 构造函数:清空信息
  25.      *
  26.      */ 
  27.     public IPEntry() { 
  28.          
  29.         beginIp = "";  
  30.         endIp = ""
  31.         country = ""
  32.         area = ""
  33.     } 
package com.lzb.ip;
/**
 * 
 * 功能描述:封装IP地址信息POJO
 * 			一条IP范围记录,不仅包括国家和区域,也包括起始IP和结束IP
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class IPEntry {
	
	// 起始IP
    public String beginIp;
    // 结束IP
    public String endIp;
    // 归属国家
    public String country;
    // 归属地区
    public String area;
    
    /**
     * 
     * 构造函数:清空信息
     *
     */
    public IPEntry() {
    	
        beginIp = ""; 
        endIp = "";
        country = "";
        area = "";
    }
}

3. 通过获取的IP信息,查询出所在地的信息,其实就是一个读取QQWry.dat匹配获取相关的信息。

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2.  
  3. import java.io.File; 
  4. import java.io.FileNotFoundException; 
  5. import java.io.IOException; 
  6. import java.io.RandomAccessFile; 
  7. import java.net.URL; 
  8. import java.nio.ByteOrder; 
  9. import java.nio.MappedByteBuffer; 
  10. import java.nio.channels.FileChannel; 
  11. import java.util.ArrayList; 
  12. import java.util.HashMap; 
  13. import java.util.List; 
  14. import java.util.Map; 
  15. import org.apache.log4j.Level; 
  16.  
  17. import com.lzb.io.ReadProUtil; 
  18. /**
  19. *
  20. * 功能描述: 用来读取QQwry.dat文件,确定Ip
  21. *
  22. * @author  Administrator
  23. *
  24. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  25. */ 
  26. public class IPSeeker { 
  27.      
  28.     //纯真IP数据库名 
  29.     private String IP_FILE = "QQWry.Dat"
  30.     //保存的文件夹 
  31.     private String INSTALL_DIR = null;   
  32.      
  33.     // 一些固定常量,比如记录长度等等 
  34.     private static final int IP_RECORD_LENGTH = 7
  35.     private static final byte REDIRECT_MODE_1 = 0x01
  36.     private static final byte REDIRECT_MODE_2 = 0x02
  37.      
  38.     // 用来做为cache,查询一个ip时首先查看cache,以减少不必要的重复查找 
  39.     private Map<String, IPLocation> ipCache; 
  40.     // 随机文件访问类 
  41.     private RandomAccessFile ipFile; 
  42.  
  43.     // 内存映射文件 
  44.     private MappedByteBuffer mbb; 
  45.     // 起始地区的开始和结束的绝对偏移 
  46.     private long ipBegin, ipEnd; 
  47.     // 为提高效率而采用的临时变量 
  48.     private IPLocation loc; 
  49.     private byte[] buf; 
  50.     private byte[] b4; 
  51.     private byte[] b3; 
  52.      
  53.     /**
  54.      *
  55.      * 构造函数:初始化
  56.      *
  57.      */ 
  58.     public IPSeeker()  { 
  59.          
  60.         // 获取文件的路径和名称 
  61.         StringBuffer sb = new StringBuffer(); 
  62.         URL url = IPSeeker.class.getResource(""); 
  63.         sb.append(url.getPath() + this.IP_FILE);         
  64.         this.IP_FILE = sb.toString();; 
  65.         ipCache = new HashMap<String, IPLocation>(); 
  66.         loc = new IPLocation(); 
  67.         buf = new byte[100]; 
  68.         b4 = new byte[4]; 
  69.         b3 = new byte[3]; 
  70.          
  71.         try
  72.              
  73.             ipFile = new RandomAccessFile(IP_FILE, "r"); 
  74.              
  75.         } catch (FileNotFoundException e) { 
  76.             // 如果找不到这个文件,再尝试再当前目录下搜索,这次全部改用小写文件名 
  77.             // 因为有些系统可能区分大小写导致找不到ip地址信息文件 
  78.             String filename = new File(IP_FILE).getName().toLowerCase(); 
  79.             File[] files = new File(INSTALL_DIR).listFiles(); 
  80.             for(int i = 0; i < files.length; i++) { 
  81.                 if(files[i].isFile()) { 
  82.                     if(files[i].getName().toLowerCase().equals(filename)) { 
  83.                         try
  84.                             ipFile = new RandomAccessFile(files[i], "r"); 
  85.                         } catch (FileNotFoundException e1) { 
  86.                             LogFactory.log("IP地址信息文件没有找到,IP显示功能将无法使用",Level.ERROR,e1); 
  87.                             ipFile = null
  88.                         } 
  89.                         break
  90.                     } 
  91.                 } 
  92.             } 
  93.         }  
  94.         // 如果打开文件成功,读取文件头信息 
  95.         if(ipFile != null) { 
  96.             try
  97.                 ipBegin = readLong4(0); 
  98.                 ipEnd = readLong4(4); 
  99.                 if(ipBegin == -1 || ipEnd == -1) { 
  100.                     ipFile.close(); 
  101.                     ipFile = null
  102.                 }            
  103.             } catch (IOException e) { 
  104.                 LogFactory.log("IP地址信息文件格式有错误,IP显示功能将无法使用",Level.ERROR,e); 
  105.                 ipFile = null
  106.             }            
  107.         } 
  108.     } 
  109.      
  110.     /**
  111.      *
  112.      * 功能描述:给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
  113.      *
  114.      * @author  Administrator
  115.      * <p>创建日期 :2012-3-14 上午9:53:22</p>
  116.      *
  117.      * @param s
  118.      *          地点子串
  119.      * @return
  120.      *          包含IPEntry类型的List
  121.      *
  122.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  123.      */ 
  124.     public List getIPEntriesDebug(String s) { 
  125.          
  126.         List<IPEntry> ret = new ArrayList<IPEntry>(); 
  127.         long endOffset = ipEnd + 4
  128.         for(long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { 
  129.             // 读取结束IP偏移 
  130.             long temp = readLong3(offset); 
  131.             // 如果temp不等于-1,读取IP的地点信息 
  132.             if(temp != -1) { 
  133.                 IPLocation ipLoc = getIPLocation(temp); 
  134.                 // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续 
  135.                 if(ipLoc.getCountry().indexOf(s) != -1 || ipLoc.getArea().indexOf(s) != -1) { 
  136.                     IPEntry entry = new IPEntry(); 
  137.                     entry.country = ipLoc.getCountry(); 
  138.                     entry.area = ipLoc.getArea(); 
  139.                     // 得到起始IP 
  140.                     readIP(offset - 4, b4); 
  141.                     entry.beginIp = Util.getIpStringFromBytes(b4); 
  142.                     // 得到结束IP 
  143.                     readIP(temp, b4); 
  144.                     entry.endIp = Util.getIpStringFromBytes(b4); 
  145.                     // 添加该记录 
  146.                     ret.add(entry); 
  147.                 } 
  148.             } 
  149.         } 
  150.         return ret; 
  151.     } 
  152.      
  153.     /**
  154.      *
  155.      * 功能描述:获取Ip所在地区信息
  156.      *
  157.      * @author  Administrator
  158.      * <p>创建日期 :2012-3-14 上午9:54:15</p>
  159.      *
  160.      * @param ip
  161.      *          IP地址
  162.      * @return
  163.      *
  164.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  165.      */ 
  166.     public IPLocation getIPLocation(String ip) { 
  167.          
  168.         IPLocation location = new IPLocation(); 
  169.         location.setArea(this.getArea(ip)); 
  170.         location.setCountry(this.getCountry(ip)); 
  171.  
  172.         return location; 
  173.     } 
  174.      
  175.     /**
  176.      *
  177.      * 功能描述:给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
  178.      *
  179.      * @author  Administrator
  180.      * <p>创建日期 :2012-3-14 上午9:55:22</p>
  181.      *
  182.      * @param s
  183.      *          地点子串
  184.      * @return
  185.      *          包含IPEntry类型的List
  186.      *
  187.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  188.      */ 
  189.     public List<IPEntry> getIPEntries(String s) { 
  190.          
  191.         List<IPEntry> ret = new ArrayList<IPEntry>(); 
  192.         try
  193.             // 映射IP信息文件到内存中 
  194.             if(mbb == null) { 
  195.                 FileChannel fc = ipFile.getChannel(); 
  196.                 mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length()); 
  197.                 mbb.order(ByteOrder.LITTLE_ENDIAN);              
  198.             } 
  199.              
  200.             int endOffset = (int)ipEnd; 
  201.             for(int offset = (int)ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { 
  202.                 int temp = readInt3(offset); 
  203.                 if(temp != -1) { 
  204.                     IPLocation ipLoc = getIPLocation(temp); 
  205.                     // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续 
  206.                     if(ipLoc.getCountry().indexOf(s) != -1 || ipLoc.getArea().indexOf(s) != -1) { 
  207.                         IPEntry entry = new IPEntry(); 
  208.                         entry.country = ipLoc.getCountry(); 
  209.                         entry.area = ipLoc.getArea(); 
  210.                         // 得到起始IP 
  211.                         readIP(offset - 4, b4); 
  212.                         entry.beginIp = Util.getIpStringFromBytes(b4); 
  213.                         // 得到结束IP 
  214.                         readIP(temp, b4); 
  215.                         entry.endIp = Util.getIpStringFromBytes(b4); 
  216.                         // 添加该记录 
  217.                         ret.add(entry); 
  218.                     } 
  219.                 } 
  220.             }            
  221.         } catch (IOException e) { 
  222.             LogFactory.log("",Level.ERROR,e); 
  223.         } 
  224.         return ret; 
  225.     } 
  226.  
  227.     /**
  228.      *
  229.      * 功能描述:从内存映射文件的offset位置开始的3个字节读取一个int
  230.      *
  231.      * @author  Administrator
  232.      * <p>创建日期 :2012-3-14 上午9:57:26</p>
  233.      *
  234.      * @param offset
  235.      * @return
  236.      *
  237.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  238.      */ 
  239.     private int readInt3(int offset) { 
  240.          
  241.         mbb.position(offset); 
  242.         return mbb.getInt() & 0x00FFFFFF
  243.     } 
  244.  
  245.     /**
  246.      *
  247.      * 功能描述: 从内存映射文件的当前位置开始的3个字节读取一个int
  248.      *
  249.      * @author  Administrator
  250.      * <p>创建日期 :2012-3-14 上午9:57:47</p>
  251.      *
  252.      * @return
  253.      *
  254.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  255.      */ 
  256.     private int readInt3() { 
  257.          
  258.         return mbb.getInt() & 0x00FFFFFF
  259.     } 
  260.      
  261.     /**
  262.      *
  263.      * 功能描述:根据IP得到国家名
  264.      *
  265.      * @author  Administrator
  266.      * <p>创建日期 :2012-3-14 上午9:58:01</p>
  267.      *
  268.      * @param ip
  269.      *          ip的字节数组形式
  270.      * @return
  271.      *          国家名字符串
  272.      *
  273.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  274.      */ 
  275.     public String getCountry(byte[] ip) { 
  276.          
  277.         // 检查ip地址文件是否正常 
  278.         if(ipFile == null)  
  279.             return Message.bad_ip_file; 
  280.         // 保存ip,转换ip字节数组为字符串形式 
  281.         String ipStr = Util.getIpStringFromBytes(ip); 
  282.         // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件 
  283.         if(ipCache.containsKey(ipStr)) { 
  284.             IPLocation ipLoc = ipCache.get(ipStr); 
  285.             return ipLoc.getCountry(); 
  286.         } else
  287.             IPLocation ipLoc = getIPLocation(ip); 
  288.             ipCache.put(ipStr, ipLoc.getCopy()); 
  289.             return ipLoc.getCountry(); 
  290.         } 
  291.     } 
  292.  
  293.     /**
  294.      *
  295.      * 功能描述:国家名字符串
  296.      *
  297.      * @author  Administrator
  298.      * <p>创建日期 :2012-3-14 上午9:58:32</p>
  299.      *
  300.      * @param ip
  301.      *          IP的字符串形式
  302.      * @return
  303.      *          国家名字符串
  304.      *
  305.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  306.      */ 
  307.     public String getCountry(String ip) { 
  308.          
  309.         return getCountry(Util.getIpByteArrayFromString(ip)); 
  310.     } 
  311.      
  312.     /**
  313.      *
  314.      * 功能描述:国家名字符串
  315.      *
  316.      * @author  Administrator
  317.      * <p>创建日期 :2012-3-14 上午9:59:11</p>
  318.      *
  319.      * @param ip
  320.      *          ip的字节数组形式
  321.      * @return
  322.      *          ip的字节数组形式
  323.      *
  324.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  325.      */ 
  326.     public String getArea(byte[] ip) { 
  327.         // 检查ip地址文件是否正常 
  328.         if(ipFile == null)  
  329.             return Message.bad_ip_file; 
  330.         // 保存ip,转换ip字节数组为字符串形式 
  331.         String ipStr = Util.getIpStringFromBytes(ip); 
  332.         // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件 
  333.         if(ipCache.containsKey(ipStr)) { 
  334.             IPLocation ipLoc = ipCache.get(ipStr); 
  335.             return ipLoc.getArea(); 
  336.         } else
  337.             IPLocation ipLoc = getIPLocation(ip); 
  338.             ipCache.put(ipStr, ipLoc.getCopy()); 
  339.             return ipLoc.getArea(); 
  340.         } 
  341.     } 
  342.      
  343.     /**
  344.      *
  345.      * 功能描述:根据IP得到地区名
  346.      *
  347.      * @author  Administrator
  348.      * <p>创建日期 :2012-3-14 上午9:59:46</p>
  349.      *
  350.      * @param ip
  351.      *          ip IP的字符串形式
  352.      * @return
  353.      *          地区名字符串
  354.      *
  355.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  356.      */ 
  357.     public String getArea(String ip) { 
  358.          
  359.         return getArea(Util.getIpByteArrayFromString(ip)); 
  360.     } 
  361.      
  362.     /**
  363.      * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到
  364.      * @param ip 要查询的IP
  365.      * @return IPLocation结构
  366.      */ 
  367.     private IPLocation getIPLocation(byte[] ip) { 
  368.         IPLocation info = null
  369.         long offset = locateIP(ip); 
  370.         if(offset != -1
  371.             info = getIPLocation(offset); 
  372.         if(info == null) { 
  373.             info = new IPLocation(); 
  374.             info.setCountry (  Message.unknown_country); 
  375.             info.setArea(Message.unknown_area); 
  376.         } 
  377.         return info; 
  378.     }    
  379.  
  380.     /**
  381.      * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法
  382.      * 用了这么一个函数来做转换
  383.      * @param offset
  384.      * @return 读取的long值,返回-1表示读取文件失败
  385.      */ 
  386.     private long readLong4(long offset) { 
  387.          
  388.         long ret = 0
  389.         try
  390.             ipFile.seek(offset); 
  391.             ret |= (ipFile.readByte() & 0xFF); 
  392.             ret |= ((ipFile.readByte() << 8) & 0xFF00); 
  393.             ret |= ((ipFile.readByte() << 16) & 0xFF0000); 
  394.             ret |= ((ipFile.readByte() << 24) & 0xFF000000); 
  395.             return ret; 
  396.         } catch (IOException e) { 
  397.             return -1
  398.         } 
  399.     } 
  400.  
  401.     /**
  402.      * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法
  403.      * 用了这么一个函数来做转换
  404.      * @param offset 整数的起始偏移
  405.      * @return 读取的long值,返回-1表示读取文件失败
  406.      */ 
  407.     private long readLong3(long offset) { 
  408.         long ret = 0
  409.         try
  410.             ipFile.seek(offset); 
  411.             ipFile.readFully(b3); 
  412.             ret |= (b3[0] & 0xFF); 
  413.             ret |= ((b3[1] << 8) & 0xFF00); 
  414.             ret |= ((b3[2] << 16) & 0xFF0000); 
  415.             return ret; 
  416.         } catch (IOException e) { 
  417.             return -1
  418.         } 
  419.     }    
  420.      
  421.     /**
  422.      * 从当前位置读取3个字节转换成long
  423.      * @return 读取的long值,返回-1表示读取文件失败
  424.      */ 
  425.     private long readLong3() { 
  426.         long ret = 0
  427.         try
  428.             ipFile.readFully(b3); 
  429.             ret |= (b3[0] & 0xFF); 
  430.             ret |= ((b3[1] << 8) & 0xFF00); 
  431.             ret |= ((b3[2] << 16) & 0xFF0000); 
  432.             return ret; 
  433.         } catch (IOException e) { 
  434.             return -1
  435.         } 
  436.     } 
  437.    
  438.     /**
  439.      * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
  440.      * 文件中是little-endian形式,将会进行转换
  441.      * @param offset
  442.      * @param ip
  443.      */ 
  444.     private void readIP(long offset, byte[] ip) { 
  445.         try
  446.             ipFile.seek(offset); 
  447.             ipFile.readFully(ip); 
  448.             byte temp = ip[0]; 
  449.             ip[0] = ip[3]; 
  450.             ip[3] = temp; 
  451.             temp = ip[1]; 
  452.             ip[1] = ip[2]; 
  453.             ip[2] = temp; 
  454.         } catch (IOException e) { 
  455.             LogFactory.log("",Level.ERROR,e); 
  456.         } 
  457.     } 
  458.      
  459.     /**
  460.      * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
  461.      * 文件中是little-endian形式,将会进行转换
  462.      * @param offset
  463.      * @param ip
  464.      */ 
  465.     private void readIP(int offset, byte[] ip) { 
  466.         mbb.position(offset); 
  467.         mbb.get(ip); 
  468.         byte temp = ip[0]; 
  469.         ip[0] = ip[3]; 
  470.         ip[3] = temp; 
  471.         temp = ip[1]; 
  472.         ip[1] = ip[2]; 
  473.         ip[2] = temp; 
  474.     } 
  475.      
  476.     /**
  477.      * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的
  478.      * @param ip 要查询的IP
  479.      * @param beginIp 和被查询IP相比较的IP
  480.      * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。
  481.      */ 
  482.     private int compareIP(byte[] ip, byte[] beginIp) { 
  483.         for(int i = 0; i < 4; i++) { 
  484.             int r = compareByte(ip[i], beginIp[i]); 
  485.             if(r != 0
  486.                 return r; 
  487.         } 
  488.         return 0
  489.     } 
  490.      
  491.     /**
  492.      * 把两个byte当作无符号数进行比较
  493.      * @param b1
  494.      * @param b2
  495.      * @return 若b1大于b2则返回1,相等返回0,小于返回-1
  496.      */ 
  497.     private int compareByte(byte b1, byte b2) { 
  498.         if((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于 
  499.             return 1
  500.         else if((b1 ^ b2) == 0)// 判断是否相等 
  501.             return 0
  502.         else  
  503.             return -1
  504.     } 
  505.      
  506.     /**
  507.      * 这个方法将根据ip的内容,定位到包含这个ip国家地区的记录处,返回一个绝对偏移
  508.      * 方法使用二分法查找。
  509.      * @param ip 要查询的IP
  510.      * @return 如果找到了,返回结束IP的偏移,如果没有找到,返回-1
  511.      */ 
  512.     private long locateIP(byte[] ip) { 
  513.         long m = 0
  514.         int r; 
  515.         // 比较第一个ip项 
  516.         readIP(ipBegin, b4); 
  517.         r = compareIP(ip, b4); 
  518.         if(r == 0) return ipBegin; 
  519.         else if(r < 0) return -1
  520.         // 开始二分搜索 
  521.         for(long i = ipBegin, j = ipEnd; i < j; ) { 
  522.             m = getMiddleOffset(i, j); 
  523.             readIP(m, b4); 
  524.             r = compareIP(ip, b4); 
  525.             // log.debug(Utils.getIpStringFromBytes(b)); 
  526.             if(r > 0
  527.                 i = m; 
  528.             else if(r < 0) { 
  529.                 if(m == j) { 
  530.                     j -= IP_RECORD_LENGTH; 
  531.                     m = j; 
  532.                 } else  
  533.                     j = m; 
  534.             } else 
  535.                 return readLong3(m + 4); 
  536.         } 
  537.         // 如果循环结束了,那么i和j必定是相等的,这个记录为最可能的记录,但是并非 
  538.         //     肯定就是,还要检查一下,如果是,就返回结束地址区的绝对偏移 
  539.         m = readLong3(m + 4); 
  540.         readIP(m, b4); 
  541.         r = compareIP(ip, b4); 
  542.         if(r <= 0) return m; 
  543.         else return -1
  544.     } 
  545.      
  546.     /**
  547.      * 得到begin偏移和end偏移中间位置记录的偏移
  548.      * @param begin
  549.      * @param end
  550.      * @return
  551.      */ 
  552.     private long getMiddleOffset(long begin, long end) { 
  553.         long records = (end - begin) / IP_RECORD_LENGTH; 
  554.         records >>= 1
  555.         if(records == 0) records = 1
  556.         return begin + records * IP_RECORD_LENGTH; 
  557.     } 
  558.      
  559.     /**
  560.      * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构
  561.      * @param offset 国家记录的起始偏移
  562.      * @return IPLocation对象
  563.      */ 
  564.     private IPLocation getIPLocation(long offset) { 
  565.         try
  566.             // 跳过4字节ip 
  567.             ipFile.seek(offset + 4); 
  568.             // 读取第一个字节判断是否标志字节 
  569.             byte b = ipFile.readByte(); 
  570.             if(b == REDIRECT_MODE_1) { 
  571.                 // 读取国家偏移 
  572.                 long countryOffset = readLong3(); 
  573.                 // 跳转至偏移处 
  574.                 ipFile.seek(countryOffset); 
  575.                 // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向 
  576.                 b = ipFile.readByte(); 
  577.                 if(b == REDIRECT_MODE_2) { 
  578.                     loc.setCountry (  readString(readLong3())); 
  579.                     ipFile.seek(countryOffset + 4); 
  580.                 } else 
  581.                     loc.setCountry ( readString(countryOffset)); 
  582.                 // 读取地区标志 
  583.                 loc.setArea( readArea(ipFile.getFilePointer())); 
  584.             } else if(b == REDIRECT_MODE_2) { 
  585.                 loc.setCountry ( readString(readLong3())); 
  586.                 loc.setArea( readArea(offset + 8)); 
  587.             } else
  588.                 loc.setCountry (  readString(ipFile.getFilePointer() - 1)); 
  589.                 loc.setArea( readArea(ipFile.getFilePointer())); 
  590.             } 
  591.             return loc; 
  592.         } catch (IOException e) { 
  593.             return null
  594.         } 
  595.     }    
  596.      
  597.     /**
  598.      * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构,此方法应用与内存映射文件方式
  599.      * @param offset 国家记录的起始偏移
  600.      * @return IPLocation对象
  601.      */ 
  602.     private IPLocation getIPLocation(int offset) { 
  603.         // 跳过4字节ip 
  604.         mbb.position(offset + 4); 
  605.         // 读取第一个字节判断是否标志字节 
  606.         byte b = mbb.get(); 
  607.         if(b == REDIRECT_MODE_1) { 
  608.             // 读取国家偏移 
  609.             int countryOffset = readInt3(); 
  610.             // 跳转至偏移处 
  611.             mbb.position(countryOffset); 
  612.             // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向 
  613.             b = mbb.get(); 
  614.             if(b == REDIRECT_MODE_2) { 
  615.                 loc.setCountry (  readString(readInt3())); 
  616.                 mbb.position(countryOffset + 4); 
  617.             } else 
  618.                 loc.setCountry (  readString(countryOffset)); 
  619.             // 读取地区标志 
  620.             loc.setArea(readArea(mbb.position())); 
  621.         } else if(b == REDIRECT_MODE_2) { 
  622.             loc.setCountry ( readString(readInt3())); 
  623.             loc.setArea(readArea(offset + 8)); 
  624.         } else
  625.             loc.setCountry (  readString(mbb.position() - 1)); 
  626.             loc.setArea(readArea(mbb.position())); 
  627.         } 
  628.         return loc; 
  629.     } 
  630.      
  631.     /**
  632.      * 从offset偏移开始解析后面的字节,读出一个地区名
  633.      * @param offset 地区记录的起始偏移
  634.      * @return 地区名字符串
  635.      * @throws IOException
  636.      */ 
  637.     private String readArea(long offset) throws IOException { 
  638.         ipFile.seek(offset); 
  639.         byte b = ipFile.readByte(); 
  640.         if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) { 
  641.             long areaOffset = readLong3(offset + 1); 
  642.             if(areaOffset == 0
  643.                 return Message.unknown_area; 
  644.             else 
  645.                 return readString(areaOffset); 
  646.         } else 
  647.             return readString(offset); 
  648.     } 
  649.      
  650.     /**
  651.      * @param offset 地区记录的起始偏移
  652.      * @return 地区名字符串
  653.      */ 
  654.     private String readArea(int offset) { 
  655.         mbb.position(offset); 
  656.         byte b = mbb.get(); 
  657.         if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) { 
  658.             int areaOffset = readInt3(); 
  659.             if(areaOffset == 0
  660.                 return Message.unknown_area; 
  661.             else 
  662.                 return readString(areaOffset); 
  663.         } else 
  664.             return readString(offset); 
  665.     } 
  666.      
  667.     /**
  668.      * 从offset偏移处读取一个以0结束的字符串
  669.      * @param offset 字符串起始偏移
  670.      * @return 读取的字符串,出错返回空字符串
  671.      */ 
  672.     private String readString(long offset) { 
  673.         try
  674.             ipFile.seek(offset); 
  675.             int i; 
  676.             for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte()); 
  677.             if(i != 0)  
  678.                 return Util.getString(buf, 0, i, "GBK"); 
  679.         } catch (IOException e) {            
  680.             LogFactory.log("",Level.ERROR,e); 
  681.         } 
  682.         return ""
  683.     } 
  684.      
  685.     /**
  686.      * 从内存映射文件的offset位置得到一个0结尾字符串
  687.      * @param offset 字符串起始偏移
  688.      * @return 读取的字符串,出错返回空字符串
  689.      */ 
  690.     private String readString(int offset) { 
  691.         try
  692.             mbb.position(offset); 
  693.             int i; 
  694.             for(i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get()); 
  695.             if(i != 0)  
  696.                 return Util.getString(buf, 0, i, "GBK");        
  697.         } catch (IllegalArgumentException e) { 
  698.             LogFactory.log("",Level.ERROR,e); 
  699.         } 
  700.         return "";    
  701.     } 
package com.lzb.ip;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
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 org.apache.log4j.Level;

import com.lzb.io.ReadProUtil;
/**
 * 
 * 功能描述: 用来读取QQwry.dat文件,确定Ip
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class IPSeeker {
	
	//纯真IP数据库名
	private String IP_FILE = "QQWry.Dat";
	//保存的文件夹
	private String INSTALL_DIR = null;	
	
	// 一些固定常量,比如记录长度等等
	private static final int IP_RECORD_LENGTH = 7;
	private static final byte REDIRECT_MODE_1 = 0x01;
	private static final byte REDIRECT_MODE_2 = 0x02;
	
	// 用来做为cache,查询一个ip时首先查看cache,以减少不必要的重复查找
	private Map<String, IPLocation> ipCache;
	// 随机文件访问类
	private RandomAccessFile ipFile;

	// 内存映射文件
	private MappedByteBuffer mbb;
	// 起始地区的开始和结束的绝对偏移
	private long ipBegin, ipEnd;
	// 为提高效率而采用的临时变量
	private IPLocation loc;
	private byte[] buf;
	private byte[] b4;
	private byte[] b3;
	
	/**
	 * 
	 * 构造函数:初始化
	 *
	 */
	public IPSeeker()  {
		
		// 获取文件的路径和名称
		StringBuffer sb = new StringBuffer();
		URL url = IPSeeker.class.getResource("");
		sb.append(url.getPath() + this.IP_FILE);		
		this.IP_FILE = sb.toString();;
		ipCache = new HashMap<String, IPLocation>();
		loc = new IPLocation();
		buf = new byte[100];
		b4 = new byte[4];
		b3 = new byte[3];
		
		try {
			
			ipFile = new RandomAccessFile(IP_FILE, "r");
			
		} catch (FileNotFoundException e) {
			// 如果找不到这个文件,再尝试再当前目录下搜索,这次全部改用小写文件名
			// 因为有些系统可能区分大小写导致找不到ip地址信息文件
			String filename = new File(IP_FILE).getName().toLowerCase();
			File[] files = new File(INSTALL_DIR).listFiles();
			for(int i = 0; i < files.length; i++) {
				if(files[i].isFile()) {
					if(files[i].getName().toLowerCase().equals(filename)) {
						try {
							ipFile = new RandomAccessFile(files[i], "r");
						} catch (FileNotFoundException e1) {
							LogFactory.log("IP地址信息文件没有找到,IP显示功能将无法使用",Level.ERROR,e1);
							ipFile = null;
						}
						break;
					}
				}
			}
		} 
		// 如果打开文件成功,读取文件头信息
		if(ipFile != null) {
			try {
				ipBegin = readLong4(0);
				ipEnd = readLong4(4);
				if(ipBegin == -1 || ipEnd == -1) {
					ipFile.close();
					ipFile = null;
				}			
			} catch (IOException e) {
				LogFactory.log("IP地址信息文件格式有错误,IP显示功能将无法使用",Level.ERROR,e);
				ipFile = null;
			}			
		}
	}
	
	/**
	 * 
	 * 功能描述:给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:53:22</p>
	 *
	 * @param s
	 * 			地点子串
	 * @return
	 * 			包含IPEntry类型的List
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public List getIPEntriesDebug(String s) {
		
	    List<IPEntry> ret = new ArrayList<IPEntry>();
	    long endOffset = ipEnd + 4;
	    for(long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) {
	        // 读取结束IP偏移
	        long temp = readLong3(offset);
	        // 如果temp不等于-1,读取IP的地点信息
	        if(temp != -1) {
	            IPLocation ipLoc = getIPLocation(temp);
	            // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续
	            if(ipLoc.getCountry().indexOf(s) != -1 || ipLoc.getArea().indexOf(s) != -1) {
	                IPEntry entry = new IPEntry();
	                entry.country = ipLoc.getCountry();
	                entry.area = ipLoc.getArea();
	                // 得到起始IP
	    	        readIP(offset - 4, b4);
	                entry.beginIp = Util.getIpStringFromBytes(b4);
	                // 得到结束IP
	                readIP(temp, b4);
	                entry.endIp = Util.getIpStringFromBytes(b4);
	                // 添加该记录
	                ret.add(entry);
	            }
	        }
	    }
	    return ret;
	}
	
	/**
	 * 
	 * 功能描述:获取Ip所在地区信息
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:54:15</p>
	 *
	 * @param ip
	 * 			IP地址
	 * @return
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public IPLocation getIPLocation(String ip) {
		
		IPLocation location = new IPLocation();
		location.setArea(this.getArea(ip));
		location.setCountry(this.getCountry(ip));

		return location;
	}
	
	/**
	 * 
	 * 功能描述:给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:55:22</p>
	 *
	 * @param s
	 * 			地点子串
	 * @return
	 * 			包含IPEntry类型的List
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public List<IPEntry> getIPEntries(String s) {
		
	    List<IPEntry> ret = new ArrayList<IPEntry>();
	    try {
	        // 映射IP信息文件到内存中
	        if(mbb == null) {
			    FileChannel fc = ipFile.getChannel();
	            mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length());
	            mbb.order(ByteOrder.LITTLE_ENDIAN);	            
	        }
            
		    int endOffset = (int)ipEnd;
            for(int offset = (int)ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) {
                int temp = readInt3(offset);
                if(temp != -1) {
    	            IPLocation ipLoc = getIPLocation(temp);
    	            // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续
    	            if(ipLoc.getCountry().indexOf(s) != -1 || ipLoc.getArea().indexOf(s) != -1) {
    	                IPEntry entry = new IPEntry();
    	                entry.country = ipLoc.getCountry();
    	                entry.area = ipLoc.getArea();
    	                // 得到起始IP
    	    	        readIP(offset - 4, b4);
    	                entry.beginIp = Util.getIpStringFromBytes(b4);
    	                // 得到结束IP
    	                readIP(temp, b4);
    	                entry.endIp = Util.getIpStringFromBytes(b4);
    	                // 添加该记录
    	                ret.add(entry);
    	            }
                }
            }           
        } catch (IOException e) {
            LogFactory.log("",Level.ERROR,e);
        }
        return ret;
	}

	/**
	 * 
	 * 功能描述:从内存映射文件的offset位置开始的3个字节读取一个int
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:57:26</p>
	 *
	 * @param offset
	 * @return
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	private int readInt3(int offset) {
		
	    mbb.position(offset);
	    return mbb.getInt() & 0x00FFFFFF;
	}

	/**
	 * 
	 * 功能描述: 从内存映射文件的当前位置开始的3个字节读取一个int
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:57:47</p>
	 *
	 * @return
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	private int readInt3() {
		
	    return mbb.getInt() & 0x00FFFFFF;
	}
	
	/**
	 * 
	 * 功能描述:根据IP得到国家名
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:58:01</p>
	 *
	 * @param ip
	 * 			ip的字节数组形式
	 * @return
	 * 			国家名字符串
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public String getCountry(byte[] ip) {
		
		// 检查ip地址文件是否正常
		if(ipFile == null) 
			return Message.bad_ip_file;
		// 保存ip,转换ip字节数组为字符串形式
		String ipStr = Util.getIpStringFromBytes(ip);
		// 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件
		if(ipCache.containsKey(ipStr)) {
			IPLocation ipLoc = ipCache.get(ipStr);
			return ipLoc.getCountry();
		} else {
			IPLocation ipLoc = getIPLocation(ip);
			ipCache.put(ipStr, ipLoc.getCopy());
			return ipLoc.getCountry();
		}
	}

	/**
	 * 
	 * 功能描述:国家名字符串
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:58:32</p>
	 *
	 * @param ip
	 * 			IP的字符串形式
	 * @return
	 * 			国家名字符串
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public String getCountry(String ip) {
		
	    return getCountry(Util.getIpByteArrayFromString(ip));
	}
	
	/**
	 * 
	 * 功能描述:国家名字符串
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:59:11</p>
	 *
	 * @param ip
	 * 			ip的字节数组形式
	 * @return
	 * 			ip的字节数组形式
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public String getArea(byte[] ip) {
		// 检查ip地址文件是否正常
		if(ipFile == null) 
			return Message.bad_ip_file;
		// 保存ip,转换ip字节数组为字符串形式
		String ipStr = Util.getIpStringFromBytes(ip);
		// 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件
		if(ipCache.containsKey(ipStr)) {
			IPLocation ipLoc = ipCache.get(ipStr);
			return ipLoc.getArea();
		} else {
			IPLocation ipLoc = getIPLocation(ip);
			ipCache.put(ipStr, ipLoc.getCopy());
			return ipLoc.getArea();
		}
	}
	
	/**
	 * 
	 * 功能描述:根据IP得到地区名
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午9:59:46</p>
	 *
	 * @param ip
	 * 			ip IP的字符串形式
	 * @return
	 * 			地区名字符串
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public String getArea(String ip) {
		
	    return getArea(Util.getIpByteArrayFromString(ip));
	}
	
	/**
	 * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到
	 * @param ip 要查询的IP
	 * @return IPLocation结构
	 */
	private IPLocation getIPLocation(byte[] ip) {
		IPLocation info = null;
		long offset = locateIP(ip);
		if(offset != -1)
			info = getIPLocation(offset);
		if(info == null) {
			info = new IPLocation();
			info.setCountry (  Message.unknown_country);
			info.setArea(Message.unknown_area);
		}
		return info;
	}	

	/**
	 * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法
	 * 用了这么一个函数来做转换
	 * @param offset
	 * @return 读取的long值,返回-1表示读取文件失败
	 */
	private long readLong4(long offset) {
		
		long ret = 0;
		try {
			ipFile.seek(offset);
			ret |= (ipFile.readByte() & 0xFF);
			ret |= ((ipFile.readByte() << 8) & 0xFF00);
			ret |= ((ipFile.readByte() << 16) & 0xFF0000);
			ret |= ((ipFile.readByte() << 24) & 0xFF000000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}

	/**
	 * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法
	 * 用了这么一个函数来做转换
	 * @param offset 整数的起始偏移
	 * @return 读取的long值,返回-1表示读取文件失败
	 */
	private long readLong3(long offset) {
		long ret = 0;
		try {
			ipFile.seek(offset);
			ipFile.readFully(b3);
			ret |= (b3[0] & 0xFF);
			ret |= ((b3[1] << 8) & 0xFF00);
			ret |= ((b3[2] << 16) & 0xFF0000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}	
	
	/**
	 * 从当前位置读取3个字节转换成long
	 * @return 读取的long值,返回-1表示读取文件失败
	 */
	private long readLong3() {
		long ret = 0;
		try {
			ipFile.readFully(b3);
			ret |= (b3[0] & 0xFF);
			ret |= ((b3[1] << 8) & 0xFF00);
			ret |= ((b3[2] << 16) & 0xFF0000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}
  
	/**
	 * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
	 * 文件中是little-endian形式,将会进行转换
	 * @param offset
	 * @param ip
	 */
	private void readIP(long offset, byte[] ip) {
		try {
			ipFile.seek(offset);
			ipFile.readFully(ip);
			byte temp = ip[0];
			ip[0] = ip[3];
			ip[3] = temp;
			temp = ip[1];
			ip[1] = ip[2];
			ip[2] = temp;
		} catch (IOException e) {
		    LogFactory.log("",Level.ERROR,e);
		}
	}
	
	/**
	 * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
	 * 文件中是little-endian形式,将会进行转换
	 * @param offset
	 * @param ip
	 */
	private void readIP(int offset, byte[] ip) {
	    mbb.position(offset);
	    mbb.get(ip);
		byte temp = ip[0];
		ip[0] = ip[3];
		ip[3] = temp;
		temp = ip[1];
		ip[1] = ip[2];
		ip[2] = temp;
	}
	
	/**
	 * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的
	 * @param ip 要查询的IP
	 * @param beginIp 和被查询IP相比较的IP
	 * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。
	 */
	private int compareIP(byte[] ip, byte[] beginIp) {
		for(int i = 0; i < 4; i++) {
			int r = compareByte(ip[i], beginIp[i]);
			if(r != 0)
				return r;
		}
		return 0;
	}
	
	/**
	 * 把两个byte当作无符号数进行比较
	 * @param b1
	 * @param b2
	 * @return 若b1大于b2则返回1,相等返回0,小于返回-1
	 */
	private int compareByte(byte b1, byte b2) {
		if((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于
			return 1;
		else if((b1 ^ b2) == 0)// 判断是否相等
			return 0;
		else 
			return -1;
	}
	
	/**
	 * 这个方法将根据ip的内容,定位到包含这个ip国家地区的记录处,返回一个绝对偏移
	 * 方法使用二分法查找。
	 * @param ip 要查询的IP
	 * @return 如果找到了,返回结束IP的偏移,如果没有找到,返回-1
	 */
	private long locateIP(byte[] ip) {
		long m = 0;
		int r;
		// 比较第一个ip项
		readIP(ipBegin, b4);
		r = compareIP(ip, b4);
		if(r == 0) return ipBegin;
		else if(r < 0) return -1;
		// 开始二分搜索
		for(long i = ipBegin, j = ipEnd; i < j; ) {
			m = getMiddleOffset(i, j);
			readIP(m, b4);
			r = compareIP(ip, b4);
			// log.debug(Utils.getIpStringFromBytes(b));
			if(r > 0)
				i = m;
			else if(r < 0) {
				if(m == j) {
					j -= IP_RECORD_LENGTH;
					m = j;
				} else 
					j = m;
			} else
				return readLong3(m + 4);
		}
		// 如果循环结束了,那么i和j必定是相等的,这个记录为最可能的记录,但是并非
		//     肯定就是,还要检查一下,如果是,就返回结束地址区的绝对偏移
		m = readLong3(m + 4);
		readIP(m, b4);
		r = compareIP(ip, b4);
		if(r <= 0) return m;
		else return -1;
	}
	
	/**
	 * 得到begin偏移和end偏移中间位置记录的偏移
	 * @param begin
	 * @param end
	 * @return
	 */
	private long getMiddleOffset(long begin, long end) {
		long records = (end - begin) / IP_RECORD_LENGTH;
		records >>= 1;
		if(records == 0) records = 1;
		return begin + records * IP_RECORD_LENGTH;
	}
	
	/**
	 * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构
	 * @param offset 国家记录的起始偏移
	 * @return IPLocation对象
	 */
	private IPLocation getIPLocation(long offset) {
		try {
			// 跳过4字节ip
			ipFile.seek(offset + 4);
			// 读取第一个字节判断是否标志字节
			byte b = ipFile.readByte();
			if(b == REDIRECT_MODE_1) {
				// 读取国家偏移
				long countryOffset = readLong3();
				// 跳转至偏移处
				ipFile.seek(countryOffset);
				// 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向
				b = ipFile.readByte();
				if(b == REDIRECT_MODE_2) {
					loc.setCountry (  readString(readLong3()));
					ipFile.seek(countryOffset + 4);
				} else
					loc.setCountry ( readString(countryOffset));
				// 读取地区标志
				loc.setArea( readArea(ipFile.getFilePointer()));
			} else if(b == REDIRECT_MODE_2) {
				loc.setCountry ( readString(readLong3()));
				loc.setArea( readArea(offset + 8));
			} else {
				loc.setCountry (  readString(ipFile.getFilePointer() - 1));
				loc.setArea( readArea(ipFile.getFilePointer()));
			}
			return loc;
		} catch (IOException e) {
			return null;
		}
	}	
	
	/**
	 * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构,此方法应用与内存映射文件方式
	 * @param offset 国家记录的起始偏移
	 * @return IPLocation对象
	 */
	private IPLocation getIPLocation(int offset) {
		// 跳过4字节ip
	    mbb.position(offset + 4);
		// 读取第一个字节判断是否标志字节
		byte b = mbb.get();
		if(b == REDIRECT_MODE_1) {
			// 读取国家偏移
			int countryOffset = readInt3();
			// 跳转至偏移处
			mbb.position(countryOffset);
			// 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向
			b = mbb.get();
			if(b == REDIRECT_MODE_2) {
				loc.setCountry (  readString(readInt3()));
				mbb.position(countryOffset + 4);
			} else
				loc.setCountry (  readString(countryOffset));
			// 读取地区标志
			loc.setArea(readArea(mbb.position()));
		} else if(b == REDIRECT_MODE_2) {
			loc.setCountry ( readString(readInt3()));
			loc.setArea(readArea(offset + 8));
		} else {
			loc.setCountry (  readString(mbb.position() - 1));
			loc.setArea(readArea(mbb.position()));
		}
		return loc;
	}
	
	/**
	 * 从offset偏移开始解析后面的字节,读出一个地区名
	 * @param offset 地区记录的起始偏移
	 * @return 地区名字符串
	 * @throws IOException
	 */
	private String readArea(long offset) throws IOException {
		ipFile.seek(offset);
		byte b = ipFile.readByte();
		if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {
			long areaOffset = readLong3(offset + 1);
			if(areaOffset == 0)
				return Message.unknown_area;
			else
				return readString(areaOffset);
		} else
			return readString(offset);
	}
	
	/**
	 * @param offset 地区记录的起始偏移
	 * @return 地区名字符串
	 */
	private String readArea(int offset) {
		mbb.position(offset);
		byte b = mbb.get();
		if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {
			int areaOffset = readInt3();
			if(areaOffset == 0)
				return Message.unknown_area;
			else
				return readString(areaOffset);
		} else
			return readString(offset);
	}
	
	/**
	 * 从offset偏移处读取一个以0结束的字符串
	 * @param offset 字符串起始偏移
	 * @return 读取的字符串,出错返回空字符串
	 */
	private String readString(long offset) {
		try {
			ipFile.seek(offset);
			int i;
			for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte());
			if(i != 0) 
			    return Util.getString(buf, 0, i, "GBK");
		} catch (IOException e) {			
		    LogFactory.log("",Level.ERROR,e);
		}
		return "";
	}
	
	/**
	 * 从内存映射文件的offset位置得到一个0结尾字符串
	 * @param offset 字符串起始偏移
	 * @return 读取的字符串,出错返回空字符串
	 */
	private String readString(int offset) {
	    try {
			mbb.position(offset);
			int i;
			for(i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get());
			if(i != 0) 
			    return Util.getString(buf, 0, i, "GBK");       
	    } catch (IllegalArgumentException e) {
	        LogFactory.log("",Level.ERROR,e);
	    }
	    return "";	 
	}
}

4. 返回相关的国家,区域信息

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2. /**
  3. *
  4. * 功能描述:用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区
  5. *
  6. * @author  Administrator
  7. *
  8. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  9. */ 
  10. public class IPLocation { 
  11.      
  12.     private String country; 
  13.     private String area; 
  14.      
  15.     /**
  16.      *
  17.      * 构造函数:
  18.      *
  19.      */ 
  20.     public IPLocation() { 
  21.          
  22.         country = ""
  23.         area = ""
  24.     } 
  25.      
  26.     public IPLocation getCopy() { 
  27.          
  28.         IPLocation ret = new IPLocation(); 
  29.         ret.country = country; 
  30.         ret.area = area; 
  31.         return ret; 
  32.     } 
  33.  
  34.     public String getCountry() { 
  35.         return country; 
  36.     } 
  37.  
  38.     public void setCountry(String country) { 
  39.         this.country = country; 
  40.     } 
  41.  
  42.     public String getArea() { 
  43.         return area; 
  44.     } 
  45.  
  46.     public void setArea(String area) { 
  47.          
  48.         //如果为局域网,纯真IP地址库的地区会显示CZ88.NET,这里把它去掉 
  49.         if(area.trim().equals("CZ88.NET")){ 
  50.             this.area="本机或本网络"
  51.         }else
  52.             this.area = area; 
  53.         } 
  54.     } 
package com.lzb.ip;
/**
 * 
 * 功能描述:用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class IPLocation {
	
	private String country;
	private String area;
	
	/**
	 * 
	 * 构造函数:
	 *
	 */
	public IPLocation() {
		
	    country = "";
	    area = "";
	}
	
	public IPLocation getCopy() {
		
	    IPLocation ret = new IPLocation();
	    ret.country = country;
	    ret.area = area;
	    return ret;
	}

	public 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;
		}
	}
}

5. 日志工厂

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2.  
  3. import org.apache.log4j.Level; 
  4. import org.apache.log4j.Logger; 
  5. /**
  6. *
  7. * 功能描述:日志工厂
  8. *
  9. * @author  Administrator
  10. *
  11. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  12. */ 
  13. public class LogFactory { 
  14.      
  15.     private static final Logger logger; 
  16.      
  17.     static
  18.         logger = Logger.getLogger("stdout"); 
  19.         logger.setLevel(Level.DEBUG); 
  20.     } 
  21.  
  22.     public static void log(String info, Level level, Throwable ex) { 
  23.         logger.log(level, info, ex); 
  24.     } 
  25.      
  26.     public static Level  getLogLevel(){ 
  27.         return logger.getLevel(); 
  28.     } 
  29.  
package com.lzb.ip;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
/**
 * 
 * 功能描述:日志工厂
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class LogFactory {
	
	private static final Logger logger;
	
	static {
		logger = Logger.getLogger("stdout");
		logger.setLevel(Level.DEBUG);
	}

	public static void log(String info, Level level, Throwable ex) {
		logger.log(level, info, ex);
	}
	
	public static Level  getLogLevel(){
		return logger.getLevel();
	}

}

6. 提示的日志信息

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2. /**
  3. *
  4. * 功能描述:提示的信息
  5. *
  6. * @author  Administrator
  7. *
  8. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  9. */ 
  10. public interface Message { 
  11.      
  12.     static String bad_ip_file = "IP地址库文件错误"
  13.     static String unknown_country = "未知国家"
  14.     static String unknown_area = "未知地区"
  15.      
package com.lzb.ip;
/**
 * 
 * 功能描述:提示的信息
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public interface Message {
	
	static String bad_ip_file = "IP地址库文件错误";
	static String unknown_country = "未知国家";
	static String unknown_area = "未知地区";
	
}

7. 转换工具类,字符串,数组

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2.  
  3. import java.io.UnsupportedEncodingException; 
  4. import java.util.StringTokenizer; 
  5. import org.apache.log4j.Level; 
  6. /**
  7. *
  8. * 功能描述:转换工具类
  9. *
  10. * @author  Administrator
  11. *
  12. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  13. */ 
  14. public class Util { 
  15.      
  16.     private static StringBuilder sb = new StringBuilder(); 
  17.  
  18.     /**
  19.      *
  20.      * 功能描述:从ip的字符串形式得到字节数组形式
  21.      *
  22.      * @author  Administrator
  23.      * <p>创建日期 :2012-3-14 上午10:04:02</p>
  24.      *
  25.      * @param ip
  26.      *          字符串形式的ip
  27.      * @return
  28.      *          字节数组形式的ip
  29.      *
  30.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  31.      */ 
  32.     public static byte[] getIpByteArrayFromString(String ip) { 
  33.          
  34.         byte[] ret = new byte[4]; 
  35.         StringTokenizer st = new StringTokenizer(ip, "."); 
  36.         try
  37.             ret[0] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF); 
  38.             ret[1] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF); 
  39.             ret[2] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF); 
  40.             ret[3] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF); 
  41.         } catch (Exception e) { 
  42.           LogFactory.log("从ip的字符串形式得到字节数组形式报错", Level.ERROR, e); 
  43.         } 
  44.         return ret; 
  45.     } 
  46.      
  47.     /**
  48.      *
  49.      * 功能描述 IP数组转换成字符串
  50.      *
  51.      * @author  Administrator
  52.      * <p>创建日期 :2012-3-14 上午10:04:51</p>
  53.      *
  54.      * @param ip
  55.      *          ip的字节数组形式
  56.      * @return
  57.      *          字符串形式的ip
  58.      *
  59.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  60.      */ 
  61.     public static String getIpStringFromBytes(byte[] ip) { 
  62.          
  63.         sb.delete(0, sb.length()); 
  64.         sb.append(ip[0] & 0xFF); 
  65.         sb.append('.');      
  66.         sb.append(ip[1] & 0xFF); 
  67.         sb.append('.');      
  68.         sb.append(ip[2] & 0xFF); 
  69.         sb.append('.');      
  70.         sb.append(ip[3] & 0xFF); 
  71.         return sb.toString(); 
  72.     } 
  73.      
  74.     /**
  75.      *
  76.      * 功能描述:根据某种编码方式将字节数组转换成字符串
  77.      *
  78.      * @author  Administrator
  79.      * <p>创建日期 :2012-3-14 上午10:05:50</p>
  80.      *
  81.      * @param b
  82.      *          字节数组
  83.      * @param offset
  84.      *          要转换的起始位置
  85.      * @param len
  86.      *          要转换的长度
  87.      * @param encoding
  88.      *          编码方式
  89.      * @return
  90.      *          如果encoding不支持,返回一个缺省编码的字符串
  91.      *
  92.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  93.      */ 
  94.     public static String getString(byte[] b, int offset, int len, String encoding) { 
  95.          
  96.         try
  97.             return new String(b, offset, len, encoding); 
  98.         } catch (UnsupportedEncodingException e) { 
  99.             return new String(b, offset, len); 
  100.         } 
  101.     } 
package com.lzb.ip;

import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;
import org.apache.log4j.Level;
/**
 * 
 * 功能描述:转换工具类
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class Util {
	
	private static StringBuilder sb = new StringBuilder();

	/**
	 * 
	 * 功能描述:从ip的字符串形式得到字节数组形式
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午10:04:02</p>
	 *
	 * @param ip
	 * 			字符串形式的ip
	 * @return
	 * 			字节数组形式的ip
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
    public static byte[] getIpByteArrayFromString(String ip) {
        
    	byte[] ret = new byte[4];
        StringTokenizer st = new StringTokenizer(ip, ".");
        try {
            ret[0] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);
            ret[1] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);
            ret[2] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);
            ret[3] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);
        } catch (Exception e) {
          LogFactory.log("从ip的字符串形式得到字节数组形式报错", Level.ERROR, e);
        }
        return ret;
    }
    
    /**
     * 
     * 功能描述 IP数组转换成字符串
     *
     * @author  Administrator
     * <p>创建日期 :2012-3-14 上午10:04:51</p>
     *
     * @param ip
     * 			ip的字节数组形式
     * @return
     * 			字符串形式的ip
     *
     * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
     */
    public static String getIpStringFromBytes(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();
    }
    
    /**
     * 
     * 功能描述:根据某种编码方式将字节数组转换成字符串
     *
     * @author  Administrator
     * <p>创建日期 :2012-3-14 上午10:05:50</p>
     *
     * @param b
     * 			字节数组
     * @param offset
     * 			要转换的起始位置
     * @param len
     * 			要转换的长度
     * @param encoding
     * 			编码方式
     * @return
     * 			如果encoding不支持,返回一个缺省编码的字符串
     *
     * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
     */
    public static String getString(byte[] b, int offset, int len, String encoding) {
        
    	try {
            return new String(b, offset, len, encoding);
        } catch (UnsupportedEncodingException e) {
            return new String(b, offset, len);
        }
    }
}

8. 验证测试方法(Junit)

Java代码 复制代码 收藏代码
  1. package com.lzb.ip; 
  2.  
  3. import java.net.InetAddress; 
  4. import java.net.NetworkInterface; 
  5. import java.net.SocketException; 
  6. import java.net.UnknownHostException; 
  7. import java.util.Enumeration; 
  8.  
  9. import org.junit.Test; 
  10.  
  11. import junit.framework.TestCase; 
  12. /**
  13. *
  14. * 功能描述:测试
  15. *
  16. * @author  Administrator
  17. *
  18. * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
  19. */ 
  20. public class IPtest extends TestCase { 
  21.      
  22.     /**
  23.      *
  24.      * 功能描述:测试IP归属地
  25.      *
  26.      * @author  Administrator
  27.      * <p>创建日期 :2012-3-14 上午10:28:48</p>
  28.      *
  29.      *
  30.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  31.      */ 
  32.     @Test 
  33.     public void testIp(){ 
  34.          
  35. //      InetAddress addr = null; 
  36. //      try { 
  37. //          addr = InetAddress.getLocalHost(); 
  38. //      } catch (UnknownHostException e) { 
  39. //          e.printStackTrace(); 
  40. //      } 
  41. //      String ipStr = addr.getHostAddress().toString();//获得本机IP 
  42. //      String addressStr = addr.getHostName().toString();//获得本机名称 
  43. //       
  44. //      System.out.println("本机IP地址: " + ipStr + " 本机名称: " + addressStr); 
  45.          
  46.         String ipStr = this.getRealIp(); 
  47.         System.out.println("本机IP地址: " + ipStr); 
  48.          
  49.         //指定纯真数据库的文件名,所在文件夹 
  50.         IPSeeker ip=new IPSeeker(); 
  51.         //测试IP 58.20.43.13 
  52.         System.out.println(ip.getIPLocation(ipStr).getCountry()+":"+ip.getIPLocation(ipStr).getArea()); 
  53.     } 
  54.      
  55.     /**
  56.      *
  57.      * 功能描述:获取本地真实的IP地址
  58.      *
  59.      * @author  Administrator
  60.      * <p>创建日期 :2012-3-14 上午10:23:01</p>
  61.      *
  62.      * @return
  63.      * @throws SocketException
  64.      *
  65.      * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
  66.      */ 
  67.     public static String getRealIp() { 
  68.          
  69.         String localip = null;// 本地IP,如果没有配置外网IP则返回它 
  70.         String netip = null;// 外网IP 
  71.  
  72.         Enumeration<NetworkInterface> netInterfaces = null
  73.         try
  74.             netInterfaces = NetworkInterface.getNetworkInterfaces(); 
  75.         } catch (SocketException e) { 
  76.             e.printStackTrace(); 
  77.         } 
  78.          
  79.         InetAddress ip = null
  80.         boolean finded = false;// 是否找到外网IP 
  81.         while (netInterfaces.hasMoreElements() && !finded) { 
  82.              
  83.             NetworkInterface ni = netInterfaces.nextElement(); 
  84.             Enumeration<InetAddress> address = ni.getInetAddresses(); 
  85.             while (address.hasMoreElements()) { 
  86.                 ip = address.nextElement(); 
  87.                 if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP 
  88.                     netip = ip.getHostAddress(); 
  89.                     finded = true
  90.                     break
  91.                 } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP 
  92.                     localip = ip.getHostAddress(); 
  93.                 } 
  94.             } 
  95.         } 
  96.      
  97.         if (netip != null && !"".equals(netip)) { 
  98.             return netip; 
  99.         } else
  100.             return localip; 
  101.         } 
  102.     } 
package com.lzb.ip;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;

import org.junit.Test;

import junit.framework.TestCase;
/**
 * 
 * 功能描述:测试
 *
 * @author  Administrator
 *
 * <p>修改历史:(修改人,修改时间,修改原因/内容)</p>
 */
public class IPtest extends TestCase {
	
	/**
	 * 
	 * 功能描述:测试IP归属地
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午10:28:48</p>
	 *
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	@Test
	public void testIp(){
		
//		InetAddress addr = null;
//		try {
//			addr = InetAddress.getLocalHost();
//		} catch (UnknownHostException e) {
//			e.printStackTrace();
//		}
//		String ipStr = addr.getHostAddress().toString();//获得本机IP
//		String addressStr = addr.getHostName().toString();//获得本机名称
//		
//		System.out.println("本机IP地址: " + ipStr + " 本机名称: " + addressStr);
		
		String ipStr = this.getRealIp();
		System.out.println("本机IP地址: " + ipStr);
		
		//指定纯真数据库的文件名,所在文件夹
		IPSeeker ip=new IPSeeker();
		//测试IP 58.20.43.13
		System.out.println(ip.getIPLocation(ipStr).getCountry()+":"+ip.getIPLocation(ipStr).getArea());
	}
	
	/**
	 * 
	 * 功能描述:获取本地真实的IP地址
	 *
	 * @author  Administrator
	 * <p>创建日期 :2012-3-14 上午10:23:01</p>
	 *
	 * @return
	 * @throws SocketException
	 *
	 * <p>修改历史 :(修改人,修改时间,修改原因/内容)</p>
	 */
	public static String getRealIp() {
		
		String localip = null;// 本地IP,如果没有配置外网IP则返回它
		String netip = null;// 外网IP

		Enumeration<NetworkInterface> netInterfaces = null;
		try {
			netInterfaces = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			e.printStackTrace();
		}
		
		InetAddress ip = null;
		boolean finded = false;// 是否找到外网IP
		while (netInterfaces.hasMoreElements() && !finded) {
			
			NetworkInterface ni = netInterfaces.nextElement();
			Enumeration<InetAddress> address = ni.getInetAddresses();
			while (address.hasMoreElements()) {
				ip = address.nextElement();
				if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
					netip = ip.getHostAddress();
					finded = true;
					break;
				} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
					localip = ip.getHostAddress();
				}
			}
		}
	
		if (netip != null && !"".equals(netip)) {
			return netip;
		} else {
			return localip;
		}
	}
}

运行结果:

Console代码 复制代码 收藏代码
  1. 本机IP地址: 10.10.1.24 
  2. 局域网:对方和您在同一内部网 

 

注意,需要使用还需要下载QQWry.dat文件,google,百度一下很多

 

http://lizhenbin.iteye.com/blog/1453019

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值