純真IPデータベースQQWry.datフォーマットの詳細

7078 ワード

IPデータベースというものがあってから、QQの外挂の表示IP机能も生まれて、本人の见识はとても狭くて、他の応用があるかどうか分かりませんが、IPデータベースは确かに良いものです.今ネット上で最も流行しているIPデータベースは純真版だと思います(間違っても私をひっくり返さないでください)、今までそのIP記録の本数は30000に近く、一部のIPに対しては階まで正確に着くことができて、速くありません.2004年4、5月の间に、ちょうどLumaQQの破土が着工して、この人を加えるためにすべて好きで、しかし谁もがどうして好きな顕IP机能を知らないようで、私も纯真版IPデータベースを采用して、その利点は记录が多くて、検索のスピードが速くて、それは1つのファイルQQQWry.datだけですべての记录を含んで、他のプログラムに埋め込むのが便利で、アップグレードも便利です.
きほんこうぞう
QQQWry.datファイルは構造的に3つに分けられる:ファイルヘッダ、記録領域、インデックス領域.一般的にIPを検索する場合は、インデックス領域でレコードオフセットを検索してから、レコード領域に情報を読み出します.記録領域の記録は不定長であるため,直接記録領域で検索することは不可能である.レコード数が多いため、インデックス領域を巡回するのも少し遅い場合があります.一般的には、インデックス領域を巡回するよりも数桁速い速度で検索することができます.【図1】QQWry.datのファイル構造図である.
図1.QQWry.datファイル構造
QQWry.datにはlittle-endianバイトシーケンスがすべて採用されていることに注意してください.
一.ファイルヘッダの理解
QQQWry.datのファイルヘッダは8バイトしかなく、その構造は非常に簡単で、最初の4バイトは最初のインデックスの絶対オフセットであり、後の4バイトは最後のインデックスの絶対オフセットである.
二.記録領域の理解
それぞれのIP記録は国と地域名で構成されており、国の地域はここではあまり正確ではありません.「清華大学コンピュータ学部」などが検出される可能性があるので、清華大学は国家名になっているので、この国の地域名はIPデータベースの作成時と関係があります.だから記録のフォーマットはQNameに似ていて、全体の部分と局部の部分が構成されていて、私たちはここでやはり国家名と地区名の言い方に沿っています.
そこで、記録のフォーマットは「IPアドレス」「国家名」「地域名」であるべきだと想像しています.もちろん、これは問題ありませんが、これは最も簡単な状況です.国名と地域名が重複する可能性があることは明らかです.各レコードに完全な名前コピーを保存するのは理想的ではありません.そのため、リダイレクトしてスペースを節約する必要があります.したがって、1つの国名または地域名を得るために、1つ目は直接文字列で表される国名であり、2つ目は4バイトの構造であり、1つ目のバイトはリダイレクトのパターンを示し、後の3バイトは国名または地域名の実際のオフセット位置である.国名にとって、このようなリダイレクトは最大2回ある可能性があるため、状況はさらに複雑になる可能性があります.
リダイレクトモードとは何ですか?以上より、1つのレコードのフォーマットは[IPアドレス][国家レコード][地域レコード]であり、国家レコードがリダイレクトであれば、地域レコードがない可能性があり、2つのケースがあり、私は彼をモード1とモード2と呼ぶ.これらのフォーマットの状況について図を挙げて説明します.
図2.IP記録の最も簡単な形式
図2は最も簡単なIP記録フォーマットを示しており、説明できるものは何もないと思います.
図3.リダイレクトモード1
【図3】リダイレクトモード1の場合を示す図である.パターン1の場合,地域記録も国記録に追随し,IPアドレスの後に国記録の4バイトしか残っておらず,後の3バイトが1つのポインタを構成し,実際の国名を指し,その後アドレス名に追随する.パターン1の識別バイトは0 x 01である.
図4.リダイレクトモード2
【図4】リダイレクトモード2の場合を示す図である.パターン2の場合(識別バイトが0 x 02である)、地域レコードは国レコードに従っていないため、国レコードの4バイト後に地域レコードがあることを示した.パターン1とパターン2の違いは、パターン1の国家記録の後に地域記録はなく、パターン2の国家記録の後に地域記録があることが分かったと思います.もっと複雑な状況を見てみましょう.
図5.混和状況1
図5は、国がモード1として記録された場合に発生する可能性のあるより複雑な状況を示しており、この場合、リダイレクトが指す位置は依然としてリダイレクトであるが、2回目のリダイレクトはモード2である.心配しないでください.モード3がなくても、このリダイレクトはせいぜい2回しかありません.また、2回目のリダイレクトが発生したら、必ずモード2になります.また、このような状況は国家記録でしか発生しません.地域記録では、モード1はモード2と同じで、地域記録でも2回のリダイレクトは発生しません.ただし、図7のように、この図はさらに複雑であってもよい.
図6.混和状況2
図6はモード1で最も複雑な混和状況であるが、地域記録もリダイレクトに来ているだけで、リダイレクトのアドレスが0であれば未知の地域名を表すことを注意したい.
そこで、1つのIP記録は[IPアドレス][国家記録][地域記録]からなり、国家記録については、文字列形式、リダイレクトモード1、リダイレクトモード2の3つの表現が可能であるとまとめた.地域レコードについては、文字列形式とリダイレクトの2つの表現があり、リダイレクトモード1の国レコード後に地域レコードに追従できないルールがあります.このまとめによれば,これらの方式の中で合理的に組み合わせることで,IP記録のすべての可能性を構成する.
デザインの理由
インデックス領域の構造を理解し続ける前に、レコード領域の構造がこのように設計されている理由を理解します.文字列の再利用を考えたかもしれません.そうですね.このような構造の下で、一つの国名と地域名に対して、私は一度だけ保存すればいいです.例えば、便利さを表すために、IP記録を小文字で表し、Cは国家名を表し、Aは地域名を表す.
2つのレコードa(C 1,A 1),b(C 2,A 2)があり,C 1=C 2,A 1=A 2であれば,図3に示す構造を用いての再利用を実現することができる.
3つのレコードa(C 1,A 1),b(C 2,A 2),c(C 3,A 3)があり,C 1=C 2,A 2=A 3であれば,レコードbを格納したいと考え,図6の構造での再利用を実現できる.
2つのレコードa(C 1,A 1),b(C 2,A 2)があり,C 1=C 2であれば,レコードbを格納したいと考えると,パターン2でC 2を表し,文字列でA 2 を表すことができる.
より多くの場合を挙げることができますが、このような構造では、異なる文字列を一度だけ保存する必要があります.
インデックス領域の理解
「ファイルヘッダの理解」セクションでは、最初のインデックスと最後のインデックスの絶対オフセットをそれぞれ指す2つのポインタについて説明します.図8に示すように、
図8.ファイルヘッダがインデックス領域を指す図
本当に簡単ではないでしょうか.ファイルヘッダからインデックス領域にナビゲートし、IP検索を開始できます.各インデックス長は7バイトで、最初の4バイトは開始IPアドレスで、後の3バイトはIPレコードを指します.ここでいくつかの概念は、開始IPとは何かを説明する必要がありますが、IPは終了していますか?166.111.0.0–166.111.255.255という記録があるとすると、166.111.0.0が開始IP、166.111.255.255が終了IP、終了IPがIP記録の最初の4バイトであることがわかります.すると、インデックスごとにレコードが結合され、IP範囲が構成されます.166.111.138.138の位置を検索すると、166.111.138.138が166.111.0.0–166.111.11.255.255の範囲に落ちていることがわかります.このインデックスに沿って国と地域名を読み取ることができます.では、最も詳細な図解を示しましょう.
図9.ファイル詳細構造
今はすべてが明らかになったのではないでしょうか.QQWry.datのバージョン情報はどこにあるのか分からないかもしれません.答えは:最後のIP記録は実際にバージョン情報であり、最後の記録はこのように表示されている:255.255.255.0255.255.255.255.255.255純真ネットワーク2004年6月25日IPデータ.OK、今まであなたはすべて知っているはずです.
Demo
次のステップ:私はIPレコードを読み取るプログラムの断片を提供します.この断片はLumaQソースファイルedu.tsinghua.lumaqq.IPSeeker.javaから抜粋されています.興味があれば、ソースコードをダウンロードして詳しく見てください.
	/**
	 *     ip         ,    IPLocation  
	 * @param offset          
	 * @return IPLocation  
	 */
	private IPLocation getIPLocation(long offset) {
		try {
			//   4  ip
			ipFile.seek(offset + 4);
			//                
			byte b = ipFile.readByte();
			if(b == REDIRECT_MODE_1) {
				//       
				long countryOffset = readLong3();
				//       
				ipFile.seek(countryOffset);
				//          ,                   
				b = ipFile.readByte();
				if(b == REDIRECT_MODE_2) {
					loc.country = readString(readLong3());
					ipFile.seek(countryOffset + 4);
				} else
					loc.country = readString(countryOffset);
				//       
				loc.area = readArea(ipFile.getFilePointer());
			} else if(b == REDIRECT_MODE_2) {
				loc.country = readString(readLong3());
				loc.area = readArea(offset + 8);
			} else {
				loc.country = readString(ipFile.getFilePointer() - 1);
				loc.area = readArea(ipFile.getFilePointer());
			}
			return loc;
		} catch (IOException e) {
			return null;
		}
	}	

	/**
	 *  offset           ,       
	 * @param offset          
	 * @return       
	 * @throws IOException       
	 */
	private String readArea(long offset) throws IOException {
		ipFile.seek(offset);
		byte b = ipFile.readByte();
		if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {
			long areaOffset = readLong3(offset + 1);
			if(areaOffset == 0)
				return LumaQQ.getString("unknown.area");
			else
				return readString(areaOffset);
		} else
			return readString(offset);
	}

	/**
	 *  offset    3      long,  java big-endian  ,     
	 *             
	 * @param offset        
	 * @return    long ,  -1        
	 */
	private long readLong3(long offset) {
		long ret = 0;
		try {
			ipFile.seek(offset);
			ipFile.readFully(b3);
			ret |= (b3[0] & 0xFF);
			ret |= ((b3[1] < < 8) & 0xFF00);
			ret |= ((b3[2] << 16) & 0xFF0000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}	
	
	/**
	 *        3      long
	 * @return    long ,  -1        
	 */
	private long readLong3() {
		long ret = 0;
		try {
			ipFile.readFully(b3);
			ret |= (b3[0] & 0xFF);
			ret |= ((b3[1] << 8) & 0xFF00);
			ret |= ((b3[2] << 16) & 0xFF0000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}

	/**
	 *  offset        0      
	 * @param offset        
	 * @return       ,        
	 */
	private String readString(long offset) {
		try {
			ipFile.seek(offset);
			int i;
			for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte());
			if(i != 0) 
			    return Utils.getString(buf, 0, i, "GBK");
		} catch (IOException e) {			
		    log.error(e.getMessage());
		}
		return "";
	}

コードは複雑ではありません.getIPLocationは主な方法で、国の記録フォーマットをチェックし、文字列形式、モード1、モード2に対して異なるコードを採用します.readAreaは比較的簡単です.文字列とリダイレクトの2つの状況だけが処理する必要があるからです.
まとめ
純粋なIPデータベースの構造はIPを探すのが簡単で迅速ですが、編集するのは面倒です.QQQWry.datファイルを生成するには専門的なツールが必要だと思います.ファイルフォーマットの制限のため、直接IP記録を追加するのは容易ではありません.でも、IPが調べられるのは嬉しいし、純真な記録が増えてほしいな~.
原文リンク:純真IPデータベースフォーマットの詳細