存储格式: A.文件头(大小8字节), B.记录区(不定长),C.索引区(大小由文件头决定)
A.文件头是8个字节长度,前四个字节存储了第一条索引的绝对地址,
后四个字节存储了最后一条索引的绝对地址。
(2^32字节=4GB)
B.记录的格式是[IP地址][国家记录][地区记录]: ip:4字节, 国家记录(字符串,0结尾), 地区记录(字符串,0结尾)
[字段形式]
国家名或者地区名,我们就有了两个可能:
第一就是直接的字符串表示的国家名,
第二就是一个4字节的结构, 第一个字节表明了重定向的模式, 后面3个字节是国家名或者地区名的实际偏移位置(指针)。
[重定向的模式]有两种:0x01、0x02,
0x01:表示指针指向完整的信息;
0x02:表示指针指向国家或地区其中一个字段信息.
[实际的case]:
a:0x01XXXXXX->[国家][地区]
b:(0x02XXXXXX->[国家])[地区]
[国家](0x02XXXXXX->[地区])
c: 0x01XXXXXX->(0x02XXXXXX->[国家])[地区]
0x01XXXXXX->[国家](0x02XXXXXX->[地区])
0x01XXXXXX->(0x02XXXXXX->[国家])(02XXXXXX->[地区])
[版权信息]
实际上就是最后一条IP记录,最后一条记录显示出来就是这样:
255.255.255.0 255.255.255.255 纯真网络 2004年6月25日IP数据。
C.索引结构. 条索引长度为7个字节,前4个字节是起始IP地址,
后三个字节就指向了IP记录。
那么有没有结束IP? 假设有这么一条记录:166.111.0.0 - 166.111.255.255,
那么166.111.0.0就是起始IP,166.111.255.255就是结束IP,
结束IP就是IP记录中的那头4个字节,
1 public class IPLocation 2 { 3 public String country; 4 public String area; 5 6 public IPLocation() 7 { 8 country = area = ""; 9 } 10 } 11 12 public class IPUtil 13 { 14 15 public static string GetIpArea(string filepath, string ip) 16 { 17 if (string.IsNullOrEmpty(filepath) || !File.Exists(filepath)) 18 return string.Empty; 19 IPWry Instance=new IPWry(filepath); 20 if (string.IsNullOrEmpty(ip)) 21 return string.Empty; 22 else if (ip == "0.0.0.0") 23 return "(未获得IP)"; 24 25 else if (ip == "127.0.0.1") 26 return "服务器"; 27 28 else if (ip.StartsWith("192.168.") || ip.StartsWith("10.")) 29 return "本地网络"; 30 else if (ip == "::1") 31 return "服务器"; 32 33 else if (IsIPv6(ip)) 34 return "(不支持ipv6)"; 35 36 try 37 { 38 IPLocation location =Instance.SearchIPLocation(ip); 39 if (location != null) 40 return location.country + location.area; 41 } 42 catch 43 { 44 return "(未知地址)"; 45 } 46 47 return string.Empty; 48 } 49 50 51 public static bool IsIPv6(string ip) 52 { 53 int flag = ip.IndexOf(':'); 54 if (flag > 0 && flag < ip.LastIndexOf(':')) 55 { 56 return true; 57 } 58 return false; 59 } 60 61 62 63 64 }
1 public class IPWry 2 { 3 //文件头: 读取索引的开始位置和结束位置 4 private long ipBegin,ipEnd; 5 6 //记录区 重定向的两种模式 7 private const byte REDIRECT_MODE_1 = 0x01; 8 private const byte REDIRECT_MODE_2 = 0x02; 9 10 //索引区: 字节长度为7 其中[4B(IP)+3B(记录区地址)] 11 private const int IP_RECORD_LENGTH = 7; 12 13 14 //数据库文件 15 private FileStream ipFile; 16 private const string unCountry = "未知国家"; 17 private const string unArea = "未知地区"; 18 19 //存储文本内容 100字节缓冲区 20 private byte[] buf; 21 22 //存储3个字节 23 private byte[] b3; 24 25 //存储4个字节 26 private byte[] b4; 27 28 //IP地址对象 29 private IPLocation loc; 30 31 //ipfile:IP数据库文件绝对路径 32 public IPWry(string ipfile) 33 { 34 35 buf = new byte[100]; 36 b3 = new byte[3]; 37 b4 = new byte[4]; 38 try 39 { 40 ipFile = new FileStream(ipfile, FileMode.Open, FileAccess.Read, FileShare.Read); 41 42 } 43 catch 44 { 45 throw new ArgumentNullException("打开IP数据库文件出错!"); 46 } 47 48 ipBegin = readLong4(0); 49 ipEnd = readLong4(4); 50 loc = new IPLocation(); 51 } 52 53 54 public IPLocation SearchIPLocation(string ip) 55 { 56 57 //将字符IP转换为字节 58 string[] ipSp = ip.Split('.'); 59 if (ipSp.Length != 4) 60 { 61 throw new ArgumentOutOfRangeException("不是合法的IP地址!"); 62 } 63 byte[] IP = new byte[4]; 64 for (int i = 0; i < IP.Length; i++) 65 { 66 IP[i] = (byte)(Int32.Parse(ipSp[i]) & 0xFF); 67 } 68 69 IPLocation local = null; 70 long offset = locateIP(IP);//获取 71 72 if (offset != -1) 73 { 74 local = getIPLocation(offset); 75 } 76 77 if (local == null) 78 { 79 local = new IPLocation(); 80 local.area = unArea; 81 local.country = unCountry; 82 } 83 return local; 84 } 85 86 87 //取得具体信息 88 private IPLocation getIPLocation(long offset) 89 { 90 ipFile.Position = offset + 4; 91 //读取第一个字节判断是否是标志字节 92 byte one = (byte)ipFile.ReadByte(); 93 if (one == REDIRECT_MODE_1) 94 { 95 //第一种模式 96 //读取国家偏移 97 long countryOffset = readLong3(); 98 //转至偏移处 99 ipFile.Position = countryOffset; 100 //再次检查标志字节 101 byte b = (byte)ipFile.ReadByte(); 102 if (b == REDIRECT_MODE_2) 103 { 104 loc.country = readString(readLong3()); 105 ipFile.Position = countryOffset + 4; 106 } 107 else 108 loc.country = readString(countryOffset); 109 110 //读取地区标志 111 loc.area = readArea(ipFile.Position); 112 113 } 114 else if (one == REDIRECT_MODE_2) 115 { 116 //第二种模式 117 loc.country = readString(readLong3()); 118 loc.area = readArea(offset + 8); 119 } 120 else 121 { 122 //普通模式 123 loc.country = readString(--ipFile.Position); 124 loc.area = readString(ipFile.Position); 125 } 126 return loc; 127 } 128 129 130 //取得地区信息 131 private string readArea(long offset) 132 { 133 ipFile.Position = offset; 134 byte one = (byte)ipFile.ReadByte(); 135 if (one == REDIRECT_MODE_1 || one == REDIRECT_MODE_2) 136 { 137 long areaOffset = readLong3(offset + 1); 138 if (areaOffset == 0) 139 return unArea; 140 else 141 { 142 return readString(areaOffset); 143 } 144 } 145 else 146 { 147 return readString(offset); 148 } 149 } 150 151 152 //读取字符串 153 private string readString(long offset) 154 { 155 ipFile.Position = offset; 156 int i = 0; 157 158 do 159 { 160 buf[i] = (byte)ipFile.ReadByte(); 161 } while (buf[i++] != (byte)(0)); 162 163 i--; 164 if (i > 0) 165 return Encoding.Default.GetString(buf, 0, i); 166 else 167 return ""; 168 } 169 170 171 //查找IP地址所在的绝对偏移量 172 private long locateIP(byte[] ip) 173 { 174 long m = 0; 175 int r; 176 177 //比较第一个IP项 178 readIP(ipBegin, b4); 179 r = compareIP(ip, b4); 180 if (r == 0) 181 return ipBegin; 182 else if (r < 0) 183 return -1; 184 //开始二分搜索 185 for (long i = ipBegin, j = ipEnd; i < j; ) 186 { 187 m = this.getMiddleOffset(i, j); 188 readIP(m, b4); 189 r = compareIP(ip, b4); 190 if (r > 0) 191 i = m; 192 else if (r < 0) 193 { 194 if (m == j) 195 { 196 j -= IP_RECORD_LENGTH; 197 m = j; 198 } 199 else 200 { 201 j = m; 202 } 203 } 204 else 205 return readLong3(m + 4); 206 } 207 m = readLong3(m + 4); 208 readIP(m, b4); 209 r = compareIP(ip, b4); 210 if (r <= 0) 211 return m; 212 else 213 return -1; 214 } 215 216 //取得begin和end之间的偏移量 用于二分搜索 217 private long getMiddleOffset(long begin, long end) 218 { 219 long records = (end - begin) / IP_RECORD_LENGTH; 220 records >>= 1; 221 if (records == 0) 222 records = 1; 223 return begin + records * IP_RECORD_LENGTH; 224 } 225 226 227 //读出4字节的IP地址 调换字节顺序 228 private void readIP(long offset, byte[] ip) 229 { 230 ipFile.Position = offset; 231 ipFile.Read(ip, 0, ip.Length); 232 byte tmp = ip[0]; 233 ip[0] = ip[3]; 234 ip[3] = tmp; 235 tmp = ip[1]; 236 ip[1] = ip[2]; 237 ip[2] = tmp; 238 } 239 240 241 //比较IP地址是否相同 242 private int compareIP(byte[] ip, byte[] beginIP) 243 { 244 for (int i = 0; i < 4; i++) 245 { 246 int r = compareByte(ip[i], beginIP[i]); 247 if (r != 0) 248 return r; 249 } 250 return 0; 251 } 252 253 254 //比较两个字节是否相等 255 private int compareByte(byte bsrc, byte bdst) 256 { 257 if ((bsrc & 0xFF) > (bdst & 0xFF)) 258 return 1; 259 else if ((bsrc ^ bdst) == 0) 260 return 0; 261 else 262 return -1; 263 } 264 265 //字节序为little-endian即低位在低地址 266 //从绝对位置 读取4字节 267 private long readLong4(long offset) 268 { 269 long ret = 0; 270 ipFile.Position = offset; 271 ret |= (ipFile.ReadByte() & 0xFF); 272 ret |= ((ipFile.ReadByte() << 8) & 0xFF00); 273 ret |= ((ipFile.ReadByte() << 16) & 0xFF0000); 274 ret |= ((ipFile.ReadByte() << 24) & 0xFF000000); 275 return ret; 276 } 277 278 279 //从绝对位置 读取3字节 280 private long readLong3(long offset) 281 { 282 long ret = 0; 283 ipFile.Position = offset; 284 ret |= (ipFile.ReadByte() & 0xFF); 285 ret |= ((ipFile.ReadByte() << 8) & 0xFF00); 286 ret |= ((ipFile.ReadByte() << 16) & 0xFF0000); 287 return ret; 288 } 289 290 //从当前位置读取3字节 291 private long readLong3() 292 { 293 long ret = 0; 294 ret |= (ipFile.ReadByte() & 0xFF); 295 ret |= ((ipFile.ReadByte() << 8) & 0xFF00); 296 ret |= ((ipFile.ReadByte() << 16) & 0xFF0000); 297 return ret; 298 } 299 300 }
[注意]QQWry.dat里面全部采用了little-endian字节序
[参考]http://www.jb51.net/article/17197.htm