博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux下电骡aMule Kademlia网络构建分析I
阅读量:6390 次
发布时间:2019-06-23

本文共 15741 字,大约阅读时间需要 52 分钟。

(以下分析基于ubuntu aMule 2.3.1进行。)

aMule代码的下载和编译

为了能尽量缩短aMule代码的下载、编译及编译运行所依赖的环境的建立所耗费的时间,并尽快启动对于它的研究学习,而直接使用了ubuntu的代码下载及编译工具。具体的代码下载及编译方法如下:

apt-get source amulesudo apt-get build-dep amulecd amule-2.3.1dpkg-buildpackage -rfakeroot -uc -b

Kademlia网络的启动

首先来看一下aMule的Kademlia网络启动的UI操作。

101017_NKUL_919237.png

步骤一:点击左上角的那个启动按钮,会弹出如下这样的一个dialog:

101228_QVqd_919237.png

步骤二:点击对话框中的“是(Y)”按钮,就会启动一个线程来下在dat文件,并弹出如下的一个对话框:

101841_l3oT_919237.png

接着我们看一下aMule的代码,来了解一下这样一个过程的具体实现。从何处入手呢?我想那个URL的定义应该是个不错的入口。搜索那个URL在何处定义。在amule-2.3.1/src/Preferences.cpp中有如下的几行:

s_MiscList.push_back( new Cfg_Bool( wxT("/eMule/DropSlowSources"), s_DropSlowSources, false ) );s_MiscList.push_back( new Cfg_Str(  wxT("/eMule/KadNodesUrl"),s_KadURL, wxT("http://download.tuxfamily.org/technosalad/utils/nodes.dat") ) );s_MiscList.push_back( new Cfg_Str(  wxT("/eMule/Ed2kServersUrl"),s_Ed2kURL, wxT("http://gruk.org/server.met.gz") ) );

不难理解,那个URL是某个preference item的默认值,而那个item的key正如上面的代码所示的那样,为"/eMule/KadNodesUrl",那个item的值保存在静态变量s_KadURL中。

搜索"/eMule/KadNodesUrl",搜不到任何引用的地方。

再来搜s_KadURL。可以看到在amule-2.3.1/src/Preferences.h文件中有如下的几行:

// server.met and nodes.dat urls	static const wxString& GetKadNodesUrl() { return s_KadURL; }	static void SetKadNodesUrl(const wxString& url) { s_KadURL = url; }
setter/getter函数。那就接着搜引用了这两个函数的地方。引用了GetKadNodesUrl()的,在amule-2.3.1/src/ServerWnd.cpp的CServerWnd::CServerWnd(wxWindow* pParent /*=NULL*/, int splitter_pos)构造函数中:

CastChild( ID_SRV_SPLITTER, wxSplitterWindow )->SetSashGravity(0.5f);	CastChild( IDC_NODESLISTURL, wxTextCtrl )->SetValue(thePrefs::GetKadNodesUrl());	CastChild( IDC_SERVERLISTURL, wxTextCtrl )->SetValue(thePrefs::GetEd2kServersUrl());
然后是SetKadNodesUrl(),其中一个引用到该函数的地方为amule-2.3.1/src/KadDlg.cpp:
void CKadDlg::OnBnClickedUpdateNodeList(wxCommandEvent& WXUNUSED(evt)){	if ( wxMessageBox( wxString(_("Are you sure you want to download a new nodes.dat file?\n")) +						_("Doing so will remove your current nodes and restart Kademlia connection.")					, _("Continue?"), wxICON_EXCLAMATION | wxYES_NO, this) == wxYES ) {		wxString strURL = ((wxTextCtrl*)FindWindowById( IDC_NODESLISTURL ))->GetValue();		thePrefs::SetKadNodesUrl(strURL);		theApp->UpdateNotesDat(strURL);	}}

看看wxMessageBox中的那段文字,是多么的亲切啊。而这个OnBnClickedUpdateNodeList()是通过一个表,而被注册为事件的处理函数的:

BEGIN_EVENT_TABLE(CKadDlg, wxPanel)	EVT_TEXT(ID_NODE_IP1, CKadDlg::OnFieldsChange)	EVT_TEXT(ID_NODE_IP2, CKadDlg::OnFieldsChange)	EVT_TEXT(ID_NODE_IP3, CKadDlg::OnFieldsChange)		EVT_TEXT(ID_NODE_IP4, CKadDlg::OnFieldsChange)	EVT_TEXT(ID_NODE_PORT, CKadDlg::OnFieldsChange)	EVT_TEXT_ENTER(IDC_NODESLISTURL ,CKadDlg::OnBnClickedUpdateNodeList)		EVT_BUTTON(ID_NODECONNECT, CKadDlg::OnBnClickedBootstrapClient)	EVT_BUTTON(ID_KNOWNNODECONNECT, CKadDlg::OnBnClickedBootstrapKnown)	EVT_BUTTON(ID_KADDISCONNECT, CKadDlg::OnBnClickedDisconnectKad)	EVT_BUTTON(ID_UPDATEKADLIST, CKadDlg::OnBnClickedUpdateNodeList)END_EVENT_TABLE()

总结一下,aMule中Kademlia网络启动的UI操作。在APP启动的时候,初始化所有的UI组件。在amule-2.3.1/src/ServerWnd.cpp中CServerWnd的构造过程中,创建了一个wxTextCtrl组件,其ID为IDC_NODESLISTURL,其值被设置为KadNodesUrl。在步骤一点击启动按钮时,会触发该按钮的事件,并执行CKadDlg::OnBnClickedUpdateNodeList()函数,在这个方法中,会弹出一个如我们步骤一执行之后所看到的那个wxMessageBox。我们确认后,CKadDlg::OnBnClickedUpdateNodeList()函数通过ID查找到UI组建,并获取到其值,也就是KadNodesUrl,并从这个URL下载dat文件。

KadNodesDat文件的下在过程又是怎样的呢?可以看到在CKadDlg::OnBnClickedUpdateNodeList()函数中有执行到theApp->UpdateNotesDat(strURL),这个函数在amule-2.3.1/src/amule.cpp中定义:

void CamuleApp::UpdateNotesDat(const wxString& url){	wxString strTempFilename(theApp->ConfigDir + wxT("nodes.dat.download"));			CHTTPDownloadThread *downloader = new CHTTPDownloadThread(url, strTempFilename, theApp->ConfigDir + wxT("nodes.dat"), HTTP_NodesDat, true, false);	downloader->Create();	downloader->Run();}
先构造一个临时文件名,然后通过CHTTPDownloadThread,起一个线程,下载或更新
KadNodesDat文件
(amule-2.3.1/src/HTTPDownload.cpp):
CHTTPDownloadThread::CHTTPDownloadThread(const wxString& url, const wxString& filename, const wxString& oldfilename, HTTP_Download_File file_id,										bool showDialog, bool checkDownloadNewer)#ifdef AMULE_DAEMON	: CMuleThread(wxTHREAD_DETACHED),#else	: CMuleThread(showDialog ? wxTHREAD_JOINABLE : wxTHREAD_DETACHED),#endif	  m_url(url),	  m_tempfile(filename),	  m_result(-1),	  m_file_id(file_id),	  m_companion(NULL){	if (showDialog) {#ifndef AMULE_DAEMON		CHTTPDownloadDialog* dialog = new CHTTPDownloadDialog(this);		dialog->Show(true);		m_companion = dialog;#endif	}	// Get the date on which the original file was last modified	// Only if it's the same URL we used for the last download and if the file exists.	if (checkDownloadNewer && thePrefs::GetLastHTTPDownloadURL(file_id) == url) {		wxFileName origFile = wxFileName(oldfilename);		if (origFile.FileExists()) {			AddDebugLogLineN(logHTTP, CFormat(wxT("URL %s matches and file %s exists, only download if newer")) % url % oldfilename);			m_lastmodified = origFile.GetModificationTime();		}	}	wxMutexLocker lock(s_allThreadsMutex);	s_allThreads.insert(this);}CMuleThread::ExitCode CHTTPDownloadThread::Entry(){	if (TestDestroy()) { 		return NULL;	}		wxHTTP* url_handler = NULL;		AddDebugLogLineN(logHTTP, wxT("HTTP download thread started"));		const CProxyData* proxy_data = thePrefs::GetProxyData();	bool use_proxy = proxy_data != NULL && proxy_data->m_proxyEnable;
让人不得不感慨,aMule项目真是与wxWidgets绑定的太紧了。在aMule的整个代码中,对于wxWdigets API的调用真的是无处不在。

KadNodesDat下载之后,aMule对它又是如何处理的呢?这个文件在整个的Kademlia网络的构建过程中究竟又起到一个什么样的作用呢?

再瞅一眼amule-2.3.1/src/HTTPDownload.cpp文件,可以看到有一个OnExit() 函数:

void CHTTPDownloadThread::OnExit() {#ifndef AMULE_DAEMON	if (m_companion) {		CMuleInternalEvent termEvent(wxEVT_HTTP_SHUTDOWN);		wxPostEvent(m_companion, termEvent);		}#endif		// Notice the app that the file finished download	CMuleInternalEvent evt(wxEVT_CORE_FINISHED_HTTP_DOWNLOAD);	evt.SetInt((int)m_file_id);	evt.SetExtraLong((long)m_result);	wxPostEvent(wxTheApp, evt);	wxMutexLocker lock(s_allThreadsMutex);	s_allThreads.erase(this);}
不难判断,这个函数在文件下载完成之后调用。它产生一个类型为wxEVT_CORE_FINISHED_HTTP_DOWNLOAD的事件并post出去。而该事件最终将会被传递给CamuleApp::OnFinishedHTTPDownload(CMuleInternalEvent& event)(文件amule-2.3.1/src/amule-gui.cpp中):
// HTTPDownload finished	EVT_MULE_INTERNAL(wxEVT_CORE_FINISHED_HTTP_DOWNLOAD, -1, CamuleGuiApp::OnFinishedHTTPDownload)
而此处引用的CamuleGuiApp::OnFinishedHTTPDownload()函数则在文件amule-2.3.1/src/amule.cpp中定义:

void CamuleApp::OnFinishedHTTPDownload(CMuleInternalEvent& event){	switch (event.GetInt()) {		case HTTP_IPFilter:			ipfilter->DownloadFinished(event.GetExtraLong());			break;		case HTTP_ServerMet:			serverlist->DownloadFinished(event.GetExtraLong());			break;		case HTTP_ServerMetAuto:			serverlist->AutoDownloadFinished(event.GetExtraLong());			break;		case HTTP_VersionCheck:			CheckNewVersion(event.GetExtraLong());			break;		case HTTP_NodesDat:			if (event.GetExtraLong() == HTTP_Success) {								wxString file = ConfigDir + wxT("nodes.dat");				if (wxFileExists(file)) {					wxRemoveFile(file);				}				if ( Kademlia::CKademlia::IsRunning() ) {					Kademlia::CKademlia::Stop();				}				wxRenameFile(file + wxT(".download"),file);								Kademlia::CKademlia::Start();				theApp->ShowConnectionState();							} else if (event.GetExtraLong() == HTTP_Skipped) {				AddLogLineN(CFormat(_("Skipped download of %s, because requested file is not newer.")) % wxT("nodes.dat"));			} else {				AddLogLineC(_("Failed to download the nodes list."));			}			break;#ifdef ENABLE_IP2COUNTRY		case HTTP_GeoIP:			theApp->amuledlg->IP2CountryDownloadFinished(event.GetExtraLong());			// If we updated, the dialog is already up. Redraw it to show the flags.			theApp->amuledlg->Refresh();			break;#endif	}}

回忆一下上面CamuleApp::UpdateNotesDat()中创建CHTTPDownloadThread对象时,传递的file_id HTTP_NodesDat。

对于我们的Kademlia网络启动而言,自是主要关注上面CamuleGuiApp::OnFinishedHTTPDownload()函数的case HTTP_NodesDat了。也正是在这个case中,Kademlia相关的一些设施被创建出来。可以看到,在这个case block中,所做的事情主要为:

1. 如果HTTP下载没有成功,则打印出log,并退出。

2. 对于HTTP下载成功的情况。

(1)、先检查是否有旧的"nodes.dat"文件,如果有,则移除该文件。

(2)、检查Kademlia是否正在运行,如果是,则停掉Kademlia。

(3)、将下载到的文件重命名为"nodes.dat"。

(4)、调用Kademlia::CKademlia::Start(),启动Kademlia。

(5)、调用theApp->ShowConnectionState(),显示连接状态。

Kademlia命名空间中的都是和Kademlia网络有关的东西。总算是找到了Kademlia模块的入口了。在Kademlia::CKademlia::Start()中初始化整个的Kademlia相关的东西,其实现如下:

在文件amule-2.3.1/src/kademlia/kademlia/Kademlia.h中:

static void Start() 		{ Start(new CPrefs); }	static void Start(CPrefs *prefs);

在文件amule-2.3.1/src/kademlia/kademlia/Kademlia.cpp中:

void CKademlia::Start(CPrefs *prefs){	if (instance) {		// If we already have an instance, something is wrong.		delete prefs;		wxASSERT(instance->m_running);		wxASSERT(instance->m_prefs);		return;	}	// Make sure a prefs was passed in..	if (!prefs) {		return;	}	AddDebugLogLineN(logKadMain, wxT("Starting Kademlia"));	// Init jump start timer.	m_nextSearchJumpStart = time(NULL);	// Force a FindNodeComplete within the first 3 minutes.	m_nextSelfLookup = time(NULL) + MIN2S(3);	// Init status timer.	m_statusUpdate = time(NULL);	// Init big timer for Zones	m_bigTimer = time(NULL);	// First Firewall check is done on connect, init next check.	m_nextFirewallCheck = time(NULL) + (HR2S(1));	// Find a buddy after the first 5mins of starting the client.	// We wait just in case it takes a bit for the client to determine firewall status..	m_nextFindBuddy = time(NULL) + (MIN2S(5));	// Init contact consolidate timer;	m_consolidate = time(NULL) + (MIN2S(45));	// Look up our extern port	m_externPortLookup = time(NULL);	// Init bootstrap time.	m_bootstrap = 0;	// Init our random seed.	srand((uint32_t)time(NULL));	// Create our Kad objects.	instance = new CKademlia();	instance->m_prefs = prefs;	instance->m_indexed = new CIndexed();	instance->m_routingZone = new CRoutingZone();	instance->m_udpListener = new CKademliaUDPListener();	// Mark Kad as running state.	m_running = true;}

在这个函数中,主要做的事就是,1. 初始化了一堆时间;2. 创建了几个对象:CKademlia、CIndexed、CRoutingZone和CKademliaUDPListener。CKademlia是整个Kademlia网络的主控类。Kademlia网络的所有功能,都通过这个class暴露给外部,外部也只通过这个类来访问Kademlia网络。这让人想起了外观模式(Facade Pattern)。

先来看一下CRoutingZone的创建过程(amule-2.3.1/src/kademlia/routing/RoutingZone.cpp):

CRoutingZone::CRoutingZone(){	// Can only create routing zone after prefs	// Set our KadID for creating the contact tree	me = CKademlia::GetPrefs()->GetKadID();	AddLogLineNS(wxT("CRoutingZone KadID: ") + me.ToBinaryString(false));	// Set the preference file name.	m_filename = theApp->ConfigDir + wxT("nodes.dat");	Init(NULL, 0, CUInt128((uint32_t)0));}void CRoutingZone::Init(CRoutingZone *super_zone, int level, const CUInt128& zone_index){	// Init all Zone vars	// Set this zone's parent	m_superZone = super_zone;	// Set this zone's level	m_level = level;	// Set this zone's CUInt128 index	m_zoneIndex = zone_index;	// Mark this zone as having no leafs.	m_subZones[0] = NULL;	m_subZones[1] = NULL;	// Create a new contact bin as this is a leaf.	m_bin = new CRoutingBin();	// Set timer so that zones closer to the root are processed earlier.	m_nextSmallTimer = time(NULL) + m_zoneIndex.Get32BitChunk(3);	// Start this zone.	StartTimer();	// If we are initializing the root node, read in our saved contact list.	if ((m_superZone == NULL) && (m_filename.Length() > 0)) {		ReadFile();	}}void CRoutingZone::ReadFile(const wxString& specialNodesdat){	if (m_superZone != NULL || (m_filename.IsEmpty() && specialNodesdat.IsEmpty())) {		wxFAIL;		return;	}	bool doHaveVerifiedContacts = false;	// Read in the saved contact list	try {		uint32_t numContacts = 0;		uint32_t validContacts = 0;		CFile file;		if (CPath::FileExists(specialNodesdat.IsEmpty() ? m_filename : specialNodesdat) && file.Open(m_filename, CFile::read)) {			// Get how many contacts in the saved list.			// NOTE: Older clients put the number of contacts here...			//       Newer clients always have 0 here to prevent older clients from reading it.			numContacts = file.ReadUInt32();			uint32_t fileVersion = 0;			if (numContacts == 0) {				if (file.GetLength() >= 8) {					fileVersion = file.ReadUInt32();					if (fileVersion == 3) {						uint32_t bootstrapEdition = file.ReadUInt32();						if (bootstrapEdition == 1) {							// this is a special bootstrap-only nodes.dat, handle it in a separate reading function							ReadBootstrapNodesDat(file);							file.Close();							return;						}					}					if (fileVersion >= 1 && fileVersion <= 3) {						numContacts = file.ReadUInt32();					}				}			} else {				// Don't read version 0 nodes.dat files, because they can't tell the kad version of the contacts stored.				AddLogLineC(_("Failed to read nodes.dat file - too old. This version (0) is not supported anymore."));				numContacts = 0;			}			DEBUG_ONLY( unsigned kad1Count = 0; )			if (numContacts != 0 && numContacts * 25 <= (file.GetLength() - file.GetPosition())) {				for (uint32_t i = 0; i < numContacts; i++) {					CUInt128 id = file.ReadUInt128();					uint32_t ip = file.ReadUInt32();					uint16_t udpPort = file.ReadUInt16();					uint16_t tcpPort = file.ReadUInt16();					uint8_t contactVersion = 0;					contactVersion = file.ReadUInt8();					CKadUDPKey kadUDPKey;					bool verified = false;					if (fileVersion >= 2) {						kadUDPKey.ReadFromFile(file);						verified = file.ReadUInt8() != 0;						if (verified) {							doHaveVerifiedContacts = true;						}					}					// IP appears valid					if (contactVersion > 1) {						if(IsGoodIPPort(wxUINT32_SWAP_ALWAYS(ip),udpPort)) {							if (!theApp->ipfilter->IsFiltered(wxUINT32_SWAP_ALWAYS(ip)) &&							    !(udpPort == 53 && contactVersion <= 5 /*No DNS Port without encryption*/)) {								// This was not a dead contact, inc counter if add was successful								if (AddUnfiltered(id, ip, udpPort, tcpPort, contactVersion, kadUDPKey, verified, false, false)) {									validContacts++;								}							}						}					} else {						DEBUG_ONLY( kad1Count++; )					}				}			}			file.Close();			AddLogLineN(CFormat(wxPLURAL("Read %u Kad contact", "Read %u Kad contacts", validContacts)) % validContacts);#ifdef __DEBUG__			if (kad1Count > 0) {				AddDebugLogLineN(logKadRouting, CFormat(wxT("Ignored %u kad1 %s in nodes.dat file.")) % kad1Count % (kad1Count > 1 ? wxT("contacts"): wxT("contact")));			}#endif			if (!doHaveVerifiedContacts) {				AddDebugLogLineN(logKadRouting, wxT("No verified contacts found in nodes.dat - might be an old file version. Setting all contacts verified for this time to speed up Kad bootstrapping."));				SetAllContactsVerified();			}		}		if (validContacts == 0) {			AddLogLineC(_("No contacts found, please bootstrap, or download a nodes.dat file."));		}	} catch (const CSafeIOException& DEBUG_ONLY(e)) {		AddDebugLogLineN(logKadRouting, wxT("IO error in CRoutingZone::readFile: ") + e.what());	}}void CRoutingZone::StartTimer(){	// Start filling the tree, closest bins first.	m_nextBigTimer = time(NULL) + SEC(10);	CKademlia::AddEvent(this);}

为什么要从CRoutingZone对象的创建开始看起呢?主要是因为,在这个class中处理了从网络下载的"nodes.dat"文件。CRoutingZone创建的主要过程为,创建了一个CRoutingBin对象,初始化了一些我们现在看还是不明觉厉的变量,并解析了从网络下载的"nodes.dat"文件

此处我们可以看一下"nodes.dat"文件的文件结构(ReadFile()中)。这个文件是一个纯二进制文件。这个版本的代码处理了多个版本的文件,不同版本的文件也就有着不同的文件结构。

先是文件头的结构,主要分为如下的几种:

1. Version 0的文件:文件开头是一个32位的无符号整型值,表示文件中保存的联系人的个数。这个版本的代码完全抛弃Version 0的文件不作处理。

2. Version 1, Version 2:文件开头是一个值为0的32位无符号整型值。紧随其后的是一个32位的无符号整型值,表示文件的版本号。再后面是一个32位长的无符号整型值,表示联系人的个数。

3. Version 3:文件开头是一个值为0的32位无符号整型值。紧随其后的是一个32位的无符号整型值,表示文件的版本号。再后面的是bootstrap信息,先是bootstrap的版本号

如果bootstrap版本号为1,则紧随其后的是Bootstrap节点信息,一直到达文件尾。

如果bootstrap版本号不为1,则在bootstrap版本号后面跟着的是联系人的个数。

4. 其他版本的文件,不能处理。

然后是联系人的结构。Version 1,Version 2,Version 3 bootstrap版本不为1时,在联系人的个数后面都会有一连串的联系人信息。联系人的信息保存有两种结构:

1. Version 1:128位也就是16字节的KadID->32位的IP地址->16位的UDP端口号->16位的TCP端口号->8位的联系人版本号。

2. Version 2、Version 3128位也就是16字节的KadID->32位的IP地址->16位的UDP端口号->16位的TCP端口号->8位的联系人版本号->64位8字节的KadUDPKey,内含4个字节的key和4个字节的IP->8位的Verified。

OK,从文件中解析出了一个个Contact的信息,那之后要怎么处理呢?在CRoutingZone::ReadFile()中可以看到,它首先会对Contact做一个过滤。如果一个contact不是dead的,则会为相应的Contact创建一个CContact对象,并保存起来。

转载于:https://my.oschina.net/wolfcs/blog/485405

你可能感兴趣的文章
转-linux系统脚本 环境变量 的启动顺序
查看>>
我的友情链接
查看>>
My blog please navigate to http://hi.baidu.com/248828412
查看>>
spring in action 4 第5章
查看>>
sed用法整理
查看>>
Weblogic 前端热部署
查看>>
zimba安装
查看>>
F5内网大二层负载均衡业务访问故障解析(CISCO OTV+LISP-MTU问题导致)
查看>>
有一种失败叫瞎忙
查看>>
Linux——文件管理之inode
查看>>
nginx反向代理,实现负载均衡
查看>>
IDEA 13 tomcat 进行远程调试
查看>>
Successor,Fesible Successor,FD,AD,eigrp
查看>>
仿百度GIF验证码 GIFEncoder 跳动验证码 随机背景色、颜色、字体、子大小、偏移、干扰线等...
查看>>
值得学习的寓言故事和哲理
查看>>
静态路由中使用一跳和出接口的区别
查看>>
ARM1176JZF-S/S3C6410处理器的操作模式和寄存器
查看>>
centos 编译安装mysql
查看>>
Save could not be completed. Eclipse国际化的问题解决
查看>>
我的友情链接
查看>>