一、基础知识
大家都知道,emule运行后客户端需要立刻连接ed服务器,以确定该emule client 端是HighID还是LowID(如果是LowID,下载速度会受到一定的影响)。
大部分情况来说,外网用户的emule都是HighID,而内网用户是LowID,但也有以下例外:外网用户的机器如果有防火墙把自己的emule tcp listen服务端口禁掉了的话,那么就只能拿到LowID;内网用户如果靠静态映射或UPNP动态映射成功的话,也可以拿到HighID(大部分有经验的驴友都会很注意观察自己的emule主窗口右下角的emule连接图标,验证自己是否拿到HighID)。
既然每个emule客户端有HighID/LowID两种情况,那么对于整个P2P网络应用来说,按照连接的发起方和接收方组合,就会有以下四种情况连接:
1) HighID --> HighID
2) HighID --> LowID
3) LowID --> LowID
4) LowID --> HighID
其中第 1,4 种情况最简单,往接收方HighID的TCP Listen端口直接发连接,连接成功后,双方就开始通信;但是对于第 2,3 种情况来说,可就不是那么简单了。其中第3中情况就是大家都都知道的Low2Low内网穿透的实现(可以通过TCP穿透实现,也可以实现UDP穿透后模拟TCP,VeryCD emule的Low2Low传输中实现的就是后一种方案,具体可参考VeryCD emule Low2Low实现原理和方法介绍)。
二、emule 的 HighID->LowID
今天我主要和大家介绍一下emule中是如何实现(HighID-->LowID)的,其实也很简单,就是emule callback机制,简单的来说就是:由于HighID无法直接主动发起连接到LowID,因此也要靠一个第三方的搭桥,由这个第三方去通知该LowID主动发连接到这个HighID,连接成功后即可通信。
emule中的HighID-->LowID的通信既有TCP,也有UDP; 既有靠ed服务器完成HighID-->LowID,也有靠LowID的buddy来完成HighID-->LowID。下面我将分别以代码来仔细介绍:
2.1 Tcp callback over Emule.Server
这种情况的Callback是High.A 和 Low.B 必须在同一emule Server上, 我们先假定有HighID.A要连接LowID.B,通信连接过程图如下:

协议参数List:
①<Low.B 的 32bit Id>
②<High.A IP><High.A Tcp Port>
③直接发送连接,进行握手协议
具体框架代码List:
①High.A 通过tcp向Server 发 OP_CALLBACKREQUEST,
参数:<Low.B 的 32bit Id>
CUpDownClient::TryToConnect( ... ) //High.A TryToConnect Low.B
{
if (theApp.serverconnect->IsLocalServer(m_dwServerIP,m_nServerPort))
{
Packet* packet = new Packet(OP_CALLBACKREQUEST,4);
}
...
SetDownloadState( DS_WAITCALLBACK );
}
② Low.B 收到 Server 发送的OP_CALLBACKREQUESTED 后, 然后向 High.A 发送 TryToConnect ,完成反向连接
CServerSocket::ProcessPacket( ... )
{
...
case OP_CALLBACKREQUESTED
{
...
client->TryToConnect();
}
}
③ 如果 Server 无法通知 Low.B ,则向 High.A 发送 OP_CALLBACK_FAIL
2.2 基于Buddy的[TCP]CallBack
基于Buddy的CallBack 可能是emule中独有的,emule中的 Buddy 机制是一个很有意思的机制,这个Buddy机制其实就是为了协助LowID能更好的与HighId通信而实现的。具体怎么找Buddy的过程我下次另外分析。这里大家只要把LowID的Buddy看成是一个HighID的emule客户端即可,他和一个LowID的Buddy互为Buddy,形成“兄弟结盟”一样。
通信顺序过程图如下:
图中我们可以看到,基于Buddy完成Tcp CallBack的前提条件是:
1) LowID.B 必须找到一个稳定的Buddy HighID.D
2) HighID.A 必须已知 LowID.B 的Buddy High.D 的对外IP.Port信息
协议参数List:
① <Low.B ~KadId><File Id><High.A Port>
② <Low.B ~KadId><File Id><High.A ip> <HighA.port>
③ 直接发Connect,进行握手
具体框架代码List:
①/ High.A 向 Low.B 的 Buddy High.D 发送[UDP] KADEMLIA_CALLBACK_REQ
协议参数: <Low.B ~KadId><File Id><High.A Port>
CUpDownClient::TryToConnect( ... ) //High.A TryToConnect Low.B
{
...
if [ A 已知 B的Buddy D的IP/Port]
Packet* packet = new Packet(&bio, OP_KADEMLIAHEADER);
packet->opcode = KADEMLIA_CALLBACK_REQ;
theApp.clientudp->SendPacket(packet, GetBuddyIP(), GetBuddyPort(), false, NULL); // kad doesnt supports obfuscation yet
else[基于Kad 网络Search B的Buddy High.D 的IP/Port]
//Create search to find buddy.
Kademlia::CSearch *findSource = new Kademlia::CSearch;
findSource->SetSearchTypes(Kademlia::CSearch::FINDSOURCE);
findSource->SetTargetID(Kademlia::CUInt128(GetBuddyID()));
findSource->AddFileID(Kademlia::CUInt128(reqfile->GetFileHash()));
if(Kademlia::CSearchManager::StartSearch(findSource))
//Peer.B DownState 从 DS_WAITCALLBACK -> DS_WAITCALLBACKKAD
}
②/ High.D 处理 KADEMLIA_CALLBACK_REQ ,通过[tcp] 向其Buddy Low.B 发 OP_CALLBACK
CKademliaUDPListener::Process_KADEMLIA_CALLBACK_REQ
{
...
Packet* pPacket = new Packet(&fileIO2, OP_EMULEPROT, OP_CALLBACK);//OP_CALLBACK
}
OP_CALLBACK 协议参数:
<HASH 16> <HASH 16><uint 32> <uint 16>
<Low.B ~KadId> <File Id> <High.A ip> <HighA.port>
③ Low.B 处理收到的 OP_CALLBACK , 向 High.A 发送Tcp 连接 [反向连接到此结束]
CClientReqSocket::ProcessExtPacket( )
{
data.ReadUInt128(&check);
check.Xor(Kademlia::CUInt128(true)); // 这里的Id会再反向一次
...
callback = theApp.clientlist->FindClientByIP(ntohl(ip), tcp);
if( callback == NULL )
{
callback = new CUpDownClient(NULL,tcp,ip,0,0);
theApp.clientlist->AddClient(callback);
}
callback->TryToConnect(true);
}
未完,待续...
转载请注明出处: VeryCD emule 软件开发组 emuledev@VeryCD.com














































