第二章 Session接口以及实现分析
目录
和libtorrent一样,libed2k的功能大部分需要使用session类提供的接口,而session类所有功能的具体实现都是在session_impl类中,session对象通过内部的一个session_impl对象指针向外提供接口。session类共有54个成员函数,大致可以分为几类:
- 构造及初始化,session()和init()
- 获取session的状态,status()
- 管理文件传输任务(通过transfer_handle)的接口
- 管理点对点连接(通过peer_connection_handle)的接口
- 管理alert的一系列接口
- 在服务器上查找文件的接口
- P2P本地监听端口有关的接口
- session选项设置和获取接口
- 设置和获取IP过滤的接口
- 上传下载限速的接口
- 连接/断开服务器的接口
- 暂停/恢复整个session的接口
- 分享和取消分享的接口
- 端口映射相关的接口
- 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做的事情是:
- 检查session的状态,如果session处于abort状态,则报告错误并返回一个空句柄。
- 确认任务是否已经在session中,如果已经存在则报告错误并返回一个空句柄。
- 以传入的add_transfer_params对象作为参数创建一个transfer对象,并调用它的start方法
- 将新创建的transfer对象插入到内部列表。
- 发送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() cons