libed2k源码导读:(四)P2P消息

本文深入解析libed2k的P2P消息处理,包括HELLO和HELLO answer消息的定义、收取与发送,以及文件请求的详细流程,涵盖文件请求概述、文件状态请求、分片哈希集消息和开始上传文件请求等关键步骤,揭示了P2P连接的工作流和消息交互机制。
摘要由CSDN通过智能技术生成

目录

 

第四章 P2P消息

4.1 libed2k p2p 连接工作流

4.2 HELLO和HELLO answer

4.2.1消息定义

4.2.2 收取Hello和helloAnswer消息

4.2.3 发送hello和helloAnswer消息

4.2.4 client_ext_hello和client_ext_hello_answer

4.3 文件请求

4.3.1 文件请求概述

4.3.2 “文件请求”消息和响应

4.3.3 "文件状态"请求

4.3.4 "分片哈希集"消息及响应

4.3.5 "开始上传文件"请求及其响应(OP_STARTUPLOADREQ)

4.3.6 "请求文件数据块"消息以及响应


第四章 P2P消息

 

4.1 libed2k p2p 连接工作流

 

处理P2P消息的逻辑被封装在 `base_connection`和`peer_connection`对象中,调用栈如下:

session::listen_on(int port, const char* net_interface /*= 0*/) <--在这里开始监听TCP连接

|---void session_impl::open_listen_port()

    |---async_accept

        |---session_impl::on_accept_connection <--TCP 建立

            |---session_impl::incoming_connection(boost::shared_ptr<tcp::socket> const& s)

                |---new peer_connection(*this, s, endp, NULL)

                    |---setup P2P message hander

                    |---peer_connection::do_read

                        |---base_connection::do_read(); //握手,设置选项等。

                        |     |---base_connection::on_read_header

                        |        |---base_connection::on_read_packet

                        |            |---find handler by type and protocol in header

                        |            |---call handler() <-- 处理收到的数据

                    或者

                        |---peer_connection::receive_data() //当文件传输正在进行时

                            |---peer_connection::on_receive_data(error, bytes_transferred)

                                |--process data received. <-- 处理收到的数据

 

点对点消息处理函数的设置在`peer_connection::reset()`函数中,接下来分各节说明P2P消息。

 

4.2 HELLO和HELLO answer

4.2.1消息定义

(From EMule Protocol 第六章 6.4.1)

This message is the first message in the handshake between two e-mule clients. This message is very much like the server login message (see in section 6.2.1). Both messages have the same type code (0x01), they are the only messages in the protocol that have overlapping type code. Both messages provide the same data and even in the same order. There are two main differences: the client hello message begins with a user hash size field while the server login message immediately begins with the user hash value, also the client hello message ends with additional server IP and port information which is not relevant for the server login message.

Name

Size in bytes

Default Value

Comment

Protocol

1

0xE3

 

Size

4

 

The size of the message in bytes not including the header and size fields

Type

1

0x01

The value of the OP HELLO opcode

User Hash size

1

16

The size of the user hash field

     

以下部分和Hello answer消息的结构一致

User Hash

16

 

TBD

Client ID

4

0

TBD

TCP Port

2

4662

The TCP port used by the client, configurable

Tag Count

4

4

The number of tags following in the message

Tag list

varies

NA

A list of tags specifying remote client’s properties

Server IP

4

NA

The IP of the server to which the client is connected

Server TCP Port

2

NA

The TCP port on which the server listens

There are three types of tags that may appear in the tag-list. The port tag is optional and usually not provided. Tag encoding rules are described is detail an the beginning of this chapter.

Name

Tag name

Tag Type

Comment

Username

Integer, 0x01

String

 

Version

Integer 0x11

String

 

Port

Integer 0x0F

Integer

 

 

HELLO Answer (From EMule Protocol 6.4.1)

Sent as an answer to a Hello message. Contains exactly the same fields as the hello message except for the message type.

Name

Size in bytes

Default Value

Comment

Protocol

1

0xE3

 

Size

4

 

The size of the message in bytes not including the header and size fields

Type

1

0x4C

The value of the OP HELLOANSWER op-code

Hello fields

   

The same fields as in the hello message starting with the user hash

 

4.2.2 收取Hello和helloAnswer消息

message handlers在"peer_connection::reset()"中设置:

add_handler(std::make_pair(OP_HELLO, OP_EDONKEYPROT), 
    boost::bind(&peer_connection::on_hello, this, _1)); 

add_handler(get_proto_pair<client_hello_answer>(), 
    boost::bind(&peer_connection::on_hello_answer, this, _1));

从网络流中解析HELLO消息peer_connection::on_hello:

void peer_connection::on_hello(const error_code& error) 
{ 
    if (!error){ 
        DECODE_PACKET(client_hello, hello); 
        // store user info 
        m_hClient = hello.m_hClient; 
        m_options.m_nPort = hello.m_network_point.m_nPort; 
        //解析hello消息中的tag_list并存放到m_options对应的项中 
        parse_misc_info(hello.m_list); 
        DBG("hello {" << " server point = " << hello.m_server_network_point << " network point = " << hello.m_network_point << "} <== " << m_remote); 
        //在CALLBACK列表中寻找当前连接的用户,如果找到就从列表中移除并返回文件md4hash 
        //前面有说明CALLBACK已经不被大部分的服务器所支持,这里我们跳过对它的说明。 
        md4_hash file_hash = m_ses.callbacked_lowid(hello.m_network_point.m_nIP); 
        if (file_hash != md4_hash::invalid)
        { 
            DBG("lowid peer detected for " << file_hash.toString()); 
            m_active = true; 
            attach_to_transfer(file_hash); 
        } 
        //收到了对方发送的hello消息,给他一个ACK。 
        write_hello_answer(); 
    }
    else
    { 
        ERR("hello packet received error " << error.message()); 
    } 
}

反序列化流到`client_hello`对象:

DECODE_PACKET with T=client_hello, t = hello in following code:

template<typename T> 
bool decode_packet(T& t)
{ 
    try{ 
        if (!m_in_container.empty()){ 
            boost::iostreams::stream_buffer<base_connection::Device> buffer( 
                    &m_in_container[0], 
                    m_in_header.m_size - 1); 
            std::istream in_array_stream(&buffer); 
            archive::ed2k_iarchive ia(in_array_stream); 
            ia >> t; 
        } 
    }catch(libed2k_exception& e){
        DBG("Error on conversion " << e.what()); 
        return (false); 
    } 
    return (true); 
}

"client_hello"序列化:

struct client_hello : public client_hello_answer 
{ 
    boost::uint8_t m_nHashLength; //!< clients hash length 
    //... 
    template<typename Archive> 
    void serialize(Archive& ar)
    { 
        ar & m_nHashLength; 
        client_hello_answer::serialize(ar); 
    } 
};

`client_hello_answer`类型定义:

struct client_hello_answer 
{ 
    md4_hash m_hClient; 
    net_identifier m_network_point; 
    tag_list<boost::uint32_t> m_list; 
    net_identifier m_server_network_point; 
    //... 
    template<typename Archive> 
    void serialize(Archive& ar)
    { 
        ar & m_hClient & m_network_point & m_list & m_server_network_point; 
    } 
};

client_hello和client_hello_answer唯一的不同是增加了一个字节的m_nHashLength。

下面是从网络流中读取client_hello_answer:

void peer_connection::on_hello_answer(const error_code& error) 
{ 
    if (!error){ 
        DECODE_PACKET(client_hello_answer, packet); 
        //解析对方hello响应消息中的tag_list并存放到m_options对应的项中 
        parse_misc_info(packet.m_list); 
        m_hClient = packet.m_hClient; 
        DBG("hello answer {name: " << m_options.m_strName << " : mod name: " << m_options.m_strModVersion << ", port: " << m_options.m_nPort << "} <== " << m_remote); 
        m_ses.m_alerts.post_alert_should( 
            peer_connected_alert(get_network_point(), 
                get_connection_hash(), 
                m_active)
            ); 
        //我们主动发起的连接,收到hello answer消息就完成了握手。 
        //在下面的函数中发起一条“文件请求”消息,传入我们所需文件的md4 hash。 
        finalize_handshake(); 
    }
    else
    { 
        ERR("hello error " << error.message() << " <== " << m_remote); 
    } 
}

 

4.2.3 发送hello和helloAnswer消息

 

在发起连接完成后(peer_connection::on_connect),连接发起方首条发送的消息就是hello:

void peer_connection::write_hello() { 
    DBG("hello ==> " << m_remote); 
    const session_settings& settings = m_ses.settings(); 
    client_hello hello(m_ses.settings().user_agent, 
        net_identifier(m_ses.m_server_connection->client_id(),m_ses.settings().listen_port), 
        net_identifier(address2int(m_ses.server().address()), m_ses.server().port()), 
        m_ses.settings().client_name, 
        m_ses.settings().mod_name, 
        m_ses.settings().m_version); 
    // fill special fields 
    hello.m_nHashLength = MD4_DIGEST_LENGTH; 
    hello.m_network_point.m_nIP = m_ses.m_server_connection->client_id();
    hello.m_network_point.m_nPort = settings.listen_port; 
    // 这个函数填充hello/helloAnswer消息的tag_list, 
    // 上面提到过这两个消息的结构仅有一个HashLength不一样。 
    append_misc_info(hello.m_list); 
    write_struct(hello); 
}

接收方收到HELLO消息时发回响应如下:

void peer_connection::write_hello_answer() 
{ 
    // prepare hello answer 
    client_hello_answer cha(m_ses.settings().user_agent, 
        net_identifier(m_ses.m_server_connection->client_id(),
        m_ses.settings().listen_port), 
        net_identifier(address2int(m_ses.server().address()), m_ses.server().port()), 
        m_ses.settings().client_name, 
        m_ses.settings().mod_name, 
        m_ses.settings().m_version); 
    //这个函数创建hello/helloAnswer的tag_list 
    append_misc_info(cha.m_list); 
    cha.dump(); 
    DBG("hello answer ==> " << m_remote); 
    write_struct(cha); 
    //注意下面这个函数在对方发起连接时接收方的on_hello_answer中也有调用。
    //在这里我们是发送方,所以这个函数此时仅创建和发送缓冲区有关的数据结构实例。 
    finalize_handshake(); 
}

append_misc_info函数的实现:

void peer_connection::append_misc_info(tag_list<boost::uint32_t>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值