1. 程式人生 > >以太坊學習(8)編寫C++程式與以太坊節點進行互動【2】

以太坊學習(8)編寫C++程式與以太坊節點進行互動【2】

編寫C++程式與節點進行互動


【1】簡單版本,需對http request有一定了解,以及QT的基本操作 

【2】進行類的封裝,個人水平有限,如有紕漏,請下方留言

 


測試環境:


編寫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_OBJECT

public:
    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);
    });
}

實現效果: