一、Tracker HTTP协议 Tracker是一种HTTP/HTTPS服务, 它是专门为BitTorrent而设计的,和普通HTTP协议一样,采取请求和应答方式。BT客户端得到有关下载文件的各种动态信息,最主要的是下载同一文件的其他下载者。.Tracker使用CGI方法提出申请,如"param1=value & param2 = val"
注意:所有不在0-9、a-z,、A-Z,和 $-_.+!*'()的字符集都要转义。比如空格要转义成" ",其中20是空格的ASCII符。详看RFC1738.
客户端根据Metafile指出的"announce"地址,对tracker提出申请:如http://tk.greedland.net/announce?info_hash=XXXXXXXXXXXXXXXXXXXX&peer_id=AZ34343&port=6881...
下面是向tracker申请的参数:
info_hash: Info键值的20字节SHA杂凑值. peer_id: 20字节长的独一无二的伙伴标识,在client启动时生成. 可用BT下载客户端的程序名和随机数来生成. port: 客户端监听端口.典型的端口是6881-6889. uploaded: 上传总量 (从前是客户端对tracker发送"开始"event) 十进制ASCII码数字. 它表示客户端上传字节总量.这一参数并没被官方描述提到. downloaded: 下载总量 (从前是客户端对tracker发送"开始"event) 十进制ASCII码数字. 它表示客户端下载字节总量.这一参数并没被官方描述提到. left: 剩余下载字节, 十进制ASCII码数字. compact: 此参数值为1,表示期望得到紧凑模式的节点列表. 否则表示期望得到普通模式的节点列表. 指出客户端是否支持压缩模式. 如果是,伙伴列表将被一个伙伴字符串所代替.每个伙伴占6个字节.前4个字节是主机(网络字序) , 后2个字节是端口(网络字序). event: 事件有3种: 开始(started), 完成(completed), 停止(stopped) started: 对tracker的第一个请求必须包括开始事件. stopped: 必须在客户端关闭时发送此事件. completed: 必须在完成下载时发送此事件. 可是, 当在完成下载后重新开始任务就不可重发"完成"事件. ip: 可选的IPV6地址 numwant: 可选的期望Tracker最大返回数.缺省为50个. trackerid: 可选. 如果上次发布含有trackerid,这次就要重新扫送. HTTP Tracker 回应消息这是一个回应的样子
如果返回的bencode编码中包含failure reason字段,则表示处理请求失败,此字段的值即为失败原因.如果请求成功,则有两个字段是必须出现的: peers:节点列表 interval:服务器期望的下次查询间隔时间,单位为秒通常还会有如下一些字段出现: done peers:下载完毕的节点个数 num peers或者incomplete: 当前下载的节点个数
普通模式的回复其peers字段包括ip,port两个字段,如果未指定no_peer_id参数还将包括peer id字段.
Tracker交将返回一个"text/plain"文档,含有Bencode编码的字典: failure reason: 如果有本项,说明发生了一个严重错误,将不会返回其他任何信息. 键值是人类可读的错误信息. warning message: (新的) 键值是人类可读的的一般警告信息. interval: 发送请求之间必须的间隔时间(秒) (必须执行) min interval: 最小的发布间隔时间 (秒). 限制客户端重新发布. tracker id: 一个必须被回送的字符串,当客户端再次发布. complete: 整数, 拥有完全文件的伙伴数. incomplete: 整数, 拥有不完全文件的伙伴数,也就是"水蛭". peers: 一个含有字典的列表, 每一个字典含有如下内容: peer id: 字符串, 伙伴的唯一名称. ip: 字符串,伙伴的IPv4或IPv6地址,或是DNS名. port: 整数,伙伴的端口有一些Tracker能返回"Compact"模式的伙伴列表,如果是这种 情况,peers列表就会被一个peers字符串所代替,每个伙伴占6个字节.其中前4个字节是主机IP(网络字序) , 后2个字节是端口(网络字序).
BitComet对Tracke请求的扩展: localip: 发送本地IP hide: 隐身模式,不允许tracker返回你的IP给别人.对返回的扩展: tracker_alias_url:返回别名tracker的列表.如是同一个主机,主机名可以省略如udp://:8080/ 二、Tracker UDP协议先发送以下数据格式Connecttion_ID(8位) 0(4位) Transaction_ID(4位) 收到以下数据格式 0(4位) Transaction_ID(4位) Connecttion_ID(8位)
然后再发送以下数据
InfoHash 20位PeerID 20位DownLoad 8位Left 8位UpLoad 8位Event 4位IP 4位下面是8个字节的空(88-96位是空的内容)port 4位然后是可选内容主要是服务器的HostName, UserName, PassWord
收到数据格式 1(4位) Transaction_ID(4位) Interval Time(4) 当前下载数目(4) 种子数(4) 然后是IP(4位)和Port(2位)
程序实现如下:
class CBTTracker { enum T_EVENT { EVENT_NONE, EVENT_COMPLETED, EVENT_STARTED, EVENT_STOPPED };public: CBTTracker(); ~CBTTracker();public: int PreSelect(CBTFile* pCoreFile, fd_set* pReadFD, fd_set* pWriteFD, fd_set* pExceptFD); int PosSelect(CBTFile* pCoreFile, fd_set* pReadFD, fd_set* pWriteFD, fd_set* pExceptFD); void Close(CBTFile* pCoreFile);private: BOOL ParaseData(CBTFile* pCoreFile, const char* pData, int nSize); //针对HTTP TCP协议private: CBTSocket m_TrackSocket[MAX_TRACKER];
CBTTrackerURL m_TrackerURL; CBTTrackerURL m_TempTrackers[MAX_TRACKER]; int m_CurTracker; //当前的数量 int m_State[MAX_TRACKER]; //状态 int m_CreateTime[MAX_TRACKER]; int m_nEvent; int m_TransactionID; int m_ConnectID; int m_nInterval; //查询间隔时间 int m_CompletedFlag;
};
CBTTracker::CBTTracker(){ m_CurTracker = -1; memset(m_State, 0, MAX_TRACKER * sizeof(int)); memset(m_CreateTime, 0, MAX_TRACKER * sizeof(int)); m_nEvent = EVENT_STARTED; m_TransactionID = 0; m_ConnectID = 0; m_CompletedFlag = 0;}
CBTTracker::~CBTTracker(){ for (int i = 0; i < MAX_TRACKER; ++i) { if (m_TrackSocket[i] > 0) m_TrackSocket[i].Close(); }}
int CBTTracker::PreSelect(CBTFile* pCoreFile, fd_set* pReadFD, fd_set* pWriteFD, fd_set* pExceptFD){
int nSize = pCoreFile->m_Trackers.size() - 1;// nSize = 1; if (m_CurTracker < nSize) { m_CurTracker++; } else { m_CurTracker = 0; //循环 } BOOL bHttpPro = TRUE; int thisTime = time(NULL); switch(m_State[m_CurTracker]) { case 0: if (m_CreateTime[m_CurTracker] > thisTime) return 0; if (m_TempTrackers[m_CurTracker].Valid()) { m_TrackerURL = m_TempTrackers[m_CurTracker]; m_TempTrackers[m_CurTracker].Clear(); } else { m_TrackerURL.SetParase(pCoreFile->m_Trackers[m_CurTracker]); } if (!m_TrackerURL.Valid()) { m_CompletedFlag++; Close(pCoreFile); return 0; } switch(m_TrackerURL.GetProtocol()) { case CBTTrackerURL::BT_URL_HTTP: { m_CreateTime[m_CurTracker] = time(NULL) + 30; if (m_TrackSocket[m_CurTracker] == INVALID_SOCKET) { if (!m_TrackSocket[m_CurTracker].Create(SOCK_STREAM, FALSE)) return 0; } string strAddr = CBTTCPSocket::GetHost(m_TrackerURL.GetHost().c_str()); if (strAddr.empty()) { Close(pCoreFile); return 0; } if (!m_TrackSocket[m_CurTracker].Connect(strAddr.c_str(), m_TrackerURL.GetPort()) && WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) { LOG("Connect HTTP Failed %s : %d", strAddr.c_str(), m_TrackerURL.GetPort()); return 0; } LOG("Connect HTTP Succeed %s port = %d ", m_TrackerURL.GetHost().c_str(), m_TrackerURL.GetPort()); m_State[m_CurTracker] = 1; } break; case CBTTrackerURL::BT_URL_UDP: { m_CreateTime[m_CurTracker] = time(NULL) + 30; if (m_TrackSocket[m_CurTracker] == INVALID_SOCKET) { if (!m_TrackSocket[m_CurTracker].Create(SOCK_DGRAM, FALSE)) return 0; } string strAddr = CBTUDPSocket::GetHost(m_TrackerURL.GetHost().c_str()); if (strAddr.empty()) { Close(pCoreFile); return 0; } if (!m_TrackSocket[m_CurTracker].Connect(strAddr.c_str(), m_TrackerURL.GetPort()) && WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) { return 0; } LOG("Connect UDP Succeed %s port = %d ", m_TrackerURL.GetHost().c_str(), m_TrackerURL.GetPort()); char SendData[16] = {0}; write_int(8, SendData, 0x41727101980); write_int(4, SendData + 8, 0); m_TransactionID = time(NULL); write_int(4, SendData + 12, m_TransactionID); if (m_TrackSocket[m_CurTracker].Send(SendData, 16) != 16) return 0; m_State[m_CurTracker] = 3; LOG("UDP Send Succeeded"); } break; default: return 0; } case 1: { FD_SET(m_TrackSocket[m_CurTracker], pWriteFD); FD_SET(m_TrackSocket[m_CurTracker], pExceptFD); } case 2: case 3: case 4: { FD_SET(m_TrackSocket[m_CurTracker], pReadFD); return m_TrackSocket[m_CurTracker]; }
} return 0;}int CBTTracker::PosSelect(CBTFile* pCoreFile, fd_set* pReadFD, fd_set* pWriteFD, fd_set* pExceptFD){ switch(m_State[m_CurTracker]) { case 1:
if (FD_ISSET(m_TrackSocket[m_CurTracker], pWriteFD)) //connect成功 { stringstream os; os << "GET " << m_TrackerURL.GetPath() << "?info_hash=" << uri_encode(pCoreFile->m_InfoHash) << "&peer_id=" << uri_encode(pCoreFile->m_PeerID) << "&port=" << pCoreFile->GetLocalPort() << "&downloaded=" << Int2String(pCoreFile->m_DownLoaded) << "&left=" << Int2String(pCoreFile->m_Left) << "&uploaded=" << Int2String(pCoreFile->m_UpLoaded) << "&compact=1"; //紧凑模式 /*if (f.local_ipa()) { in_addr a; a.s_addr = f.local_ipa(); os << "&ip=" << inet_ntoa(a); }*/ switch (m_nEvent) { case EVENT_COMPLETED: os << "&event=completed"; break; case EVENT_STARTED: os << "&event=started"; break; case EVENT_STOPPED: os << "&event=stopped"; break; } m_nEvent = EVENT_NONE; os << " HTTP/1.0/r" << endl << "accept-encoding: gzip/r" << endl << "host: " << m_TrackerURL.GetHost(); if (m_TrackerURL.GetPort() != 80) os << ':' << m_TrackerURL.GetPort(); os << '/r' << endl << '/r' << endl; //向服务器发数据 if (m_TrackSocket[m_CurTracker].Send((void*)os.str().c_str(), os.str().size()) != os.str().size()) { LOG("Send HTTP Traceker Failed"); Close(pCoreFile); } else { m_State[m_CurTracker] = 2; LOG("Send HTTP traceker Succeeded"); } } else if (FD_ISSET(m_TrackSocket[m_CurTracker], pExceptFD)) { LOG("HTTP WirteExept"); Close(pCoreFile); } break; case 2: if (FD_ISSET(m_TrackSocket[m_CurTracker], pReadFD)) { char RecvData[16 * 1024] = {0}; int nRecTotalSize = 0; for (int nRecSize = 0; nRecSize = m_TrackSocket[m_CurTracker].Receive(RecvData + nRecSize, 16 * 1024);) { if (nRecSize == SOCKET_ERROR) { int nError = WSAGetLastError(); if (nError != WSAEWOULDBLOCK) { LOG("Receive HTTP Failed! : %H", nError); Close(pCoreFile); } return FALSE; } nRecTotalSize += nRecSize; } LOG("Receive HTTP Succeeded, nSize = %d", nRecTotalSize); ParaseData(pCoreFile, RecvData, nRecTotalSize); } break; case 3: if (FD_ISSET(m_TrackSocket[m_CurTracker], pReadFD)) { char RecvData[2048] = {0}; int ret = m_TrackSocket[m_CurTracker].Receive(RecvData, 2048); if (ret != SOCKET_ERROR && ret >= 16 && read_int(4, RecvData + 4) == m_TransactionID && read_int(4, RecvData) == 0) { LOG("UDP Receive Succeeded"); m_ConnectID = read_int(8, RecvData + 8); memset(RecvData, 0, 2048 * sizeof(char)); //包头 write_int(8, RecvData, m_TransactionID); write_int(4, RecvData + 8, 0); m_TransactionID = time(NULL); write_int(4, RecvData + 12, m_TransactionID); //内容 memcpy(RecvData + 16, pCoreFile->m_InfoHash.c_str(), 20); memcpy(RecvData + 36, pCoreFile->m_PeerID.c_str(), 20); write_int(8, RecvData + 56, pCoreFile->m_DownLoaded); write_int(8, RecvData + 64, pCoreFile->m_Left); write_int(8, RecvData + 72, pCoreFile->m_UpLoaded); write_int(4, RecvData + 80, m_nEvent); write_int(4, RecvData + 84, ::ntohl(pCoreFile->GetLocalIP())); write_int(2, RecvData + 96, pCoreFile->GetLocalPort()); m_TrackSocket[m_CurTracker].Send(RecvData, 2048); m_State[m_CurTracker] = 4; } } break; case 4: if (FD_ISSET(m_TrackSocket[m_CurTracker], pReadFD)) { char RecvData[2048] = {0}; int Ret = m_TrackSocket[m_CurTracker].Receive(RecvData, 2048); if (Ret != SOCKET_ERROR && Ret >= 8 && read_int(4, RecvData + 4) == m_TransactionID && read_int(4, RecvData) == 1) { m_nInterval = read_int(4, RecvData + 8); pCoreFile->m_nCurPeerNum = read_int(4, RecvData + 12); pCoreFile->m_nSeedTotal = read_int(4, RecvData + 16); for (int i = 20; i + 6 < Ret; i += 6) { int nIP = read_int(4, RecvData + i); int nPort = read_int(2, RecvData + i + 4); LOG("New Peer"); pCoreFile->InsertPeer(nIP, nPort); } } else {
} } break; } return 0;}
void CBTTracker::Close(CBTFile* pCoreFile){ //m_TrackSocket.Close();
/* memset(m_State, 0, MAX_TRACKER * sizeof(int)); if (m_CompletedFlag == 0) { swap(pCoreFile->m_Trackers[0], pCoreFile->m_Trackers[m_CurTracker]); m_CurTracker = -1; } else if (++m_CurTracker < pCoreFile->m_Trackers.size()) { memset(m_CreateTime, 0, MAX_TRACKER * sizeof(int)); m_CompletedFlag--; } else */ { // m_CurTracker = -1; } }
//紧凑模式和普通模式BOOL CBTTracker::ParaseData(CBTFile* pCoreFile, const char* pData, int nSize){
for (int i = 0; i < nSize; ++i) { if (pData[i] == ' ') //找到第一个空格 { int nResult = atoi(pData + i); //回应码一般只有3位 if (nResult == 302 || nResult == 301) { string strTemp = string(pData + i, nSize - i); int a = strTemp.find("http://"); int b = strTemp.find_first_of(":", a + strlen("http://")); if (string::npos == b) { m_TempTrackers[m_CurTracker].SetHost(strTemp.substr(a)); } else { m_TempTrackers[m_CurTracker].SetHost(strTemp.substr(a + strlen("http://"), b - a - strlen("http//"))); if (strTemp[b] == '/') { m_TempTrackers[m_CurTracker].SetPath(strTemp.substr(b)); } else { b++; a = strTemp.find('/', b); if (string::npos == a) { m_TempTrackers[m_CurTracker].SetPort(atoi(strTemp.substr(b).c_str())); } else { m_TempTrackers[m_CurTracker].SetPort(atoi(strTemp.substr(b, a - b).c_str())); m_TempTrackers[m_CurTracker].SetPath(strTemp.substr(a, strlen("/announce"))); } } } m_TempTrackers[m_CurTracker].SetProtocol(m_TrackerURL.GetProtocol()); return FALSE; } else if (nResult != 200) { return FALSE; } for (i = 0; i + 4 < nSize; ++i) { if (strncmp(pData + i, "/n/n", 2) == 0 || strncmp(pData + i, "/r/n/r/n", 4) == 0) //找到空格换行 { const BYTE* pRes = NULL; int nLen = 0; if (pData[i] == '/n') { pRes = (BYTE*)pData + i + 2; nLen = nSize - i - 2; } else { pRes = (BYTE*)pData + i + 4; nLen = nSize - i - 4; } CBTCode TrackerRes; if (!TrackerRes.BTEnCode(pRes, nLen)) { return FALSE; } CBTDicItem* pBase = (CBTDicItem*)TrackerRes.GetValue(); if (pBase == NULL) { return FALSE; } CBTStrItem* pStr = (CBTStrItem*)pBase->GetValue("failure reason"); if (NULL == pStr) { CBTIntItem* pInt = (CBTIntItem*)pBase->GetValue("incomplete"); if (pInt) pCoreFile->m_nCurPeerNum = pInt->GetValue(); pInt = (CBTIntItem*)pBase->GetValue("complete"); if (pInt) pCoreFile->m_nSeedTotal = pInt->GetValue(); //下面解析Peers,有可能是字符串,也可能是列表结构 pStr = (CBTStrItem*)pBase->GetValue("peers"); if (pStr && !pStr->GetValue().empty()) { string strPeers = pStr->GetValue(); const char* p = strPeers.c_str(); for (; p + 6 < strPeers.c_str() + strPeers.size(); p += 6) { int nIP = atoi(p); int nPort = atoi(p + 4); pCoreFile->InsertPeer(nPort, nPort); } } else { CBTListItem* pPeerList = (CBTListItem*)pBase->GetValue("peers"); if (pPeerList) { for (int i = 0; i < pPeerList->GetSize(); ++i) { CBTDicItem* pDic = (CBTDicItem*)pPeerList->GetValue(i); int nIP = inet_addr(((CBTStrItem*)pDic->GetValue("ip"))->GetValue().c_str()); int nPort = ((CBTIntItem*)pDic->GetValue("port"))->GetValue(); pCoreFile->InsertPeer(nIP, nPort); } } } } return TRUE; } return FALSE; } return FALSE; } return FALSE; } return FALSE;}