编写C++程序与节点进行交互
【1】简单版本,需对http request有一定了解,以及QT的基本操作
【2】进行类的封装,个人水平有限,如有纰漏,请下方留言
测试环境:
- debian 9
- QT 5.5
- geth/v1.8.14
- C++大数bigint library下载: https://pan.baidu.com/s/1bc_d81J2lsqs4bRKa9rNyA 提取码: 5f7m
在编写C++程序与以太坊节点进行交互【1】中,我们用QT的Network类实现了通过post 到以太坊的JSON-RPC接口与节点进行交互。显然,大量的这些代码在mainwindow.cpp中出现并不利于代码的编写和阅读。
本文将一步步的将这些代码封装起来,便于调用。
一、新建一个类rpc_json_http
(1)在项目中,右键,添加文件,选择C++ Class,基类选择QObject(因为用到了信号和槽)
(2)编辑rpc_json_http.h
(为了方便对比,这里没有用代码编辑器)
#ifndef RPC_JSON_HTTP_H
#define RPC_JSON_HTTP_H#include <QObject>
#include<QtNetwork/QNetworkReply>
#include<QtNetwork/QNetworkRequest>
#include<QtNetwork/QNetworkAccessManager>
class RPC_JSON_Http : public QObject
{
Q_OBJECT
public:
explicit RPC_JSON_Http(QObject *parent = 0);
~RPC_JSON_Http();
void init();//初始化网络管理接口QNetworkAccessManager
void get(const QString url);//get方法的封装
void post(const QUrl url, const QByteArray &data);//post方法的封装
private:
QNetworkAccessManager *manager; //声明网络管理接口QNetworkAccessManager
QNetworkRequest httpRequest;
protected:
//纯虚函数,在finishedSlot槽函数中调用
//具体实现在子类
virtual void requestFinished(QNetworkReply *reply, const QByteArray data, const int statusCode) = 0;
signals:
public slots:
private slots:
void finishedSlot(QNetworkReply *reply);//槽函数
};#endif // RPC_JSON_HTTP_H
代码编辑器的:
#ifndef RPC_JSON_HTTP_H
#define RPC_JSON_HTTP_H
#include <QObject>
#include<QtNetwork/QNetworkReply>
#include<QtNetwork/QNetworkRequest>
#include<QtNetwork/QNetworkAccessManager>
class RPC_JSON_Http : public QObject
{
Q_OBJECT
public:
explicit RPC_JSON_Http(QObject *parent = 0);
~RPC_JSON_Http();
void init();//初始化网络管理接口QNetworkAccessManager
void get(const QString url);//get方法的封装
void post(const QUrl url, const QByteArray &data);//post方法的封装
private:
QNetworkAccessManager *manager; //声明网络管理接口QNetworkAccessManager
QNetworkRequest httpRequest;
protected:
//纯虚函数,在finishedSlot槽函数中调用
//具体实现在子类
virtual void requestFinished(QNetworkReply *reply, const QByteArray data, const int statusCode) = 0;
signals:
public slots:
private slots:
void finishedSlot(QNetworkReply *reply);//槽函数
};
#endif // RPC_JSON_HTTP_H
(3)编辑 rpc_json_http.cpp
#include "rpc_json_http.h"
RPC_JSON_Http::RPC_JSON_Http(QObject *parent) : QObject(parent)
{
init();//初始化网络管理接口
}
RPC_JSON_Http::~RPC_JSON_Http()
{
manager->disconnect();//关闭
manager->deleteLater();
}
void RPC_JSON_Http::init()
{
//实例化网络管理器
manager=new QNetworkAccessManager(this);
//Header设置
httpRequest.setRawHeader("Accept", "application/json");
httpRequest.setRawHeader("Content-Type", "application/json");
//信号建立,收到返回数据后,调用槽函数
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finishedSlot(QNetworkReply*)));
}
void RPC_JSON_Http::get(const QString url)
{
//根据参数url调用get方法
httpRequest.setUrl(QUrl(url));
manager->get(httpRequest);
}
void RPC_JSON_Http::post(const QUrl url, const QByteArray &data)
{
//根据参数url、data,调用post方法
httpRequest.setUrl(url);
manager->post(httpRequest,data);
}
void RPC_JSON_Http::finishedSlot(QNetworkReply *reply)
{
//获取返回状态
// Reading attributes of the reply
QVariant statusCodeV=reply->attribute(
QNetworkRequest::HttpStatusCodeAttribute);
// Or the target URL if it was a redirect:
QVariant redirectionTargetUrl=reply->attribute(
QNetworkRequest::RedirectionTargetAttribute);
if(reply->error()==QNetworkReply::NoError){
//调用纯虚函数,将post\get后得到的返回数据、状态作为参数传入
requestFinished(reply, reply->readAll(), statusCodeV.toInt());
//纯虚函数在子类中实现
}else {
//错误则输出
requestFinished(reply, "something worng", statusCodeV.toInt());
qDebug()<<"something worng:"<<statusCodeV.toInt();
}
}
(4)到这里,这个rpc_json_http类就到这里,实际上就是对QNetwork的简单封装,也是作为我们实现C++以太坊接口的基类,
这个rpc_json_http有4个方法:
- init():初始化
- get():get方法
- post():post方法
- finishedSlot():槽函数,收到返回的数据后会自动调用,然后调用子类实现的纯虚函数
二、 新建一个子类http_for_ethereum,这个类也就是我们最终与节点进行交互的类
(1)在项目中,右键,添加文件,选择C++ Class,基类选择自定义custom,勾上include<QObject>
(2)编辑http_for_ethereum.h,这是rpc_json_http的子类,因此要设置父类rpc_json_http
这里引入了bigInt库,用来处理获取账户余额数字过大的问题
#ifndef HTTP_FOR_ETHEREUM_H
#define HTTP_FOR_ETHEREUM_H#include"rpc_json_http.h"
#include<QList>
#include<QObject>
#include<functional>
#include<QStringList>
#include<bigint-10-2-src/bigInt.h>
class http_for_ethereum : public RPC_JSON_Http
{
Q_OBJECT
public:
http_for_ethereum(QString address);
//~http_for_ethereum();
//获取节点账户
void getAccount(int id,std::function<void(bool,const QByteArray data)>callback);
//获取节点账户回调处理
QStringList getAccount_process(bool success,QByteArray data);
//获取账户余额
void getBalance(int id,QString address,std::function<void(bool,const QByteArray data)>callback);
//获取账户余额回调处理
std::string getBalance_process(bool success,QByteArray data);
private:
//节点地址
QUrl node_address;
//回调函数声明
std::function<void(bool,const QByteArray data)>checkCallback;
protected:
//父类声明的纯虚函数
void requestFinished(QNetworkReply* reply, const QByteArray data, const int statusCode);
};#endif // HTTP_FOR_ETHEREUM_H
#ifndef HTTP_FOR_ETHEREUM_H
#define HTTP_FOR_ETHEREUM_H
#include"rpc_json_http.h"
#include<QList>
#include<QObject>
#include<functional>
#include<QStringList>
#include<bigint-10-2-src/bigInt.h>
class http_for_ethereum : public RPC_JSON_Http
{
Q_OBJECT
public:
http_for_ethereum(QString address);
//~http_for_ethereum();
//获取节点账户
void getAccount(int id,std::function<void(bool,const QByteArray data)>callback);
//获取节点账户回调处理
QStringList getAccount_process(bool success,QByteArray data);
//获取账户余额
void getBalance(int id,QString address,std::function<void(bool,const QByteArray data)>callback);
//获取账户余额回调处理
std::string getBalance_process(bool success,QByteArray data);
private:
//节点地址
QUrl node_address;
//回调函数声明
std::function<void(bool,const QByteArray data)>checkCallback;
protected:
//父类声明的纯虚函数
void requestFinished(QNetworkReply* reply, const QByteArray data, const int statusCode);
};
#endif // HTTP_FOR_ETHEREUM_H
(3)编辑http_for_ethereum.cpp
#include "http_for_ethereum.h"
#include "qdebug.h"
#include<QList>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include<iostream>
http_for_ethereum::http_for_ethereum(QString address)
{
//构造函数
//实例化时传入节点地址
node_address=QUrl(address);
//初始化网路管理接口
RPC_JSON_Http::init();
}
//纯虚函数的具体实现
void http_for_ethereum::requestFinished(QNetworkReply *reply, const QByteArray data, const int statusCode)
{
//判断post\get的状态
if (statusCode == 200) {
//调用回调函数,post、get返回的数据作为参数
//这个回调函数就是对不同接口进行返回数据处理
this->checkCallback(true, data);
return;
}else{
this->checkCallback(false,data);
}
}
//获取节点账户具体实现
void http_for_ethereum::getAccount(int id,std::function<void(bool,const QByteArray data)>callback)
{
//构建JSON格式
QByteArray data_send;
QJsonObject json_in;
QJsonDocument docum;
json_in.insert("jsonrpc","2.0");
json_in.insert("method","eth_accounts");
json_in.insert("id",id);
docum.setObject(json_in);
//JSON转QByteArray类型
data_send=docum.toJson(QJsonDocument::Compact);
//回调函数指定,这里回调函数作为参数传入
this->checkCallback=callback;
//post
RPC_JSON_Http::post(node_address,data_send);
}
//获取节点账户回调处理,这个函数在回调中调用
QStringList http_for_ethereum::getAccount_process(bool success, QByteArray data)
{
//若post\get成功
if(success){
//将post\get返回的数据转换为QString类型
QString temp;
temp.prepend(data);
qDebug()<<temp;
//QString转JSONObject
QJsonDocument jsonDocument = QJsonDocument::fromJson(temp.toLocal8Bit().data());
//判断转换结果
if( jsonDocument.isNull() ){
qDebug()<< "===> please check the string "<< temp.toLocal8Bit().data();
}else{
//利用QStringList类型储存
QStringList result;
QJsonObject jsonObject = jsonDocument.object();
if(jsonObject.value("result").toArray().size()>0){
for(int i=0;i<jsonObject.value("result").toArray().size();i++){
//从返回的JSON结果中提取result,即节点账户
//追加到QStringList
result.append(jsonObject.value("result").toArray()[i].toString());
}
//返回QStringList
return result;
}
}
}
}
//获取账户余额具体实现
void http_for_ethereum::getBalance(int id, QString address, std::function<void (bool, const QByteArray)> callback)
{
//构造json
QByteArray data_send;
QJsonObject json_in;
QJsonDocument docum;
QJsonArray params;
//传入要获取余额的账户
params<<address;
params<<"latest";
json_in.insert("jsonrpc","2.0");
json_in.insert("method","eth_getBalance");
json_in.insert("params",params);
json_in.insert("id",id);
//JSON转qByteArray
docum.setObject(json_in);
data_send=docum.toJson(QJsonDocument::Compact);
qDebug()<<data_send;
//指定回调
this->checkCallback=callback;
//post
RPC_JSON_Http::post(node_address,data_send);
}
//获取账户余额回调处理,这个函数在回调中调用
std::string http_for_ethereum::getBalance_process(bool success, QByteArray data)
{
//若post\get成功
if(success){
QString temp;
temp.prepend(data);
qDebug()<<temp;
//QString转JSONObject
QJsonDocument jsonDocument = QJsonDocument::fromJson(temp.toLocal8Bit().data());
//判断转换结果
if( jsonDocument.isNull() ){
qDebug()<< "===> please check the string "<< temp.toLocal8Bit().data();
}else{
QJsonObject jsonObject = jsonDocument.object();
std::string tmp;
if(jsonObject.value("result").toArray().size()>=0){
qDebug() <<jsonObject.value("result").toString();
//提取返回的数据,返回余额为16进制,例:"0x20c7b98d7a7304bf060"
//这里.mid(2)截取了"0x"后面的部分,并转换为c++的 string类型
tmp=jsonObject.value("result").toString().mid(2).toStdString();
//转换hex到bigINT,这里用到了BigINt库,处理大数字(因为返回的余额单位为wei,数字很大)
BigInt::Vin balance(tmp,16);
std::cout<<balance.toStrDec()<<std::endl;
//返回string类型
return tmp;
}
}
}
}
(4)到这里,获取节点账户、获取账户余额这两个接口就封装好了。接下来看调用
1)主窗体设计如下:
同样的,在按钮中右键,转到槽clicked()
2)编辑mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include"rpc_json_http.h"
#include"http_for_ethereum.h"
namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
//声明接口
http_for_ethereum *eth;
//定义accounts,储存节点账户
QStringList accounts;
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
3)编辑mainwindow.cpp
1、实现获取账户
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include<QStringList>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("Network request test");
eth=new http_for_ethereum("http://127.0.0.1:8545");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
//调用getAount,参数为:(id,回调函数)
eth->getAccount(1,[&](bool success,QByteArray data){
//在回调中调用数据处理函数
accounts=eth->getAccount_process(success,data);
qDebug()<<accounts;
//将账户添加到combox列表中
for(int i=0;i<accounts.size();i++){
ui->comboBox->addItem(accounts[i]);
}
});
}
运行如下:
点击getAccount
2、实现选择对应账户,则账户下面的lineEdit显示账户余额
1)在combox右键,转到槽,选择currentIndexChanged(QString)
2)在槽函数实现如下:
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
//获取余额
eth->getBalance(1,ui->comboBox->currentText(),[&](bool success,QByteArray data){
QString balance=QString::fromStdString(eth->getBalance_process(success,data));
//更新ui
ui->lineEdit2->setText(balance);
});
}
实现效果: