第二章 Session接口以及实现分析

目录

第二章 Session接口以及实现分析

2.1 获取会话状态

2.2 管理会话中所有的传输任务

2.3 管理点对点连接

2.4 管理alert

2.4.1 alert介绍

2.4.2 当前已定义的alert

2.4.3 在程序中接收和处理alert

2.4.4 其他和alert有关的接口

2.5 在服务器上查找文件

2.5.1 搜索请求的格式约定

2.5.2 创建文件查询对象

2.5.3 发送文件搜索请求


和libtorrent一样,libed2k的功能大部分需要使用session类提供的接口,而session类所有功能的具体实现都是在session_impl类中,session对象通过内部的一个session_impl对象指针向外提供接口。session类共有54个成员函数,大致可以分为几类:

  1. 构造及初始化,session()和init()
  2. 获取session的状态,status()
  3. 管理文件传输任务(通过transfer_handle)的接口
  4. 管理点对点连接(通过peer_connection_handle)的接口
  5. 管理alert的一系列接口
  6. 在服务器上查找文件的接口
  7. P2P本地监听端口有关的接口
  8. session选项设置和获取接口
  9. 设置和获取IP过滤的接口
  10. 上传下载限速的接口
  11. 连接/断开服务器的接口
  12. 暂停/恢复整个session的接口
  13. 分享和取消分享的接口
  14. 端口映射相关的接口
  15. kad(dht)功能相关的接口

2.1 获取会话状态

session状态保存在session_status,通过session类的session_status session::status()成员函数可以获取。当前版本的session_status中并不完整,大部分成员似乎仅仅是为了和尝试libtorrent兼容而设置。

当前可用的状态信息:

struct LIBED2K_EXPORT session_status{ //... int upload_rate; //上传速度 int download_rate; //下载速度 size_type total_download; //上传总字节数 size_type total_upload; //下载总字节数 int payload_upload_rate; //实际数据(负载)上传速度 int payload_download_rate; //实际数据(负载)下载速度 size_type total_payload_download; //实际数据(负载)下载总字节数 size_type total_payload_upload; //实际数据(负载)上传总字节数 //... int num_peers; //点对点连接数 int up_bandwidth_queue; //上传限速时在限速队列中排队的请求个数 int down_bandwidth_queue; //下载限速时在限速队列中排队的请求个数 int up_bandwidth_bytes_queue; //上传限速时在队列中排队的总字节数 int down_bandwidth_bytes_queue; //下载限速时在队列中排队的总字节数 //... };

接下来,我们尝试在conn中添加一条命令"sess_stat",当程序收到这个指令时会打印以上状态信息。

照旧先连接服务器:conn 176.103.48.36 4184 D:\123

然后添加一个下载任务:download_addlink:ed2k://|file|xxxx.m4|1019979999|880260967C066B3C20ED6BDF3CD45EAA|/

最后,输入sess_stat查看session的状态信息,结果如下图:

注:这里和payload_upload有关的值为0,是因为本客户端获取到的是一个lowID(本机是内网IP而且UPNP失败导致无法上传)

2.2 管理会话中所有的传输任务

transfer对象是文件传输任务的抽象,包括文件下载和文件上传的任务,而transfer_handle是一个类似"对象管理器"的概念,它封装了对transer的一些高级接口,对使用者屏蔽了传输任务的细节。接下来我们来看一下session是怎么管理文件传输任务。

session提供了以下接口用于管理文件传输任务:

transfer_handle add_transfer(const add_transfer_params& params);
void post_transfer(const add_transfer_params& params);
transfer_handle find_transfer(const md4_hash& hash) const;
std::vector<transfer_handle> get_transfers() const;
std::vector<transfer_handle> get_active_transfers() const;
void remove_transfer(const transfer_handle& h, int options = none);

其中:

  • add_transfer和post_transfer用于添加一个任务,不同在于前者是同步过程而后者是异步过程。
  • find_transfer根据文件md4_hash值从内部列表中查找一个任务。
  • get_transfers返回内部所有的任务列表。
  • get_active_transfers返回处于活动状态的任务列表。
  • remove_transfer将一个任务从任务列表里移除。

添加任务时,session::add_transfer做的事情是:

  1. 检查session的状态,如果session处于abort状态,则报告错误并返回一个空句柄。
  2. 确认任务是否已经在session中,如果已经存在则报告错误并返回一个空句柄。
  3. 以传入的add_transfer_params对象作为参数创建一个transfer对象,并调用它的start方法
  4. 将新创建的transfer对象插入到内部列表。
  5. 发送added_transfer_alert通知。

使用post_transfer方法时,将add_transfer封装为一个函数对象并传递给session的boost::io_service以达到异步执行的目的。add_transfer的参数是一个add_transfer_params,它封装了文件名、文件md4和文件存放路径,transfer对象将根据这些信息从服务器和DHT网络中搜索资源。

session内部存放任务列表的数据结构是一个std::map<md4_hash, boost::shared_ptr<transfer>>类型的map映射。一如该类型定义上的字面意思它的键是文件md4而值是一个transfer对象的智能指针。文件传输任务的添加和删除都是在这个列表上通过md4键进行操作。注:在session_impl类中有个目前尚未用到find_transfer的重载函数,它的参数是一个文件路径,在其内部通过合并add_transfer_params中传递的文件存放路径和文件名得到一个路径,再将它与输入参数对比后返回列表中匹配的值。

2.3 管理点对点连接

在session类中定义了一下成员函数用于管理点对点连接:

peer_connection_handle add_peer_connection(const net_identifier& np);
peer_connection_handle find_peer_connection(const net_identifier& np) const;
peer_connection_handle find_peer_connection(const md4_hash& hash) const;

这三个函数分别用于添加和查找一个连接,值得一提的是这三个成员函数在未来优化时可能会被从session类的公开接口中移除。因为从逻辑上说点对点的连接对象应该从属于transfer任务对象而不是从属于整个session,管理连接peer_connection的对象应该是某个具体任务(可能不是transfer任务,以后可能会增加其他需要创建连接的任务类型)。无论如何既然它们现在已经在session中,我们不妨简单看一下它们的实现。

1)add_peer_connection做以下事情:

  • 在内部的连接列表(m_connections)中查找是否连接已经存在
  • 从net_identifier类型的参数中得到需要连接的IP(仅支持IPv4)和端口
  • 在io_service中创建tcp::socket类型的连接,为连接预分配所需的收发缓冲区。
  • 插入到session内部的(connection_queue类型)m_half_open队列中,在合适的时候connection_queue会调用peer_connection::connect方法连接np参数指定的peer。在peer_connection::connect中指定了回调peer_connection::on_connect,在连接成功时它将被调用,然后启动连接任务对应的协议规定的数据收发过程。

2)两个find_peer_connection函数做以下事情:

遍历session内部的连接列表(m_connections),对每一个成员执行peer_connection::has_network_point或peer_connection::has_hash(两个重载函数一个指定的是net_identifer另一个指定的是md4_hash),找到匹配的对象后用transfer_handle封装它然后返回。

2.4 管理alert

和libtorrent相似,异步过程在执行完毕后将该操作对应的alert插入到alert队列,用户需要使用session::pop_alert获取操作的结果。从单纯使用库(简单来说就是从hpp和lib文件开发而不关心libed2k怎么实现也无需去深度定制)的开发者而言编写一个ed2k客户端需要做的事情就是设置libed2k会话参数,连接服务器并启动一个会话。添加任务,接收session的各种通知,然后根据这些通知决定下一步该执行什么。

2.4.1 alert介绍

alert类是所有通知类的基类,其定义为:

class alert { public: // only here for backwards compatibility enum severity_t { debug, info, warning, critical, fatal, none }; enum category_t { error_notification = 0x1, peer_notification = 0x2,port_mapping_notification = 0x4, storage_notification = 0x8, tracker_notification = 0x10, debug_notification = 0x20, status_notification = 0x40, progress_notification = 0x80, ip_block_notification = 0x100, performance_warning = 0x200, server_notification = 0x400, dht_notification = 0x400, stats_notification = 0x800, all_categories = 0xffffffff }; alert(); virtual ~alert(); // a timestamp is automatically created in the constructor ptime timestamp() const; virtual char const* what() const = 0;virtual std::string message() const = 0; virtual int category() const = 0; virtual std::auto_ptr<alert> clone() const = 0; private: ptime m_timestamp; };

具体的各种通知需要从这个基类中派生,它们定义在libed2k\alert_types.hpp中。alert的各个成员如下:

  • severity_t 在此版本中未使用。
  • category_t 定义了alert的类型信息,这些按位定义的值可以方便我们使用&操作符判断一个alert的派生类类型。除了按位方式外还提供了向上转换类型的alert_cast操作,可以通过它简单的将一个alert*转换为一个派生类型(如handle_alert),如果类型不对则会返回NULL。例如执行handle_alert* pcast = alert_cast(an_alert_ptr),当an_alert_ptr类型不是handle_alert时alert_cast将返回NULL。
  • timestamp方法获取alert创建时的时间。
  • what方法和message方法都返回一个字符串,what方法一般用于说明这个通知是什么类型,或者是什么操作的通知,message方法通常用于返回和这个alert有关的一些对象信息,但如果这个alert没有附带重要信息则通常这个函数的返回值和what方法相同。
  • category方法返回category_t类型信息。
  • clone方法,深拷贝自己(即创建一个和自身具有相同值的对象)并返回新对象。

2.4.2 当前已定义的alert

如上一节所说明,所有具体的通知对象都必须从alert继承并实现它的what、message、category和clone纯虚接口。在libed2k\alert_types.hpp中定义了以下类型的alert。

和ed2k服务器(类似torrent tracker服务器)有关的通知,其中2-7均派生自server_alert:

序号

对象类名

说明

通知类型

1

server_alert

abstract server notification基类

status_notification | server_notification

2

server_name_resolved_alert

server name was resolved

同上

3

server_connection_initialized_alert

handshake completed

同上

4

server_status_alert

server status information

同上

5

server_identity_alert

server identity information

同上

6

server_message_alert

incoming server message

同上

7

server_connection_closed

server connection closed

同上

与点对点传输(通信)有关的通知:

序号

对象类名

说明

通知类型

1

peer_alert

peer alert基类

peer_notification

2

shared_files_alert

this alert throws on server search results and on user shared files

server_notification | peer_notification

3

shared_directories_alert

未知

peer_notification

4

ismod_shared_directory_files_alert

未知

peer_notification

5

peer_connected_alert

peer connected alert

peer_notification

|status_notification

6

peer_disconnected_alert

peer disconnected alert

peer_notification

|status_notification

7

peer_message_alert

peer message

peer_notification

8

peer_captcha_request_alert

peer captcha request

peer_notification

9

peer_captcha_result_alert

peer captcha result

peer_notification

10

shared_files_access_denied

shared files access denied

peer_notification

和任务控制有关的通知:

序号

类名

说明

通知类型

1

added_transfer_alert

added transfer

status_notification

2

paused_transfer_alert

paused transfer

status_notification

3

resumed_transfer_alert

resumed transfer

status_notification

4

deleted_transfer_alert

deleted transfer

status_notification

5

finished_transfer_alert

transfer finished

status_notification

6

file_renamed_alert

renaming file in transfer

"renaming file in transfer {hash: {1}, from: " << transfer::name() << ", to: " << name << "}"

status_notification

7

file_rename_failed_alert

rename failed transfer

status_notification

8

deleted_file_alert

emit in remove_transfer()

status_notification

9

delete_failed_transfer_alert

delete failed transfer

status_notification

和文件传输有关的通知:

序号

类名

说明

通知类型

1

transfer_alert

transfer alert基类

未定义,由派生类决定

2

save_resume_data_alert

save resume data complete

storage_notification

3

save_resume_data_failed_alert

save resume data failed

storage_notification

4

fastresume_rejected_alert

resume data rejected

status_notification | error_notification

5

peer_blocked_alert

blocked peer

status_notification

6

file_error_alert

file error

status_notification

error_notification

storage_notification

7

transfer_checked_alert

transfer checked

status_notification

8

hash_failed_alert

piece check failed

status_notification

和端口映射有关的通知:

序号

类名

说明

通知类型

1

portmap_log_alert

portmap log

port_mapping_notification

2

portmap_alert

successfully mapped port using %s. external port: %u

port_mapping_notification

3

portmap_error_alert

portmap error

port_mapping_notification | error_notification

和DHT有关的通知:

序号

类名

说明

通知类型

1

dht_started

DHT started

dht_notification

2

dht_stopped

DHT stopped

dht_notification

3

dht_traverse_finished

DHT traverse finished

dht_notification

4

dht_announce_alert

DHT announce

"DHT announce: " + address_to_bytes(ip) + " port: " + boost::lexical_cast<std::string>(port);

dht_notification

5

dht_get_peers_alert

DHT get peers

dht_notification

6

external_ip_alert

DHT get peers

"external IP received: " + external_address.to_string(ec)

status_notification

7

dht_keyword_search_result_alert

DHT search ketyword result

dht_notification

其他未分类通知:

序号

类名

说明

通知类型

1

listen_failed_alert

监听本地端口失败

status_notification | error_notification

2

state_changed_alert

状态变化的通知

status_notification

3

storage_moved_alert

未知通知

status_notification

4

storage_moved_failed_alert

未知通知

status_notification

5

transfer_params_alert

未知通知

status_notification

6

udp_error_alert

UDP传输故障

error_notification

2.4.3 在程序中接收和处理alert

通过session::pop_alert可以获取session的通知队列,在conn示例程序中处理通知的代码如下:

首先是创建一个定时器定时调用session::pop_alert,下面这段代码在main函数中:

libed2k::io_service io;
boost::asio::deadline_timer alerts_timer(io, boost::posix_time::seconds(3));
alerts_timer.async_wait(boost::bind(alerts_reader, boost::asio::placeholders::error, &alerts_timer, &ses));

执行这段代码后,alerts_timer将在3后超时并在io对象的线程中调用alerts_reader,然后将alerts_timer自身和session作为参数传递给alerts_reader。在alerts_reader中将读取通知队列并依次处理队列中的通知:

void alerts_reader(const boost::system::error_code& ec, boost::asio::deadline_timer* pt, libed2k::session* ps) { if (ec == boost::asio::error::operation_aborted) { return; } std::auto_ptr<alert> a = ps->pop_alert(); while(a.get()) { if (dynamic_cast<server_connection_initialized_alert*>(a.get())) { server_connection_initialized_alert* p =     dynamic_cast<server_connection_initialized_alert*>(a.get()); APP("ALERT: " << "server initalized: cid: " << p->client_id); } //... else if(transfer_params_alert* p = dynamic_cast<transfer_params_alert*>(a.get())) {if (!p->m_ec) { APP("ALERT: transfer_params_alert, add transfer for: " << p->m_atp.file_path); ps->add_transfer(p->m_atp);} } else { APP("ALERT: Unknown alert: " << a.get()->message()); }a = ps->pop_alert(); } pt->expires_at(pt->expires_at() + boost::posix_time::seconds(3)); pt->async_wait(boost::bind(&alerts_reader, boost::asio::placeholders::error, pt, ps)); }

这段代码比较长,这里我们省略了部分alert的处理。它的重点在于:

  1. 调用session::pop_alert取一个通知,这也是为什么需要传入session对象的原因之一。
  2. 判断alert的类型,尝试使用类型转换dynamic_cast(alert*)转换为某个特定类型的值,如果类型不对则为空指针。
  3. 按照alert类型做各种操作,执行完毕后获取重复步骤1直到所有通知都已经处理完毕。
  4. 重设定时器,并Post一个任务到io_service。(注意:boost::io_service的工作线程如果没有任务就会退出,所以这里Post任务进去保证至少有一个。)

在下一节中,我们介绍了一个设定处理例程的方法set_alert_dispatch,这是另外一种处理alert的方式,详情见下一节这个函数的介绍。

2.4.4 其他和alert有关的接口

除了pop_alert之外还有4个成员函数与alert有关:

size_t set_alert_queue_size_limit(size_t queue_size_limit_);
void set_alert_mask(boost::uint32_t m);
alert const* wait_for_alert(time_duration max_wait);
void set_alert_dispatch(boost::function<void(alert const&)> const& fun);

其中set_alert_queue_size_limit如其字面意思是设置session通知队列的大小,如果超过了用户提供的这个值则丢弃,默认值是1000(由alert_manager::queue_size_limit_default定义)。内部实现如下:

bool alert_manager::post_alert(const alert& alert_)
{ boost::mutex::scoped_lock lock(m_mutex); if (m_dispatch) { //LIBED2K_ASSERT(m_alerts.empty()); m_ios.post(boost::bind(&dispatch_alert, m_dispatch, alert_.clone().release())); return true; } //超过最大大小则直接丢弃 if (m_alerts.size() >= m_queue_size_limit) return false; m_alerts.push(alert_.clone().release()); m_condition.notify_all(); return true;
}

set_alert_mask用于限定接收哪些alert。限定功能同样在alert_manager中实现,如下:

template <class T>
bool should_post() const
{ boost::mutex::scoped_lock lock(m_mutex); if (m_alerts.size() >= m_queue_size_limit) return false; return (m_alert_mask & T::static_category) != 0;
}
template<class T>
void post_alert_should(const T& alert)
{ if (should_post<T>()) { post_alert(alert); }
}

wait_for_alert成员函数的内部实现同样在alert_manager中,如下:

alert const* alert_manager::wait_for_alert(time_duration max_wait) {         boost::mutex::scoped_lock lock(m_mutex); if (!m_alerts.empty()) return m_alerts.front(); // system_time end = get_system_time() +     boost::posix_time::microseconds(total_microseconds(max_wait)); // apparently this call can be interrupted // prematurely if there are other signals // while (m_condition.timed_wait(lock, end)) // if (!m_alerts.empty()) return m_alerts.front(); ptime start = time_now_hires(); // TODO: change this to use an asio timer instead while (m_alerts.empty()) { lock.unlock(); sleep(50); lock.lock(); if (time_now_hires() - start >= max_wait) return 0; } return m_alerts.front();
}

可以看到内部简单的一个循环sleep(50)判断队列是不是为空,如果有或者超时了就返回。

set_alert_dispatch函数指定一个处理例程(函数对象)用于处理alert,这个设定永久有效直到用这个函数指定下一个处理例程。在设定处理例程后alert_manager内部列表不再存放alert对象(因此不能与上述三个成员函数共用,它们依赖这个列表。),对于要求实时响应通知的程序不妨设置一个自定义的处理函数以便及时处理。同样这个函数也是在alert_manager中实现:

void alert_manager::set_dispatch_function(boost::function<void(alert const&)> const& fun)
{ boost::mutex::scoped_lock lock(m_mutex); m_dispatch = fun; std::queue<alert*> alerts = m_alerts; while (!m_alerts.empty()) m_alerts.pop(); lock.unlock(); // 如果在设置例程之前已经有alert在队列中 // 那么取出所有通知并调用传入的例程处理一次。 while (!alerts.empty()) { m_dispatch(*alerts.front()); delete alerts.front(); alerts.pop(); }
} bool alert_manager::post_alert(const alert& alert_)
{ boost::mutex::scoped_lock lock(m_mutex); // 如果设置了例程,那么alert不再存入到内部的通知队列中。 if (m_dispatch) { //LIBED2K_ASSERT(m_alerts.empty()); m_ios.post(boost::bind(&dispatch_alert, m_dispatch, alert_.clone().release())); return true; } if (m_alerts.size() >= m_queue_size_limit) return false; m_alerts.push(alert_.clone().release()); m_condition.notify_all(); return true;
}

2.5 在服务器上查找文件

在这一节中,我们深入学习在服务器上查找文件的协议和它在libed2k中的实现。当前我们关注的重点是如何使用libed2k现有的接口实现查找文件的需求,因此在这一节我们尽量不去分析过于底层的实现(例如序列化和反序列化对象)。

2.5.1 搜索请求的格式约定

下面这段搬自eMule协议(英文版)6.2.9节【The eMule Protocol Specification,Yoram Kulbak and Danny Bickson,2005】。

Sent from the client to the server. The message is used to search for files by a user’s search string. The message size varies. The search string may include the boolean conditions ’AND’ ’OR’, ’NOT’. The user may specify required file type and size and also set an availability threshold (e.g. show me results that are available from at least 5 other clients)

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

OP_SEARCHREQUEST操作码,固定是0x16

Parsed search string

varies

NA

The parsed search string format is described below(字符串中可以包含NOT、AND、OR逻辑)

     

一条搜索请求至少要包含这一行以上的元素下面是可选的约束条件,诸如文件类型,大小等

File Type Constraint

varies

NA

Optional. A string constraint. The string values are one of (”Audio”, ”Video”, ”Pro” or ”Image” The type field is 3 bytes: 0x1 0x0 0x3

Min Size Constraint

Varies

NA

Optional. An integer constraint. The file size is provided in mega bytes. The type field is 4 bytes: 0x1 0x1 0x0 0x2

Max Size constraint

Varies

NA

Optional. An integer constraint. The file size is provided in mega bytes. The type field is 4 bytes: 0x2 0x1 0x0 0x2

Availability Constraint

Varies

NA

Optional. An integer constraint. Sets a lower limit on the number of client that poses the searched file The type field is 4 bytes: 0x1 0x1 0x0 0x15

Filename Extension constrain

Varies

NA

Optional. A string constraint. The type field is 3 bytes: 0x1 0x0 0x3

表格2-1 搜索字符串(Parsed search string)的格式

The parsed search string encodes a binary expression tree with Boolean operators ’AND’, ’OR’ and ’NOT’ and string operands. The tree is encoded in pre-order (注:前序意味着AND、OR、NOT逻辑操作符在前,见下图2-2左侧两个例子). The operators are encoded as 2 byte integers with values of 0x0, 0x100, 0x200 for ’AND’, ’OR’ and ’NOT’ respectively. The strings are encoded in TLV format(注:Type-Length-Value (TLV) Encoding) where the type field is a single byte of value 0x1 and the length field is a 2 byte integer. Note that when the search string is a single word it is encoded as a single string operand (no operators). Later versions of eMule encode a search expression which has only ’AND’ operators as a single string, replacing he ’AND’s by spaces, this fits the server’s search string parsing which fragments a single sentence into a series of words separated by ’AND’ operators.

可选的约束条件

The constraints is a sequence of entries. Each entry starts with and ’AND’ descriptor (2-byte 0x00) followed by the encoded constraint(注:约束条件只能是AND逻辑). Thus, the full search line format is <’search-string’ AND constraint1 AND constraint2 etc>, as described in the examples figure below. The encoded constraint is divided to 3 fields:

  1. kind - A single byte describing whether this is a string (0x2) or an integer (0x3) constraint.
  2. value - Either a type-length encoded string or a 4 byte integer value
  3. type - A 3 or 4 bytes describing the constraint’s kind (see main table above)

图片 2-2: Search string encoding example

2.5.2 创建文件查询对象

在ed2k\search.hpp中定义了生成查询对象的两种方法:

namespace libed2k
{ #define SEARCH_REQ_ELEM_LENGTH 20 #define SEARCH_REQ_QUERY_LENGTH 450 #define SEARCH_REQ_ELEM_COUNT 30 extern search_request generateSearchRequest( boost::uint64_t nMinSize, boost::uint64_t nMaxSize, unsigned int nSourcesCount, unsigned int nCompleteSourcesCount, const std::string& strFileType, const std::string& strFileExtension, const std::string& strCodec, unsigned int nMediaLength, unsigned int nMediaBitrate, const std::string& strQuery); extern search_request generateSearchRequest(const md4_hash& hFile);
}

在第一个generateSearchRequest中,各参数的意义如下:

  • nMinSize,最小大小约束,见表格2-1中的“Min Size Constraint”。
  • nMaxSize,最大大小约束,见表格2-1中的“Max Size constraint”。
  • nSourcesCount,可用资源个数,见表格2-1中的“Availability Constraint”。
  • nCompleteSourcesCount,完成度100%的资源个数(不在上一节描述的标准中)。
  • strFileType,文件类型约束,见表格2-1中的“File Type Constraint”
  • strFileExtension,文件拓展名约束,见表格2-1中的“Filename Extension constrain”
  • strCodec,多媒体文件的编码(需要eserver 16.46+,不在上一节描述的标准中)。
  • nMediaLength,多媒体文件的长度(需要eserver 16.46+,不在上一节描述的标准中)
  • nMediaBitrate,多媒体文件播放波特率(需要eserver 16.46+,不在上一节描述的标准中)
  • strQuery,查询字符串,见表格2-1中的“Parsed search string”一行。

在第二个generateSearchRequest中的参数是一个md4哈希值,实现如下:

extern search_request generateSearchRequest(const md4_hash& hFile) { search_request vPrefResult;  vPrefResult.push_back(search_request_entry(std::string("related::") + hFile.toString()));return vPrefResult;
}

可以看到它只是简单的构建了一个"related::"字符串搜索(md4文件搜索不在上节说明的标准中)。

然后在conn的示例中可以看到另外一种更为底层的生成方式,用手工组合各种条件的方式:

case cc_search_test:
{ APP("Execute search test"); search_request sr; //下面的查询条件使用前序遍历翻译后就是: //NOT (("a" AND "b") OR ("c" AND "d")) +++sr.push_back(search_request_entry(search_request_entry::SRE_NOT)); sr.push_back(search_request_entry(search_request_entry::SRE_OR)); sr.push_back(search_request_entry(search_request_entry::SRE_AND)); sr.push_back(search_request_entry("a")); sr.push_back(search_request_entry("b")); sr.push_back(search_request_entry(search_request_entry::SRE_AND)); sr.push_back(search_request_entry("c")); sr.push_back(search_request_entry("d")); sr.push_back(search_request_entry("+++")); ses.post_search_request(sr); break;
}

代码中的注释说明了构建的请求。注意"+++"也不在上节说明的标准中。

2.5.3 发送文件搜索请求

在session对象中,和发送文件搜索请求有关的函数有4个:

  1. void post_search_request(search_request& sr);
  2. void post_search_more_result_request();
  3. void post_cancel_search();
  4. void post_sources_request(const md4_hash& hFile, boost::uint64_t nSize)

在上一节中我们创建好了文件查询对象后,调用libed2k::session::post_search_request即可将它发送往服务器,我们将在下一章中专门说明网络序列化和反序列化的过程,这里仅对基本流程进行分析。

post_search_request往io_service中提交一个aux::session_impl::post_search_request异步过程,在这个异步过程函数中将调用server_connection::post_search_request序列化文件搜索请求对象,并将它发送往服务器。

post_search_more_result_request往服务器发送一个search_more_result对象,这个对象对应的数据包没有body只有header。

post_cancel_search是通过向服务器发送一个空的shared_files_list对象。实现如下:

void session_impl::post_cancel_search()
{ shared_files_list sl; m_server_connection->post_announce(sl);
}

post_sources_request向服务器查询拥有文件hFile(md4形式)的源:

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);
}

关于这个请求的细节和服务器返回的信息请见下一章3.3.7,另外可参考emule协议中6.2.11(Get sources)一节和表2-1中OP_FOUNDSOURCES的说明。

libed2k源码导读:(二)Session接口以及实现分析相关推荐

  1. libed2k源码导读:(五)文件读写

    第五章 文件读写 5.1 文件总览 libedk文件对象一览. transfer 代表一个传输任务,一个传输任务通常只有一个文件.原始ed2k不支持目录下载 piece_picker 分片选择器 pi ...

  2. libed2k源码导读:(一)从ed2k链接开始

    第一章的目的是大致了解libed2k怎么使用,libed2k库自带了一个测试工程conn,这一章我们将分析conn,让从我们最关心的下载文件开始. 1.1 解析ed2k链接 通常在网上分享的电驴资源时 ...

  3. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    2019独角兽企业重金招聘Python工程师标准>>> 我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegi ...

  4. Alian解读SpringBoot 2.6.0 源码(二):启动流程分析之监听器解析

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.记录应用启动的开始时间 三.初始化启动上下文 3.1.初始化启动上下文 3.2.初始化应用程序事件广播器 3.3.初始化应用上下文 ...

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

    目录 第三章 网络IO 3.1 数据序列化和反序列化 3.1.1 以向服务器发送数据为例 3.1.2 序列化和反序列化对象的细节 3.1.3 序列化集合类对象 3.1.4 Tag,tag_list和它 ...

  6. caffe源码导读(二)Blob数据结构介绍

    文章目录 前言 一.先看Blob的数据结构描述 二.Blob基本用法 前言 本篇<深度学习21天实战caffe>这本书的阅读笔记. 打开proto/caffe.proto中,刚开始就是介绍 ...

  7. Alamofire源码导读二:发起请求及内部加锁的逻辑

    以创建一个 DataRequest 为例子  发起请求 创建 SessionManager 顺带也创建了一个 SessionDelegate 持有一个urlSession,持有一个串行的 Dispa ...

  8. Alian解读SpringBoot 2.6.0 源码(七):启动流程分析之准备应用上下文

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.准备应用上下文 2.1.整体流程 2.2.设置环境 2.3.应用上下文进行后置处理 2.4.应用所有初始化器 2.5.发布应用上下 ...

  9. Alian解读SpringBoot 2.6.0 源码(九):启动流程分析之应用上下文刷新后处理(启动完成事件,Runner运行器,就绪事件)

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.应用上下文刷新后置处理 三.时间信息.输出日志记录执行主类名 四.发布应用上下文启动完成事件 4.1.ApplicationSta ...

最新文章

  1. 为什么像王者荣耀这样的游戏 Server 不愿意使用微服务?
  2. 多边形轮廓等比例缩放
  3. 分布式搜索引擎ElasticSearch+Kibana (Marvel插件安装详解)
  4. Xcode执行Analyze静态分析
  5. 2021-08-30 centos连接WiFi方法
  6. 计组之中央处理器:3、数据通路(单总线结构、专用通路结构)
  7. 查看Oracle实例的EM端口
  8. arm Linux 低成本方案,参赛作品《低成本基于ARM+Linux平台搭建web服务器的物联网学习板》...
  9. java正方形矩阵_已知一个NxN的矩阵A,求矩阵中所有边长为m的正方形的子矩阵
  10. linux下给源码安装好的php支持pdo_mysql
  11. PNP与NPN传感器的区别
  12. mp4视频文件压缩率大概是多大?
  13. 解决阿里云不能使用yum问题
  14. “_CRT_SECURE_NO_DEPRECATE”: 未定义宏或在预编译头使用后定义发生改变
  15. 深度推荐模型 -NFM
  16. Python 爬虫js加密破解(四) 360云盘登录password加密
  17. 再校大学生的电子产品清单
  18. 第六届全国大学生生物医学工程创新设计竞赛参赛经历
  19. 在预装win8的UFI+GTP的pc上实现ubuntu和win8双系统启动
  20. Android使用ViewPager实现图片轮播和手势滑动

热门文章

  1. Windows 远程桌面连接方法及远程桌面控制软件推荐
  2. 划分训练集、测试集,制作自己的数据集
  3. 西电 操作系统课设 在Ubuntu18.04安装pintos
  4. Cannot connect:由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。192.168.0.113:22
  5. 自然语言处理简介及研究方向
  6. Centos 安装zlib
  7. python中使用requests库获取昵图网图片,且正则中re.S的用法
  8. linux第一块ide硬盘命名为,linux下硬盘分区
  9. 计算机网络浅谈,浅谈计算机网络的重要性
  10. vmware horizon view发布win7/win10即时克隆桌面池步骤图文