以太坊學習(8)編寫C++程式與以太坊節點進行互動【2】
編寫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);
});
}
實現效果: