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

本文详细解读libed2k库中关于网络IO的操作,包括数据序列化和反序列化的实现,以及与eMule服务器的通信过程。重点分析了数据包的构造、发送过程,特别是tag、tag_list的序列化细节。同时,介绍了与服务器交互的各种消息,如登录、获取服务器列表、文件源等,以及相应消息的处理机制。
摘要由CSDN通过智能技术生成

目录

 

第三章 网络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<libed2k_header, std::string>类型的键值对,它的键为数据包头而值为数据包体。

数据包的序列化在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::const_buffer>类型的缓冲区对象并通过boost::asio::async_write发送出去。参见下方的代码:

template<typename T> 
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<std::string> inserter(m_write_order.back().second); 
    boost::iostreams::stream<boost::iostreams::back_insert_device<std::string> > 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<T>::value;
    //DBG("server_connection::do_write " << packetToString(packet_type<T>::value) << " size: " << m_write_order.back().second.size() + 1); 
    if (!write_in_progress) 
    { 
        std::vector<boost::asio::const_buffer> 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<<t;”调用的是ed2k_oarchive的成员函数serialize_impl,而对于class类型的参数serialize_impl的实现是

//Classes need to be serialize using their special method 
template<typename T> 
inline void serialize_impl(T & val, 
    typename boost::enable_if<boost::is_class<T> >::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<typename Archive> 
    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<search_request_block>)对象初始化,写入时按次序往Archive对象(这里是archive::ed2k_oarchive)写入。

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

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

template<typename T> 
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<archive::ed2k_oarchive>(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<class Archive> 
\ void serialize(Archive& ar)
\ { 
\ libed2k::archive::split_member(ar, *this); 
\ } 

template<class Archive, class T> 
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<member_saver<Archive, T> >, boost::mpl::identity<member_loader<Archive, T> > >::type typex; 
    typex::invoke(ar, t); 
}

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

typedef boost::mpl::bool_<false> is_loading; 
typedef boost::mpl::bool_<true> is_saving;

在class ed2k_iarchive中的定义为:

typedef boost::mpl::bool_<true> is_loading; 
typedef boost::mpl::bool_<false> is_saving;

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

template<class Archive, class T> 
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<class Archive, class T> 
static void member_save(Archive & ar, T& t) 
{ t.save(ar); } 

template<class Archive, class T> 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<search_request_entry::SRE_Operation>(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<boost::uint16_t>(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<typename size_type, class collection_type> 
struct container_holder 
{ 
    size_type m_size; 
    collection_type m_collection; 
    typedef typename collection_type::iterator Iterator; 
    typedef typename collection_type::value_type elem; 
    cont
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值