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

in bitshares •  7 months 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 们的映射机制.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

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

·

哈哈, 我尽量 :P

楼主啥时候讲讲static_variant啊

·

static_variant 留到交易篇讲吧