P2P学习——BT节点的下载流程分析,libtorrent部分代码阅读了解(2)

    这一篇以讲述在阅读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<int> 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<socket_type> 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);
    boost::intrusive_ptr<peer_connection> c(
    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库的一种切入方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值