在系统中,查看用户的登录信息是一个很常见的功能。我们往往会记录下用户计算机的IP和地理位置,然而IP地址记录非常容易,但是地理位置相对来说较难。开始,菜鸟是想创建一个IP地址库,根据IP地址库查找相应的地理位置。后来想有没有一个相对完整的IP地址库供菜鸟使用了,于是找到了一个QQwry.dat(纯真数据库)。然而,QQwry.dat是什么呢?以及怎么使用?菜鸟通过各位大神的笔记终于弄懂了,以致写下这篇文章供和我一样的菜鸟学习。
QQwry.dat文件在结构上分为文件头、记录区和索引区3部分。我们一般在使用时先从索引区查找记录偏移,根据记录偏移在从记录区中获取。由于记录区是不定长的,且比较多,因此,一般采用二分查找法进行查找。
QQwry.dat文件的头文件结构非常简单,为8个字节。前4个字节为第一条索引的绝对偏移,后4个字节为最后一条索引的绝对偏移。
每条IP地址的记录区都由国家和地区组成,但是在这里国家和地区都不太明确,相对而言的。国家可能是指一所学校,地区可能指学校中的某一系。于是我们想着IP地址的记录格式可能为:[IP地址][国家名称][地区名称]。
国家和地区可能有很多重复,因此我们我可以用重定向来节约空间。其重定向有两种方式:一种是直接用字符串表示国家或地区;另一种是一个4字节的结构,第1个字节表示重定向的模式,后3个字节表示国家名称或地区名称的实际偏移位置。
重定向的模式分为两种:一种是只有国家,没有地区,也就是说地区记录跟着国家记录走了,在IP地址之后只剩下国家记录的4个字节,后3个字节是一个指针,指向了实际的国家名称,其标识字节为0X01。另一种是既有国家又有地址,即地区记录没有跟着国家记录走。在4个字节的国家记录后还含有地区记录,其标识字节为0X02。
通过了解“文件头”,我们可以了解到文件头实际上是两个指针,分别指向文件的第一条索引和最后一条索引的绝对偏移。我们可以根据头文件定位到索引区,然后开始查找IP。每条索引区为7个字节,前4个字节表示起始IP地址,后3个字节表示结束IP地址。如222.11.0.1-222.11.0.240,222.11.0.1表示起始IP地址,222.11.0.240表示结束IP地址。若我们要查找的IP地址在这个IP地址的范围内,则根据这条索引区查找国家和地区。
package com.test.ip.entity;
public class IpEntity {
private String startIp;// 起始IP
private String endIp;// 结尾IP
private String country;// 国家
private String area;// 区域
// 構造方法:清空数据信息
public IpEntity() {
super();
this.startIp = "";
this.endIp = "";
this.country = "";
this.area = "";
}
// getter和setter方法提供属性对外访问接口
public String getStartIp() {
return startIp;
}
public void setStartIp(String startIp) {
this.startIp = startIp;
}
public String getEndIp() {
return endIp;
}
public void setEndIp(String endIp) {
this.endIp = endIp;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
}
package com.test.ip.entity;
/**
* 封装IP信息,如国家和地区
*
* @author aleyn
*
*/
public class IpLocation {
private String country;// 国家
private String area;// 地区
// 构造方法
public IpLocation() {
this.country = "";
this.area = "";
}
public IpLocation getCopy() {
IpLocation ipLocation = new IpLocation();
ipLocation.setCountry(this.getCountry());
ipLocation.setArea(this.getArea());
return ipLocation;
}
// getter、setter
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getArea() {
return area;
}
public void setArea(String area) {
// 若为局域网,纯真IP地址库的地区会显示CZ88.NET,去除
if (area.trim().equals("CZ88.NET")) {
this.area = "局域网";
} else {
this.area = area;
}
}
}
package com.test.ip.util;
import java.util.StringTokenizer;
/**
* 转换工具
*
* @author aleyn
*
*/
public class ConvertUtils {
private static StringBuffer sb = new StringBuffer();
/**
* IP字符串转字节数组
*
* @param ip
* @return
*/
public static byte[] getIpArray(String ip) {
byte[] buffer = new byte[4];
StringTokenizer stringTokenizer = new StringTokenizer(ip, ".");
try {
buffer[0] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);
buffer[1] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);
buffer[2] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);
buffer[3] = (byte) (Integer.parseInt(stringTokenizer.nextToken()) & 0xFF);
} catch (Exception e) {
e.printStackTrace();
}
return buffer;
}
/**
* IP字节数组换字符串
*
* @param ip
* @return
*/
public static String getIpString(byte[] ip) {
sb.delete(0, sb.length());
sb.append(ip[0] & 0xFF);
sb.append(".");
sb.append(ip[1] & 0xFF);
sb.append(".");
sb.append(ip[2] & 0xFF);
sb.append(".");
sb.append(ip[3] & 0xFF);
return sb.toString();
}
/**
* 根据某种编码将IP字节数组转为字符串
*
* @param ip
* @param offset
* @param length
* @param encode
* @return
*/
public static String getIpString(byte[] ip, int offset, int length,
String encode) {
try {
return new String(ip, offset, length, encode);
} catch (Exception e) {
return new String(ip, offset, length);
}
}
}
package com.test.ip.util;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
public class ReadUtils {
/**
* 从offset未知读取一个4字节为一个long java格式为big-endian
*
* @param offset
* @return
*/
public static long readLongByFour(RandomAccessFile randomAccessFile,
long offset) {
long result = 0;
try {
randomAccessFile.seek(offset);
result |= (randomAccessFile.readByte() & 0xFF);
result |= ((randomAccessFile.readByte() << 8) & 0xFF00);
result |= ((randomAccessFile.readByte() << 16) & 0xFF0000);
result |= ((randomAccessFile.readByte() << 24) & 0xFF000000);
return result;
} catch (Exception e) {
return -1;
}
}
/**
* 从offset位置开始读取3个字节为一个long
*
* @param randomAccessFile
* @param offset
* @param buffer
* @return
*/
public static long readLongByThree(RandomAccessFile randomAccessFile,
long offset, byte[] buffer) {
long result = 0;
try {
randomAccessFile.seek(offset);
randomAccessFile.readFully(buffer);
result |= (buffer[0] & 0xFF);
result |= ((buffer[1] << 8) & 0xFF00);
result |= ((buffer[2] << 16) & 0xFF0000);
return result;
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* 从当前位置读取3个字节为一个long
*
* @param randomAccessFile
* @param buffer
* @return
*/
public static long readLongByThree(RandomAccessFile randomAccessFile,
byte[] buffer) {
long result = 0;
try {
randomAccessFile.readFully(buffer);
result |= (buffer[0] & 0xFF);
result |= ((buffer[1] << 8) & 0xFF00);
result |= ((buffer[2] << 16) & 0xFF0000);
return result;
} catch (Exception e) {
return -1;
}
}
/**
* 从内存映射的offset位置,开始3个字节读取一个int
*
* @param offset
* @return
*/
public static int readIntByThree(MappedByteBuffer mappedByteBuffer,
int offset) {
mappedByteBuffer.position(offset);
return mappedByteBuffer.getInt() & 0x00FFFFFF;
}
/**
* 从内存映射的当前位置,开始3个字节读取一个int
*
* @param mappedByteBuffer
* @return
*/
public static int readIntByThree(MappedByteBuffer mappedByteBuffer) {
return mappedByteBuffer.getInt() & 0x00FFFFFF;
}
/**
* 从offset位置读取4个字节的IP地址放入IP数组中,读取后的IP地址格式为big-endian
*
* @param offset
* @param ip
*/
public static void readIp(RandomAccessFile randomAccessFile, long offset,
byte[] ip) {
try {
randomAccessFile.seek(offset);
randomAccessFile.readFully(ip);
byte temp = ip[0];
ip[0] = ip[3];
ip[3] = temp;
temp = ip[1];
ip[1] = ip[2];
ip[2] = temp;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从offset位置读取一个以0结束的字符串
*
* @param offset
* @return
*/
public static String readString(RandomAccessFile randomAccessFile,
long offset, byte[] buffer) {
try {
randomAccessFile.seek(offset);
int i;
for (i = 0, buffer[i] = randomAccessFile.readByte(); buffer[i] != 0; buffer[++i] = randomAccessFile
.readByte())
;
if (i != 0) {
return ConvertUtils.getIpString(buffer, 0, i, "GBK");
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 从offset位置读取一个以0结束的字符串
*
* @param offset
* @return
*/
public static String readString(MappedByteBuffer mappedByteBuffer,
int offset, byte[] buffer) {
try {
mappedByteBuffer.position(offset);
int i;
for (i = 0, buffer[i] = mappedByteBuffer.get(); buffer[i] != 0; buffer[++i] = mappedByteBuffer
.get())
;
if (i != 0)
return ConvertUtils.getIpString(buffer, 0, i, "GBK");
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return "";
}
/**
* 从offset位置读取4个字节的IP地址放入IP数组中,读取后的IP地址格式为big-endian
*
* @param mappedByteBuffer
* @param offset
* @param ip
*/
public static void readIp(MappedByteBuffer mappedByteBuffer, int offset,
byte[] ip) {
try {
mappedByteBuffer.position(offset);
mappedByteBuffer.get(ip);
byte temp = ip[0];
ip[0] = ip[3];
ip[3] = temp;
temp = ip[1];
ip[1] = ip[2];
ip[2] = temp;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 比较两个字节的大小
*
* @param b1
* @param b2
* @return
*/
private static int compareByte(byte b1, byte b2) {
if ((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于
return 1;
else if ((b1 ^ b2) == 0)// 判断是否相等
return 0;
else
return -1;
}
/**
* 将要查询的IP和起始的IP进行比较
*
* @param ip
* @param beginIp
* @return
*/
public static int compareIp(byte[] ip, byte[] beginIp) {
for (int i = 0; i < 4; i++) {
int j = compareByte(ip[i], beginIp[i]);
if (j != 0) {
return j;
}
}
return 0;
}
}
package com.test.ip.util;
/**
* 提示信息
*
* @author aleyn
*
*/
public interface Message {
public static final String BAD_IP_FILE = "IP地址库文件错误";
public static final String UNKNOW_COUNTRY = "未知国家";
public static final String UNKNOW_AREA = "未知区域";
}
package com.test.ip.viewer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.test.ip.entity.IpEntity;
import com.test.ip.entity.IpLocation;
import com.test.ip.util.ConvertUtils;
import com.test.ip.util.LogFactory;
import com.test.ip.util.Message;
import com.test.ip.util.ReadUtils;
public class IpViewer {
private String fileName = "qqwry.dat";// 纯真IP数据库名
private String fileDir = null;// 保存的文件夹
// 固定常量,如记录长度
private static final int IP_RECORD_LENGTH = 7;
private static final byte REDIRECT_MODE_ONE = 0x01;
private static final byte REDIRECT_MODE_TWO = 0x02;
// 缓存已经查找过的IP地址,避免二次查找,加快速度
private Map cache;
// 随机文件访问类
private RandomAccessFile randomAccessFile;
// 内存映射文件
private MappedByteBuffer mappedByteBuffer;
// 起始地区的开始和结束绝对偏移量
private long begin, end;
// 为提高效率,临时变量
private IpLocation ipLocation;
private byte[] buffer;
private byte[] bufferOne;
private byte[] bufferTwo;
/**
* 初始化
*/
public IpViewer() {
// 获取文件路径和名称
String path = IpViewer.class.getResource("").getPath() + fileName;
fileName = path;
// 初始化变量
this.cache = new HashMap();
this.ipLocation = new IpLocation();
this.buffer = new byte[100];
this.bufferOne = new byte[3];
this.bufferTwo = new byte[4];
try {
randomAccessFile = new RandomAccessFile(fileName, "r");
} catch (FileNotFoundException e) {
// 若文件找不到,在当前目录下重新搜索,并将文件名全部改为小写(有些系统只能识别小写)
String name = new File(fileName).getName().toLowerCase();
File[] files = new File(fileDir).listFiles();
for (int i = 0; i < files.length; i++) {
// 判断是否是文件
if (files[i].isFile()) {
// 判断文件名称
if (files[i].getName().toLowerCase().equals(name)) {
try {
randomAccessFile = new RandomAccessFile(files[i],
"r");
} catch (FileNotFoundException fileNotFoundException) {
e.printStackTrace();
fileName = null;
}
break;
}
}
}
}
// 若文件打开成功,读取文件头信息
if (randomAccessFile != null) {
try {
begin = ReadUtils.readLongByFour(randomAccessFile, 0);
end = ReadUtils.readLongByFour(randomAccessFile, 4);
if (begin == -1 || end == -1) {
randomAccessFile.close();
randomAccessFile = null;
}
} catch (Exception e) {
e.printStackTrace();
randomAccessFile = null;
}
}
}
/**
* 给定一个不完全的地点名称,得到包含该地点的IP范围记录
*
* @param str
* @return
*/
public List getIpEntityDebug(String str) {
List list = new ArrayList();
long endOffset = end + 4;
for (long offset = begin + 4; offset < endOffset; offset += IP_RECORD_LENGTH) {
// 读取结束IP偏移量
long temp = ReadUtils.readLongByThree(randomAccessFile, offset,
bufferOne);
// 若temp不等于-1,则读取IP信息
if (temp != -1) {
IpLocation ipLocation = this.getIpLocation(temp);
// 判断是否包含改地名,若包含,择添加到list
if (ipLocation.getCountry().indexOf(str) != -1
|| ipLocation.getArea().indexOf(str) != -1) {
IpEntity ipEntity = new IpEntity();
ipEntity.setCountry(ipLocation.getCountry());
ipEntity.setArea(ipLocation.getArea());
// 获取起始IP
ReadUtils.readIp(randomAccessFile, offset - 4, bufferTwo);
ipEntity.setStartIp(ConvertUtils.getIpString(bufferTwo));
// 获取结束IP
ReadUtils.readIp(randomAccessFile, temp, bufferTwo);
ipEntity.setEndIp(ConvertUtils.getIpString(bufferTwo));
list.add(ipEntity);
}
}
}
return list;
}
public List getIpEntity(String s) {
List list = new ArrayList();
try {
// 映射IP信息文件到内存中
if (mappedByteBuffer == null) {
FileChannel fc = randomAccessFile.getChannel();
mappedByteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0,
randomAccessFile.length());
mappedByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
}
int endOffset = (int) end;
for (int offset = (int) begin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) {
int temp = ReadUtils.readIntByThree(mappedByteBuffer, offset);
if (temp != -1) {
IpLocation location = this.getIpLocation(temp);
// 地点是否包含要查找的地点名称
if (location.getCountry().indexOf(s) != -1
|| location.getArea().indexOf(s) != -1) {
IpEntity entity = new IpEntity();
entity.setCountry(location.getCountry());
entity.setArea(location.getArea());
// 得到起始IP
ReadUtils.readIp(mappedByteBuffer, offset, bufferTwo);
entity.setStartIp(ConvertUtils.getIpString(bufferTwo));
// 得到结束IP
ReadUtils.readIp(mappedByteBuffer, temp, bufferTwo);
entity.setEndIp(ConvertUtils.getIpString(bufferTwo));
// 添加该记录
list.add(entity);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 获取IP所在的地区信息
*/
public IpLocation getIpLocation(String ip) {
IpLocation location = new IpLocation();
location.setCountry(this.getCountry(ip));
location.setArea(this.getArea(ip));
return location;
}
/**
* 根据IP地区偏移量,获取IP信息
*
* @param offset
* @return
*/
private IpLocation getIpLocation(long offset) {
try {
// 跳过4字节IP
randomAccessFile.seek(offset + 4);
// 读取第一个字节,判断是否是标识字节
byte result = randomAccessFile.readByte();
if (result == REDIRECT_MODE_ONE) {
// 读取国家偏移量
long countryOffset = ReadUtils.readLongByThree(
randomAccessFile, bufferOne);
// 跳转至偏移处
randomAccessFile.seek(countryOffset);
// 再次检查标识符
result = randomAccessFile.readByte();
if (result == REDIRECT_MODE_TWO) {
ipLocation.setCountry(ReadUtils.readString(
randomAccessFile, ReadUtils.readLongByThree(
randomAccessFile, bufferOne), buffer));
randomAccessFile.seek(countryOffset + 4);
} else {
ipLocation.setCountry(ReadUtils.readString(
randomAccessFile, countryOffset, buffer));
}
// 读取地区标识符
ipLocation.setArea(this.readArea(randomAccessFile
.getFilePointer()));
} else if (result == REDIRECT_MODE_TWO) {
ipLocation.setCountry(ReadUtils.readString(randomAccessFile,
ReadUtils.readLongByThree(randomAccessFile, bufferOne),
buffer));
ipLocation.setArea(this.readArea(offset + 8));
} else {
ipLocation.setCountry(ReadUtils.readString(randomAccessFile,
randomAccessFile.getFilePointer() - 1, buffer));
ipLocation.setArea(this.readArea(randomAccessFile
.getFilePointer()));
}
return ipLocation;
} catch (Exception e) {
return null;
}
}
/**
* 根据IP地区偏移量,获取IP信息
*
* @param offset
* @return
*/
private IpLocation getIpLocation(int offset) {
// 跳过4字节IP
mappedByteBuffer.position(offset + 4);
// 读取第一个字节,判断是否是标识字符
byte result = mappedByteBuffer.get();
if (result == REDIRECT_MODE_ONE) {
// 读取国家偏移量
int countryOffset = ReadUtils.readIntByThree(mappedByteBuffer);
// 跳转至偏移处
mappedByteBuffer.position(countryOffset);
// 再次检查标识符
result = mappedByteBuffer.get();
if (result == REDIRECT_MODE_TWO) {
ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,
ReadUtils.readIntByThree(mappedByteBuffer), buffer));
mappedByteBuffer.position(countryOffset + 4);
} else {
ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,
countryOffset, buffer));
}
// 设置地区标识
ipLocation.setArea(this.readArea(mappedByteBuffer.position()));
} else if (result == REDIRECT_MODE_TWO) {
ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,
ReadUtils.readIntByThree(mappedByteBuffer), buffer));
ipLocation.setArea(this.readArea(offset + 8));
} else {
ipLocation.setCountry(ReadUtils.readString(mappedByteBuffer,
mappedByteBuffer.position() - 1, buffer));
ipLocation.setArea(this.readArea(mappedByteBuffer.position()));
}
return ipLocation;
}
/**
* 获取国家名称字符串
*
* @return
*/
public String getCountry(String ip) {
return this.getCountry(ConvertUtils.getIpArray(ip));
}
/**
* 获取国家地区
*
* @param ip
* @return
*/
public String getCountry(byte[] ip) {
// 检测IP地址文件是否正常
if (randomAccessFile == null) {
return Message.BAD_IP_FILE;
}
// 保存IP,转IP字节数组为字符串
String ipStr = ConvertUtils.getIpString(ip);
// 先从cache中检查ip,若没有在检测文件
if (cache.containsKey(ipStr)) {
IpLocation ipLocation = cache.get(ipStr);
return ipLocation.getCountry();
} else {
IpLocation ipLocation = this.getIpLocation(ip);
cache.put(ipStr, ipLocation.getCopy());
return ipLocation.getCountry();
}
}
/**
* 根据IP搜索IP文件
*
* @param ip
* @return
*/
public IpLocation getIpLocation(byte[] ip) {
IpLocation location = null;
long offset = this.getLocateIp(ip);
if (offset != -1) {
location = this.getIpLocation(offset);
}
if (location == null) {
location = new IpLocation();
location.setCountry(Message.UNKNOW_COUNTRY);
location.setArea(Message.UNKNOW_AREA);
}
return location;
}
/**
* 根据IP内容,定位IP地址所在的国家,返回一个偏移量
*
* @param ip
* @return
*/
public long getLocateIp(byte[] ip) {
long m = 0;
int n;
ReadUtils.readIp(randomAccessFile, begin, bufferTwo);
n = ReadUtils.compareIp(ip, bufferTwo);
if (n == 0)
return begin;
else if (n < 0)
return -1;
// 二分查找法查找IP
for (long i = begin, j = end; i < j;) {
m = this.getMiddleOffset(i, j);
ReadUtils.readIp(randomAccessFile, m, bufferTwo);
n = ReadUtils.compareIp(ip, bufferTwo);
if (n > 0) {
i = m;
} else if (n < 0) {
if (m == j) {
j -= IP_RECORD_LENGTH;
m = j;
} else {
j = m;
}
} else {
return ReadUtils.readLongByThree(randomAccessFile, m + 4,
bufferOne);
}
}
m = ReadUtils.readLongByThree(randomAccessFile, m + 4, bufferOne);
ReadUtils.readIp(randomAccessFile, m, bufferTwo);
n = ReadUtils.compareIp(ip, bufferTwo);
if (n <= 0)
return m;
else
return -1;
}
/**
* 根据IP字节数组获取地区名称
*
* @param ip
* @return
*/
private String getArea(byte[] ip) {
// 检查IP文件是否正常
if (randomAccessFile == null)
return Message.BAD_IP_FILE;
// 保存IP,转换字节数组IP为字符串
String ipStr = ConvertUtils.getIpString(ip);
// 现在cache中搜索结果,若没有,再从文件中查找
if (cache.containsKey(ipStr)) {
IpLocation location = cache.get(ipStr);
return location.getArea();
} else {
IpLocation location = this.getIpLocation(ip);
cache.put(ipStr, location.getCopy());
return location.getArea();
}
}
/**
* 根据IP获取地区名称
*
* @param ip
* @return
*/
private String getArea(String ip) {
return this.getArea(ConvertUtils.getIpArray(ip));
}
/**
* 从offset位置读取地区信息
*
* @param offset
* @return
*/
private String readArea(long offset) {
try {
randomAccessFile.seek(offset);
byte result = randomAccessFile.readByte();
if (result == REDIRECT_MODE_ONE || result == REDIRECT_MODE_TWO) {
long areaOffset = ReadUtils.readLongByThree(randomAccessFile,
offset + 1, bufferOne);
if (areaOffset == 0)
return Message.UNKNOW_AREA;
else
return ReadUtils.readString(randomAccessFile, areaOffset,
buffer);
} else {
return ReadUtils.readString(randomAccessFile, offset, buffer);
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private String readArea(int offset) {
mappedByteBuffer.position(offset);
byte result = mappedByteBuffer.get();
if (result == REDIRECT_MODE_ONE || result == REDIRECT_MODE_TWO) {
int areaOffset = ReadUtils.readIntByThree(mappedByteBuffer);
if (areaOffset == 0)
return Message.UNKNOW_AREA;
else
return ReadUtils.readString(mappedByteBuffer, areaOffset,
buffer);
} else {
return ReadUtils.readString(mappedByteBuffer, offset, buffer);
}
}
/**
* 获取begin和end中间偏移量
*
* @param begin
* @param end
* @return
*/
private long getMiddleOffset(long begin, long end) {
long middle = (end - begin) / IP_RECORD_LENGTH;
middle >>= 1;
if (middle == 0)
middle = 1;
return begin + middle * IP_RECORD_LENGTH;
}
}
package com.test.ip;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import com.test.ip.viewer.IpViewer;
public class Test {
public static void main(String[] args){
String ip = "63.251.90.8";
IpViewer ipViewer = new IpViewer();
System.out.println(ipViewer.getIpLocation(ip).getCountry()+":"+ipViewer.getIpLocation(ip).getArea());
}
}