├── README.md ├── ip-locator.iml ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── lic │ │ └── ip │ │ ├── crawler │ │ ├── Crawler.java │ │ ├── IPRange.java │ │ ├── IPv4Network.java │ │ ├── IPv4RadixTree.java │ │ ├── IpData.java │ │ ├── LimitQueue.java │ │ └── LimitRate.java │ │ ├── iplocator │ │ ├── IPLocation.java │ │ └── IPv4RadixIntTree.java │ │ ├── ipseeker │ │ ├── IPEntry.java │ │ └── IPSeeker.java │ │ └── util │ │ ├── CountryUtil.java │ │ ├── HttpClientPool.java │ │ └── IPUtil.java └── resources │ ├── dist.xml │ ├── ip_location.txt │ ├── log4j.properties │ └── qqwry.dat └── test └── java └── org └── lic └── test └── Test1.java /README.md: -------------------------------------------------------------------------------- 1 | ip-locator 2 | ========== 3 | 4 |

5 | 2015-01-15 增加IP爬虫功能,对国内的每一个C段ip,进行扫描,然后对结果进行合并,得到国内ip库 6 |

7 | 8 |

9 | 用于IP定位,包含了纯真IP库(ipseeker),和本地文本IP库两种(iplocator)。
10 | IP库需要经常保持更新才能保证准确度。 11 |

12 |

13 | 本地ip库的数据来源:
14 | 1.从http://ftp.apnic.net/stats/获取最新的delegated-*-latest文件
15 | 2.分配给CN之外的各个IP段,通过简称对应国家名称写入IP库
16 | 3.分配给CN的IP段,通过ip.taobao.com及其他ip网站爬取一遍,细分出每个省份等信息,写入IP库
17 | 4.利用RadixTree把本地文件读入内存
18 |

19 |

20 | 将delegated-*-latest文件转换为本地IP库的程序,参见ipdb_creator 21 |

22 | -------------------------------------------------------------------------------- /ip-locator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.lic.iplocator 8 | ip-locator 9 | 1.0-SNAPSHOT 10 | jar 11 | 12 | 13 | 14 | 15 | src/main/resources 16 | 17 | 18 | *.* 19 | 20 | 21 | 22 | 23 | 24 | 25 | maven-compiler-plugin 26 | 2.3.2 27 | 28 | 1.6 29 | 1.6 30 | UTF-8 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-assembly-plugin 37 | 2.2 38 | 39 | 40 | assemble 41 | package 42 | 43 | single 44 | 45 | false 46 | 47 | 48 | src/main/resources/dist.xml 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | com.alibaba 60 | fastjson 61 | 1.2.3 62 | 63 | 64 | 65 | org.apache.httpcomponents 66 | httpclient 67 | 4.3.4 68 | 69 | 70 | 71 | junit 72 | junit 73 | 4.11 74 | 75 | 76 | 77 | org.slf4j 78 | slf4j-api 79 | 1.7.5 80 | 81 | 82 | 83 | org.slf4j 84 | slf4j-log4j12 85 | 1.7.5 86 | 87 | 88 | 89 | commons-lang 90 | commons-lang 91 | 2.6 92 | 93 | 94 | 95 | log4j 96 | log4j 97 | 1.2.17 98 | 99 | 100 | 101 | com.googlecode.concurrentlinkedhashmap 102 | concurrentlinkedhashmap-lru 103 | 1.4 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/Crawler.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.lic.ip.util.HttpClientPool; 6 | import org.lic.ip.util.IPUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.File; 12 | import java.io.FileReader; 13 | import java.io.IOException; 14 | import java.util.HashMap; 15 | import java.util.LinkedList; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created by lc on 15/1/8. 20 | */ 21 | public class Crawler { 22 | private static final Logger logger = LoggerFactory.getLogger(Crawler.class); 23 | 24 | private static final String TAOBAO_URL = "http://ip.taobao.com/service/getIpInfo.php"; 25 | 26 | private LimitRate limitRate = new LimitRate(1000L, 10); 27 | 28 | private static String cn_delegated = "delegated-apnic-test"; 29 | 30 | private static String CN_OUT_ORIGINAL = "delegated-cn-original"; 31 | 32 | private static String FN_OUT_ORIGINAL = "delegated-fn-original"; 33 | 34 | private static String CN_OUT_MERGED = "delegated-cn-merged"; 35 | 36 | private static String FN_OUT_MERGED = "delegated-fn-merged"; 37 | 38 | private static String IN_PATH = "input/"; 39 | 40 | private static String OUT_PATH = "output/"; 41 | 42 | private static String[] all_delegated = {"delegated-afrinic-latest","delegated-apnic-latest", 43 | "delegated-arin-latest", "delegated-lacnic-latest", "delegated-ripencc-latest"}; 44 | 45 | private static Map countryCode = new HashMap(); 46 | 47 | private static Map> dict = new HashMap>(); 48 | 49 | private static LinkedList availableIPs = new LinkedList(); 50 | 51 | public IPv4RadixTree scanFNIP() { 52 | IPv4RadixTree fnTree = new IPv4RadixTree(); 53 | BufferedReader reader = null; 54 | try { 55 | // load country code 56 | reader = new BufferedReader( 57 | new FileReader(new File(IN_PATH + "country_code"))); 58 | String line; 59 | while ((line = reader.readLine()) != null) { 60 | String[] sp = line.split(" "); 61 | countryCode.put(sp[0].trim(), sp[1].trim()); 62 | } 63 | reader.close(); 64 | 65 | for (String file : all_delegated) { 66 | reader = new BufferedReader( 67 | new FileReader(new File(IN_PATH + file))); 68 | while ((line = reader.readLine()) != null) { 69 | String[] params = line.split("\\|"); 70 | 71 | if (params.length >= 4 72 | && params[2].equals("ipv4") 73 | && !params[3].equals("*") 74 | && !params[1].equals("CN")) { 75 | long startIP = IPUtil.ipString2Long(params[3]); 76 | long endIP = startIP + Integer.parseInt(params[4]); 77 | logger.info(startIP + " " + endIP + " " + Integer 78 | .parseInt(params[4])); 79 | IPRange ipRange = new IPRange(startIP, endIP); 80 | ipRange.prefixlen = IPUtil.getSmallestMasklen(Integer.parseInt(params[4])); 81 | if (params[1].equals("")) { 82 | availableIPs.addAll(IPUtil.iprangeToCidrs(ipRange)); 83 | } else { 84 | if (dict.containsKey(params[1])) { 85 | LinkedList lst = dict.get(params[1]); 86 | lst.addAll(IPUtil.iprangeToCidrs(ipRange)); 87 | dict.put(params[1], lst); 88 | } else { 89 | dict.put(params[1], IPUtil.iprangeToCidrs(ipRange)); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | System.out.println("finish read"); 96 | 97 | for (String key : dict.keySet()) { 98 | IPv4Network net = dict.get(key).getLast(); 99 | String randomIP = IPUtil 100 | .getRandomIp(net.getCIDR().split("/")[0], net.getMasklen()); 101 | for (IPv4Network network : dict.get(key)) { 102 | IpData ipData = new IpData(); 103 | ipData.setNetwork(network.getCIDR()); 104 | ipData.setCountry(countryCode.get(key)); 105 | ipData.setProvince(""); 106 | ipData.setCity(""); 107 | ipData.setIsp(""); 108 | ipData.setIp(randomIP); 109 | ipData.setIpAmount(IPUtil.getAmount(network.getCIDR())); 110 | fnTree.put(network.getCIDR(), ipData); 111 | } 112 | } 113 | 114 | for (IPv4Network network : availableIPs) { 115 | String randomIP = IPUtil.getRandomIp(network.getCIDR().split("/")[0],network.getMasklen()); 116 | IpData ipData = null; 117 | System.out.println("query ip " + network.getCIDR()); 118 | if (ipData == null) { 119 | while (ipData == null) { 120 | try { 121 | ipData = queryFromTaobao(randomIP); 122 | } catch (Exception e) { 123 | logger.error( 124 | "queryFromTaobao exception: " + e 125 | .getMessage(), e); 126 | } 127 | } 128 | } 129 | ipData.setIpAmount(IPUtil.getAmount(network.getCIDR())); 130 | ipData.setNetwork(network.getCIDR()); 131 | fnTree.put(network.getCIDR(), ipData); 132 | } 133 | 134 | } catch (Exception e) { 135 | logger.error(e.getMessage(), e); 136 | e.printStackTrace(); 137 | } finally { 138 | if (reader != null) { 139 | try { 140 | reader.close(); 141 | } catch (IOException e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | try { 146 | fnTree.writeRawToFile(OUT_PATH + FN_OUT_ORIGINAL); 147 | fnTree.merge(); 148 | fnTree.writeRawToFile(OUT_PATH + FN_OUT_MERGED); 149 | } catch (IOException e) { 150 | logger.error(e.getMessage(), e); 151 | } 152 | } 153 | return fnTree; 154 | } 155 | 156 | public IPv4RadixTree scanCNIP() { 157 | IPv4RadixTree cnTree = new IPv4RadixTree(); 158 | BufferedReader reader = null; 159 | try { 160 | reader = new BufferedReader( 161 | new FileReader(new File(IN_PATH + cn_delegated))); 162 | String line; 163 | while ((line = reader.readLine()) != null) { 164 | String[] params = line.split("\\|"); 165 | if (params.length >= 4 && params[1].equals("CN") 166 | && params[2].equals("ipv4") && !params[3].equals("*")) { 167 | 168 | String baseIP = params[3]; 169 | int masklen = 32 - (int) (log(Integer.parseInt(params[4]), 2)); 170 | String prefix = baseIP + "/" + masklen; 171 | if (masklen > 24) masklen = 24; 172 | IPv4Network networks = new IPv4Network(prefix); 173 | for (String subnet : networks.getSubnet(24)) { 174 | String startIP = subnet.split("/")[0]; 175 | String subMasklen = subnet.split("/")[1]; 176 | String randomIP = IPUtil.getRandomIp(startIP, Integer.parseInt(subMasklen)); 177 | IpData ipData = null; 178 | while (ipData == null) { 179 | try { 180 | ipData = queryFromTaobao(randomIP); 181 | } catch (Exception e) { 182 | logger.error( 183 | "queryFromTaobao exception: " + e 184 | .getMessage(), e); 185 | } 186 | } 187 | int amount = 1<<(32 - masklen); 188 | ipData.setIpAmount(amount); 189 | ipData.setNetwork(subnet); 190 | logger.info(ipData.toFileString()); 191 | cnTree.put(subnet, ipData); 192 | } 193 | } 194 | } 195 | } catch (Exception e) { 196 | logger.error(e.getMessage(), e); 197 | } finally { 198 | if (reader != null) { 199 | try { 200 | reader.close(); 201 | } catch (IOException e) { 202 | e.printStackTrace(); 203 | } 204 | } 205 | try { 206 | cnTree.writeRawToFile(OUT_PATH + CN_OUT_ORIGINAL); 207 | cnTree.merge(); 208 | cnTree.writeRawToFile(OUT_PATH + CN_OUT_MERGED); 209 | } catch (IOException e) { 210 | logger.error(e.getMessage(), e); 211 | } 212 | } 213 | return cnTree; 214 | } 215 | 216 | private IpData queryFromTaobao(String ip) throws Exception { 217 | limitRate.check(); 218 | String ret = HttpClientPool.getInstance().getMethod(TAOBAO_URL + "?ip=" + ip, 5000); 219 | if (ret == null) { 220 | return null; 221 | } else { 222 | JSONObject json = JSON.parseObject(ret); 223 | if (json.getInteger("code") == 0) { 224 | JSONObject dataJson = json.getJSONObject("data"); 225 | IpData ipData = new IpData(); 226 | ipData.setCountry(dataJson.getString("country")); 227 | ipData.setProvince(dataJson.getString("region")); 228 | ipData.setCity(dataJson.getString("city")); 229 | ipData.setIsp(dataJson.getString("isp")); 230 | ipData.setIp(ip); 231 | return ipData; 232 | } else { 233 | return null; 234 | } 235 | } 236 | } 237 | 238 | public static double log(double value, double base) { 239 | return Math.log(value) / Math.log(base); 240 | } 241 | 242 | public static void main(String[] args) throws Exception { 243 | if (args.length > 1) { 244 | IN_PATH = args[0]; 245 | OUT_PATH = args[1]; 246 | Crawler crawler = new Crawler(); 247 | crawler.scanCNIP(); 248 | crawler.scanFNIP(); 249 | IPv4RadixTree retTree = new IPv4RadixTree(); 250 | retTree.loadFromLocalFile(OUT_PATH + FN_OUT_MERGED); 251 | retTree.loadFromLocalFile(OUT_PATH + CN_OUT_MERGED); 252 | retTree.merge(); 253 | retTree.writeRawToFile(OUT_PATH + "ipdb.dat"); 254 | logger.info("finish"); 255 | } else { 256 | System.out.println("miss param, abandon !!!"); 257 | System.exit(1); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/IPRange.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | /** 4 | * Created by lc on 15/1/14. 5 | */ 6 | public class IPRange implements Comparable { 7 | 8 | public IPRange(long start, long end) { 9 | this.start = start; 10 | this.end = end; 11 | } 12 | 13 | public IPRange(long start, long end, String cidr) { 14 | this.start = start; 15 | this.end = end; 16 | this.cidr = cidr; 17 | } 18 | 19 | public long start; 20 | 21 | public long end; 22 | 23 | public String cidr; 24 | 25 | public int prefixlen; 26 | 27 | @Override public int compareTo(IPRange ipRange) { 28 | long ret = start - ipRange.start; 29 | if (ret != 0) 30 | return (int)ret; 31 | else 32 | return (int) (ipRange.end - end); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/IPv4Network.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | import org.lic.ip.util.IPUtil; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by lc on 15/1/9. 10 | */ 11 | public class IPv4Network { 12 | long baseIPnumeric; // 起始ip 13 | 14 | int netmaskNumeric; // 掩码 netmask 15 | 16 | int numericCIDR; // cidr 17 | 18 | /** 19 | * i.e. IPv4Network("1.1.1.0/24"); 20 | * 21 | * @param IPinCIDRFormat 22 | */ 23 | public IPv4Network(String IPinCIDRFormat) throws NumberFormatException { 24 | 25 | String[] st = IPinCIDRFormat.split("/"); 26 | if (st.length != 2) { 27 | throw new NumberFormatException("Invalid CIDR format '" 28 | + IPinCIDRFormat + "', should be: xx.xx.xx.xx/xx"); 29 | } 30 | String symbolicIP = st[0]; 31 | String symbolicCIDR = st[1]; 32 | 33 | Integer numericCIDR = new Integer(symbolicCIDR); 34 | if (numericCIDR > 32) { 35 | throw new NumberFormatException("CIDR can not be greater than 32: " + IPinCIDRFormat); 36 | } 37 | 38 | st = symbolicIP.split("\\."); 39 | if (st.length != 4) { 40 | throw new NumberFormatException("Invalid IP address: " + IPinCIDRFormat); 41 | } 42 | 43 | int i = 24; 44 | baseIPnumeric = 0; 45 | for (int n = 0; n < st.length; n++) { 46 | int value = Integer.parseInt(st[n]); 47 | if (value != (value & 0xff)) { 48 | throw new NumberFormatException("Invalid IP address: " + IPinCIDRFormat); 49 | } 50 | baseIPnumeric += value << i; 51 | i -= 8; 52 | } 53 | 54 | /* netmask from CIDR */ 55 | if (numericCIDR < 8) { 56 | throw new NumberFormatException("Netmask CIDR can not be less than 8: " + IPinCIDRFormat); 57 | } 58 | netmaskNumeric = 0xffffffff; 59 | netmaskNumeric = netmaskNumeric << (32 - numericCIDR); 60 | this.numericCIDR = numericCIDR; 61 | } 62 | 63 | /** 64 | * i.e. IPv4Network(16843008, 24); 65 | * 66 | * @param ip 67 | * @param prefixlen 68 | * @throws NumberFormatException 69 | */ 70 | public IPv4Network(long ip, int prefixlen) throws NumberFormatException { 71 | 72 | if (prefixlen > 32 || prefixlen < 8) { 73 | throw new NumberFormatException("CIDR can not be >32 or <8 " + prefixlen); 74 | } 75 | 76 | baseIPnumeric = ip; 77 | netmaskNumeric = 0xffffffff; 78 | netmaskNumeric = netmaskNumeric << (32 - prefixlen); 79 | this.numericCIDR = prefixlen; 80 | } 81 | 82 | /** 83 | * 起始ip i.e. xxx.xxx.xxx.xxx 84 | * 85 | * @return 86 | */ 87 | public String getStartIP() { 88 | return IPUtil.ipLong2String(baseIPnumeric); 89 | } 90 | 91 | /** 92 | * int型ip转为string型 93 | * 94 | * @param ip 95 | * @return 96 | */ 97 | private String convertNumericIpToSymbolic(Integer ip) { 98 | StringBuffer sb = new StringBuffer(15); 99 | for (int shift = 24; shift > 0; shift -= 8) { 100 | sb.append(Integer.toString((ip >>> shift) & 0xff)); 101 | sb.append('.'); 102 | } 103 | sb.append(Integer.toString(ip & 0xff)); 104 | return sb.toString(); 105 | } 106 | 107 | /** 108 | * 获取子网掩码 i.e. 255.255.255.0 109 | * 110 | * @return 111 | */ 112 | public String getNetmask() { 113 | StringBuffer sb = new StringBuffer(15); 114 | for (int shift = 24; shift > 0; shift -= 8) { 115 | sb.append(Long.toString((netmaskNumeric >>> shift) & 0xff)); 116 | sb.append('.'); 117 | } 118 | sb.append(Long.toString(netmaskNumeric & 0xff)); 119 | return sb.toString(); 120 | } 121 | 122 | /** 123 | * 包含CIDR的IP i.e. 1.1.1.0/24 124 | * 125 | * @return 126 | */ 127 | public String getCIDR() { 128 | int i; 129 | for (i = 0; i < 32; i++) { 130 | if ((netmaskNumeric << i) == 0) 131 | break; 132 | } 133 | return IPUtil.ipLong2String(baseIPnumeric & netmaskNumeric) + "/" 134 | + i; 135 | } 136 | 137 | // CIDR数值 138 | public int getMasklen() { 139 | return this.numericCIDR; 140 | } 141 | 142 | /** 143 | * 有效IPs 144 | * 145 | * @return 146 | */ 147 | public List getAvailableIPs(Integer numberofIPs) { 148 | ArrayList result = new ArrayList(); 149 | int numberOfBits; 150 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) { 151 | if ((netmaskNumeric << numberOfBits) == 0) 152 | break; 153 | } 154 | 155 | Integer numberOfIPs = 0; 156 | for (int n = 0; n < (32 - numberOfBits); n++) { 157 | numberOfIPs = numberOfIPs << 1; 158 | numberOfIPs = numberOfIPs | 0x01; 159 | } 160 | 161 | Long baseIP = baseIPnumeric & netmaskNumeric; 162 | for (int i = 1; i < (numberOfIPs) && i < numberofIPs; i++) { 163 | Long ourIP = baseIP + i; 164 | String ip = IPUtil.ipLong2String(ourIP); 165 | result.add(ip); 166 | } 167 | return result; 168 | } 169 | 170 | /** 171 | * IP范围 i.e. 1.1.1.1 - 1.1.1.255 172 | * 173 | * @return 174 | */ 175 | public String getHostAddressRange() { 176 | 177 | int numberOfBits; 178 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) { 179 | if ((netmaskNumeric << numberOfBits) == 0) 180 | break; 181 | } 182 | Integer numberOfIPs = 0; 183 | for (int n = 0; n < (32 - numberOfBits); n++) { 184 | numberOfIPs = numberOfIPs << 1; 185 | numberOfIPs = numberOfIPs | 0x01; 186 | } 187 | 188 | Long baseIP = baseIPnumeric & netmaskNumeric; 189 | String firstIP = IPUtil.ipLong2String(baseIP + 1); 190 | String lastIP = IPUtil.ipLong2String(baseIP + numberOfIPs - 1); 191 | return firstIP + " - " + lastIP; 192 | } 193 | 194 | /** 195 | * ip范围 196 | * 197 | * @return 198 | */ 199 | public IPRange getIPRange() { 200 | long endIP = baseIPnumeric + (1<<(32 - numericCIDR)); 201 | return new IPRange(baseIPnumeric, endIP, getCIDR()); 202 | } 203 | 204 | public List getSubnet(int masklen) { 205 | if (masklen > 32 || masklen < 8 || masklen < numericCIDR) { 206 | throw new NumberFormatException("masklen can not be greater than 32"); 207 | } 208 | int numberOfIPs = 1 << (32 - masklen); 209 | Long startIP = baseIPnumeric & netmaskNumeric; 210 | List list = new ArrayList(); 211 | for (int i=0; i 0; shift -= 8) { 253 | 254 | // process 3 bytes, from high order byte down. 255 | sb.append(Long.toString((wildcardMask >>> shift) & 0xff)); 256 | 257 | sb.append('.'); 258 | } 259 | sb.append(Long.toString(wildcardMask & 0xff)); 260 | 261 | return sb.toString(); 262 | 263 | } 264 | 265 | public String getBroadcastAddress() { 266 | 267 | if (netmaskNumeric == 0xffffffff) 268 | return "0.0.0.0"; 269 | 270 | int numberOfBits; 271 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) { 272 | 273 | if ((netmaskNumeric << numberOfBits) == 0) 274 | break; 275 | 276 | } 277 | Integer numberOfIPs = 0; 278 | for (int n = 0; n < (32 - numberOfBits); n++) { 279 | 280 | numberOfIPs = numberOfIPs << 1; 281 | numberOfIPs = numberOfIPs | 0x01; 282 | } 283 | 284 | Long baseIP = baseIPnumeric & netmaskNumeric; 285 | Long ourIP = baseIP + numberOfIPs; 286 | 287 | String ip = IPUtil.ipLong2String(ourIP); 288 | 289 | return ip; 290 | } 291 | 292 | private String getBinary(int number) { 293 | String result = ""; 294 | 295 | Integer ourMaskBitPattern = 1; 296 | for (int i = 1; i <= 32; i++) { 297 | 298 | if ((number & ourMaskBitPattern) != 0) { 299 | 300 | result = "1" + result; // the bit is 1 301 | } else { // the bit is 0 302 | 303 | result = "0" + result; 304 | } 305 | if ((i % 8) == 0 && i != 0 && i != 32) 306 | 307 | result = "." + result; 308 | ourMaskBitPattern = ourMaskBitPattern << 1; 309 | 310 | } 311 | return result; 312 | } 313 | 314 | public String getNetmaskInBinary() { 315 | 316 | return getBinary(netmaskNumeric); 317 | } 318 | 319 | /** 320 | * Checks if the given IP address contains in subnet 321 | * 322 | * @param IPaddress 323 | * @return 324 | */ 325 | public boolean contains(String IPaddress) { 326 | 327 | Integer checkingIP = 0; 328 | String[] st = IPaddress.split("\\."); 329 | 330 | if (st.length != 4) 331 | throw new NumberFormatException("Invalid IP address: " + IPaddress); 332 | 333 | int i = 24; 334 | for (int n = 0; n < st.length; n++) { 335 | 336 | int value = Integer.parseInt(st[n]); 337 | 338 | if (value != (value & 0xff)) { 339 | 340 | throw new NumberFormatException("Invalid IP address: " 341 | + IPaddress); 342 | } 343 | 344 | checkingIP += value << i; 345 | i -= 8; 346 | } 347 | 348 | if ((baseIPnumeric & netmaskNumeric) == (checkingIP & netmaskNumeric)) 349 | 350 | return true; 351 | else 352 | return false; 353 | } 354 | 355 | public boolean contains(IPv4Network child) { 356 | 357 | Long subnetID = child.baseIPnumeric; 358 | 359 | int subnetMask = child.netmaskNumeric; 360 | 361 | if ((subnetID & this.netmaskNumeric) == (this.baseIPnumeric & this.netmaskNumeric)) { 362 | 363 | if ((this.netmaskNumeric < subnetMask) == true 364 | && this.baseIPnumeric <= subnetID) { 365 | 366 | return true; 367 | } 368 | 369 | } 370 | return false; 371 | 372 | } 373 | 374 | /** 375 | * @param args 376 | */ 377 | public static void main(String[] args) { 378 | IPv4Network ipv4 = new IPv4Network("192.72.40.0/21"); 379 | System.out.println(ipv4.getCIDR()); 380 | System.out.println(ipv4.getNetmask()); 381 | System.out.println(ipv4.getNumberOfHosts()); 382 | System.out.println(ipv4.getWildcardMask()); 383 | System.out.println(ipv4.getBroadcastAddress()); 384 | System.out.println(ipv4.getHostAddressRange()); 385 | System.out.println(ipv4.getSubnet(24)); 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/IPv4RadixTree.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | 4 | import org.lic.ip.util.IPUtil; 5 | 6 | import java.io.*; 7 | import java.net.InetAddress; 8 | import java.net.UnknownHostException; 9 | import java.nio.ByteBuffer; 10 | import java.util.*; 11 | 12 | /** 13 | * Created by lc on 15/1/8. 14 | */ 15 | public class IPv4RadixTree { 16 | /** 17 | * Special value that designates that there are no value stored in the key so far. 18 | * One can't use store value in a tree. 19 | */ 20 | public static final IpData NO_VALUE = null; 21 | 22 | private static final int NULL_PTR = -1; 23 | private static final int ROOT_PTR = 0; 24 | 25 | private static final long MAX_IPV4_BIT = 0x80000000L; 26 | 27 | private int[] rights; 28 | private int[] lefts; 29 | private IpData[] values; 30 | 31 | private int allocatedSize; 32 | private int size; 33 | 34 | /** 35 | * Initializes IPv4 radix tree with default capacity of 1024 nodes. It should 36 | * be sufficient for small databases. 37 | */ 38 | public IPv4RadixTree() { 39 | init(1024); 40 | } 41 | 42 | /** 43 | * Initializes IPv4 radix tree with a given capacity. 44 | * @param allocatedSize initial capacity to allocate 45 | */ 46 | public IPv4RadixTree(int allocatedSize) { 47 | init(allocatedSize); 48 | } 49 | 50 | private void init(int allocatedSize) { 51 | this.allocatedSize = allocatedSize; 52 | 53 | rights = new int[this.allocatedSize]; 54 | lefts = new int[this.allocatedSize]; 55 | values = new IpData[this.allocatedSize]; 56 | 57 | size = 1; 58 | lefts[0] = NULL_PTR; 59 | rights[0] = NULL_PTR; 60 | values[0] = NO_VALUE; 61 | } 62 | 63 | /** 64 | * Puts a key-value pair in a tree. 65 | * @param key IPv4 network prefix 66 | * @param mask IPv4 netmask in networked byte order format (for example, 67 | * 0xffffff00L = 4294967040L corresponds to 255.255.255.0 AKA /24 network 68 | * bitmask) 69 | * @param value an arbitrary value that would be stored under a given key 70 | */ 71 | public void put(long key, long mask, IpData value) { 72 | long bit = 0x80000000L; // 128.0.0.0 73 | int node = ROOT_PTR; 74 | int next = ROOT_PTR; 75 | 76 | while ((bit & mask) != 0) { 77 | next = ((key & bit) != 0) ? rights[node] : lefts[node]; 78 | if (next == NULL_PTR) 79 | break; 80 | bit >>= 1; 81 | node = next; 82 | } 83 | 84 | if (next != NULL_PTR) { 85 | values[node] = value; 86 | return; 87 | } 88 | 89 | while ((bit & mask) != 0) { 90 | if (size == allocatedSize) 91 | expandAllocatedSize(); 92 | next = size; 93 | values[next] = NO_VALUE; 94 | rights[next] = NULL_PTR; 95 | lefts[next] = NULL_PTR; 96 | if ((key & bit) != 0) { 97 | rights[node] = next; 98 | } else { 99 | lefts[node] = next; 100 | } 101 | bit >>= 1; 102 | node = next; 103 | size++; 104 | } 105 | values[node] = value; 106 | } 107 | 108 | private void expandAllocatedSize() { 109 | int oldSize = allocatedSize; 110 | allocatedSize = allocatedSize * 2; 111 | 112 | int[] newLefts = new int[allocatedSize]; 113 | System.arraycopy(lefts, 0, newLefts, 0, oldSize); 114 | lefts = newLefts; 115 | 116 | int[] newRights = new int[allocatedSize]; 117 | System.arraycopy(rights, 0, newRights, 0, oldSize); 118 | rights = newRights; 119 | 120 | IpData[] newValues = new IpData[allocatedSize]; 121 | System.arraycopy(values, 0, newValues, 0, oldSize); 122 | values = newValues; 123 | } 124 | 125 | /** 126 | * Selects a value for a given IPv4 address, traversing tree and choosing 127 | * most specific value available for a given address. 128 | * @param key IPv4 address to look up 129 | * @return value at most specific IPv4 network in a tree for a given IPv4 130 | * address 131 | */ 132 | public IpData selectValue(long key) { 133 | long bit = MAX_IPV4_BIT; 134 | IpData value = NO_VALUE; 135 | int node = ROOT_PTR; 136 | 137 | while (node != NULL_PTR) { 138 | if (values[node] != NO_VALUE) 139 | value = values[node]; 140 | node = ((key & bit) != 0) ? rights[node] : lefts[node]; 141 | bit >>= 1; 142 | } 143 | 144 | return value; 145 | } 146 | 147 | /** 148 | * Puts a key-value pair in a tree, using a string representation of IPv4 prefix. 149 | * @param ipNet IPv4 network as a string in form of "a.b.c.d/e", where a, b, c, d 150 | * are IPv4 octets (in decimal) and "e" is a netmask in CIDR notation 151 | * @param value an arbitrary value that would be stored under a given key 152 | * @throws java.net.UnknownHostException 153 | */ 154 | public void put(String ipNet, IpData value) throws UnknownHostException { 155 | int pos = ipNet.indexOf('/'); 156 | String ipStr = ipNet.substring(0, pos); 157 | long ip = inet_aton(ipStr); 158 | 159 | String netmaskStr = ipNet.substring(pos + 1); 160 | int cidr = Integer.parseInt(netmaskStr); 161 | long netmask = ((1L << (32 - cidr)) - 1L) ^ 0xffffffffL; 162 | 163 | put(ip, netmask, value); 164 | } 165 | 166 | /** 167 | * Selects a value for a given IPv4 address, traversing tree and choosing 168 | * most specific value available for a given address. 169 | * @param ipStr IPv4 address to look up, in string form (i.e. "a.b.c.d") 170 | * @return value at most specific IPv4 network in a tree for a given IPv4 171 | * address 172 | * @throws java.net.UnknownHostException 173 | */ 174 | public IpData selectValue(String ipStr) throws UnknownHostException { 175 | return selectValue(inet_aton(ipStr)); 176 | } 177 | 178 | /** 179 | * Helper function that reads IPv4 radix tree from a local file in tab-separated format: 180 | * (IPv4 net => value) 181 | * @param filename name of a local file to read 182 | * @return a fully constructed IPv4 radix tree from that file 183 | * @throws java.io.IOException 184 | */ 185 | public void loadFromLocalFile(String filename) throws IOException { 186 | IPv4RadixTree tr = new IPv4RadixTree(countLinesInLocalFile(filename)); 187 | BufferedReader br = new BufferedReader(new FileReader(filename)); 188 | String l; 189 | IpData value; 190 | //["country", "province", "city", "isp", "ip", "ip_amount"] 191 | while ((l = br.readLine()) != null) { 192 | String[] c = l.split(";"); 193 | //1.0.0.0/24;澳大利亚;;;;223.255.255.111;256 194 | //1.0.1.0/24;中国;福建省;福州市;电信;1.0.1.53;256 195 | value = new IpData(); 196 | value.setNetwork(c[0]); 197 | value.setCountry(c[1]); 198 | value.setProvince(c[2]); 199 | value.setCity(c[3]); 200 | value.setIsp(c[4]); 201 | value.setIp(c[5]); 202 | value.setIpAmount(Integer.parseInt(c[6])); 203 | 204 | put(c[0], value); 205 | } 206 | } 207 | 208 | private static long inet_aton(String ipStr) throws UnknownHostException { 209 | ByteBuffer bb = ByteBuffer.allocate(8); 210 | bb.putInt(0); 211 | bb.put(InetAddress.getByName(ipStr).getAddress()); 212 | bb.rewind(); 213 | return bb.getLong(); 214 | } 215 | 216 | private static int countLinesInLocalFile(String filename) throws IOException { 217 | BufferedReader br = new BufferedReader(new FileReader(filename)); 218 | int n = 0; 219 | String l; 220 | while ((l = br.readLine()) != null) { 221 | n++; 222 | } 223 | return n; 224 | } 225 | 226 | /** 227 | * Returns a size of tree in number of nodes (not number of prefixes stored). 228 | * @return a number of nodes in current tree 229 | */ 230 | public int size() { return size; } 231 | 232 | public void writeRawToFile(String filename) throws IOException { 233 | 234 | OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(filename)), "UTF-8"); 235 | TreeSet valuesTree = new TreeSet(); 236 | for (IpData ipData : values) { 237 | if (ipData != null) { 238 | valuesTree.add(ipData); 239 | } 240 | } 241 | for (IpData ipData : valuesTree) { 242 | if (ipData != null) { 243 | writer.write(ipData.toFileString() + "\n"); 244 | System.out.println(ipData.toFileString()); 245 | } 246 | } 247 | writer.close(); 248 | } 249 | 250 | public void merge() throws UnknownHostException { 251 | Deque mergedDeque = new ArrayDeque(); 252 | Deque tmpDeque = new ArrayDeque(); 253 | TreeSet valuesTree = new TreeSet(); 254 | for (IpData ipData : values) { 255 | if (ipData != null) { 256 | valuesTree.add(ipData); 257 | } 258 | } 259 | for (IpData ipData : valuesTree) { 260 | if (ipData == null) continue; 261 | ipData.setIpAmount(IPUtil.getAmount(ipData.getNetwork())); 262 | if (!tmpDeque.isEmpty()) { 263 | IpData pdata = tmpDeque.peekLast(); 264 | if (ipData.equals(pdata) || (!ipData.getCountry().equals("中国") && ipData 265 | .getCountry().equals(pdata.getCountry()))) { 266 | // 相同 267 | tmpDeque.addLast(ipData); 268 | } else { 269 | // 不同,合并tmpDeque,写入mergedDeque 270 | IpData first = tmpDeque.peekFirst().copy(); 271 | List tmpCidrs = new ArrayList(); 272 | for (IpData d : tmpDeque) { 273 | tmpCidrs.add(d.getNetwork()); 274 | } 275 | List mergedCidrs = IPUtil.mergeCidrs(tmpCidrs); 276 | for (String cidr : mergedCidrs) { 277 | IpData d = first.copy(); 278 | d.setNetwork(cidr); 279 | d.setIpAmount(IPUtil.getAmount(cidr)); 280 | mergedDeque.addLast(d); 281 | } 282 | tmpDeque.clear(); 283 | tmpDeque.add(ipData); 284 | } 285 | } else { 286 | tmpDeque.addLast(ipData); 287 | } 288 | } 289 | if (tmpDeque.size() > 0) { 290 | IpData first = tmpDeque.peekFirst().copy(); 291 | List tmpCidrs = new ArrayList(); 292 | for (IpData d : tmpDeque) { 293 | tmpCidrs.add(d.getNetwork()); 294 | } 295 | List mergedCidrs = IPUtil.mergeCidrs(tmpCidrs); 296 | for (String cidr : mergedCidrs) { 297 | IpData d = first.copy(); 298 | d.setNetwork(cidr); 299 | d.setIpAmount(IPUtil.getAmount(cidr)); 300 | mergedDeque.addLast(d); 301 | } 302 | tmpDeque.clear(); 303 | } 304 | 305 | init(1024); 306 | for (IpData ipData : mergedDeque) { 307 | put(ipData.getNetwork(), ipData); 308 | } 309 | } 310 | 311 | public static void main(String[] args) throws IOException { 312 | IPv4RadixTree tree = new IPv4RadixTree(); 313 | tree.loadFromLocalFile("/Users/lc/github/ipdb_creator/output/delegated-fn-original"); 314 | tree.merge(); 315 | tree.writeRawToFile("/Users/lc/github/ipdb_creator/output/delegated-fn-merged"); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/IpData.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | import org.lic.ip.util.IPUtil; 4 | 5 | /** 6 | * Created by lc on 15/1/9. 7 | */ 8 | public class IpData implements Comparable { 9 | private String network; 10 | 11 | private String country; 12 | 13 | private String province; 14 | 15 | private String city; 16 | 17 | private String isp; 18 | 19 | private String ip; 20 | 21 | private int ipAmount; 22 | 23 | public String getCountry() { 24 | return country; 25 | } 26 | 27 | public void setCountry(String country) { 28 | this.country = country; 29 | } 30 | 31 | public String getProvince() { 32 | return province; 33 | } 34 | 35 | public void setProvince(String province) { 36 | this.province = province; 37 | } 38 | 39 | public String getCity() { 40 | return city; 41 | } 42 | 43 | public void setCity(String city) { 44 | this.city = city; 45 | } 46 | 47 | public String getIsp() { 48 | return isp; 49 | } 50 | 51 | public void setIsp(String isp) { 52 | this.isp = isp; 53 | } 54 | 55 | public String getIp() { 56 | return ip; 57 | } 58 | 59 | public void setIp(String ip) { 60 | this.ip = ip; 61 | } 62 | 63 | public int getIpAmount() { 64 | return ipAmount; 65 | } 66 | 67 | public void setIpAmount(int ipAmount) { 68 | this.ipAmount = ipAmount; 69 | } 70 | 71 | public String getNetwork() { 72 | return network; 73 | } 74 | 75 | public void setNetwork(String network) { 76 | this.network = network; 77 | } 78 | 79 | public String toFileString() { 80 | //["country", "province", "city", "isp", "ip", "ip_amount"] 81 | return new StringBuilder(network).append(";") 82 | .append(country).append(";") 83 | .append(province).append(";") 84 | .append(city).append(";") 85 | .append(isp).append(";") 86 | .append(ip).append(";") 87 | .append(ipAmount).toString(); 88 | } 89 | 90 | public IpData copy() { 91 | IpData d = new IpData(); 92 | d.setNetwork(network); 93 | d.setCountry(country); 94 | d.setCity(city); 95 | d.setProvince(province); 96 | d.setIsp(isp); 97 | d.setIp(ip); 98 | d.setIpAmount(ipAmount); 99 | return d; 100 | } 101 | 102 | @Override public String toString() { 103 | return "Data{" + 104 | "network='" + network + '\'' + 105 | ", country='" + country + '\'' + 106 | ", province='" + province + '\'' + 107 | ", city='" + city + '\'' + 108 | ", isp='" + isp + '\'' + 109 | ", ip='" + ip + '\'' + 110 | ", ipAmount=" + ipAmount + 111 | '}'; 112 | } 113 | 114 | @Override 115 | public boolean equals(Object o) { 116 | if (this == o) { 117 | return true; 118 | } 119 | if (o == null || getClass() != o.getClass()) { 120 | return false; 121 | } 122 | 123 | IpData ipData = (IpData) o; 124 | 125 | if (city != null ? !city.equals(ipData.city) : ipData.city != null) { 126 | return false; 127 | } 128 | if (country != null ? 129 | !country.equals(ipData.country) : 130 | ipData.country != null) { 131 | return false; 132 | } 133 | if (isp != null ? !isp.equals(ipData.isp) : ipData.isp != null) { 134 | return false; 135 | } 136 | if (province != null ? 137 | !province.equals(ipData.province) : 138 | ipData.province != null) { 139 | return false; 140 | } 141 | 142 | return true; 143 | } 144 | 145 | @Override 146 | public int hashCode() { 147 | int result = country != null ? country.hashCode() : 0; 148 | result = 31 * result + (province != null ? province.hashCode() : 0); 149 | result = 31 * result + (city != null ? city.hashCode() : 0); 150 | result = 31 * result + (isp != null ? isp.hashCode() : 0); 151 | return result; 152 | } 153 | 154 | @Override public int compareTo(IpData ipData) { 155 | long startIP = IPUtil.ipString2Long(network.split("/")[0]); 156 | long cIP = IPUtil.ipString2Long(ipData.network.split("/")[0]); 157 | if (startIP - cIP > 0) return 1; 158 | else if (startIP - cIP < 0) return -1; 159 | else return 0; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/LimitQueue.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.LinkedList; 6 | import java.util.Queue; 7 | 8 | /** 9 | * Created by lc on 15/1/9. 10 | */ 11 | public class LimitQueue implements Queue{ 12 | //队列长度 13 | private int limit; 14 | 15 | private Queue queue = new LinkedList(); 16 | 17 | public LimitQueue(int limit){ 18 | this.limit = limit; 19 | } 20 | 21 | @Override 22 | public boolean offer(E e){ 23 | if(queue.size() >= limit){ 24 | //如果超出长度,入队时,先出队 25 | queue.poll(); 26 | } 27 | return queue.offer(e); 28 | } 29 | 30 | @Override 31 | public E poll() { 32 | return queue.poll(); 33 | } 34 | 35 | /** 36 | * 获取限制大小 37 | * @return 38 | */ 39 | public int getLimit(){ 40 | return limit; 41 | } 42 | 43 | @Override 44 | public boolean add(E e) { 45 | return queue.add(e); 46 | } 47 | 48 | @Override 49 | public E element() { 50 | return queue.element(); 51 | } 52 | 53 | @Override 54 | public E peek() { 55 | return queue.peek(); 56 | } 57 | 58 | @Override 59 | public boolean isEmpty() { 60 | return queue.size() == 0 ? true : false; 61 | } 62 | 63 | @Override 64 | public int size() { 65 | return queue.size(); 66 | } 67 | 68 | @Override 69 | public E remove() { 70 | return queue.remove(); 71 | } 72 | 73 | @Override 74 | public boolean addAll(Collection c) { 75 | return queue.addAll(c); 76 | } 77 | 78 | @Override 79 | public void clear() { 80 | queue.clear(); 81 | } 82 | 83 | @Override 84 | public boolean contains(Object o) { 85 | return queue.contains(o); 86 | } 87 | 88 | @Override 89 | public boolean containsAll(Collection c) { 90 | return queue.containsAll(c); 91 | } 92 | 93 | @Override 94 | public Iterator iterator() { 95 | return queue.iterator(); 96 | } 97 | 98 | @Override 99 | public boolean remove(Object o) { 100 | return queue.remove(o); 101 | } 102 | 103 | @Override 104 | public boolean removeAll(Collection c) { 105 | return queue.removeAll(c); 106 | } 107 | 108 | @Override 109 | public boolean retainAll(Collection c) { 110 | return queue.retainAll(c); 111 | } 112 | 113 | @Override 114 | public Object[] toArray() { 115 | return queue.toArray(); 116 | } 117 | 118 | @Override 119 | public T[] toArray(T[] a) { 120 | return queue.toArray(a); 121 | } 122 | 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/crawler/LimitRate.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.crawler; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Queue; 7 | 8 | /** 9 | * Created by lc on 15/1/9. 10 | */ 11 | public class LimitRate { 12 | private static final Logger logger = LoggerFactory.getLogger(LimitRate.class); 13 | 14 | private LimitQueue queue; 15 | 16 | private long duration; 17 | 18 | private int limit; 19 | 20 | public LimitRate(long duration, int limit) { 21 | queue = new LimitQueue(limit); 22 | this.duration = duration; 23 | this.limit = limit; 24 | } 25 | 26 | public void check() throws InterruptedException { 27 | if (queue.size() < limit) 28 | return; 29 | Long first = queue.peek(); 30 | if (first == null) 31 | return; 32 | long now = System.currentTimeMillis(); 33 | if (now - first <= duration) { 34 | logger.info("limit rate checked, sleep a while"); 35 | Thread.sleep(duration - now + first + 1); 36 | } 37 | queue.offer(now); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/iplocator/IPLocation.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.iplocator; 2 | 3 | /** 4 | * 用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区 5 | * 6 | * @author lc 7 | */ 8 | public class IPLocation { 9 | 10 | public static final String UNKNOWN_COUNTRY = "unknown_country"; 11 | 12 | public static final String UNKNOWN_AREA = "unknown_area"; 13 | 14 | public String country; 15 | 16 | public String area; 17 | 18 | public static IPLocation getNullInstance() { 19 | IPLocation ipl = new IPLocation(); 20 | ipl.country = UNKNOWN_COUNTRY; 21 | ipl.area = UNKNOWN_AREA; 22 | return ipl; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "IPLocation [country=" + country + ", area=" + area + "]"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/iplocator/IPv4RadixIntTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Openstat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.lic.ip.iplocator; 18 | 19 | import org.apache.commons.lang.time.StopWatch; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.io.*; 24 | import java.net.InetAddress; 25 | import java.net.UnknownHostException; 26 | import java.nio.ByteBuffer; 27 | 28 | /** 29 | * A minimalistic, memory size-savvy and fairly fast radix tree (AKA Patricia 30 | * trie) implementation that uses IPv4 addresses with netmasks as keys and 31 | * 32-bit signed integers as values. This tree is generally uses in read-only 32 | * manner: there are no key removal operation and the whole thing works best in 33 | * pre-allocated fashion. 34 | */ 35 | 36 | /** 37 | * https://github.com/openstat/ip-radix-tree 38 | */ 39 | public class IPv4RadixIntTree { 40 | private static final Logger logger = LoggerFactory 41 | .getLogger(IPv4RadixIntTree.class); 42 | 43 | /** 44 | * Special value that designates that there are no value stored in the key 45 | * so far. One can't use store value in a tree. 46 | */ 47 | public static final IPLocation NO_VALUE = IPLocation.getNullInstance(); 48 | 49 | private static final int NULL_PTR = -1; 50 | 51 | private static final int ROOT_PTR = 0; 52 | 53 | private static final long MAX_IPV4_BIT = 0x80000000L; 54 | 55 | private int[] rights; 56 | 57 | private int[] lefts; 58 | 59 | private IPLocation[] values; 60 | 61 | private int allocatedSize; 62 | 63 | private int size; 64 | 65 | private static class SingletonHolder { 66 | public static final IPv4RadixIntTree instance = new IPv4RadixIntTree(); 67 | } 68 | 69 | public static IPv4RadixIntTree getInstance() { 70 | return SingletonHolder.instance; 71 | } 72 | 73 | private IPv4RadixIntTree() { 74 | StopWatch sw = new StopWatch(); 75 | sw.start(); 76 | 77 | try { 78 | String filepath = getClass().getClassLoader() 79 | .getResource("ipdb_all_2015-01-19").getPath(); 80 | 81 | int lines = countLinesInLocalFile(filepath); 82 | logger.info("file lines: {}", lines); 83 | 84 | init(lines); 85 | loadFromLocalFile(filepath); 86 | } catch (Exception e) { 87 | logger.error(e.getMessage(), e); 88 | } 89 | 90 | sw.stop(); 91 | logger.info("init cost: {}ms", sw.getTime()); 92 | } 93 | 94 | private void init(int allocatedSize) { 95 | this.allocatedSize = allocatedSize; 96 | 97 | rights = new int[this.allocatedSize]; 98 | lefts = new int[this.allocatedSize]; 99 | values = new IPLocation[this.allocatedSize]; 100 | 101 | size = 1; 102 | lefts[0] = NULL_PTR; 103 | rights[0] = NULL_PTR; 104 | values[0] = NO_VALUE; 105 | } 106 | 107 | private int countLinesInLocalFile(String filepath) throws IOException { 108 | BufferedReader br = new BufferedReader(new FileReader(filepath)); 109 | int n = 0; 110 | while (br.readLine() != null) { 111 | n++; 112 | } 113 | br.close(); 114 | return n; 115 | } 116 | 117 | /** 118 | * Helper function that reads IPv4 radix tree from a local file in 119 | * tab-separated format: (IPv4 net => value) 120 | * 121 | * @param filepath 122 | * name of a local file to read 123 | * @return a fully constructed IPv4 radix tree from that file 124 | * @throws java.io.IOException 125 | */ 126 | private void loadFromLocalFile(String filepath) throws IOException { 127 | BufferedReader br = new BufferedReader(new InputStreamReader( 128 | new FileInputStream(filepath), "UTF-8")); 129 | String l; 130 | IPLocation value; 131 | 132 | // 1.0.1.0/24;中国;福建省;福州市;电信;1.0.1.208;256 133 | while ((l = br.readLine()) != null) { 134 | String[] c = l.split(";"); 135 | 136 | // value = String.format("%s %s %s %s", c[1], c[2], c[3], c[4]); 137 | value = new IPLocation(); 138 | value.country = c[1].equals("中国") ? c[2] : c[1]; // 如果是国内ip,country字段放省名 139 | value.area = c[4]; // 运营商名 140 | 141 | put(c[0], value); 142 | } 143 | 144 | br.close(); 145 | logger.info("load ok, tree size: {}", size()); 146 | } 147 | 148 | public void prefixMerge() { 149 | 150 | } 151 | 152 | /** 153 | * Puts a key-value pair in a tree, using a string representation of IPv4 154 | * prefix. 155 | * 156 | * @param ipNet 157 | * IPv4 network as a string in form of "a.b.c.d/e", where a, b, 158 | * c, d are IPv4 octets (in decimal) and "e" is a netmask in CIDR 159 | * notation 160 | * @param value 161 | * an arbitrary value that would be stored under a given key 162 | * @throws java.net.UnknownHostException 163 | */ 164 | private void put(String ipNet, IPLocation value) 165 | throws UnknownHostException { 166 | int pos = ipNet.indexOf('/'); 167 | String ipStr = ipNet.substring(0, pos); 168 | long ip = inet_aton(ipStr); 169 | 170 | String netmaskStr = ipNet.substring(pos + 1); 171 | int cidr = Integer.parseInt(netmaskStr); 172 | long netmask = ((1L << (32 - cidr)) - 1L) ^ 0xffffffffL; 173 | 174 | put(ip, netmask, value); 175 | } 176 | 177 | /** 178 | * Puts a key-value pair in a tree. 179 | * 180 | * @param key 181 | * IPv4 network prefix 182 | * @param mask 183 | * IPv4 netmask in networked byte order format (for example, 184 | * 0xffffff00L = 4294967040L corresponds to 255.255.255.0 AKA /24 185 | * network bitmask) 186 | * @param value 187 | * an arbitrary value that would be stored under a given key 188 | */ 189 | private void put(long key, long mask, IPLocation value) { 190 | long bit = MAX_IPV4_BIT; 191 | int node = ROOT_PTR; 192 | int next = ROOT_PTR; 193 | 194 | while ((bit & mask) != 0) { 195 | next = ((key & bit) != 0) ? rights[node] : lefts[node]; 196 | if (next == NULL_PTR) 197 | break; 198 | bit >>= 1; 199 | node = next; 200 | } 201 | 202 | if (next != NULL_PTR) { 203 | // if (node.value != NO_VALUE) { 204 | // throw new IllegalArgumentException(); 205 | // } 206 | 207 | values[node] = value; 208 | return; 209 | } 210 | 211 | while ((bit & mask) != 0) { 212 | if (size == allocatedSize) 213 | expandAllocatedSize(); 214 | 215 | next = size; 216 | values[next] = NO_VALUE; 217 | rights[next] = NULL_PTR; 218 | lefts[next] = NULL_PTR; 219 | 220 | if ((key & bit) != 0) { 221 | rights[node] = next; 222 | } else { 223 | lefts[node] = next; 224 | } 225 | 226 | bit >>= 1; 227 | node = next; 228 | size++; 229 | } 230 | 231 | values[node] = value; 232 | } 233 | 234 | private void expandAllocatedSize() { 235 | int oldSize = allocatedSize; 236 | allocatedSize = allocatedSize * 2; 237 | logger.info("expandAllocatedSize: {} -> {}", oldSize, allocatedSize); 238 | 239 | int[] newLefts = new int[allocatedSize]; 240 | System.arraycopy(lefts, 0, newLefts, 0, oldSize); 241 | lefts = newLefts; 242 | 243 | int[] newRights = new int[allocatedSize]; 244 | System.arraycopy(rights, 0, newRights, 0, oldSize); 245 | rights = newRights; 246 | 247 | IPLocation[] newValues = new IPLocation[allocatedSize]; 248 | System.arraycopy(values, 0, newValues, 0, oldSize); 249 | values = newValues; 250 | } 251 | 252 | /** 253 | * Selects a value for a given IPv4 address, traversing tree and choosing 254 | * most specific value available for a given address. 255 | * 256 | * @param ipStr 257 | * IPv4 address to look up, in string form (i.e. "a.b.c.d") 258 | * @return value at most specific IPv4 network in a tree for a given IPv4 259 | * address 260 | * @throws java.net.UnknownHostException 261 | */ 262 | public IPLocation get(String ipStr) { 263 | return get(inet_aton(ipStr)); 264 | } 265 | 266 | /** 267 | * Selects a value for a given IPv4 address, traversing tree and choosing 268 | * most specific value available for a given address. 269 | * 270 | * @param key 271 | * IPv4 address to look up 272 | * @return value at most specific IPv4 network in a tree for a given IPv4 273 | * address 274 | */ 275 | public IPLocation get(long key) { 276 | long bit = MAX_IPV4_BIT; 277 | IPLocation value = NO_VALUE; 278 | int node = ROOT_PTR; 279 | 280 | while (node != NULL_PTR) { 281 | if (values[node] != NO_VALUE) 282 | value = values[node]; 283 | node = ((key & bit) != 0) ? rights[node] : lefts[node]; 284 | bit >>= 1; 285 | } 286 | 287 | return value; 288 | } 289 | 290 | private static long inet_aton(String ipStr) { 291 | try { 292 | ByteBuffer bb = ByteBuffer.allocate(8); 293 | bb.putInt(0); 294 | bb.put(InetAddress.getByName(ipStr).getAddress()); 295 | bb.rewind(); 296 | return bb.getLong(); 297 | } catch (UnknownHostException e) { 298 | logger.error(e.getMessage(), e); 299 | } 300 | return 0; 301 | } 302 | 303 | /** 304 | * Returns a size of tree in number of nodes (not number of prefixes 305 | * stored). 306 | * 307 | * @return a number of nodes in current tree 308 | */ 309 | public int size() { 310 | return size; 311 | } 312 | 313 | public static void main(String[] args) throws Exception { 314 | final IPv4RadixIntTree ipTree = IPv4RadixIntTree.getInstance(); 315 | // 316 | // final String ipArray[] = { "123.58.181.1", "115.236.97.158", 317 | // "182.140.134.24", "115.236.153.148", "114.113.197.131", 318 | // "115.236.153.148", "123.58.181.1", "115.236.153.148", 319 | // "123.58.181.58", "127.0.0.1" }; 320 | // 321 | // for (int i = 0; i < 1; i++) { 322 | // new Thread() { 323 | // @Override 324 | // public void run() { 325 | // // while (true) { 326 | // try { 327 | // for (String ip: ipArray) { 328 | // IPLocation ipl = ipTree.get(ip); 329 | // System.out.println(String.format("%s [%s %s]", ip, 330 | // ipl.country, ipl.area)); 331 | // } 332 | // 333 | // Thread.sleep(10000); 334 | // } catch (Exception e) { 335 | // e.printStackTrace(); 336 | // } 337 | // // } 338 | // } 339 | // }.start(); 340 | // } 341 | // int cidr = 16; 342 | // long netmask = ((1L << (32 - cidr)) - 1L) ^ 0xffffffffL; 343 | // System.out.println(Long.toHexString(netmask)); 344 | } 345 | 346 | } 347 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/ipseeker/IPEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LumaQQ - Java QQ Client 3 | * 4 | * Copyright (C) 2004 luma 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | package org.lic.ip.ipseeker; 21 | 22 | /** 23 | *
24 |  * 一条IP范围记录,不仅包括国家和区域,也包括起始IP和结束IP
25 |  * 
26 | * 27 | * @author luma 28 | */ 29 | public class IPEntry { 30 | public String beginIp; 31 | 32 | public String endIp; 33 | 34 | public String country; 35 | 36 | public String area; 37 | 38 | /** 39 | * 构造函数 40 | */ 41 | public IPEntry() { 42 | beginIp = endIp = country = area = ""; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/ipseeker/IPSeeker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LumaQQ - Java QQ Client 3 | * 4 | * Copyright (C) 2004 luma 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | /* 22 | * 去掉了每次都需要直接访问磁盘文件的查询代码,只允许访问内存映射文件 23 | * modified by liyalong 2012.07.31 24 | */ 25 | package org.lic.ip.ipseeker; 26 | 27 | import java.io.IOException; 28 | import java.io.RandomAccessFile; 29 | import java.io.UnsupportedEncodingException; 30 | import java.nio.ByteOrder; 31 | import java.nio.MappedByteBuffer; 32 | import java.nio.channels.FileChannel; 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.Random; 37 | import java.util.StringTokenizer; 38 | 39 | 40 | import org.apache.commons.lang.time.StopWatch; 41 | import org.apache.log4j.Logger; 42 | 43 | import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; 44 | import org.lic.ip.iplocator.IPLocation; 45 | 46 | /** 47 | *
 48 |  * 关于IP数据库文件格式,请参考LumaQQ主页文档“纯真IP数据库格式详解”一文。
 49 |  * 
50 | * 51 | * @author luma 52 | */ 53 | public class IPSeeker { 54 | 55 | private static final int IP_RECORD_LENGTH = 7; 56 | 57 | private static final byte REDIRECT_MODE_1 = 0x01; 58 | 59 | private static final byte REDIRECT_MODE_2 = 0x02; 60 | 61 | private static Logger logger = Logger.getLogger(IPSeeker.class); 62 | 63 | private static final ConcurrentLinkedHashMap ipCacheMap = new ConcurrentLinkedHashMap.Builder() 64 | .initialCapacity(100000).maximumWeightedCapacity(100000).build(); 65 | 66 | private RandomAccessFile ipFile; 67 | 68 | private MappedByteBuffer mbb; 69 | 70 | private static ThreadLocal localInstance = new ThreadLocal(); 71 | 72 | private int ipBegin; 73 | 74 | private int ipEnd; 75 | 76 | private byte[] tmpBuf; 77 | 78 | private byte[] tmpB4; 79 | 80 | private static final String ip_filename = "qqwry.dat"; 81 | 82 | /** 83 | * 私有构造函数 84 | */ 85 | private IPSeeker() { 86 | tmpBuf = new byte[100]; 87 | tmpB4 = new byte[4]; 88 | 89 | try { 90 | String filepath = getClass().getClassLoader() 91 | .getResource("qqwry.dat").getPath(); 92 | ipFile = new RandomAccessFile(filepath, "r"); 93 | // ipFile = new RandomAccessFile(ClassLoader.getSystemResource( 94 | // ip_filename).getPath(), "r"); 95 | } catch (IOException e) { 96 | logger.error("IP地址信息文件没有找到,IP显示功能将无法使用"); 97 | return; 98 | } 99 | 100 | if (ipFile == null) 101 | return; 102 | 103 | // 读取文件头信息 104 | try { 105 | ipBegin = readInt4(0); 106 | ipEnd = readInt4(4); 107 | if (ipBegin == -1 || ipEnd == -1) { 108 | ipFile.close(); 109 | ipFile = null; 110 | return; 111 | } 112 | } catch (IOException e) { 113 | logger.error("IP地址信息文件格式有错误,IP显示功能将无法使用"); 114 | ipFile = null; 115 | return; 116 | } 117 | 118 | // 映射IP信息文件到内存中 119 | try { 120 | FileChannel fc = ipFile.getChannel(); 121 | long ipFileLen = ipFile.length(); 122 | mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFileLen); 123 | mbb.order(ByteOrder.LITTLE_ENDIAN); 124 | ipFile.close(); 125 | 126 | logger 127 | .info("read ip file to memory, len = " + ipFileLen + " bytes"); 128 | } catch (IOException e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | 133 | public static IPSeeker getInstance() { 134 | IPSeeker instance = localInstance.get(); 135 | if (instance == null) { 136 | instance = new IPSeeker(); 137 | localInstance.set(instance); 138 | } 139 | return instance; 140 | } 141 | 142 | /** 143 | * 根据ip查归属地 144 | */ 145 | public IPLocation getIPLocation(String ip) { 146 | byte[] ipBytes = getIpByteArrayFromString(ip); // 注:末位统一重置为0,以提高缓存命中率 147 | String ipSeg = getIpStringFromBytes(ipBytes); // 形如123.58.181.0的/24网段 148 | 149 | IPLocation loc = ipCacheMap.get(ipSeg); 150 | if (loc == null) { 151 | loc = getIPLocation(ipBytes); 152 | ipCacheMap.put(ipSeg, loc); 153 | } 154 | return loc; 155 | } 156 | 157 | /** 158 | * 根据地点查ip区间 159 | */ 160 | public List getIPEntries(String s) { 161 | List ret = new ArrayList(); 162 | 163 | int endOffset = ipEnd; 164 | for (int offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { 165 | int temp = readInt3(offset); 166 | if (temp != -1) { 167 | IPLocation ipLoc = getIPLocation(temp); 168 | // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续 169 | if (ipLoc.country.indexOf(s) != -1 170 | || ipLoc.area.indexOf(s) != -1) { 171 | IPEntry entry = new IPEntry(); 172 | entry.country = ipLoc.country; 173 | entry.area = ipLoc.area; 174 | // 得到起始IP 175 | readIP(offset - 4, tmpB4); 176 | entry.beginIp = getIpStringFromBytes(tmpB4); 177 | // 得到结束IP 178 | readIP(temp, tmpB4); 179 | entry.endIp = getIpStringFromBytes(tmpB4); 180 | // 添加该记录 181 | ret.add(entry); 182 | } 183 | } 184 | } 185 | return ret; 186 | } 187 | 188 | /** 189 | * 从内存映射文件的offset位置开始的3个字节读取一个int 190 | * 191 | * @param offset 192 | * @return 193 | */ 194 | private int readInt3(int offset) { 195 | mbb.position(offset); 196 | return mbb.getInt() & 0x00FFFFFF; 197 | } 198 | 199 | /** 200 | * 从内存映射文件的当前位置开始的3个字节读取一个int 201 | * 202 | * @return 203 | */ 204 | private int readInt3() { 205 | return mbb.getInt() & 0x00FFFFFF; 206 | } 207 | 208 | /** 209 | * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到 210 | * 211 | * @param ip 212 | * 要查询的IP 213 | * @return IPLocation结构 214 | */ 215 | private IPLocation getIPLocation(byte[] ip) { 216 | IPLocation loc = null; 217 | int offset = locateIP(ip); 218 | if (offset != -1) 219 | loc = getIPLocation(offset); 220 | if (loc == null) { 221 | loc = new IPLocation(); 222 | loc.country = IPLocation.UNKNOWN_COUNTRY; 223 | loc.area = IPLocation.UNKNOWN_AREA; 224 | } 225 | return loc; 226 | } 227 | 228 | /** 229 | * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法 用了这么一个函数来做转换 230 | * 231 | * @param offset 232 | * @return 读取的long值,返回-1表示读取文件失败 233 | */ 234 | private int readInt4(int offset) { 235 | if (ipFile == null) 236 | return -1; 237 | 238 | int ret = 0; 239 | try { 240 | ipFile.seek(offset); 241 | ret |= (ipFile.readByte() & 0xFF); 242 | ret |= ((ipFile.readByte() << 8) & 0xFF00); 243 | ret |= ((ipFile.readByte() << 16) & 0xFF0000); 244 | ret |= ((ipFile.readByte() << 24) & 0xFF000000); 245 | return ret; 246 | } catch (IOException e) { 247 | return -1; 248 | } 249 | } 250 | 251 | /** 252 | * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是 253 | * 文件中是little-endian形式,将会进行转换 254 | * 255 | * @param offset 256 | * @param ip 257 | */ 258 | private void readIP(int offset, byte[] ip) { 259 | mbb.position(offset); 260 | mbb.get(ip); 261 | byte temp = ip[0]; 262 | ip[0] = ip[3]; 263 | ip[3] = temp; 264 | temp = ip[1]; 265 | ip[1] = ip[2]; 266 | ip[2] = temp; 267 | } 268 | 269 | /** 270 | * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的 271 | * 272 | * @param ip 273 | * 要查询的IP 274 | * @param beginIp 275 | * 和被查询IP相比较的IP 276 | * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。 277 | */ 278 | private int compareIP(byte[] ip, byte[] beginIp) { 279 | for (int i = 0; i < 4; i++) { 280 | int r = compareByte(ip[i], beginIp[i]); 281 | if (r != 0) 282 | return r; 283 | } 284 | return 0; 285 | } 286 | 287 | /** 288 | * 把两个byte当作无符号数进行比较 289 | * 290 | * @param b1 291 | * @param b2 292 | * @return 若b1大于b2则返回1,相等返回0,小于返回-1 293 | */ 294 | private int compareByte(byte b1, byte b2) { 295 | if ((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于 296 | return 1; 297 | else if ((b1 ^ b2) == 0)// 判断是否相等 298 | return 0; 299 | else 300 | return -1; 301 | } 302 | 303 | /** 304 | * 这个方法将根据ip的内容,定位到包含这个ip国家地区的记录处,返回一个绝对偏移 方法使用二分法查找。 305 | * 306 | * @param ip 307 | * 要查询的IP 308 | * @return 如果找到了,返回结束IP的偏移,如果没有找到,返回-1 309 | */ 310 | private int locateIP(byte[] ip) { 311 | int m = 0; 312 | int r; 313 | // 比较第一个ip项 314 | readIP(ipBegin, tmpB4); 315 | r = compareIP(ip, tmpB4); 316 | if (r == 0) 317 | return ipBegin; 318 | else if (r < 0) 319 | return -1; 320 | // 开始二分搜索 321 | for (int i = ipBegin, j = ipEnd; i < j;) { 322 | m = getMiddleOffset(i, j); 323 | readIP(m, tmpB4); 324 | r = compareIP(ip, tmpB4); 325 | // log.debug(Utils.getIpStringFromBytes(b)); 326 | if (r > 0) 327 | i = m; 328 | else if (r < 0) { 329 | if (m == j) { 330 | j -= IP_RECORD_LENGTH; 331 | m = j; 332 | } else 333 | j = m; 334 | } else 335 | return readInt3(m + 4); 336 | } 337 | // 如果循环结束了,那么i和j必定是相等的,这个记录为最可能的记录,但是并非 338 | // 肯定就是,还要检查一下,如果是,就返回结束地址区的绝对偏移 339 | m = readInt3(m + 4); 340 | readIP(m, tmpB4); 341 | r = compareIP(ip, tmpB4); 342 | if (r <= 0) 343 | return m; 344 | else 345 | return -1; 346 | } 347 | 348 | /** 349 | * 得到begin偏移和end偏移中间位置记录的偏移 350 | * 351 | * @param begin 352 | * @param end 353 | * @return 354 | */ 355 | private int getMiddleOffset(int begin, int end) { 356 | int records = (end - begin) / IP_RECORD_LENGTH; 357 | records >>= 1; 358 | if (records == 0) 359 | records = 1; 360 | return begin + records * IP_RECORD_LENGTH; 361 | } 362 | 363 | /** 364 | * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构,此方法应用与内存映射文件方式 365 | * 366 | * @param offset 367 | * 国家记录的起始偏移 368 | * @return IPLocation对象 369 | */ 370 | private IPLocation getIPLocation(int offset) { 371 | IPLocation loc = new IPLocation(); 372 | // 跳过4字节ip 373 | mbb.position(offset + 4); 374 | // 读取第一个字节判断是否标志字节 375 | byte b = mbb.get(); 376 | if (b == REDIRECT_MODE_1) { 377 | // 读取国家偏移 378 | int countryOffset = readInt3(); 379 | // 跳转至偏移处 380 | mbb.position(countryOffset); 381 | // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向 382 | b = mbb.get(); 383 | if (b == REDIRECT_MODE_2) { 384 | loc.country = readString(readInt3()); 385 | mbb.position(countryOffset + 4); 386 | } else 387 | loc.country = readString(countryOffset); 388 | // 读取地区标志 389 | loc.area = readArea(mbb.position()); 390 | } else if (b == REDIRECT_MODE_2) { 391 | loc.country = readString(readInt3()); 392 | loc.area = readArea(offset + 8); 393 | } else { 394 | loc.country = readString(mbb.position() - 1); 395 | loc.area = readArea(mbb.position()); 396 | } 397 | return loc; 398 | } 399 | 400 | /** 401 | * @param offset 402 | * 地区记录的起始偏移 403 | * @return 地区名字符串 404 | */ 405 | private String readArea(int offset) { 406 | mbb.position(offset); 407 | byte b = mbb.get(); 408 | if (b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) { 409 | int areaOffset = readInt3(); 410 | if (areaOffset == 0) 411 | return IPLocation.UNKNOWN_AREA; 412 | else 413 | return readString(areaOffset); 414 | } else 415 | return readString(offset); 416 | } 417 | 418 | /** 419 | * 从内存映射文件的offset位置得到一个0结尾字符串 420 | * 421 | * @param offset 422 | * 字符串起始偏移 423 | * @return 读取的字符串,出错返回空字符串 424 | */ 425 | private String readString(int offset) { 426 | try { 427 | mbb.position(offset); 428 | int i; 429 | for (i = 0, tmpBuf[i] = mbb.get(); tmpBuf[i] != 0; tmpBuf[++i] = mbb 430 | .get()); 431 | if (i != 0) 432 | return getString(tmpBuf, 0, i, "GBK"); 433 | } catch (IllegalArgumentException e) { 434 | logger.error(e.getMessage()); 435 | } 436 | return ""; 437 | } 438 | 439 | /** 440 | * @param ip 441 | * ip的字节数组形式 442 | * @return 字符串形式的ip 443 | */ 444 | private static String getIpStringFromBytes(byte[] ip) { 445 | StringBuilder sb = new StringBuilder(); 446 | sb.append(ip[0] & 0xFF); 447 | sb.append('.'); 448 | sb.append(ip[1] & 0xFF); 449 | sb.append('.'); 450 | sb.append(ip[2] & 0xFF); 451 | sb.append('.'); 452 | sb.append(ip[3] & 0xFF); 453 | return sb.toString(); 454 | } 455 | 456 | /** 457 | * 从ip的字符串形式得到字节数组形式 458 | * 459 | * @param ip 460 | * 字符串形式的ip 461 | * @return 字节数组形式的ip 462 | */ 463 | private static byte[] getIpByteArrayFromString(String ip) { 464 | byte[] ret = new byte[4]; 465 | StringTokenizer st = new StringTokenizer(ip, "."); 466 | try { 467 | ret[0] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 468 | ret[1] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 469 | ret[2] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 470 | ret[3] = 0; // [!] 末位统一置0,相同/24网段的ip视为同一个,提高缓存命中率 471 | } catch (Exception e) { 472 | logger.error(e.getMessage()); 473 | } 474 | return ret; 475 | } 476 | 477 | /** 478 | * 根据某种编码方式将字节数组转换成字符串 479 | * 480 | * @param b 481 | * 字节数组 482 | * @param offset 483 | * 要转换的起始位置 484 | * @param len 485 | * 要转换的长度 486 | * @param encoding 487 | * 编码方式 488 | * @return 如果encoding不支持,返回一个缺省编码的字符串 489 | */ 490 | private static String getString(byte[] b, int offset, int len, 491 | String encoding) { 492 | try { 493 | return new String(b, offset, len, encoding); 494 | } catch (UnsupportedEncodingException e) { 495 | return new String(b, offset, len); 496 | } 497 | } 498 | 499 | private static void benchmark() throws Exception { 500 | String ip = "123.58.182.1"; 501 | 502 | StopWatch sw = new StopWatch(); 503 | int times = 100000000; 504 | Random rand = new Random(); 505 | 506 | sw.start(); 507 | for (int i = 0; i < times; i++) { 508 | IPSeeker ips = IPSeeker.getInstance(); 509 | IPLocation ipl = ips.getIPLocation(String.valueOf(rand.nextInt())); 510 | } 511 | System.out.println(sw.getTime() + "ms"); 512 | System.out.println(IPSeeker.ipCacheMap.size()); 513 | 514 | while (true) { 515 | Thread.sleep(1); 516 | } 517 | } 518 | 519 | public static void main(String[] args) throws Exception { 520 | IPSeeker ips = IPSeeker.getInstance(); 521 | 522 | String ipArray[] = { "123.58.181.1", /* 523 | * "115.236.97.158", 524 | * "182.140.134.24", 525 | * "115.236.153.148", 526 | * "114.113.197.131", 527 | */ 528 | "115.236.153.148", "123.58.181.1", "115.236.153.148", "123.58.181.58" }; 529 | for (String ip: ipArray) { 530 | IPLocation ipl = ips.getIPLocation(ip); 531 | System.out.println(ip + " [" + ipl.country + " " + ipl.area + "]"); 532 | } 533 | 534 | System.out.println("\r\ncache: " + IPSeeker.ipCacheMap.size()); 535 | for (Map.Entry entry: IPSeeker.ipCacheMap 536 | .entrySet()) { 537 | System.out.println(entry.getKey() + " [" + entry.getValue().country 538 | + " " + entry.getValue().area + "]"); 539 | } 540 | 541 | // benchmark(); 542 | } 543 | } 544 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/util/CountryUtil.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | public class CountryUtil { 11 | 12 | private static final Logger logger = LoggerFactory 13 | .getLogger(CountryUtil.class); 14 | 15 | private static final Set CHINA = new HashSet(Arrays.asList( 16 | "北京市", "天津市", 17 | "上海市", 18 | "重庆市", 19 | // 5个自治区 20 | "内蒙古自治区", 21 | "新疆维吾尔自治区", 22 | "西藏自治区", 23 | "宁夏回族自治区", 24 | "广西壮族自治区", 25 | // 香港特别行政区(港) 26 | // 澳门特别行政区(澳) 27 | // 23个省 28 | "黑龙江省", "吉林省", "辽宁省", "河北省", "山西省", "青海省", "山东省", "河南省", "江苏省", "安徽省", 29 | "浙江省", "福建省", "江西省", "湖南省", "湖北省", "广东省", "台湾省", "海南省", "甘肃省", "陕西省", 30 | "四川省", "贵州省", "云南省", 31 | 32 | "北京", "天津", 33 | "上海", 34 | "重庆", 35 | // 5个自治区 36 | "内蒙古", 37 | "新疆", 38 | "西藏", 39 | "宁夏", 40 | "广西", 41 | // 香港特别行政区(港) 42 | // 澳门特别行政区(澳) 43 | // 23个省 44 | "黑龙江", "吉林", "辽宁", "河北", "山西", "青海", "山东", "河南", "江苏", "安徽", 45 | "浙江", "福建", "江西", "湖南", "湖北", "广东", "台湾", "海南", "甘肃", "陕西", 46 | "四川", "贵州", "云南", 47 | 48 | "unknown_country")); 49 | 50 | private static Set SPECIFIED_COUNTRY_SET = new HashSet(); 51 | 52 | public static boolean isChina(String input) { 53 | return CHINA.contains(input); 54 | } 55 | 56 | public static void reloadSpecifiedCountry(String countryList) { 57 | String[] countryArray = countryList.split(","); 58 | Set newCountrySet = new HashSet(); 59 | for (String ip: countryArray) { 60 | if (!ip.isEmpty()) 61 | newCountrySet.add(ip); 62 | } 63 | // 配置发生变化时才更新 64 | if (!newCountrySet.equals(SPECIFIED_COUNTRY_SET)) { 65 | logger.info("reloadSpecifiedCountry: {} -> {}", 66 | SPECIFIED_COUNTRY_SET, newCountrySet); 67 | SPECIFIED_COUNTRY_SET = newCountrySet; 68 | } 69 | } 70 | 71 | /** 72 | * 判断是否属于指定的国家 73 | */ 74 | public static boolean isInSpecifiedCountry(String input) { 75 | return SPECIFIED_COUNTRY_SET.contains(input); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/util/HttpClientPool.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | 6 | import org.apache.http.*; 7 | import org.apache.http.client.ClientProtocolException; 8 | import org.apache.http.client.config.RequestConfig; 9 | import org.apache.http.client.entity.UrlEncodedFormEntity; 10 | import org.apache.http.client.methods.HttpGet; 11 | import org.apache.http.client.methods.HttpPost; 12 | import org.apache.http.client.methods.HttpRequestBase; 13 | import org.apache.http.config.ConnectionConfig; 14 | import org.apache.http.config.MessageConstraints; 15 | import org.apache.http.entity.StringEntity; 16 | import org.apache.http.impl.client.CloseableHttpClient; 17 | import org.apache.http.impl.client.HttpClients; 18 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 19 | import org.apache.http.message.BasicNameValuePair; 20 | import org.apache.http.util.EntityUtils; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.io.IOException; 25 | import java.nio.charset.CodingErrorAction; 26 | import java.util.*; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.Executors; 29 | 30 | public class HttpClientPool { 31 | private static Logger logger = LoggerFactory 32 | .getLogger(HttpClientPool.class); 33 | 34 | private static int defaultHttpTimeoutThreshold = 1000; 35 | 36 | private static class HttpClientPoolHolder { 37 | static final HttpClientPool INSTANCE = new HttpClientPool(); 38 | } 39 | 40 | private CloseableHttpClient client; 41 | 42 | MessageConstraints messageConstraints = MessageConstraints.custom() 43 | .setMaxHeaderCount(200).setMaxLineLength(2000).build(); 44 | 45 | // connection config 46 | ConnectionConfig connectionConfig = ConnectionConfig.custom() 47 | .setMalformedInputAction(CodingErrorAction.IGNORE) 48 | .setUnmappableInputAction(CodingErrorAction.IGNORE) 49 | .setCharset(Consts.UTF_8).setMessageConstraints(messageConstraints) 50 | .build(); 51 | 52 | // request config 53 | RequestConfig defaultRequestConfig = RequestConfig.custom() 54 | .setSocketTimeout(defaultHttpTimeoutThreshold) 55 | .setConnectTimeout(defaultHttpTimeoutThreshold) 56 | .setConnectionRequestTimeout(defaultHttpTimeoutThreshold).build(); 57 | 58 | private HttpClientPool() { 59 | PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 60 | cm.setDefaultMaxPerRoute(50); 61 | cm.setMaxTotal(200); 62 | 63 | cm.setDefaultConnectionConfig(connectionConfig); 64 | 65 | client = HttpClients.custom().setConnectionManager(cm) 66 | .setDefaultRequestConfig(defaultRequestConfig).build(); 67 | 68 | } 69 | 70 | static public HttpClientPool getInstance() { 71 | return HttpClientPoolHolder.INSTANCE; 72 | } 73 | 74 | public StatusLine getMethod(String url) throws ClientProtocolException, 75 | IOException { 76 | HttpGet get = new HttpGet(url); 77 | try { 78 | HttpResponse response = client.execute(get); 79 | StatusLine sl = response.getStatusLine(); 80 | EntityUtils.consume(response.getEntity()); 81 | return sl; 82 | } finally { 83 | get.releaseConnection(); 84 | } 85 | 86 | } 87 | 88 | public String getMethod(String url, int timeout) 89 | throws ClientProtocolException, IOException { 90 | HttpGet get = new HttpGet(url); 91 | try { 92 | RequestConfig requestConfig = RequestConfig 93 | .copy(defaultRequestConfig).setSocketTimeout(timeout).build(); 94 | get.setConfig(requestConfig); 95 | HttpResponse response = client.execute(get); 96 | StatusLine sl = response.getStatusLine(); 97 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 98 | HttpEntity entity = response.getEntity(); 99 | return EntityUtils.toString(entity, "utf-8"); 100 | } else { 101 | logger.error("req url failed, url: {} ,retcode: {}", url, 102 | sl.getStatusCode()); 103 | EntityUtils.consume(response.getEntity()); 104 | } 105 | 106 | } finally { 107 | get.releaseConnection(); 108 | } 109 | return null; 110 | } 111 | 112 | public String getMethod(String url, int timeout, int retryTimes) { 113 | String ret = null; 114 | for (int i = 0; i < retryTimes; i++) { 115 | try { 116 | ret = getMethod(url, timeout); 117 | if (ret == null) 118 | continue; 119 | } catch (Exception e) { 120 | logger.warn("getMethod fail, left retry times: " 121 | + (retryTimes - i - 1), e); 122 | } 123 | 124 | if (ret != null) 125 | break; 126 | } 127 | return ret; 128 | } 129 | 130 | /** 131 | * 邮箱插件相关post请求 132 | * 133 | * @param url 134 | * 要请求的URL 135 | * @param params 136 | * 对应的请求参数 137 | * @return 138 | * @throws ClientProtocolException 139 | * @throws java.io.IOException 140 | */ 141 | public String postMail(String url, Map params) 142 | throws ClientProtocolException, IOException { 143 | return postMail(url, params.entrySet(), null, false); 144 | } 145 | 146 | /** 147 | * 邮箱插件相关post请求 148 | * 149 | * @param url 150 | * 要请求的URL 151 | * @param params 152 | * 对应的请求参数 153 | * @param timeout 154 | * 请求超时时间 155 | * @return 156 | * @throws ClientProtocolException 157 | * @throws java.io.IOException 158 | */ 159 | public String postMail(String url, Map params, int timeout) 160 | throws ClientProtocolException, IOException { 161 | return postMail(url, params.entrySet(), timeout, false); 162 | } 163 | 164 | public String postMail(String url, Map params, int timeout, 165 | boolean setContentType) throws ClientProtocolException, IOException { 166 | return postMail(url, params.entrySet(), timeout, setContentType); 167 | } 168 | 169 | public String postMail(String url, Set> params, 170 | Integer timeout) throws ClientProtocolException, IOException { 171 | return postMail(url, params, timeout, false); 172 | } 173 | 174 | /** 175 | * 邮箱插件相关post请求 176 | * 177 | * @param url 178 | * 要请求的URL 179 | * @param params 180 | * 对应的请求参数 181 | * @param timeout 182 | * 请求超时时间 183 | * @return 184 | * @throws ClientProtocolException 185 | * @throws java.io.IOException 186 | */ 187 | public String postMail(String url, Set> params, 188 | Integer timeout, boolean setContentType) 189 | throws ClientProtocolException, IOException { 190 | HttpPost post = new HttpPost(url); 191 | try { 192 | List nvps = new ArrayList(); 193 | for (Map.Entry entry: params) { 194 | nvps.add(new BasicNameValuePair(entry.getKey(), entry 195 | .getValue())); 196 | } 197 | // 超时判断 198 | if (timeout == null) { 199 | timeout = defaultHttpTimeoutThreshold; 200 | } 201 | 202 | RequestConfig requestConfig = RequestConfig 203 | .copy(defaultRequestConfig).setSocketTimeout(timeout).build(); 204 | post.setConfig(requestConfig); 205 | post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); 206 | if (setContentType) { 207 | //有些URL不支持。 208 | post.addHeader("content-type", 209 | "application/x-www-form-urlencoded"); 210 | } 211 | HttpResponse response = client.execute(post); 212 | 213 | StatusLine sl = response.getStatusLine(); 214 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 215 | HttpEntity entity = response.getEntity(); 216 | return EntityUtils.toString(entity, "utf-8"); 217 | } else { 218 | logger.error("req url failed, url: {} ,retcode: {}", url, 219 | sl.getStatusCode()); 220 | EntityUtils.consume(response.getEntity()); 221 | } 222 | 223 | return null; 224 | } finally { 225 | post.releaseConnection(); 226 | } 227 | } 228 | 229 | /** 230 | * cookies中解析出的 231 | * 232 | * @param headers 233 | * @return 234 | */ 235 | private String getMainAccount(Header[] headers) { 236 | for (Header h: headers) { 237 | String value = h.getValue(); 238 | if (value.contains("P_INFO")) { 239 | 240 | int index = value.indexOf("="); 241 | int len = value.indexOf("|"); 242 | if (len > index) { 243 | return value.substring(index + 1, len); 244 | } 245 | } 246 | } 247 | return null; 248 | } 249 | 250 | /** 251 | * 提交一个post请求,类型是application/json 252 | * 253 | * @param url 254 | * @param jsonContent 255 | * json格式的字符串 256 | * @return 257 | * @throws ClientProtocolException 258 | * @throws java.io.IOException 259 | */ 260 | public String postJson(String url, String jsonContent) 261 | throws ClientProtocolException, IOException { 262 | HttpPost post = new HttpPost(url); 263 | try { 264 | StringEntity requestEntity = new StringEntity(jsonContent, "utf-8"); 265 | requestEntity.setContentType("application/json"); 266 | post.setEntity(requestEntity); 267 | 268 | HttpResponse response = client.execute(post); 269 | StatusLine sl = response.getStatusLine(); 270 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 271 | HttpEntity entity = response.getEntity(); 272 | return EntityUtils.toString(entity, "utf-8"); 273 | } else { 274 | logger.error("req url failed, url: {} ,retcode: {}", url, 275 | sl.getStatusCode()); 276 | EntityUtils.consume(response.getEntity()); 277 | } 278 | return null; 279 | } finally { 280 | post.releaseConnection(); 281 | } 282 | } 283 | 284 | /** 285 | * @param url 286 | * @param params 287 | * @return 288 | * @throws ClientProtocolException 289 | * @throws java.io.IOException 290 | */ 291 | public String postMethod(String url, Map params) 292 | throws ClientProtocolException, IOException { 293 | HttpPost post = new HttpPost(url); 294 | try { 295 | List nvps = new ArrayList(); 296 | for (Map.Entry entry: params.entrySet()) { 297 | nvps.add(new BasicNameValuePair(entry.getKey(), entry 298 | .getValue().toString())); 299 | } 300 | 301 | post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); 302 | HttpResponse response = client.execute(post); 303 | StatusLine sl = response.getStatusLine(); 304 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 305 | HttpEntity entity = response.getEntity(); 306 | return EntityUtils.toString(entity, "utf-8"); 307 | } else { 308 | logger.error("req url failed, url: {} ,retcode: {}", url, 309 | sl.getStatusCode()); 310 | EntityUtils.consume(response.getEntity()); 311 | } 312 | 313 | return null; 314 | } finally { 315 | post.releaseConnection(); 316 | } 317 | } 318 | 319 | public String postMethod(String url, Map params, int timeout) 320 | throws ClientProtocolException, IOException { 321 | return postMethod(url, params, timeout, false); 322 | } 323 | 324 | /** 325 | * @param url 326 | * @param params 327 | * @param timeout 328 | * @param useContentType 329 | * 有些URL接口不支持contenttype不设置的请求 330 | * @return 331 | * @throws ClientProtocolException 332 | * @throws java.io.IOException 333 | */ 334 | public String postMethod(String url, Map params, 335 | int timeout, boolean useContentType) throws ClientProtocolException, 336 | IOException { 337 | HttpPost post = new HttpPost(url); 338 | try { 339 | List nvps = new ArrayList(); 340 | for (Map.Entry entry: params.entrySet()) { 341 | nvps.add(new BasicNameValuePair(entry.getKey(), entry 342 | .getValue().toString())); 343 | } 344 | RequestConfig requestConfig = RequestConfig 345 | .copy(defaultRequestConfig).setSocketTimeout(timeout).build(); 346 | post.setConfig(requestConfig); 347 | 348 | post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); 349 | if (useContentType) { 350 | post.addHeader("content-type", 351 | "application/x-www-form-urlencoded"); 352 | } 353 | HttpResponse response = client.execute(post); 354 | StatusLine sl = response.getStatusLine(); 355 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 356 | HttpEntity entity = response.getEntity(); 357 | return EntityUtils.toString(entity, "utf-8"); 358 | } else { 359 | logger.error("req url failed, url: {} ,retcode: {}", url, 360 | sl.getStatusCode()); 361 | 362 | EntityUtils.consume(response.getEntity()); 363 | 364 | } 365 | return null; 366 | } finally { 367 | post.releaseConnection(); 368 | } 369 | } 370 | 371 | public String antiSpamPost(String url, Map params) 372 | throws ClientProtocolException, IOException { 373 | // HttpClient client = new DefaultHttpClient(); 374 | HttpPost post = new HttpPost(url); 375 | try { 376 | List nvps = new ArrayList(); 377 | for (Map.Entry entry: params.entrySet()) { 378 | nvps.add(new BasicNameValuePair(entry.getKey(), entry 379 | .getValue().toString())); 380 | } 381 | // todo 临时代码, 先强制把超时时间设短 382 | RequestConfig requestConfig = RequestConfig 383 | .copy(defaultRequestConfig).setSocketTimeout(200) 384 | .setConnectTimeout(200).build(); 385 | post.setConfig(requestConfig); 386 | post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); 387 | HttpResponse response = client.execute(post); 388 | StatusLine sl = response.getStatusLine(); 389 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 390 | HttpEntity entity = response.getEntity(); 391 | return EntityUtils.toString(entity, "utf-8"); 392 | } else { 393 | logger.error("req url failed, url: {} ,retcode: {}", url, 394 | sl.getStatusCode()); 395 | EntityUtils.consume(response.getEntity()); 396 | } 397 | 398 | return null; 399 | } finally { 400 | post.releaseConnection(); 401 | } 402 | 403 | } 404 | 405 | public String postMethod(String url, String entity) 406 | throws ClientProtocolException, IOException { 407 | HttpPost post = new HttpPost(url); 408 | try { 409 | HttpEntity str = new StringEntity(entity, "UTF-8"); 410 | post.setEntity(str); 411 | 412 | HttpResponse response = client.execute(post); 413 | StatusLine sl = response.getStatusLine(); 414 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 415 | HttpEntity respEntity = response.getEntity(); 416 | return EntityUtils.toString(respEntity, "utf-8"); 417 | } else { 418 | logger.error("req url failed, url: {} ,retcode: {}", url, 419 | sl.getStatusCode()); 420 | 421 | EntityUtils.consume(response.getEntity()); 422 | 423 | } 424 | 425 | return null; 426 | } finally { 427 | post.releaseConnection(); 428 | } 429 | } 430 | 431 | public String postMethod(String url, String entity, int timeout) 432 | throws ClientProtocolException, IOException { 433 | HttpPost post = new HttpPost(url); 434 | try { 435 | RequestConfig requestConfig = RequestConfig 436 | .copy(defaultRequestConfig).setSocketTimeout(timeout).build(); 437 | post.setConfig(requestConfig); 438 | 439 | HttpEntity str = new StringEntity(entity, "UTF-8"); 440 | post.setEntity(str); 441 | 442 | HttpResponse response = client.execute(post); 443 | StatusLine sl = response.getStatusLine(); 444 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 445 | HttpEntity respEntity = response.getEntity(); 446 | return EntityUtils.toString(respEntity, "utf-8"); 447 | } else { 448 | logger.error("req url failed, url: {} ,retcode: {}", url, 449 | sl.getStatusCode()); 450 | 451 | EntityUtils.consume(response.getEntity()); 452 | 453 | } 454 | 455 | return null; 456 | } finally { 457 | post.releaseConnection(); 458 | } 459 | } 460 | 461 | public String execute(HttpRequestBase post) throws ClientProtocolException, 462 | IOException { 463 | try { 464 | 465 | HttpResponse response = client.execute(post); 466 | StatusLine sl = response.getStatusLine(); 467 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 468 | logger.info("Http execute success"); 469 | HttpEntity respEntity = response.getEntity(); 470 | return EntityUtils.toString(respEntity, "utf-8"); 471 | } else { 472 | logger.error("req url failed, url: " + post.getURI().toString() 473 | + ",retcode: " + sl.getStatusCode()); 474 | EntityUtils.consume(response.getEntity()); 475 | } 476 | 477 | return null; 478 | } finally { 479 | post.releaseConnection(); 480 | } 481 | } 482 | 483 | public String execute(HttpRequestBase post, int timeout) 484 | throws ClientProtocolException, IOException { 485 | try { 486 | RequestConfig requestConfig = RequestConfig 487 | .copy(defaultRequestConfig).setSocketTimeout(timeout).build(); 488 | post.setConfig(requestConfig); 489 | HttpResponse response = client.execute(post); 490 | StatusLine sl = response.getStatusLine(); 491 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 492 | logger.info("Http execute success"); 493 | HttpEntity respEntity = response.getEntity(); 494 | return EntityUtils.toString(respEntity, "utf-8"); 495 | } else { 496 | logger.error("req url failed, url: " + post.getURI().toString() 497 | + ",retcode: " + sl.getStatusCode()); 498 | EntityUtils.consume(response.getEntity()); 499 | } 500 | return null; 501 | } finally { 502 | post.releaseConnection(); 503 | } 504 | } 505 | 506 | /** 507 | * httpsync特有execute,需要特殊处理下返回码code. 508 | * 509 | * @param post 510 | * @return 511 | * @throws ClientProtocolException 512 | * @throws java.io.IOException 513 | */ 514 | public String httpSyncExecute(HttpRequestBase post) 515 | throws ClientProtocolException, IOException { 516 | try { 517 | 518 | HttpResponse response = client.execute(post); 519 | StatusLine sl = response.getStatusLine(); 520 | if (sl.getStatusCode() == HttpStatus.SC_OK) { 521 | logger.info("Http execute success"); 522 | HttpEntity respEntity = response.getEntity(); 523 | return EntityUtils.toString(respEntity, "utf-8"); 524 | } else { 525 | logger.info("req url failed, url: " + post.getURI().toString() 526 | + ",retcode: " + sl.getStatusCode()); 527 | // httpsync访问内部应用时,直接HttpStatus.400了,对应的code放在了entity了。 528 | HttpEntity entity = response.getEntity(); 529 | return EntityUtils.toString(entity, "utf-8"); 530 | } 531 | } finally { 532 | post.releaseConnection(); 533 | } 534 | } 535 | 536 | public static void main(final String[] args) 537 | throws ClientProtocolException, IOException { 538 | final String[] uris = { "http://hc.apache.org/", 539 | "http://hc.apache.org/httpcomponents-core-ga/", 540 | "http://hc.apache.org/httpcomponents-client-ga/", 541 | "http://svn.apache.org/viewvc/httpcomponents/", 542 | "http://www.baidu.com", "http://www.google.com", 543 | "http://www.youdao.com", "http://www.soso.com", 544 | "http://www.sogou.com", "http://www.badurltest.com", 545 | "http://192.168.164.95", }; 546 | final Map params = new HashMap(); 547 | params.put("1", "2"); 548 | ExecutorService es = Executors.newFixedThreadPool(10); 549 | for (int i = 0; i < 10; i++) { 550 | es.execute(new Runnable() { 551 | 552 | @Override 553 | public void run() { 554 | // TODO Auto-generated method stub 555 | try { 556 | String url = uris[(int) (Thread.currentThread().getId() % uris.length)]; 557 | logger.debug("begin post {}", url); 558 | String ret = HttpClientPool.getInstance().postMethod( 559 | url, params, 1000); 560 | System.out.println(ret); 561 | } catch (ClientProtocolException e) { 562 | // TODO Auto-generated catch block 563 | logger.debug(e.getMessage(), e); 564 | } catch (IOException e) { 565 | // TODO Auto-generated catch block 566 | logger.debug(e.getMessage(), e); 567 | } 568 | } 569 | }); 570 | } 571 | 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /src/main/java/org/lic/ip/util/IPUtil.java: -------------------------------------------------------------------------------- 1 | package org.lic.ip.util; 2 | 3 | import org.lic.ip.crawler.IPRange; 4 | import org.lic.ip.crawler.IPv4Network; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.FileReader; 9 | import java.util.*; 10 | 11 | /** 12 | * Created by lc on 14/10/30. 13 | */ 14 | public class IPUtil { 15 | private static Random random = new Random(); 16 | 17 | // Convert dotted IPv4 address to integer. 18 | public static long ipString2Long(String ip) { 19 | String[] ss = ip.split("\\."); 20 | if (ss.length != 4) { 21 | return -1L; 22 | } 23 | long ret = 0; 24 | for (int i=0; i<4; i++) { 25 | ret = ret << 8 | Integer.parseInt(ss[i]); 26 | } 27 | return ret; 28 | } 29 | 30 | // Convert 32-bit integer to dotted IPv4 address. 31 | public static String ipLong2String(long lip) { 32 | String s1 = String.valueOf(lip >> 24 & 0xFF); 33 | String s2 = String.valueOf(lip >> 16 & 0xFF); 34 | String s3 = String.valueOf(lip >> 8 & 0xFF); 35 | String s4 = String.valueOf(lip >> 0 & 0xFF); 36 | String ip = s1 + "." + s2 + "." + s3 + "." + s4; 37 | return ip; 38 | } 39 | 40 | public static String convertIntIpToString(Integer ip) { 41 | StringBuffer sb = new StringBuffer(15); 42 | for (int shift = 24; shift > 0; shift -= 8) { 43 | // process 3 bytes, from high order byte down. 44 | sb.append(Integer.toString((ip >>> shift) & 0xff)); 45 | sb.append('.'); 46 | } 47 | sb.append(Integer.toString(ip & 0xff)); 48 | return sb.toString(); 49 | } 50 | 51 | public static int convertStringIpToInt(String ip) { 52 | // String[] ss = ip.split("\\."); 53 | // if (ss.length != 4) { 54 | // return -1; 55 | // } 56 | // int ret = 0; 57 | // for (int i=0; i<4; i++) { 58 | // ret = ret << 8 | Integer.parseInt(ss[i]); 59 | // } 60 | // return ret; 61 | String[] ss = ip.split("\\."); 62 | if (ss.length != 4) { 63 | return -1; 64 | } 65 | long ret = 0; 66 | for (int i=0; i<4; i++) { 67 | ret = ret << 8 | Integer.parseInt(ss[i]); 68 | } 69 | return (int)ret; 70 | } 71 | 72 | public static String getRandomIp(long netWork) { 73 | int tmp = random.nextInt(255); 74 | return ipLong2String(netWork + tmp); 75 | } 76 | 77 | public static int getAmount(String cidr) { 78 | int masklen = Integer.parseInt(cidr.split("/")[1]); 79 | return 1<<(32-masklen); 80 | } 81 | 82 | public static int getPrefixlen(String cidr) { 83 | int masklen = Integer.parseInt(cidr.split("/")[1]); 84 | return masklen; 85 | } 86 | 87 | public static String getRandomIp(String baseIP, int masklen) { 88 | int tmp = Math.abs(random.nextInt((1<<(32-masklen)) -1)); 89 | return ipLong2String(ipString2Long(baseIP) + tmp); 90 | } 91 | 92 | public static int getSmallestMasklen(int amount) { 93 | // int i = 0; 94 | // while (Math.pow(2, i) != amount) { 95 | // if (Math.pow(2, i) < amount) { 96 | // i++; 97 | // } else { 98 | // amount = amount - (int)Math.pow(2, i-1); 99 | // i = 0; 100 | // } 101 | // } 102 | // return 32 - i; 103 | int i = 1; 104 | int ret = 0; 105 | while ((i&amount) == 0) { 106 | i = i<<1; 107 | ret++; 108 | } 109 | return 32 - ret; 110 | } 111 | 112 | public static LinkedList mergeCidrs(List cidrs) { 113 | TreeSet rangesSet = new TreeSet(); 114 | for (String cidr : cidrs) { 115 | rangesSet.add(new IPv4Network(cidr).getIPRange()); 116 | } 117 | LinkedList ranges = new LinkedList(rangesSet); 118 | int i = 1; 119 | while (i < ranges.size()) { 120 | IPRange pRange = ranges.get(i-1); 121 | IPRange range = ranges.get(i); 122 | if (range.start - 1 <= pRange.end) { 123 | IPRange newRange = new IPRange(pRange.start, Math.max(range.end, pRange.end)); 124 | newRange.prefixlen = getPrefixlen(range.cidr); 125 | ranges.set(i-1, newRange); 126 | ranges.remove(i); 127 | } else { 128 | i++; 129 | } 130 | } 131 | LinkedList result = new LinkedList(); 132 | for (IPRange range : ranges) { 133 | if (range.cidr != null) { 134 | result.add(range.cidr); 135 | } else { 136 | for (IPv4Network network : iprangeToCidrs(range)) { 137 | result.add(network.getCIDR()); 138 | } 139 | } 140 | } 141 | return result; 142 | } 143 | 144 | public static LinkedList iprangeToCidrs(IPRange range) { 145 | if (range.prefixlen < 8 || range.prefixlen > 32) { 146 | range.prefixlen = 24; 147 | } 148 | LinkedList cidrList = new LinkedList(); 149 | IPv4Network spanCidr = spanningCidr(range.start, range.end, 150 | range.prefixlen); 151 | if (spanCidr.getIPRange().start < range.start) { 152 | IPv4Network exclude = new IPv4Network(range.start, range.prefixlen); 153 | cidrList = cidrPartition(spanCidr, exclude).get(2); 154 | spanCidr = cidrList.pollLast(); 155 | } 156 | if (spanCidr.getIPRange().end > range.end) { 157 | IPv4Network exclude = new IPv4Network(range.end, range.prefixlen); 158 | cidrList.addAll(cidrPartition(spanCidr, exclude).get(0)); 159 | } else { 160 | cidrList.addLast(spanCidr); 161 | } 162 | return cidrList; 163 | } 164 | 165 | public static IPv4Network spanningCidr(long start, long end, int prefixlen) { 166 | if (start == 704380928) { 167 | System.out.println("debug"); 168 | } 169 | //int prefixlen = 24; 170 | // int ipnum = end; //- (1<<(32 - prefixlen)); 171 | // while (prefixlen > 0 && ipnum > start) { 172 | // prefixlen--; 173 | // ipnum &= -(1<<(32-prefixlen)); 174 | // //ipnum = ipnum - (1<<(32 - prefixlen)); 175 | // } 176 | // if (prefixlen < 8 || prefixlen > 32) { 177 | // System.out.println("debug"); 178 | // } 179 | while (end - start > (1<<(32-prefixlen))) { 180 | prefixlen--; 181 | } 182 | return new IPv4Network(end - (1<<(32-prefixlen)), prefixlen); 183 | } 184 | 185 | public static LinkedList> cidrPartition(IPv4Network target, IPv4Network exclude) { 186 | LinkedList> ret = new LinkedList>(); 187 | 188 | LinkedList left = new LinkedList(); 189 | LinkedList middle = new LinkedList(); 190 | LinkedList right = new LinkedList(); 191 | if (exclude.getIPRange().end < target.getIPRange().start) { 192 | right.add(target); 193 | ret.addLast(left); 194 | ret.addLast(middle); 195 | ret.addLast(right); 196 | return ret; 197 | } 198 | if (target.getIPRange().end < exclude.getIPRange().start) { 199 | left.add(target); 200 | ret.addLast(left); 201 | ret.addLast(middle); 202 | ret.addLast(right); 203 | return ret; 204 | } 205 | if (target.getMasklen() >= exclude.getMasklen()) { 206 | middle.add(target); 207 | ret.addLast(left); 208 | ret.addLast(middle); 209 | ret.addLast(right); 210 | return ret; 211 | } 212 | 213 | int newPrefixlen = target.getMasklen() + 1; 214 | long targetStart = target.getIPRange().start; 215 | long i_lower = targetStart; 216 | long i_upper = targetStart + (1 << (32 - newPrefixlen)); 217 | long matched; 218 | while (exclude.getMasklen() >= newPrefixlen) { 219 | if (exclude.getIPRange().start > i_upper) { 220 | left.add(new IPv4Network(i_lower, newPrefixlen)); 221 | matched = i_upper; 222 | } else { 223 | right.add(new IPv4Network(i_upper, newPrefixlen)); 224 | matched = i_lower; 225 | } 226 | newPrefixlen++; 227 | 228 | if (newPrefixlen > 32) 229 | break; 230 | 231 | i_lower = matched; 232 | i_upper = matched + (1 << (32 - newPrefixlen)); 233 | } 234 | middle.add(exclude); 235 | Collections.reverse(right); 236 | 237 | ret.addLast(left); 238 | ret.addLast(middle); 239 | ret.addLast(right); 240 | return ret; 241 | } 242 | 243 | public static void main(String[] args) throws Exception { 244 | // BufferedReader reader = new BufferedReader(new FileReader(new File("/Users/lc/github/ipdb_creator/output/cn-original"))); 245 | // List cidrs = new ArrayList(); 246 | // String line; 247 | // while((line = reader.readLine()) != null) { 248 | // cidrs.add(line.split(";")[0]); 249 | // } 250 | // System.out.println(mergeCidrs(cidrs)); 251 | //System.out.println(getSmallestMasklen(1024)); 252 | //System.out.println(ipLong2String(0x80000000L)); 253 | System.out.println(ipString2Long("255.255.255.255")); 254 | // int ip = 704643072; 255 | // ip &= -(1<<(32-19)); 256 | // System.out.println(ip); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/resources/dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | bin 9 | 10 | zip 11 | 12 | 13 | 14 | 15 | true 16 | lib 17 | 18 | 19 | 20 | 21 | 22 | 23 | ./conf 24 | src/main/resources 25 | 26 | log4j.properties 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------- 2 | # Log4J configuration for NetEase PoPo 2006 server 3 | # -------------------------------------------------------------- 4 | 5 | log4j.logger.org.lic = INFO, consoleAppender 6 | 7 | log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender 8 | log4j.appender.consoleAppender.Encoding = UTF-8 9 | log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout 10 | log4j.appender.consoleAppender.layout.ConversionPattern = [%-5p]%d{ISO8601}, [Class]%-c{1}, %m%n 11 | 12 | log4j.appender.appServerRolling = org.apache.log4j.RollingFileAppender 13 | log4j.appender.appServerRolling.Append = true 14 | log4j.appender.appServerRolling.BufferedIO = false 15 | log4j.appender.appServerRolling.File = /Users/lc/dev/log/ip.log 16 | log4j.appender.appServerRolling.Encoding = UTF-8 17 | log4j.appender.appServerRolling.layout = org.apache.log4j.PatternLayout 18 | log4j.appender.appServerRolling.layout.ConversionPattern = [%-5p]%d{ISO8601}, [Class]%-c{1}, %m%n 19 | log4j.appender.appServerRolling.MaxBackupIndex = 10 20 | log4j.appender.appServerRolling.MaxFileSize = 512MB 21 | -------------------------------------------------------------------------------- /src/main/resources/qqwry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/licheng-xd/ip-locator/a11b3633d038cacbe4a85f1f7909eb87a3d7ed48/src/main/resources/qqwry.dat -------------------------------------------------------------------------------- /src/test/java/org/lic/test/Test1.java: -------------------------------------------------------------------------------- 1 | package org.lic.test; 2 | 3 | import org.junit.Test; 4 | import org.lic.ip.iplocator.IPv4RadixIntTree; 5 | import org.lic.ip.ipseeker.IPSeeker; 6 | 7 | /** 8 | * Created by lc on 14-10-9. 9 | */ 10 | public class Test1 { 11 | 12 | //private static final IPSeeker ipSeeker = IPSeeker.getInstance(); // 纯真 13 | 14 | private static final IPv4RadixIntTree ipLoactor = IPv4RadixIntTree.getInstance(); // 文本库 15 | 16 | @Test 17 | public void test() throws Exception { 18 | String ip = "172.29.31.1"; 19 | //System.out.println(ipSeeker.getIPLocation(ip)); 20 | System.out.println(ipLoactor.get(ip)); 21 | } 22 | } 23 | --------------------------------------------------------------------------------