这一篇以讲述在阅读libtorrent-0.15.10这个版本时的记录内容为主。在上一篇P2P学习的基础上,这一篇给出本人在阅读0.15.10这个版本的时候的一些记录。仅以个人观点为主,仅作参考。
先对几个文件/数据结构进行简单说明(这里说的会存在不准确,或错误的情况;请找到更靠谱的依据,以及凭借实际看代码时的感受为评价):
0,peer_connection(及其子类) : 该类是用于peer之间的连接的抽象,其子类有bt_peer_connection, web_peer_connection, http_seed_connection。
1,torrent : 可以看做该类是作为一种文件/下载&上传过程的映射。
2,piece_picker : 负责处理piece的相关工作。
3,piece_manager : 这个数据结构与文件数据的存储有关,除了与disk_io_thread,torrent, peer_connection。
4,downloading_piece : 应该是piece的抽象数据结构。
5,其他还有如policy, session(_impl)等,有些是我也还没有弄清楚,另有些是没有什么说明的必要。
先来看看 peer_connection::incoming_bitfield(bitfield const& bits)。按照之前一篇的介绍,这个方法是当两个节点建立连接后,最先(可能)被调用的。
我们主要看这一部分代码:
...
bool interesting = false;
if (!t->is_seed())
{
t->peer_has(bits);
for (int i = 0; i < (int)m_have_piece.size(); ++i)
{
bool have = bits[i];
if (
have && !m_have_piece[i])
{
if (!t->have_piece(i) && t->picker().piece_priority(i) != 0)
interesting = true;
}
else if (
!have && m_have_piece[i])
{
// this should probably not be allowed
t->peer_lost(i);
}
}
}
...
if (interesting) t->get_policy().peer_is_interesting(*this);
else if (upload_only()) disconnect(errors::upload_upload_connection);
}
划线部分,(bitfield) m_have_piece[i]是the pieces the other end have,即远程节点的片的持有情况。(bool) have表示本次连接接受的bitfield的情况,也就是最新的bitfield的情况,而m_have_piece应该为前次连接时所得到的bitfield的情况,考虑片的变动(最新获取,丢失?),所以hvae与m_have_piece表示的对于相同片的不同情况是存在的。
当接受到新的bitfied,在处理后,判断为对远程节点(的数据)感兴趣时,将会把远程节点设置为 m_pee_interested,即peer_in_interesting。并且,仅当本地进入 upload_only 模式时,对于对远程节点不感兴趣的情况下才会断开连接。并不是一旦不感兴趣,就马上断开连接。
接下来再看看 void policy::peer_is_interesting(peer_connection& c)。
我们只看这部分:
...
c.send_interested();
if (
c.has_peer_choked() && c.allowed_fast().empty())
return;
request_a_block(*m_torrent, c);
c.send_block_requests();
当对远程节点感兴趣后,首先发送 interested 报文,即c.send_interested,以通知远程节点做相应工作(至少是修改 m_peer_interested 状态位什么的)。划线部分表示实在没什么数据可以传,远程节点即choke了本地,并且也没有suggest_piece可以获取(或者已经获取完了),所以只能先返回,维持连接状态,并不多做工作。接下来两句可以看出虽然感兴趣的最小单元是piece,但实际request的最小单元应该是block。
接下来接着看 void request_a_block(torrent& t, peer_connection& c)。函数内部会看到 c.downoad_queue(),c.request_queue(),c.suggest_piece(), 这三个方法返回的数据代表了已经请求了的piece,预留请求(即将请求)的piece,suggest_piece。如果考虑数据交互的相应延迟,以及实际传输的是piece中的各个block,所以连接数的限制,所以分别设立已经请求的piece和即将请求的piece有一定道理;由于目前知识有限,我对这样做的必要性持保留态度。
具体代码,我们只看这些部分:
...
if (
c.has_peer_choked())
{
// if we are choked we can only pick pieces from the
// allowed fast set. The allowed fast set is sorted
// in ascending priority order
std::vector const& allowed_fast = c.allowed_fast();
// build a bitmask with only the allowed pieces in it
...
}
...
p.pick_pieces(...);
...
// if the number of pieces we have + the number of pieces
// we're requesting from is less than the number of pieces
// in the torrent, there are still some unrequested pieces
// and we're not strictly speaking in end-game mode yet
// also, if we already have at least one outstanding
// request, we shouldn't pick any busy pieces either
...
for (...interesting_pieces...)
{
...
// don't request pieces we already have in our request queue
// This happens when pieces time out or the peer sends us
// pieces we didn't request. Those aren't marked in the
// piece picker, but we still keep track of them in the
// download queue
...
// ok, we found a piece that's not being downloaded
// by somebody else. request it from this peer
// and return
if (!
c.add_request(*i, 0)) continue; // c.add_request(..) will do something with m_request_queue;
...
}
...
/* do something with the busy_block and endgame, then return */
...
以上,我基于我的理解,简化了很多内容(不然篇幅太大了),已达到便于表达并且足够的目的,当然这是一个见仁见智的事情。我只对可能下一步需要关注的函数方法调用/入口等进行了保留,并划线了。其他的保留更多的则是注释,我希望看到这里,您能明白我保留这些注释是因为它们很值得读,对于了解一些处理细节和流程都是值得读的。具体更多的东西,当然还是以个人对代码的实际体验为主。
首先检查本地是否被远程节点choke了,那样的话我们只能下载一些suggest_piece了。之后用 piece_picker对我们有打算的piece进行处理,p.pick_pieces()上面"..."省略的部分包含足够多的内容说明这一句是用来干嘛的。再接下来,将本地感兴趣的非busy的piece加入request_queue。再后面,就是对busy_block和endgame的检查以及处理了。我的关注点还没有到达更细节的地方,所以那些地方,我没有去深入了解。
接下来再看看 peer_connection::send_block_request()。上一步骤中本地节点的工作概况来说就是将piece合适的放入请求队列中,在这一步骤才会发出请求。没太多要说的,write_request(..)是个虚函数,time_now()函数记录的时间可能在timeout、片选、 节点选择方面用到,还有你可能想看看peer_request是个怎么样的数据结构。
我们可以以bt_peer_connection为例,看看write_request(peer_request const& r)干了什么。其实也没什么内容可说,具体的即使按照协议格式,提取peer_request中的信息,制作报文,然后调用peer_connection::send_buffer(...),将报文发送出去了。如果你愿意具体了解报文格式,那么先看看文档什么的,在看看这里吧。目前我对这里也不是特别关心,所以也就略过了。
上面我们说了从 incoming_bitfield 开始的一些内容,总结下来就是,收到bitfield后,检查&判断是不是感兴趣,不感兴趣也不一定断开连接,感兴趣的话,就努力去发送请求。那么谁调用 incoming_bitfield 呢,或者说之前的步骤或相关内容呢?可以去看看 peer_connection 的 on_connect(..),on_connected(..),以及torrent的 connect_to_peer(..)。那些就涉及到建立连接,握手之类的了。
对于建立连接,我们先看看 session_impl::incoming_connection(bost::shared_ptr const& s)。
还是简化的只看部分代码和注释:
...
tcp::endpoint endp = s->remote_endpoint(ec);
...
// local addresses do not count, since it's likely
// coming from our own client through local service discovery
// and it does not reflect whether or not a router is open
// for incoming connections or not.
if (!is_local(endp.address()))
m_incoming_connection = true;
...
// don't allow more connections than the max setting
...
// check if we have any active torrents
// if we don't reject the connection
...
setup_socket_buffers(*s);
new bt_peer_connection(*this, s, endp, 0));
...
if (!c->is_disconnecting())
{
m_connections.insert(c);
c->start();
}
这部分内部,如果不想关心太多东西的话,那么只看最后的c->start();就可以了。前面的内容大致就是体现了对远程节点是local address的处理和最大连接数的问题。
由 c->start(); 我们可以按照 bt_peer_connection::start() -> peer_connection::start() -> peer_connection::init() 的顺序追溯到 peer_connection::init(),这也是接下来可以看看的东西。对于追求细节的各位,看看前面那两个也无妨。
对于 peer_connection::init() 我们只看这部分:
...
// now that we have a piece_picker,
// update it with this peer's pieces
...
// if we're a seed, we don't keep track of piece availability
if (!t->is_seed())
{
t->peer_has(m_have_piece);
bool interesting = false;
for (int i = 0; i < int(m_have_piece.size()); ++i)
{
if (m_have_piece[i])
{
// if the peer has a piece and we don't, the peer is interesting
if (!t->have_piece(i)
&& t->picker().piece_priority(i) != 0)
interesting = true;
}
}
if (interesting) t->get_policy().peer_is_interesting(*this);
else send_not_interested();
}
else
{
update_interest();
}
并且其中update_interested()的内容简单看来就是:
...
if (!interested) send_not_interested();
else t->get_policy().peer_is_interesting(*this);
...
总是简而言之就是纠结是否interesting的问题。需要强调一下的是,这里是peer_connection::init(),上面那部分讲的是peer_connection::incoming_bitfield(bitfield const& bits),两边的内容都其实是在纠结是否感兴趣的问题。不过一个是在init,也就是第一次创建连接的抽象映射是做的检查,而另一个则应该是两个节点断了连、连了断,出现这样纠结的过程下才会用到的。
再往前,那就应该是向tracker获取peers的事情了,我是按照 http_tracker_connection::on_responce(...) -> http_tracker_connection::parse(...) -> torrent::tracker_responce(..) -> policy::add_peer(..) 的顺序,简单粗暴的看了看。在 http_tracker_connection::on_responce(..)中会看到http_tracker_connection::parse(..),这是按照协议格式解包报文什么的。此外再注意一下tracker_responce在torrent.hpp中的注释描述是"callback",其他的就没什么要注意的了。当然如果追求细节那就看看吧,也无妨。
上一篇内容中提及的关于 choke, unchoke, interested, not_interested 报文的两组函数,这里就不多说了。最主要要看的还是 peer_connection::incoming_interested()和peer_connection::incoming_not_interested(),应该主要看看什么情况下会发送choke/unchoke报文,什么情况下不会,其他的基本看点不大。
以上就是本篇内容了,可以算做对上一篇的补充,也可以看做是学习libtorrent库的一种切入方式。