目录
4.2.4 client_ext_hello和client_ext_hello_answer
4.3.5 "开始上传文件"请求及其响应(OP_STARTUPLOADREQ)
第四章 P2P消息
4.1 libed2k p2p 连接工作流
处理P2P消息的逻辑被封装在 `base_connection`和`peer_connection`对象中,调用栈如下:
session::listen_on(int port, const char* net_interface /*= 0*/) <--在这里开始监听TCP连接
|---void session_impl::open_listen_port()
|---async_accept
|---session_impl::on_accept_connection <--TCP 建立
|---session_impl::incoming_connection(boost::shared_ptr<tcp::socket> const& s)
|---new peer_connection(*this, s, endp, NULL)
|---setup P2P message hander
|---peer_connection::do_read
|---base_connection::do_read(); //握手,设置选项等。
| |---base_connection::on_read_header
| |---base_connection::on_read_packet
| |---find handler by type and protocol in header
| |---call handler() <-- 处理收到的数据
或者
|---peer_connection::receive_data() //当文件传输正在进行时
|---peer_connection::on_receive_data(error, bytes_transferred)
|--process data received. <-- 处理收到的数据
点对点消息处理函数的设置在`peer_connection::reset()`函数中,接下来分各节说明P2P消息。
4.2 HELLO和HELLO answer
4.2.1消息定义
(From EMule Protocol 第六章 6.4.1)
This message is the first message in the handshake between two e-mule clients. This message is very much like the server login message (see in section 6.2.1). Both messages have the same type code (0x01), they are the only messages in the protocol that have overlapping type code. Both messages provide the same data and even in the same order. There are two main differences: the client hello message begins with a user hash size field while the server login message immediately begins with the user hash value, also the client hello message ends with additional server IP and port information which is not relevant for the server login message.
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 |
0x01 |
The value of the OP HELLO opcode |
User Hash size |
1 |
16 |
The size of the user hash field |
以下部分和Hello answer消息的结构一致 |
|||
User Hash |
16 |
TBD |
|
Client ID |
4 |
0 |
TBD |
TCP Port |
2 |
4662 |
The TCP port used by the client, configurable |
Tag Count |
4 |
4 |
The number of tags following in the message |
Tag list |
varies |
NA |
A list of tags specifying remote client’s properties |
Server IP |
4 |
NA |
The IP of the server to which the client is connected |
Server TCP Port |
2 |
NA |
The TCP port on which the server listens |
There are three types of tags that may appear in the tag-list. The port tag is optional and usually not provided. Tag encoding rules are described is detail an the beginning of this chapter.
Name |
Tag name |
Tag Type |
Comment |
Username |
Integer, 0x01 |
String |
|
Version |
Integer 0x11 |
String |
|
Port |
Integer 0x0F |
Integer |
HELLO Answer (From EMule Protocol 6.4.1)
Sent as an answer to a Hello message. Contains exactly the same fields as the hello message except for the message type.
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 |
0x4C |
The value of the OP HELLOANSWER op-code |
Hello fields |
The same fields as in the hello message starting with the user hash |
4.2.2 收取Hello和helloAnswer消息
message handlers在"peer_connection::reset()"中设置:
add_handler(std::make_pair(OP_HELLO, OP_EDONKEYPROT),
boost::bind(&peer_connection::on_hello, this, _1));
add_handler(get_proto_pair<client_hello_answer>(),
boost::bind(&peer_connection::on_hello_answer, this, _1));
从网络流中解析HELLO消息peer_connection::on_hello:
void peer_connection::on_hello(const error_code& error)
{
if (!error){
DECODE_PACKET(client_hello, hello);
// store user info
m_hClient = hello.m_hClient;
m_options.m_nPort = hello.m_network_point.m_nPort;
//解析hello消息中的tag_list并存放到m_options对应的项中
parse_misc_info(hello.m_list);
DBG("hello {" << " server point = " << hello.m_server_network_point << " network point = " << hello.m_network_point << "} <== " << m_remote);
//在CALLBACK列表中寻找当前连接的用户,如果找到就从列表中移除并返回文件md4hash
//前面有说明CALLBACK已经不被大部分的服务器所支持,这里我们跳过对它的说明。
md4_hash file_hash = m_ses.callbacked_lowid(hello.m_network_point.m_nIP);
if (file_hash != md4_hash::invalid)
{
DBG("lowid peer detected for " << file_hash.toString());
m_active = true;
attach_to_transfer(file_hash);
}
//收到了对方发送的hello消息,给他一个ACK。
write_hello_answer();
}
else
{
ERR("hello packet received error " << error.message());
}
}
反序列化流到`client_hello`对象:
DECODE_PACKET with T=client_hello, t = hello in following code:
template<typename T>
bool decode_packet(T& t)
{
try{
if (!m_in_container.empty()){
boost::iostreams::stream_buffer<base_connection::Device> buffer(
&m_in_container[0],
m_in_header.m_size - 1);
std::istream in_array_stream(&buffer);
archive::ed2k_iarchive ia(in_array_stream);
ia >> t;
}
}catch(libed2k_exception& e){
DBG("Error on conversion " << e.what());
return (false);
}
return (true);
}
"client_hello"序列化:
struct client_hello : public client_hello_answer
{
boost::uint8_t m_nHashLength; //!< clients hash length
//...
template<typename Archive>
void serialize(Archive& ar)
{
ar & m_nHashLength;
client_hello_answer::serialize(ar);
}
};
`client_hello_answer`类型定义:
struct client_hello_answer
{
md4_hash m_hClient;
net_identifier m_network_point;
tag_list<boost::uint32_t> m_list;
net_identifier m_server_network_point;
//...
template<typename Archive>
void serialize(Archive& ar)
{
ar & m_hClient & m_network_point & m_list & m_server_network_point;
}
};
client_hello和client_hello_answer唯一的不同是增加了一个字节的m_nHashLength。
下面是从网络流中读取client_hello_answer:
void peer_connection::on_hello_answer(const error_code& error)
{
if (!error){
DECODE_PACKET(client_hello_answer, packet);
//解析对方hello响应消息中的tag_list并存放到m_options对应的项中
parse_misc_info(packet.m_list);
m_hClient = packet.m_hClient;
DBG("hello answer {name: " << m_options.m_strName << " : mod name: " << m_options.m_strModVersion << ", port: " << m_options.m_nPort << "} <== " << m_remote);
m_ses.m_alerts.post_alert_should(
peer_connected_alert(get_network_point(),
get_connection_hash(),
m_active)
);
//我们主动发起的连接,收到hello answer消息就完成了握手。
//在下面的函数中发起一条“文件请求”消息,传入我们所需文件的md4 hash。
finalize_handshake();
}
else
{
ERR("hello error " << error.message() << " <== " << m_remote);
}
}
4.2.3 发送hello和helloAnswer消息
在发起连接完成后(peer_connection::on_connect),连接发起方首条发送的消息就是hello:
void peer_connection::write_hello() {
DBG("hello ==> " << m_remote);
const session_settings& settings = m_ses.settings();
client_hello hello(m_ses.settings().user_agent,
net_identifier(m_ses.m_server_connection->client_id(),m_ses.settings().listen_port),
net_identifier(address2int(m_ses.server().address()), m_ses.server().port()),
m_ses.settings().client_name,
m_ses.settings().mod_name,
m_ses.settings().m_version);
// fill special fields
hello.m_nHashLength = MD4_DIGEST_LENGTH;
hello.m_network_point.m_nIP = m_ses.m_server_connection->client_id();
hello.m_network_point.m_nPort = settings.listen_port;
// 这个函数填充hello/helloAnswer消息的tag_list,
// 上面提到过这两个消息的结构仅有一个HashLength不一样。
append_misc_info(hello.m_list);
write_struct(hello);
}
接收方收到HELLO消息时发回响应如下:
void peer_connection::write_hello_answer()
{
// prepare hello answer
client_hello_answer cha(m_ses.settings().user_agent,
net_identifier(m_ses.m_server_connection->client_id(),
m_ses.settings().listen_port),
net_identifier(address2int(m_ses.server().address()), m_ses.server().port()),
m_ses.settings().client_name,
m_ses.settings().mod_name,
m_ses.settings().m_version);
//这个函数创建hello/helloAnswer的tag_list
append_misc_info(cha.m_list);
cha.dump();
DBG("hello answer ==> " << m_remote);
write_struct(cha);
//注意下面这个函数在对方发起连接时接收方的on_hello_answer中也有调用。
//在这里我们是发送方,所以这个函数此时仅创建和发送缓冲区有关的数据结构实例。
finalize_handshake();
}
append_misc_info函数的实现:
void peer_connection::append_misc_info(tag_list<boost::uint32_t>