从现在开始我们进入一个新的篇章: 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 自然也可以用这两种传输层协议.
\ | http | websocket |
---|---|---|
tcp | http:// | ws:// |
tls | https:// | 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 们的映射机制.
期望你快点解说,我就直接看了,看文章比看代码省事多了~bm开发牛人~
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
哈哈, 我尽量 :P
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
楼主啥时候讲讲static_variant啊
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
static_variant 留到交易篇讲吧
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit