目录
第三章 网络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 其他消息
这一章分析和网络传输有关的实现,主要内容包括和emule服务器的通信和数据的序列化以及反序列化。
libed2k使用了boost提供的序列化模板,通过这个模板可以简化从对象到字节流的生成方式。按照它提供的机制,需要在可序列化的对象上添加save/load方法以告知序列化模板怎么将自己转换为字节流和从一个字节流中构造自己。
每个数据包都包含包头(header)和包体(body),头部形式为{1字节:协议,4字节长度, 1字节类型(opcode)}统一为6个字节,body的长度因数据包的类型而异。
待发送数据的格式是被组织为一个message deque,每个message都是一个std::pair
数据包的序列化在server_connection.hpp的server_connection::do_write方法中,实现大致如下:
最后再将这个message转为一个std::vector
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));
}
}
执行下面序列化作为数据包体(body)的泛型T对象t时,
archive::ed2k_oarchive oa(s);
oa << t;
首先使用输出流对象s初始化ed2k_oarchive对象oa,然后“oa< 最终调用的是t.serialize(*this)(即当t为类的实例时oa << t等同于t.serialize(oa))。 下面以T = search_request_block为例分析具体实现细节。当T为search_request_block类型时,最终上面的代码就是执行下面对象的serialize方法: search_request_block由一个search_request(即deque 注意archive::ed2k_oarchive中关于‘&’操作符的重载 当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 & 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方法。它的实现是: 上面的typex::invoke定义为:当Archive::is_saving有效时类型为member_saver 在class ed2k_iarchive中的定义为: 在这个server_connection::do_write的例子中Archive泛型类型是ed2k_oarchive(因为ed2k_oarchive::is_saving == false),所以这里typex等于boost::mpl::identity 上面调用的access:member_save定义如下: 最终调用的是T类型实例的save方法,在这个例子中即为search_request_entry::save(oarchive)方法,它的实现如下: 可以看到当写入一个search_request_entry到网络流时是先序列化类型“ar & m_type;”到流中,然后根据不同类型写入不同的值。 由于搜索功能不是我们当前程序的重点,关于search_request_entry序列化再往下的细节这里就不再分析。 使用(基于boost的)序列化框架的小结: 当我们需要序列化和反序列化一个类型为ClassA对象时,在此ClassA中需要: 当序列化对象时只需要执行以下代码就可以将对象a序列化到流中了: 类似的,通过下面的代码可以从流中反序列化出一个对象: container_holder是常见的可序列化集合类的容器,它的定义如下: 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相对应,先读取(反序列化)集合的大小,然后逐个反序列化集合的元素。 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: Tags with Integer values are called Integer tags, similarly we have String and Float tags. 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. 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定义如下: 上面提到对于Tag至少有3个属性:Type/Name/Value,而Name属性可以是(一个字节的)整数也可以是一个变长的字符串。在base_tag类中定义的是就是这个Name属性。从base_tag的构造函数中可以看到,用户在创建对象时要么给它指定一个整形的Name属性要么就给它一个字符串形式的Name属性,这二者只有一个有意义且在构造完成之后这个值就不可改变(没有提供set方法)。 下面以base_tag的派生类typed_tag为例,说明完整的一个tag是怎么实现的,typed_tag 在typed_tag类中,值被放在成员“T m_value;”中,在构造函数中可以为它指定一个值,也可以暂时不指定。上面提到Tag至少有3个属性:Type/Name/Value中现在还缺少一个Type(基类指定Name),这个属性可以通过getType获取。 getType函数返回一个tag_type_number 由代码可见md4_hash对应的tag type是TAGTYPE_HASH16。这里的其他TAGTYPE_UINT8,TAGTYPE_UINT16等等就是上文协议中说明的“1个字节长度”的值,自此tag的三个属性就已经集全了。完整的TAG定义见ctag.hpp: 从base_tag中还派生了两个类型string_tag和array_tag,分别用于存放字符串值和BLOB值,这三个类的主要区别在于序列化和反序列化,其他的实现都大致相同,我们接下来分析一下各种类型的tag是怎么序列化的。 首先看值是固定长度的Tag类typed_tag的序列化和反序列化: 注意: Tag列表被定义为tag_list,它的泛型参数和container_holder的第一个参数一样用于指定列表大小在序列化的时候需要几个字节。tag_list的定义如下: 在libed2k中base_tag是typed_tag tag_list的序列化比较简单,因为base_tag以及它的派生类提供了序列化所有Tag属性的方法。tag_list的序列化代码如下: 先获取大小,写入大小,然后逐个序列化内部的Tag对象。 tag_list的反序列化很复杂,前面提到Tag对象的反序列化依赖tag_list的反序列化实现,代码很长下面只截取一部分: 相关的实现在server_connection.cpp中。 首次发起连接: 收到完整数据包后的处理: 当程序读取到数据包剩余的数据时,回调函数handle_read_packet将被调用,在这个回调函数中将根据协议和操作码(opcode)的不同分别处理。 操作码 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 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.2中整理了libed2k连接服务器的过程,同时在表3.1中描述了从服务器发往客户端的回应消息,这些消息中有些是连上之后服务器就会主动发送给客户端,而另一些则需要客户端发送请求时才会回应。在这一节中我们整理libed2k库有哪些给服务器发送消息的方法以及它怎么处理对应的服务器响应消息。 在3.2中介绍首次连接时曾提到过连接上服务器后发送的首条消息是登录消息,它对应的消息体对象是cs_login_request: 关于tag,tag_list的介绍见3.1.4节。 cs_login_request对应的消息头对象是: 从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中: tag_list类的声明在include\libed2k\ctag.hpp。填写cs_login_request各成员的代码如下: 可以看到 可能是实现的协议版本不一致,可以看到这里有两个项和文档中不一致,还需要查找资料确认现在最新版本的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在server_connection::handle_read_packet中处理这个消息的对应代码片段如下: 注意在id_change序列化的实现中,m_tcp_flags和m_aux_port成员是可选的(默认为0),如下: server_connection::m_client_id在客户端与服务器的这次会话期间会一直有效,用于标识自身的身份ID。server_connection::m_aux_port在当前版本的libed2k中并未使用到,可以忽略它。server_connection::m_tcp_flags定义了传输选项,在很多地方都会用到。当前定义的TCP Flag包括: 下面的说明摘抄自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的定义如下: 可以看到程序只是简单的把服务器的消息封装到一个server_message_alert通知里并将它塞到通知队列中。 消息被封装在server_get_list中,回应消息被封装到server_list。在libed2k中并未实际处理这个消息。发送这个消息是在server_connection::second_tick方法中,然后接收依旧实在server_connection::handle_read_packet,收到消息之后只是简单的反序列化然后丢弃。不明白为什么需要这样处理,可能需要定时向服务器发送一条消息以避免timeout? 服务器状态消息包含登录到服务器上的用户数量和用户分享的文件数量。下面的说明摘抄自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定义如下: libed2k只是简单封装了这条消息的内容到laert并将它塞到通知队列中。 服务器鉴定的消息是在客户端登录后服务器主动发送给客户端的,因此没有对应的客户端到服务器的消息。下面关于服务器鉴定的说明摘抄自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的定义如下: 程序先保存服务器的ID,然后将这条消息的内容封装到server_identity_alert对象内并将它添加到通知队列。 从客户端发起询问,在服务器中查找指定文件的源。下边这条消息的说明摘抄自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: 发送的接口在session::post_sources_request ,底层实现在server_connection::post_sources_request: 协议中并未规定需要填写大小,我猜测应该是和协议版本有关。 与这条消息对应的消息是"已搜索到文件源"消息(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结构中: 处理这条消息的逻辑在server_connection::handle_read_packet中: found_file_sources fs; ia >> fs; fs.dump(); on_found_peers(fs); 其中on_found_peers的实现: 处理流程如下: 文件传输任务将在后续章节中详细分析,在这里先暂时跳过。 客户端以指定的一个或者多个条件向服务器查询是否存在文件,服务器如果找到就返回符合条件的文件列表。发送请求的过程已经在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中处理这个消息: 这里只是将搜索到的文件列表以alert的形式交给用户使用。search_result定义如下: 其中关键的shared_files_list类定义是: 其中shared_file_entry的类定义为: 关于回调在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只是简单丢弃,这里也不再深入研究。 //Classes need to be serialize using their special method
template
/** * simple wrapper for use in do_write template call */
struct search_request_block
{
search_request_block(search_request& ro)
: m_order(ro)
{
}
template
template
// split member function serialize funcition into save/load
#define LIBED2K_SERIALIZATION_SPLIT_MEMBER()
\ template
typedef boost::mpl::bool_
typedef boost::mpl::bool_
template
class access
{
public:
// pass calls to users's class implementation
template
void search_request_entry::save(archive::ed2k_oarchive& ar)
{
ar & m_type;
if (m_type == SEARCH_TYPE_BOOL)
{
DBG("write: " << toString(static_cast
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 序列化集合类对象
/**
* common container holder structure
* contains size_type for read it from archives and some container for data
*/
template
3.1.4 Tag,tag_list和它们的序列化
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;
};
template
template
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,
// ……
}
// 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; }
/**
* class for tag list representation
* used to decode/encode tag list appended sequences in ed2k packets
* facade on std::deque stl container
*/
template
template
template
3.2 和emule服务器的网络通信实现
3.3 其他发送给服务器的消息
3.3.1 登录消息及其响应
/**
* 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
template<>
struct packet_type
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));
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) );
#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
#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)
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));
struct server_message
{
boost::uint16_t m_nLength;
std::string m_strMessage;
template
3.3.3 获取服务器列表(Get list of servers)
3.3.4 接收服务器状态(Server status)
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)
);
struct server_status
{
boost::uint32_t m_nUserCount;
boost::uint32_t m_nFilesCount;
template
3.3.5 服务器鉴定(Server identification)
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)
)
);
struct server_info_entry
{
md4_hash m_hServer;
net_identifier m_network_point;
tag_list
3.3.6 获取文件源及其响应(Get sources)
/**
* request sources for file
*/
struct get_file_sources
{
md4_hash m_hFile; //!< file hash
__file_size m_file_size; //!< file size
template
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);
}
struct found_file_sources
{
md4_hash m_hFile;
container_holder
void server_connection::on_found_peers(const found_file_sources& sources)
{
APP("found peers for hash: " << sources.m_hFile);
boost::shared_ptr
3.3.7 文件查找及其服务器响应消息的处理
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)
)
);
struct search_result {
shared_files_list m_files;
char m_more_results_avaliable; // use only for load
template
typedef container_holder
/**
* 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
3.3.8 其他消息