Graphene 源码阅读 ~ RPC 篇 ~ 通信协议与服务端实现

in #bitshares6 years ago

从现在开始我们进入一个新的篇章: RPC 篇, 这个篇章会包含客户端 (钱包, UI 等) 与节点间的通信细则, API 分类, 服务端的实现等内容, 最后会挑几个主要的 API 讲一下.

本文是 RPC 篇的第一章, 我们就先来介绍一下整体的通信机制和服务端 (节点) 实现.

通信协议

Bitshares 提供两种通信方式: http 和 websocket. 这俩最大的区别就是 websocket 是双向通信, 客户端和服务段都能主动向对方发送消息; 而 http 则只能由客户端主动发送消息. Websocket 的双向通信特性能够满足一些对实时性需求较高的应用.

Websocket 和 http 如此不同, 但却又难解难分, websocket 是在 http 之后出现的, 它复用了 http 的传输层以及协议端口, 并且握手过程也是使用 http 消息格式实现的, 只不过在 http 头部添加了几个新的 headers, 当服务端检测到 websocket 的 headers 时, 就会知道这是个 websocket 连接, 从而与传统的 http 请求过程区分开来.

刚说了 websocket 复用了 http 的传输层, http 的传输层可以是未加密的 tcp, 也可以是加密过的 tls, 那么 websocket 自然也可以用这两种传输层协议.

\httpwebsocket
tcphttp://ws://
tlshttps://wss://

关于 websocket 协议的细则可以自行 google 一下, 这里就不再敖述了.

消息格式

不管是 websocket 还是 http, 客户端与节点通信时的消息体都是 json 格式, 一个典型的请求体内容如下:

{"method":"call","params":[1,"database",[]],"id":83}

其中 id 是自增的, 对于函数调用来说 method 固定为 "call", params 是包含三个元素的数组, 三个元素分别代表 api_id (下一章介绍), 方法名, 以及方法参数. 返回体会因请求不同而不同, 但当然也是标准的 json 格式, 一般会包含 id, result 这些通用字段, 不再贴出.

服务端实现

服务端的实现借助了 websocketapp 库, 这个库能够帮助我们方便的开发 websocket 服务端程序, 不但如此, 它也支持对普通 http 消息的处理, 因为前面说了 websocket 和 http 使用共同的传输层和端口, websocket 协议也只是在握手阶段使用 http 的消息格式, 所以 websocketapp 很容易区分客户端发来的是 websocket 消息还是普通的 http 消息, 相应的做不同的处理, 为此 websocketapp 提供了两个回调接口: on_message 和 on_http, 应用程序可以注册这两个回调方法. 当收到 websocket 消息时, on_message 会被调用; 而收到普通 http 消息时, on_http 会被调用.

除了 on_message 和 on_http 之外还有一个重要的回调是 on_connection, 它代表着有新的客户端连接过来.

Bitshares 代码中当然是实现了这三个回调的, 下面我们就来看一下.

注册回调

在节点启动时, 会调用 application::startup() 方法, 而这个方法的最后一个工作就是启动 RPC server, 这在 reset_websocket_server() 方法中去做:

// 代码 1.1

 277 void application_impl::reset_websocket_server()
 278 { try {
 279    if( !_options->count("rpc-endpoint") )
 280       return;
 281
 282    _websocket_server = std::make_shared<fc::http::websocket_server>();
 283    _websocket_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) );
 284
 285    ilog("Configured websocket rpc to listen on ${ip}", ("ip",_options->at("rpc-endpoint").as<string>()));
 286    _websocket_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-endpoint").as<string>()) );
 287    _websocket_server->start_accept();
 288 } FC_CAPTURE_AND_RETHROW() }

这个方法很简单, 首先直接实例化了 _websocket_server 对象, 这个对象的类型是 fc::http::websocket_server, 它又是属于 fc 库的一部分, 然而这不重要, 这里不需要再了解 fc 库中对应的代码了. 实际上 fc::http::websocket_server 就是对前面我们说的 websocketapp 库的封装, 我们可以把 fc::http::websocket_server 就看做是 websocketapp.

那么可以看到紧接着就是注册了 on_connection 回调, 然后就是 listen, accept, 多么熟悉的套接字编程套路, websocket 服务端就这么愉快的启起来了~

我知道你要问什么, 怎么没看见注册 on_message 和 on_http 回调呢? 对了, 看到注册 on_connection 回调用的 application_impl::new_connection 方法了吗, on_message 和 on_http 实际上就是在这个方法里注册的:

// 代码 1.2

 245 void application_impl::new_connection( const fc::http::websocket_connection_ptr& c )
 246 {
 247    auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c, GRAPHENE_NET_MAX_NESTED_OBJECTS);
 248    auto login = std::make_shared<graphene::app::login_api>( std::ref(*_self) );
 249    login->enable_api("database_api");
 250
…
…

// 代码 1.3

 10 websocket_api_connection::websocket_api_connection( fc::http::websocket_connection& c, uint32_t max_depth )
 11    : api_connection(max_depth),_connection(c)
 12 {
 13    _rpc_state.add_method( "call", [this]( const variants& args ) -> variant
 14    {
 15       FC_ASSERT( args.size() == 3 && args[2].is_array() );
…
…
 49
 50    _connection.on_message_handler( [&]( const std::string& msg ){ on_message(msg,true); } );
 51    _connection.on_http_handler( [&]( const std::string& msg ){ return on_message(msg,false); } );
 52    _connection.closed.connect( [this](){ closed(); } );
 53 }

application_impl::new_connection 的参数 fc::http::websocket_connection_ptr 这个类型又是对 websocketapp 的封装, 不难理解, 我们直接认为它就是 websocketapp 传过来的对这个新连接的上下文描述就好, 紧接着实例化了一个 fc::rpc::websocket_api_connection 对象并且把这个上下文传了进去, fc::rpc::websocket_api_connection 的构造函数在代码 1.3, 可以看到在构造函数最后它注册了 on_message 和 on_http 的 handler, 而这两个 handlers 实际上是调用的同一个方法: on_message, 注意这里这个 on_message 可是 websocket_api_connection::on_message.

到这里为止, 我们就知道该如何 track 各种请求在服务期短的处理了, 新连接的处理就看 application_impl::new_connection, 来了请求怎么处理就看 websocket_api_connection::on_message.

当然对 websocket 来说还有一个过程就是服务器端主动发消息给客户端的过程, 这部分感兴趣可以自己研究一下, 提示一下: fc::http::websocket_connection::send_message 方法.

后记

本文最后引出了 on_connection 和 on_message 这两个重要的回调, 下篇文章将会简单介绍 on_connection 实现, 然后从 on_message 展开介绍一下各类 api 们, 以及从请求体到这些 api 们的映射机制.

Sort:  

期望你快点解说,我就直接看了,看文章比看代码省事多了~bm开发牛人~

哈哈, 我尽量 :P

楼主啥时候讲讲static_variant啊

static_variant 留到交易篇讲吧

Coin Marketplace

STEEM 0.27
TRX 0.12
JST 0.031
BTC 61757.83
ETH 2905.75
USDT 1.00
SBD 3.62