前言
eos代码更新很快,在4月初已经升级到3.0版本,随着版本的更迭,在各个操作系统下的编译、节点的运行都越来越集成化,不需要自己再一步步的下载依赖,如果感兴趣可以直接按照官方wiki进行编译。官方wiki地址:https://github.com/EOSIO/eos/wiki
一般不会出现什么错误,当然如果出现编译、测试节点运行出错的情况可以添加我个人微信(见本文最下方),我会尝试着去解答一下。有鉴于eos代码更新的速度,我们现在也大可不必关注一些具体的实现,而是作出一些调整,通过更深层次的探讨去观察整个eos代码的架构。
自代码更新至3.0版本之后,eosioc也变成了cleos,通过代码的注释,我们可以看出cleos是一个:基于命令行对eos进行一些简单的交易、获取nodeos的状态等功能,如果想要使用这个命令行去进行相应的操作,需要本地有一个nodeos在运行,且chain_api_plugin需要加载成功。
图1 cleos代码功能简介
cleos有很多参数可以配置选用,具体到每一个点官方wiki也都有介绍,在下一篇文章中我们也将继续进行详细分析。这些功能的实现都需要通过http请求来完成,今天我们来谈谈一些细节的东西,如cleos是如何搭建一个httpserver的。
Boost::asio简介
首先我们来看cleos的main.cpp中,不管是交易还是获取钱包、获取账户的状态等功能都会调用一个函数do_http_call,在5月5日eos-master中最新的更新记录中,Daniel Larimer也将cleos中的::call全部替换成了do_http_call,那么do_http_call到底是一个什么样的函数?他实现了什么功能?是如何实现的?让我们对他一探究竟。跳转到do_http_call这个函数,我们可以看到其实这个函数是基于boost::asio实现的一个httpserver,供cleos的http通信使用.
Boost::asio是一种跨平台的主要用于网络和其他一些底层输入/输出的C 库。Boost::asio在网络通信、COM串行端口和文件上成功的抽象了输入输出的概念。我们可以基于这些进行同步或者异步的网络编程。作为一个跨平台的库,Boost::asio可以在大多数操作系统上使用,且能够同时支持数千个并发的连接。其网络部分的灵感来源于socket协议,提供了一套可以支持TCP、UDP、IMCP协议的API,而且如果有需要的话,可以对其进行扩展。Boost::Asio基本框架如图1所示:
图2 Boost::Asio基本框架
使用者启动一个异步操作,同时创建一个异步回调的对象。然后异步操作被交给了异步操作执行者,通过异步操作执行者来执行异步操作,当异步操作完成之后,将其插入完成事件队列。前摄器驱动异步事件分发器从刚刚异步操作完成插入的完成事件队列中获取事件,这是一个阻塞的过程,一旦获取到完成事件,就会从事件中找出关联的回调对象,并执行回调。
基于Asio的HttpServer的实现
每一个Asio服务的实现都需要至少一个io_service类,io_service只有三个成员变量,简单意味着强大,也表明asio已经将功能结构划分的清晰明了。如图3所示:
图3 io_service成员变量示例
Asio提供了诸多服务,但是上层服务不会直接使用这些服务,这些服务是通过句柄对外暴露其功能,而句柄被功能对象封装,然后提供给上层应用使用。因此前面的前摄器模式可以简单的添加IO对象如图4所示:
图4 添加IO对象之后的asio结构图
以acceptor为例,通过源码可以发现他是basic_socket_acceptor在TCP模板参数下的一个实例。Asio大量采用这种技术,所有的io功能类,都是单个basic模板类的实例化。这些实例化的的类,分别负责一些具体的事物,如acceptor可以作为一个服务器进行侦听,提供了诸如bind()、listen()等接口。再如basic_socket是对socket IO操作的封装,提供了receive(),、async_recive()、read_some()、async_readsome()、write_some()、async_write_some()等接口。如图5所示:
图5 asio的io对象结构图
基于asio的HttpServer的基本框架如图6所示:
图6 基于asio的HttpServer框架
io_service::run()是io_service最重要的函数。主线程启动run()之后,将其交给impl_.run().通过源码可以发现,在windows操作系统下,如果没有禁用IOCP,asio就会采用win_iocp_io_service来完成io_service::run()的功能。win_iocp_io_service是windows操作系统下boost::asio实现的核心,他是对windows环境下IOCP(完成端口IO)模型的封装。io_service的构造函数会调用win_iocp_io_service::init(),这个过程会创建一个完成端口句柄,而当io_service::run()的时候会调用win_iocp_io_service::do_one(),do_one()会做一次完成端口状态查询并处理,run()就会一直重复调用do_one()完成I/O操作。如图7所示:
图7 impl_在windows下的结构图
Server类中主要包含有用于执行异步操作的io_service_,用于侦听连接请求的acceptor_,用于存放所有活动连接的connection_manager_,用来存放新建连接的new_connection_以及处理将要到来请求的句柄request_handler_。通过源码可以发现,用来存放新建连接的connection_ptr其实是一个shared_ptr类型,他是实现连接自动释放的关键。
connection类内存管理机制:当接收到客户端的连接请求之后,使用一个shared_ptr对象持有一个新建的连接对象,当shared_ptr转而持有其他对象时,将对此连接对象的引用计数减一,而connection类中的异步处理函数中传递的this指针都是通过share_from_this获取的,这个传递的this指针也是被shared_ptr进行管理的。处理完毕后引用计数自动减一,当与这个连接的相关操作都执行完毕以后,连接对象的引用计数为0,自动释放,由此实现了每个客户端连接创建一个连接对象,连接对象处理完请求之后释放自己。如图8所示:
图8 connection启动过程
在handle_read中使用request_parser_.parse拉解析来自客户端的数据,并将解析结果放入到buffer中。此时定义了一个三状态result。当解析数据成功的时候,则进行数据的处理并生成返回的内容。当解析数据失败的时候,则向客户端发送请求失败的内容,还有一种未知状态,则继续接收来自客户端的请求。下面以解析成功为例,如图9所示:
图9 来自客户端内容的解析
当使用parse解析来自客户端的数据正常时,则将数据存入buffer中,并进行返回结果的处理。本例将使用者在QT配置的界面中写入的数据为返回内容,以json串的形式回传给客户端。向客户端异步发送相应的数据之后,调用handle_write停止该socket的发送和接收,但是并未释放这个socket对象,结束了这一次http的请求,并从连接池中将这个连接清除掉,回收这个连接对象的内存空间。如图10所示:
图10 handle_write操作示例
Parse是对来自客户端请求内容的解析,返回请求内容、起始迭代器、截止迭代器。Consume分析传入的char类型的参数,根据协议及当前状态解析这个字符,将char字符加入到request的结构体成员中。如果一个request没有解析完成则返回未知状态,继续进行下一个字符的解析。Connection类的构造函数中会构造一个连接池对象,创建好连接之后将连接自动放入缓冲池中管理。如图11和图12所示:
图11 parse解析类的实现
图12 连接池的实现
Server类中创建connection实例需要从线程池中通过get_io_service来获io_service对象。异步操作由哪个线程执行与io_service对象有关。因此要想实现线程池,首先要在线程池对象中创建多个io_service对象同时还要创建多个线程对象,这样每个io_service调用run即可实现异步操作均匀的将多个io_service对象分配给多个线程执行了。如图13和图14所示:
图13 多线程多io_service的HttpServer的实现
图14 线程池中get_io_service示例
由于本人是做windows下qt开发的,因此基于qt界面库和boost::asio实现了一个测试小工具,支持http post(暂不支持https协议或get请求,有需要可以继续补充)方式请求、解析处理、并给出一定的返回,简单的界面如下(丑了点,但是可以用),有需要源码的可以加我个人微信。
图15 基于qt的界面的boost::asio实现的post测试小工具
结语
本次我们随着eos代码的更新,调整了一些分析策略,当然以后的源码分析也不会一成不变。先从eos命令行工具入手,查看cleos网络通信的实现,并具体到boost::asio是如何实现一个httpserver的,最后基于boost::asio和qt界面库,做一个小工具用来测试http post相关内容,如前文所说下一篇准备针对cleos的一些命令参数进行相关分析。当然本文全以个人理解进行编写,如理解有误或者有哪些意见,还请各位不吝赐教.