libed2k源码导读:(三)网络IO

目录

 

第三章 网络IO

3.1 数据序列化和反序列化

3.1.1 以向服务器发送数据为例

3.1.2 序列化和反序列化对象的细节

3.1.3 序列化集合类对象

3.1.4 Tag,tag_list和它们的序列化

3.2 和emule服务器的网络通信实现

3.3 其他发送给服务器的消息

3.3.1 登录消息及其响应

3.3.2 服务器消息(Server message)

3.3.3 获取服务器列表(Get list of servers)

3.3.4 接收服务器状态(Server status)

3.3.5 服务器鉴定(Server identification)

3.3.6 获取文件源及其响应(Get sources)

3.3.7 文件查找及其服务器响应消息的处理

3.3.8 其他消息


第三章 网络IO

这一章分析和网络传输有关的实现,主要内容包括和emule服务器的通信和数据的序列化以及反序列化。

 

3.1 数据序列化和反序列化

libed2k使用了boost提供的序列化模板,通过这个模板可以简化从对象到字节流的生成方式。按照它提供的机制,需要在可序列化的对象上添加save/load方法以告知序列化模板怎么将自己转换为字节流和从一个字节流中构造自己。

 

3.1.1 以向服务器发送数据为例

每个数据包都包含包头(header)和包体(body),头部形式为{1字节:协议,4字节长度, 1字节类型(opcode)}统一为6个字节,body的长度因数据包的类型而异。

待发送数据的格式是被组织为一个message deque,每个message都是一个std::pair类型的键值对,它的键为数据包头而值为数据包体。

数据包的序列化在server_connection.hpp的server_connection::do_write方法中,实现大致如下:

  • 它先使用archive::ed2k_oarchive序列化message的数据包body部分。
  • 计算出body的长度后将这个长度值填充进message的数据包头部(即为下面代码中的m_write_order.back().first.m_size赋值)
  • 填充头部的数据包类型(又称opcode,下面代码中的m_write_order.back().first.m_type)
  • 填充头部的协议码m_write_order.back().first.m_protocol。

最后再将这个message转为一个std::vector类型的缓冲区对象并通过boost::asio::async_write发送出去。参见下方的代码:

template 
void server_connection::do_write(T& t) 
{ 
    // skip all requests to server before connection opened 
    if (current_operation != scs_handshake && current_operation != scs_start) 
        return; 
    CHECK_ABORTED() 
    last_action_time = time_now(); 
    bool write_in_progress = !m_write_order.empty(); 
    m_write_order.push_back(std::make_pair(libed2k_header(), std::string())); 
    boost::iostreams::back_insert_device inserter(m_write_order.back().second); 
    boost::iostreams::stream > s(inserter); 
    // Serialize the data first so we know how large it is. 
    archive::ed2k_oarchive oa(s); 
    oa << t; s.flush(); 
    std::string compressed_string = compress_output_data(m_write_order.back().second); 
    if (!compressed_string.empty()) 
    { 
        m_write_order.back().second = compressed_string; 
        m_write_order.back().first.m_protocol = OP_PACKEDPROT;
    } 
    m_write_order.back().first.m_size = m_write_order.back().second.size() + 1; 
    // packet size without protocol type and packet body size field
    m_write_order.back().first.m_type = packet_type::value;
    //DBG("server_connection::do_write " << packetToString(packet_type::value) << " size: " << m_write_order.back().second.size() + 1); 
    if (!write_in_progress) 
    { 
        std::vector buffers;
        buffers.push_back(boost::asio::buffer(&m_write_order.front().first, header_size)); 
        buffers.push_back(boost::asio::buffer(m_write_order.front().second)); 
        boost::asio::async_write(m_socket, 
            buffers, 
            boost::bind(&server_connection::handle_write, 
                self(), 
                boost::asio::placeholders::error, 
                boost::asio::placeholders::bytes_transferred));
    } 
}

 

3.1.2 序列化和反序列化对象的细节

执行下面序列化作为数据包体(body)的泛型T对象t时,

archive::ed2k_oarchive oa(s); 
oa << t;

首先使用输出流对象s初始化ed2k_oarchive对象oa,然后“oa<

//Classes need to be serialize using their special method 
template 
inline void serialize_impl(T & val, 
    typename boost::enable_if >::type* = 0) 
{ 
    val.serialize(*this); 
}

最终调用的是t.serialize(*this)(即当t为类的实例时oa << t等同于t.serialize(oa))。

 

下面以T = search_request_block为例分析具体实现细节。当T为search_request_block类型时,最终上面的代码就是执行下面对象的serialize方法:

/** * simple wrapper for use in do_write template call */ 
struct search_request_block 
{ 
    search_request_block(search_request& ro) 
    : m_order(ro)
    {
    } 
    template 
    void serialize(Archive& ar)
    { 
        //使用ar对象将search_request集合中的元素逐个序列化 
        for (size_t n = 0; n < m_order.size(); n++) 
            ar & m_order[n]; 
    } 
    search_request& m_order; 
};

search_request_block由一个search_request(即deque)对象初始化,写入时按次序往Archive对象(这里是archive::ed2k_oarchive)写入。

注意archive::ed2k_oarchive中关于‘&’操作符的重载

template 
ed2k_oarchive& operator&(T& t) 
{ 
    *this << t; 
    return (*this); 
} 

template 
ed2k_oarchive& operator<<(T& t) 
{ 
    serialize_impl(t); 
    return *this; 
} 

ed2k_oarchive& operator <<(std::string& str) 
{ 
    raw_write(str.c_str(), str.size()); 
    return *this; 
} 

// special const 
ed2k_oarchive& operator <<(const std::string& str) 
{ 
    raw_write(str.c_str(), str.size()); return *this; 
}

当ar对象的实例是archive::ed2k_oarchive oa时上方的“ar & m_order[n]”等于“oa << m_order[n]”

总结一下序列化search_request_block时做的事情(上面的函数调用下面的函数):

search_request_block srb(ro); do_write(srb); [注:这里是server_connection::do_write]

    ==> archive::ed2k_oarchive oa(s); oa << srb; [注:s是输出流]

        ==> srb.serialize(oa) [search_request_block::serialize]

            ==> 循环执行oa & srb.m_order[n]

                ==> oa << srb.m_order[n] [注:m_order是search_request_entry的deque]

                    ==> srb.m_order[n].serialize(oa)

                        ==> search_request_entry::LIBED2K_SERIALIZATION_SPLIT_MEMBER(宏分析见下面)

                            ==> libed2k::archive::split_member(ar, *this) [注:this指向上面的srb.m_order[n]]

                                ==> access::member_save(ar, srb.m_order[n])

                                    ==> srb.m_order[n].save(ar) [注:最终循环调用的是search_request_entry::save方法]

注意上方LIBED2K_SERIALIZATION_SPLIT_MEMBER这个宏的作用是定义它所在类的serialize函数,在这个函数中根据流的类型将调用当前对象的序列化或反序列化方法,如果是输出流则调用save方法,反之调用load方法。它的实现是:

// split member function serialize funcition into save/load 
#define LIBED2K_SERIALIZATION_SPLIT_MEMBER() 
\ template 
\ void serialize(Archive& ar)
\ { 
\ libed2k::archive::split_member(ar, *this); 
\ } 

template 
inline void split_member( Archive & ar, T & t) 
{ 
    typedef BOOST_DEDUCED_TYPENAME boost::mpl::eval_if< BOOST_DEDUCED_TYPENAME Archive::is_saving, boost::mpl::identity >, boost::mpl::identity > >::type typex; 
    typex::invoke(ar, t); 
}

上面的typex::invoke定义为:当Archive::is_saving有效时类型为member_saver否则为member_loader。关于boost::mpl::eval_if的介绍见这里。注意Archive::is_saving在class ed2k_oarchive中的定义为:

typedef boost::mpl::bool_ is_loading; 
typedef boost::mpl::bool_ is_saving;

在class ed2k_iarchive中的定义为:

typedef boost::mpl::bool_ is_loading; 
typedef boost::mpl::bool_ is_saving;

在这个server_connection::do_write的例子中Archive泛型类型是ed2k_oarchive(因为ed2k_oarchive::is_saving == false),所以这里typex等于boost::mpl::identity >即:

template 
struct member_saver 
{ 
    static void invoke(Archive & ar,T & t) 
    { 
        access::member_save(ar, t); 
    } 
};

上面调用的access:member_save定义如下:

class access 
{ 
public: 
// pass calls to users's class implementation 
template 
static void member_save(Archive & ar, T& t) 
{ t.save(ar); } 

template static void member_load(Archive& ar, T& t) 
{ t.load(ar); } 

};

最终调用的是T类型实例的save方法,在这个例子中即为search_request_entry::save(oarchive)方法,它的实现如下:

void search_request_entry::save(archive::ed2k_oarchive& ar) 
{ 
    ar & m_type; 
    if (m_type == SEARCH_TYPE_BOOL) 
    { 
        DBG("write: " << toString(static_cast(m_operator))); 
        // it is bool operator 
        ar & m_operator; 
        return; 
    } 
    if (m_type == SEARCH_TYPE_STR || m_type == SEARCH_TYPE_STR_TAG) 
    { 
        DBG("write string: " << m_strValue); 
        // it is string value 
        boost::uint16_t nSize = static_cast(m_strValue.size()); 
        ar & nSize; 
        ar & m_strValue; 
        boost::uint16_t nMetaTagSize; 
        // write meta tag if it exists 
        if (m_strMetaName.is_initialized()) 
        { 
            nMetaTagSize = m_strMetaName.get().size(); 
            ar & nMetaTagSize; 
            ar & m_strMetaName.get(); 
        } 
        else if (m_meta_type.is_initialized()) 
        { 
            nMetaTagSize = sizeof(m_meta_type.get()); 
            ar & nMetaTagSize; 
            ar & m_meta_type.get(); 
        } 
        return; 
    } 
    if (m_type == SEARCH_TYPE_UINT32 || m_type == SEARCH_TYPE_UINT64) 
    { 
        (m_type == SEARCH_TYPE_UINT32) ? (ar & m_nValue32) : (ar & m_nValue64); ar & m_operator; 
        boost::uint16_t nMetaTagSize; 
        // write meta tag if it exists 
        if (m_strMetaName.is_initialized()) 
        { 
            nMetaTagSize = m_strMetaName.get().size(); 
            ar & nMetaTagSize; 
            ar & m_strMetaName.get(); 
        } 
        else if (m_meta_type.is_initialized()) 
        { 
            nMetaTagSize = sizeof(m_meta_type.get()); 
            ar & nMetaTagSize; 
            ar & m_meta_type.get(); 
        } 
        return; 
    } 
}

可以看到当写入一个search_request_entry到网络流时是先序列化类型“ar & m_type;”到流中,然后根据不同类型写入不同的值。

由于搜索功能不是我们当前程序的重点,关于search_request_entry序列化再往下的细节这里就不再分析。

 

使用(基于boost的)序列化框架的小结:

当我们需要序列化和反序列化一个类型为ClassA对象时,在此ClassA中需要:

  1. 定义从字节流生成对象的方法“void load(archive::ed2k_iarchive&)”
  2. 定义从对象到字节流的方法“void save(archive::ed2k_oarchive& ar);”
  3. 添加LIBED2K_SERIALIZATION_SPLIT_MEMBER()宏,它将视序列化流的类型自动调用save或者load。

当序列化对象时只需要执行以下代码就可以将对象a序列化到流中了:

archive::ed2k_oarchive oa(output_stream); 
ClassA a(some_value); 
oa << a;

类似的,通过下面的代码可以从流中反序列化出一个对象:

archive::ed2k_iarchive ia(in_stream); 
ClassA a; 
ia >> a;

 

3.1.3 序列化集合类对象

container_holder是常见的可序列化集合类的容器,它的定义如下:

/** 
 * common container holder structure 
 * contains size_type for read it from archives and some container for data 
 */ 
template 
struct container_holder 
{ 
    size_type m_size; 
    collection_type m_collection; 
    typedef typename collection_type::iterator Iterator; 
    typedef typename collection_type::value_type elem; 
    container_holder() 
        : m_size(0)
    {} 

    container_holder(const collection_type& coll) 
        : m_size(coll.size()), m_collection(coll) 
    { } 

    void clear()
    { 
        m_collection.clear(); 
        m_size = 0; 
    } 

    template 
    void save(Archive& ar)
    { 
        // save collection size in field with properly size 
        m_size = static_cast(m_collection.size()); 
        ar & m_size; 
        for(Iterator i = m_collection.begin(); i != m_collection.end(); ++i) 
        { 
            ar & *i; 
        } 
    } 

    template void load(Archive& ar) 
    { 
        ar & m_size; 
        // avoid huge memory allocation 
        if (static_cast(m_size) > MAX_COLLECTION_SIZE) 
        { 
            throw libed2k_exception(libed2k::errors::decode_packet_error); 
        } 
        m_collection.resize(static_cast(m_size)); 
        for (size_type i = 0; i < m_size; i++) 
        { 
            ar & m_collection[i]; 
        } 
    } 
    //...略过部分代码 
    LIBED2K_SERIALIZATION_SPLIT_MEMBER() 
};

container_holder的定义中包含了LIBED2K_SERIALIZATION_SPLIT_MEMBER宏(这个宏在上节有详细说明),所以container_holder序列化的函数为save,反序列化的函数为load。

container_holder的第一个泛型参数为序列化或反序列化“数组大小”时的类型,这个值视协议而定,通常在emule协议中如果消息内包含了一个集合那么对应的形式一般都是[长度][列表元素1][...][列表元素N],“长度”域的类型与消息的类型有关,1个字节、2个字节、4个字节都有可能。如果协议中某条消息规定了代表一个队列的长度的域为2个字节则在container_holder中第一泛型参数可以指定为ushort,如果协议中定义一个“长度”的域为4个字节那么则需要使用boost::uint32_t。注意如果用错类型会破坏数据结构而导致无法正确序列化和反序列化。

container_holder第二个泛型参数通常是一个STL集合类如queue, vector, list。

在save代码中可以看到,在序列化container_holder集合时,先将内部大小写入输出流中然后再逐个序列化内部集合中的元素。在load的实现中和save相对应,先读取(反序列化)集合的大小,然后逐个反序列化集合的元素。

 

3.1.4 Tag,tag_list和它们的序列化

 

tag在libed2k中被大量的使用,在emule协议的6.1.3中描述了tag:

Tags are TLV-like (Type, Length, Value) structures which are used for appending optional

data to eMule messages. There are several types of tags, all of them are listed in this section.

When referring to specific tags in protocol messages only the tag type is designated, the reader

should use this section as a reference to determine the exact structure of a protocol message.

Each tag has 4 fields, not all of them serialized into the message:

  1. type - 1 byte integer
  2. name - could be one of the following:
    1. variable length string
    2. 1 byte integer
  3. value - could be one of the following:
    1. 4 byte Integer
    2. 4 byte floating point number
    3. variable length String.
  4. special - 1 byte integer, special tag designator

Tags with Integer values are called Integer tags, similarly we have String and Float tags.

  • The type of a String tag is 2
  • the type of an Integer tag is 3
  • a Float tag is 4.

When tags are encoded on the wire they are encoded in the above order e.g. the type. then the name and finally the value.

  • The `type` is encoded in a single byte.
  • The `name` is encoded in a [2 byte] length-value scheme which is used both for String and Integer names. For example the Integer name 0x15 is encoded by the sequence 0x01 0x00 0x15.
  • Fixed `value` fields (like Integer and Float numbers) are written as they are, string values are encoded by the same length-value scheme as the name.

Note: The names given to the tags have no special protocol meaning and are here only to

ease future reference in protocol message description.

在libed2k中tag对应的数据结构为base_tag和它的各种继承类(注:base_tag有纯虚函数无法实例化)。base_tag定义如下:

class base_tag 
{ 
public: 
    //...省略部分
    typedef base_tag(tg_nid_type nNameId, bool bNewED2K = true) 
        : m_strName(""), m_nNameId(nNameId), m_bNewED2K(bNewED2K) 
    {} 

    base_tag(const std::string& strName, bool bNewED2K = true) 
        : m_strName(strName), m_nNameId(0), m_bNewED2K(bNewED2K) 
    {} 

    const std::string getName() const; 

    tg_nid_type getNameId() const; 

    virtual void load(archive::ed2k_iarchive& ar); 

    virtual void save(archive::ed2k_oarchive& ar); 

    virtual boost::uint64_t asInt() const; 
    //...省略其他asXXX 
    //...省略“==”重载 

    LIBED2K_SERIALIZATION_SPLIT_MEMBER() 
protected: 
    base_tag(const std::string& strName, tg_nid_type nNameId); 
private: 
    std::string m_strName; 
    tg_nid_type m_nNameId; 
    bool m_bNewED2K; 
};

上面提到对于Tag至少有3个属性:Type/Name/Value,而Name属性可以是(一个字节的)整数也可以是一个变长的字符串。在base_tag类中定义的是就是这个Name属性。从base_tag的构造函数中可以看到,用户在创建对象时要么给它指定一个整形的Name属性要么就给它一个字符串形式的Name属性,这二者只有一个有意义且在构造完成之后这个值就不可改变(没有提供set方法)。

下面以base_tag的派生类typed_tag为例,说明完整的一个tag是怎么实现的,typed_tag用于存放固定长度(如uint64,uint32,float)T类型的tag:

template 
class typed_tag : public base_tag 
{ 
public: 
    //... 忽略部分声明 
    typed_tag(tg_nid_type nNameId) 
        : base_tag(nNameId)
    {} 

    typed_tag(const std::string& strName) 
        : base_tag(strName)
    {} 

    // 构造时指定值 
    typed_tag(T t, tg_nid_type nNameId, bool bNewED2K) 
        : base_tag(nNameId, bNewED2K), m_value(t)
    {} 

    typed_tag(T t, const std::string& strName, bool bNewED2K) 
        : base_tag(strName, bNewED2K), m_value(t)
    {} 

    //忽略部分函数 
    virtual tg_type getType() const { return tag_type_number::value; } 

    virtual tg_type getUniformType() const { return tag_uniform_type_number::value; } 

    // can't call one serialize because base class is split 
    void load(archive::ed2k_iarchive& ar) { ar & m_value; } 

    void save(archive::ed2k_oarchive& ar) { base_tag::save(ar); ar & m_value; } 

    virtual bool is_equal(const base_tag* pt) const; 

    virtual boost::uint64_t asInt() const
    {
        return (base_tag::asInt());
    } 

    //... 省略其他asXXX 
    operator T() const { return (m_value); } 
    LIBED2K_SERIALIZATION_SPLIT_MEMBER() 

protected: 
    typed_tag(const std::string& strName, tg_nid_type nNameId) 
        : base_tag(strName, nNameId) 
    { } 

private: 
    T m_value; 
};

在typed_tag类中,值被放在成员“T m_value;”中,在构造函数中可以为它指定一个值,也可以暂时不指定。上面提到Tag至少有3个属性:Type/Name/Value中现在还缺少一个Type(基类指定Name),这个属性可以通过getType获取。

getType函数返回一个tag_type_number::value,这个tag_type_number的定义如下:

template 
struct tag_type_number; 

template<> struct tag_type_number { static const tg_type value = TAGTYPE_UINT8; }; 

template<> 
struct tag_type_number { static const tg_type value = TAGTYPE_UINT16; }; 

template<> 
struct tag_type_number { static const tg_type value = TAGTYPE_UINT32; }; 

template<> struct tag_type_number { static const tg_type value = TAGTYPE_UINT64; }; 

template<> 
struct tag_type_number { static const tg_type value = TAGTYPE_FLOAT32; }; 

template<> 
struct tag_type_number { static const tg_type value = TAGTYPE_BOOL; }; 

template<> 
struct tag_type_number { static const tg_type value = TAGTYPE_HASH16; };

由代码可见md4_hash对应的tag type是TAGTYPE_HASH16。这里的其他TAGTYPE_UINT8,TAGTYPE_UINT16等等就是上文协议中说明的“1个字节长度”的值,自此tag的三个属性就已经集全了。完整的TAG定义见ctag.hpp:

enum tg_types
{
    TAGTYPE_UNDEFINED   = 0x00, // special tag definition for empty objects
    TAGTYPE_HASH16      = 0x01,
    TAGTYPE_STRING      = 0x02,
    TAGTYPE_UINT32      = 0x03,
    TAGTYPE_FLOAT32     = 0x04,
    TAGTYPE_BOOL        = 0x05,
    TAGTYPE_BOOLARRAY   = 0x06,
    TAGTYPE_BLOB        = 0x07,
    TAGTYPE_UINT16      = 0x08,
    TAGTYPE_UINT8       = 0x09,
    TAGTYPE_BSOB        = 0x0A,
    TAGTYPE_UINT64      = 0x0B,

    // Compressed string types
    TAGTYPE_STR1        = 0x11,
    TAGTYPE_STR2,
    // ……
}

 

从base_tag中还派生了两个类型string_tag和array_tag,分别用于存放字符串值和BLOB值,这三个类的主要区别在于序列化和反序列化,其他的实现都大致相同,我们接下来分析一下各种类型的tag是怎么序列化的。

首先看值是固定长度的Tag类typed_tag的序列化和反序列化:

// can't call one serialize because base class is split 
void load(archive::ed2k_iarchive& ar) { ar & m_value; } 

// T m_value; T Can be boost::uint64_t,boost::uint32_t... 
void save(archive::ed2k_oarchive& ar) { base_tag::save(ar); ar & m_value; }

注意:

  • 序列化时先调用base_tag::save写入Type和Name属性(根据ed2k协议版本的不同会有所区别,详见base_Tag::save的实现),这里基类会调用getType纯虚函数获取派生类的Type属性(因为base_tag只存放Name)。然后再写入值。
  • 反序列化时不读取Type和Name属性而仅仅读取Value属性,所以不可以使用ed2k_iarchive直接反序列化一个Tag对象。而且需要注意基类的base_tag::load不做任何事情,为tag对象写入Type和Name属性需要依赖tag_list的反序列化实现。

Tag列表被定义为tag_list,它的泛型参数和container_holder的第一个参数一样用于指定列表大小在序列化的时候需要几个字节。tag_list的定义如下:

/** 
 * class for tag list representation 
 * used to decode/encode tag list appended sequences in ed2k packets 
 * facade on std::deque stl container 
 */ 
template class tag_list 
{ 
public: 
    typedef boost::shared_ptr value_type; 
    //...忽略其他成员 
private: 
    std::deque m_container; 
};

在libed2k中base_tag是typed_tag、string_tag和array_tag的基类,在上面代码中的std::deque m_container中实际存放的元素实际是这些派生类的值,由于m_container存放的是智能指针,所以在一个tag_list中可以存放所有类型(这里指具有不同Type属性和Name属性的)Tag对象。

tag_list的序列化比较简单,因为base_tag以及它的派生类提供了序列化所有Tag属性的方法。tag_list的序列化代码如下:

template 
void tag_list::save(archive::ed2k_oarchive& ar) 
{ 
    size_type nSize = static_cast(m_container.size()); 
    ar & nSize; 
    for (size_t n = 0; n < m_container.size(); n++) 
    { 
        ar & *(m_container[n].get()); 
    } 
}

先获取大小,写入大小,然后逐个序列化内部的Tag对象。

tag_list的反序列化很复杂,前面提到Tag对象的反序列化依赖tag_list的反序列化实现,代码很长下面只截取一部分:

template 
void tag_list::load(archive::ed2k_iarchive& ar) 
{ 
    size_type nSize; 
    ar & nSize;
    // 首先是获取tag列表的元素个数 
    for (size_t n = 0; n < nSize; n++)
    { 
        // 开始读单个tag 
        //(1)读取Tag的Name属性和Tag属性 
        tg_type nType = 0; 
        tg_nid_type nNameId = 0; 
        std::string strName; 
        ar & nType; 
        if (nType & 0x80){ 
            nType &= 0x7F; 
            //注意这是新版协议才支持的数据结构 
            ar & nNameId; 
        } 
        else { 
            boost::uint16_t nLength; 
            ar & nLength; 
            if (nLength == 1){ 
            ar & nNameId; 
        }else{ 
            strName.resize(nLength); ar & strName; 
        } 
    }
    // (2)然后构造typed_tag/array_tag/string_tag对象,塞到内部队列里。 
    // don't process bool arrays 
    if (nType == TAGTYPE_BOOLARRAY){} 
    //...省略部分代码 
    // TODO - add correct bsob handler instead of this stub 
    if (nType == TAGTYPE_BSOB) {} 
    //...省略部分代码 
    switch (nType) 
    { 
    case TAGTYPE_UINT64: 
        m_container.push_back(value_type(new typed_tag(strName, nNameId)));
        break; 
        //...忽略UINT32/UINT16/UINT8等 
    case TAGTYPE_HASH16: 
        m_container.push_back(value_type(new typed_tag(strName, nNameId))); 
        break; 
    case TAGTYPE_BLOB: 
        m_container.push_back(value_type(new array_tag(strName, nNameId))); 
        break; 
    //...忽略N个TAGTYPE 
    default: 
        throw libed2k_exception(errors::invalid_tag_type);
        break; 
    }; 
    // (3) 反序列化typed_tag/array_tag/string_tag对象,在这次才会写入Tag的Value属性。 
    ar & *m_container.back(); 
    } 
}

 

3.2 和emule服务器的网络通信实现

 

相关的实现在server_connection.cpp中。

首次发起连接:

  1. session::server_connect根据参数指定的服务器IP和端口在IO_SERVICE中异步调用server_connection::start以发起连接。
  2. 在start方法中发起异步调用执行boost::asio::ip::tcp::resolver对象的async_resolve方法解析host和端口,并设置回调函数为on_name_lookup。
  3. 解析完成后在异步回调on_name_lookup中得到boost::asio::ip::tcp::endpoint,即服务器的IP地址以及端口。然后以它为参数调用tcp::socket对象的async_connect,并设置建立连接后的回调函数为on_connection_complete。
  4. 建立连接后的on_connection_complete回调函数中:
    1. 向io_service提交一个异步读网络的请求(读取6个字节数据包头部数据),设置读回调为handle_read_header。
    2. 组织并序列化一个cs_login_request登录请求对象然后发送出去。
  5. 当asio读取到6个字节后handle_read_header将被调用(前边提交了一个异步读请求),在这个回调中做下列事情:
    1. 检查头部{1字节:协议类型,4字节:长度,1字节:opcode}是否有效。
    2. 计算本次传输尚未完成的字节数,通常等于(头部的"长度"成员-1)。仅在opcode∈{OP_SENDINGPART,OP_SENDINGPART_I64,OP_COMPRESSEDPART, OP_COMPRESSEDPART_I64}的特殊情况下,剩余长度等于与对应opcode有关的固定的一个值。
    3. 如果这个类型的数据包只有头部(头部的“长度”成员=1时)则直接调用handle_read_packet,否则再次调用async_read读取剩余长度的数据并将回调函数handle_read_packet传递给它。

收到完整数据包后的处理:

当程序读取到数据包剩余的数据时,回调函数handle_read_packet将被调用,在这个回调函数中将根据协议和操作码(opcode)的不同分别处理。

 

  1. 首先如果协议是OP_PACKEDPROT(0xD4)则意味着该条数据是压缩过的,先解压他然后继续处理,否则跳过这一步。
  2. 转换接收到数据为输入流以方便接下来解析数据。
  3. 然后按照不同操作码分别处理,具体见下表3-1。

操作码

what is it

how to process

OP_REJECT(5)

(see 6.2.16)A message sent from the server to the client indicating that the server rejected the last

command sent by the client. The size of the message is 6 (? didn’t print one ?) bytes. The

receiving client logs the message and discards it.

IGNORE

OP_DISCONNECT(0x18)

未在文档中。

IGNORE

OP_SERVERMESSAGE(0x38)

(see 6.2.2)Server messages are variable length message that are sent from the server to client on various

occasions, the first, immediately after a client login request. A single server-message may

contain several messages separated by new line characters (’\r’,’\n’ or both). Messages that

start with ”server version”, ”warning”, ”error” and ”[emDynIP: ” have special meaning for

the client. Other messages are simply displayed to the user.

POST `server_message_alert`

OP_SERVERLIST(0x32)

(see 6.2.7)Sent from the server to the client. The message contains information about additional eMule

servers to be used to expand the client’s server list. The message size varies (depends on the

number of servers transmitted).是客户端请求(6.2.5)Get list of servers

的回应。

IGNORE

OP_SERVERSTATUS(0x34)

(see 6.2.6)Sent from the server to the client. The message contains information on the current number

of users and files on the server. The information in this message is both stored by the client

and also displayed to the user. The message size is 14 bytes.

POST `server_status_alert`

OP_USERS_LIST(0x43)

未在文档中。

IGNORE

OP_IDCHANGE(0x40)

(see 6.2.3)The ID Change message is sent by the server as a response to the login request message and

signifies that the server has accepted the client connection. The message size is 14 or 10 bytes

depends on sending the optional TCP connection bitmap.

 

保存ID,然后POST一个 `server_connection_initialized_alert`通知

OP_SERVERIDENT(0x41)

(see 6.2.8)Sent from the server to the client. Contains a server hash (TBD) ,the server IP address and

TCP port (which may be useful when connecting through a proxy) and also server description

information. The message size varies.

保存服务器的md4hash ID,然后Post一个`server_identity_alert`

OP_FOUNDSOURCES(0x42)

(see 6.2.12)A message sent from the server to the client with sources (other clients) for a file requested

by the client for a file. The message size varies.

保存为found_file_sources并传递给on_found_peers

OP_SEARCHRESULT(0x33)

未在文档中。

保存search_result并post `shared_files_alert`(this alert throws on server search results and on user shared files)

OP_CALLBACKREQUESTED(0x35)

(见6.2.14,见下方注1)A message sent from the server to the client indicating another client asks the receiving client

to connect to it. The message is sent when the receiving client has a low ID (see section 2.4).

The size of the message is 12 bytes. The receiving client tries to connect to the IP and port

specified by the callback request packet.

connect to requested client.

session.add_peer_connection(cb.m_network_point, ec);

OP_CALLBACK_FAIL(0x36)

见下方注1

IGNORE

表3-1 接收到的消息类型说明和处置方法

 

注1:关于回调的说明。

The callback mechanism is designed to overcome the inability of low ID clients to accept

incoming connections and thus share their files with other clients. The mechanism is simple:

in case a clients A and B are connected to the same eMule Server and A requires a file that is

located on B but B has a low ID, A can send the server a callback request (see section 6.2.13),

requesting the server to ask B to call him back. The server, which already has an open TCP

connection to B, sends B a callback requested (section 6.2.14) message, providing him with

A’s IP and port. B can then connect to A and send him the file without further overhead on

the server. Obviously, only a high ID client can request low ID clients to call back (a low ID

client is not capable of accepting incoming connections). Figure 2.6 illustrates the callback

message exchange.

There is also a feature allowing two low ID clients to exchange files through their server

libed2k源码导读:(三)网络IO_第1张图片

 

Figure 2.6: Callback sequence

connection, using the server as a relay. most of the servers no longer support this option

because of the overhead it incurs on the server.

 

3.3 其他发送给服务器的消息

 

在3.2中整理了libed2k连接服务器的过程,同时在表3.1中描述了从服务器发往客户端的回应消息,这些消息中有些是连上之后服务器就会主动发送给客户端,而另一些则需要客户端发送请求时才会回应。在这一节中我们整理libed2k库有哪些给服务器发送消息的方法以及它怎么处理对应的服务器响应消息。

 

3.3.1 登录消息及其响应

 

在3.2中介绍首次连接时曾提到过连接上服务器后发送的首条消息是登录消息,它对应的消息体对象是cs_login_request:

/** 
 * login request structure - contain some info and 4 tag items 
 */ 
struct cs_login_request 
{ 
    md4_hash m_hClient; 
    net_identifier m_network_point; 
    tag_list m_list; 
    template 
    void serialize(Archive& ar)
    { 
    ar & m_hClient & m_network_point & m_list; 
    } 
};

关于tag,tag_list的介绍见3.1.4节。

cs_login_request对应的消息头对象是:

template<> 
struct packet_type
{ 
static const proto_type value = OP_LOGINREQUEST; //0x01 
static const proto_type protocol = OP_EDONKEYPROT; //0xE3 
};//!< on login to server

从cs_login_request的serialize可以看消息体的组织是|HASH 16|ID 4|PORT 2|1 Tag_set|,这个消息的定义见eMule协议(英文版)6.2.9节【The eMule Protocol Specification,Yoram Kulbak and Danny Bickson,2005】,格式如下图:

libed2k源码导读:(三)网络IO_第2张图片

在libed2k中:

  • cs_login_request::m_hClient对应了上图中的User Hash
  • cs_login_request::m_network_point.m_nIP 对应上图中的Client ID
  • cs_login_request::m_network_point.m_nPort对应上图中的TCP Port
  • cs_login_request::m_list对应了上图中的Tag Count以及剩余项。

tag_list类的声明在include\libed2k\ctag.hpp。填写cs_login_request各成员的代码如下:

cs_login_request login; //!< generate initial packet to server 
boost::uint32_t nVersion = 0x3c; 
boost::uint32_t nCapability = CAPABLE_ZLIB | CAPABLE_AUXPORT | CAPABLE_NEWTAGS | CAPABLE_UNICODE | CAPABLE_LARGEFILES; 
boost::uint32_t nClientVersion = (LIBED2K_VERSION_MAJOR << 24) | (LIBED2K_VERSION_MINOR << 17) | (LIBED2K_VERSION_TINY << 10) | (1 << 7); 
login.m_hClient = settings.user_agent; 
login.m_network_point.m_nIP = 0; 
login.m_network_point.m_nPort = settings.listen_port; 
login.m_list.add_tag(make_string_tag(std::string(settings.client_name), CT_NAME, true)); 
login.m_list.add_tag(make_typed_tag(nVersion, CT_VERSION, true)); 
login.m_list.add_tag(make_typed_tag(nCapability, CT_SERVER_FLAGS, true)); 
login.m_list.add_tag(make_typed_tag(nClientVersion, CT_EMULE_VERSION, true));

可以看到

  • User Hash的值是settings.user_agent,这个值默认等于libed2k::md4_hash::emule(这个值为31D6CFE0D10EE931B73C59D7E0C06FC0,详见src\md4.cpp以及include\libed2k\session_settings.hpp中class session_settings的定义)。
  • Client ID等于0。
  • TCP Port等于客户设置的TCP端口。
  • Tag Count的值是4(内部有4个元素)。
  • Name Tag的值是settings.client_name即我们设置的名字。
  • Version Tag是0x3c。
  • PortTag是(CAPABLE_ZLIB | CAPABLE_AUXPORT | CAPABLE_NEWTAGS | CAPABLE_UNICODE | CAPABLE_LARGEFILES)(注意这里和eMule协议文档中的描述不一致,目前不知道是哪边有误
  • Flag tag是LIBED2K_VERSION_MAJOR.LIBED2K_VERSION_MINOR.LIBED2K_VERSION_TINY.1(即1.0.0.1)(注意这里和eMule协议文档中的描述不一致,目前不知道是哪边有误

可能是实现的协议版本不一致,可以看到这里有两个项和文档中不一致,还需要查找资料确认现在最新版本的emule协议中在这个消息中是怎么定义的。

 

登录消息的回应是OP_IDCHANGE,下面的说明摘自mMule协议文档6.2.3:

The ID Change message is sent by the server as a response to the login request message and signifies that the server has accepted the client connection. The message size is 14 or 10 bytes depends on sending the optional TCP connection bitmap.

libed2k源码导读:(三)网络IO_第3张图片

 

libed2k在server_connection::handle_read_packet中处理这个消息的对应代码片段如下:

current_operation = scs_start; 
id_change idc; 
ia >> idc; 
m_client_id = idc.m_client_id; 
m_tcp_flags = idc.m_tcp_flags; 
m_aux_port = idc.m_aux_port; 
DBG("handshake finished. server connection opened {" << idc << "}" << (isLowId(idc.m_client_id)?"LowID":"HighID")); 
//注:下面的params是server_connection_parameters类型的对象。 
//包含了所连接服务器的信息。 
m_ses.m_alerts.post_alert_should( 
    server_connection_initialized_alert( 
        params.name, 
        params.host, 
        params.port, 
        m_client_id, 
        m_tcp_flags, 
        m_aux_port) );

 

注意在id_change序列化的实现中,m_tcp_flags和m_aux_port成员是可选的(默认为0),如下:

#define DECREMENT_READ(n, x) if (n >= sizeof(x)) 
\ { 
\ ar & x; 
\ } 
\ else 
\ { 
\ return; 
\ } 
/** 
 * this is variable size structure contains client id and some impotant information about server 
 */ 
struct id_change 
{ 
    //...忽略部分代码 
    // only for load 
    template void serialize(Archive& ar){ 
        // always read/write client id; 
        ar & m_client_id; 
        DECREMENT_READ(ar.bytes_left(), m_tcp_flags); 
        DECREMENT_READ(ar.bytes_left(), m_aux_port); 
    } 
};

server_connection::m_client_id在客户端与服务器的这次会话期间会一直有效,用于标识自身的身份ID。server_connection::m_aux_port在当前版本的libed2k中并未使用到,可以忽略它。server_connection::m_tcp_flags定义了传输选项,在很多地方都会用到。当前定义的TCP Flag包括:

#define SRV_TCPFLG_COMPRESSION 0x00000001 
#define SRV_TCPFLG_NEWTAGS 0x00000008 
#define SRV_TCPFLG_UNICODE 0x00000010 
#define SRV_TCPFLG_RELATEDSEARCH 0x00000040 
#define SRV_TCPFLG_TYPETAGINTEGER 0x00000080 
#define SRV_TCPFLG_LARGEFILES 0x00000100 
#define SRV_TCPFLG_TCPOBFUSCATION 0x00000400

 

 

3.3.2 服务器消息(Server message)

 

下面的说明摘抄自eMule协议(英文版)6.2.2节:

Server messages are variable length message that are sent from the server to client on various

occasions, the first, immediately after a client login request. A single server-message may

contain several messages separated by new line characters (’\r’,’\n’ or both). Messages that

start with ”server version”, ”warning”, ”error” and ”[emDynIP: ” have special meaning for

the client. Other messages are simply displayed to the user.

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

0x38

The value of the OP SERVERMESSAGE op-code

Size

2

NA

The number of bytes in the remainder of the message not including the fields described so far

Messages

varies

NA

A list of server messages separated by new

lines

Special messages

1. version - Usually send in a successful connection handshake

2. error -

3. warning - Usually send when the server denies the connection or when the client has a

low ID

4. emDynIP -

服务器在各种情况下(我不知道除了登录成功后其他什么时候会发)会发消息到客户端,当客户成功登录服务器后会立即发送第一条。

 

libed2k在server_connection::handle_read_packet中处理这条消息,如下:

server_message smsg; ia >> smsg; 
//注:下面的params是server_connection_parameters类型的对象。 
//包含了所连接服务器的信息。 
m_ses.m_alerts.post_alert_should( 
    server_message_alert( 
        params.name, params.host, params.port, smsg.m_strMessage));

server_message的定义如下:

struct server_message
{ 
    boost::uint16_t m_nLength; 
    std::string m_strMessage; 
    template 
    void serialize(Archive& ar)
    { 
        ar & m_nLength; 
        // allocate buffer if it needs 
        if (m_strMessage.size() != m_nLength) 
            m_strMessage.resize(m_nLength); 
        ar & m_strMessage; 
    } 
};

可以看到程序只是简单的把服务器的消息封装到一个server_message_alert通知里并将它塞到通知队列中。

 

3.3.3 获取服务器列表(Get list of servers)

 

消息被封装在server_get_list中,回应消息被封装到server_list。在libed2k中并未实际处理这个消息。发送这个消息是在server_connection::second_tick方法中,然后接收依旧实在server_connection::handle_read_packet,收到消息之后只是简单的反序列化然后丢弃。不明白为什么需要这样处理,可能需要定时向服务器发送一条消息以避免timeout?

 

3.3.4 接收服务器状态(Server status)

 

服务器状态消息包含登录到服务器上的用户数量和用户分享的文件数量。下面的说明摘抄自eMule协议(英文版)6.2.6节:

Sent from the server to the client. The message contains information on the current number

of users and files on the server. The information in this message is both stored by the client

and also displayed to the user. The message size is 14 bytes.

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

0x34

The value of the OP SERVERSTATUS op-

code

User Count

4

 

The number of users currently logged in to the

server.

File Count

4

 

The number of files that this server is in-

formed about

这条服务器是服务器主动发送给客户端。libed2k处理这条消息的实现如下:

server_status sss; 
ia >> sss; 
m_ses.m_alerts.post_alert_should( 
    server_status_alert( 
        params.name, params.host, params.port, sss.m_nFilesCount, sss.m_nUserCount) 
);

server_status定义如下:

struct server_status 
{ 
    boost::uint32_t m_nUserCount; 
    boost::uint32_t m_nFilesCount; 
    template 
    void serialize(Archive & ar)
    { 
        ar & m_nUserCount & m_nFilesCount; 
    } 
};

libed2k只是简单封装了这条消息的内容到laert并将它塞到通知队列中。

 

3.3.5 服务器鉴定(Server identification)

 

服务器鉴定的消息是在客户端登录后服务器主动发送给客户端的,因此没有对应的客户端到服务器的消息。下面关于服务器鉴定的说明摘抄自eMule协议(英文版)6.2.8节:

Sent from the server to the client. Contains a server hash (TBD) ,the server IP address and TCP port (which may be useful when connecting through a proxy) and also server description information. The message size varies.

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

0x41

The value of the OP SERVERIDENT opcode

Hash

16

NA

A GUID of the server (seems to be used for debug)

Server IP

4

NA

The IP address of the server

Server Port

4

NA

The TCP port on which the server listens

Tag Count

4

NA

The number of tags at the end of the message

ServerName Tag

Varies

NA

The name of the server. The tag is a string

tag and the tag name is an integer of value 0x1

Server Descrip-tion Tag

Varies

NA

A server description string. The tag is a string tag and the tag name is an integer of value

0xB

接收处理依旧在时server_connection::handle_read_packet中:

server_info_entry se; 
ia >> se; 
se.dump(); 
m_hServer = se.m_hServer; 
m_ses.m_alerts.post_alert_should( 
    server_identity_alert( 
        params.name, params.host, params.port, se.m_hServer, 
        se.m_network_point,
        se.m_list.getStringTagByNameId(ST_SERVERNAME), 
        se.m_list.getStringTagByNameId(ST_DESCRIPTION)
    )
);

server_info_entry的定义如下:

struct server_info_entry 
{ 
    md4_hash m_hServer; 
    net_identifier m_network_point; 
    tag_list m_list; 
    template 
    void serialize(Archive& ar)
    { 
        ar & m_hServer & m_network_point & m_list; 
    } 
    void dump() const; 
};

程序先保存服务器的ID,然后将这条消息的内容封装到server_identity_alert对象内并将它添加到通知队列。

 

3.3.6 获取文件源及其响应(Get sources)

从客户端发起询问,在服务器中查找指定文件的源。下边这条消息的说明摘抄自eMule协议(英文版)6.2.11节:A message sent from the client to the server requesting sources (other clients) for a file. The message size is 22 bytes.

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

0x19

The value of the OP GETSOURCES opcode

File hash

16

NA

The requested file hash

在ed2k中对应的对象是get_file_sources:

/** 
 * request sources for file 
 */ 
struct get_file_sources 
{ 
    md4_hash m_hFile; //!< file hash 
    __file_size m_file_size; //!< file size 
    template 
    void serialize(Archive& ar)
    { 
        ar & m_hFile; 
        // ugly eDonkey protocol need empty 32-bit part before 64-bit file size record 
        if (m_file_size.u.nHighPart > 0) 
        { 
            boost::uint32_t nZeroSize = 0; 
            ar & nZeroSize; 
        } 
        ar & m_file_size; 
    } 
};

发送的接口在session::post_sources_request ,底层实现在server_connection::post_sources_request:

void server_connection::post_sources_request(const md4_hash& hFile, boost::uint64_t nSize) 
{ 
    DBG("server_connection::post_sources_request(" << hFile.toString() << ", " << nSize << ")"); 
    get_file_sources gfs; 
    gfs.m_hFile = hFile; 
    gfs.m_file_size.nQuadPart = nSize; 
    do_write(gfs); 
}

协议中并未规定需要填写大小,我猜测应该是和协议版本有关。

 

与这条消息对应的消息是"已搜索到文件源"消息(Found sources),下面关于这条回应消息的说明摘抄自协议文档的6.2.12一节。

A message sent from the server to the client with sources (other clients) for a file requested by the client for a file. The message size varies.

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

0x42

The value of the OP FOUNDSOURCES op-code

File Hash

16

NA

The requested file hash

Sources Count

1

NA

The number of sources in this message

List of sources

Varies

NA

A list of sources

Source list item format

The table below describes the format of a source list-item. Each source includes the details of an eMule client holding the requested file.

Name

Size in bytes

Default Value

Comment

Client ID

4

NA

Client ID for an eMule peer holding the file

Client Port

2

NA

The TCP port of the client holding the file

在libed2k中这条消息被封装到found_file_sources结构中:

struct found_file_sources 
{ 
    md4_hash m_hFile; 
    container_holder > m_sources; 
    template 
    void serialize(Archive& ar)
    { 
        ar & m_hFile & m_sources; 
    } 
    void dump() const; 
};

处理这条消息的逻辑在server_connection::handle_read_packet中:

found_file_sources fs; ia >> fs; fs.dump(); on_found_peers(fs);

其中on_found_peers的实现:

void server_connection::on_found_peers(const found_file_sources& sources) 
{ 
    APP("found peers for hash: " << sources.m_hFile); 
    boost::shared_ptr t = m_ses.find_transfer(sources.m_hFile).lock(); 
    if (!t) 
        return; 
    for (std::vector::const_iterator i = sources.m_sources.m_collection.begin(); 
        i != sources.m_sources.m_collection.end(); ++i) 
    { 
        tcp::endpoint peer(ip::address::from_string(int2ipstr(i->m_nIP)), i->m_nPort); 
        if (isLowId(i->m_nIP) && !isLowId(m_client_id)) 
        { 
            // peer LowID and we is not LowID - send callback request 
            if (m_ses.register_callback(i->m_nIP, sources.m_hFile)) 
                post_callback_request(i->m_nIP); 
        // 注意:当前绝大多数服务器已经不再支持callback方法,这会使服务器过载。 
        } 
        else 
        { 
            APP("found HiID peer: " << peer); 
            t->add_peer(peer, peer_info::tracker); 
        } 
    } 
}

处理流程如下:

  1. 在当前的文件传输任务列表中查询该文件对应的任务,如果不存在任务则跳过后续步骤不做任何事情。
  2. 在文件源列表中逐个判断其用户ID是否为高ID(可上传的用户),如果是则调用transfer::add_peer方法将这个用户拉到传输文件任务的用户列表中。否则调用register_callback方法通过服务器中转传输文件。注意当前绝大多数服务器已经不再支持callback方法,这会使服务器过载。

文件传输任务将在后续章节中详细分析,在这里先暂时跳过。

 

3.3.7 文件查找及其服务器响应消息的处理

 

客户端以指定的一个或者多个条件向服务器查询是否存在文件,服务器如果找到就返回符合条件的文件列表。发送请求的过程已经在2.5一节中详细描述,我们这里关注一下收到服务器的响应后libed2k是怎么处理的。

服务器的响应消息在ed2k文档的6.2.10Search result一节中,摘抄如下:

A message sent from the server to the client as a reply to a search request. The message is usually compressed. The message size varies.

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

0x16

The value of the OP SEARCHRESULT op-code

Result Count

4

NA

The number of search results in this message

Result list

Varies

NA

A list of search results

Search result list item format

The table below describes the format of a single search result list-item. Each search result holding the file.There are also several tags describing the file attributes.The tag list

Name

Size in bytes

Default Value

Comment

File Hash

16

NA

A hash value, for unique identification of the

file

Client ID

4

NA

Client ID for an eMule peer holding the file

Client Port

2

NA

The TCP port of the client holding the file

Tag Count

4

NA

The number of descriptor tags following

Tag list

Varies

NA

A list of descriptor tags

is described below.Note that most of the tags are optional and that their order is not guaranteed. Tag encoding rules are described is detail an the beginning of this chapter.

Name

Tag name

Tag Type

Comment

File name

Integer, 0x01

String

 

File size

Integer 0x02

Integer

 

File type

Integer 0x03

String

 

File format

Integer 0x04

String

 

Sources

Integer 0x15

Integer

The number of available sources for this file

Artist

String ”Artist”

String

 

Album

String ”Album”

String

 

Title

String ”Title”

String

 

Length

String ”length”

Integer

 

Bitrate

String ”bitrate”

Integer

 

Codec

String ”codec”

Integer

 

Table 6.1: search result tag list

在libed2k的server_connection::handle_read_packet中处理这个消息:

search_result sfl; ia >> sfl; 
m_ses.m_alerts.post_alert_should( 
    shared_files_alert( 
        net_identifier(address2int(m_target.address()), 
        m_target.port()), 
        m_hServer, 
        sfl.m_files, 
        (sfl.m_more_results_avaliable != 0)
    ) 
);

这里只是将搜索到的文件列表以alert的形式交给用户使用。search_result定义如下:

struct search_result { 
    shared_files_list m_files; 
    char m_more_results_avaliable; // use only for load 
    template 
    void load(Archive& ar) 
    { 
        m_more_results_avaliable = 0; 
        ar & m_files; 
        if (ar.bytes_left() == 1) 
        { 
            ar & m_more_results_avaliable; 
        } 
    } 
    template 
    void save(Archive& ar) 
    { 
        // do nothing 
    } 
    void dump() const; 
    LIBED2K_SERIALIZATION_SPLIT_MEMBER() 
};

其中关键的shared_files_list类定义是:

typedef container_holder > shared_files_list;

其中shared_file_entry的类定义为:

/** 
 * shared file item structure in offer list 
 */ 
struct shared_file_entry 
{ 
    md4_hash m_hFile; //!< md4 file hash 
    net_identifier m_network_point; //!< network identification 
    tag_list m_list; //!< file information list 
    shared_file_entry(); 
    shared_file_entry(const md4_hash& hFile, 
        boost::uint32_t nFileId, 
        boost::uint16_t nPort); 
    bool is_empty() const 
    { 
        return !m_hFile.defined(); 
    } 
    template 
    void serialize(Archive& ar)
    { 
        ar & m_hFile & m_network_point & m_list; 
    } 
    void dump() const; 
};

3.3.8 其他消息

 

关于回调在3.2的注中有说明,与回调有关的消息有:

(客户端发送的)回调请求Callback request,回调已处理的响应消息Callback requested,以及回调失败消息Callback failed。如在3.2中所注,当前的服务器已普遍不支持callback而且我们将来的程序也不会使用它,这里就不再赘述。

 

服务器拒绝消息Message rejected,在emule文档的6.2.16中有如下说明:A message sent from the server to the client indicating that the server rejected the last command sent by the client. The size of the message is 6 (? didn’t print one ?) bytes. The receiving client logs the message and discards it.

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

0x05

The value of the OP REJECT opcode

遇到reject消息libed2k只是简单丢弃,这里也不再深入研究。

 

你可能感兴趣的:(RTFSC)