利用thrift rpc進行C++與Go的通訊

一:什麼是rpcios

 rpc通俗來理解就是遠程調用函數,相對於本地調用來講,只須要在主調函數中調用被掉函數便可,代碼以下:c++

 1 void fun(int i)
 2 {
 3     cout << "function call" << endl;
 4     cout << "args: " << i << endl;
 5     return;
 6 }
 7 
 8 int main()
 9 {
10     fun(5);
11     return 0;
12 }

在上面的代碼中,main( )函數在第10行調用了本地函數fun( ),本地調用就是這麼簡單。若是要遠程調用一個函數,那麼就須要進行網絡通訊,網絡通訊就涉及到了網絡編程,網絡編程中有一本著名的經典書籍:《UNIX網絡編程》,簡稱UNP,這本書基本上是系統層網絡編程人員必讀書籍,可是讀過這本書的人都知道,網絡的細節不少,也較複雜,若是每一個項目都須要親自寫這些底層實現,那無疑大大延緩了項目開發進度,並且不少上層開發人員不懂得這些細節。解決辦法就是造輪子,以庫的形式封裝這些底層細節,屏蔽掉底層,讓其餘開發人員能夠簡單直接使用。git

 

二:thrift的安裝golang

thrift 就是前面提到的一種rpc庫。它是跨語言的,而且是C/S模式。thrift 的安裝與使用過程在其官網是有說明的:https://thrift.apache.org/apache

本文安裝過程基於官網教程,安裝環境爲:Ubuntu 16.04 x64,下面是具體過程:編程

1. 首先須要下載 thrift 工具,下載地址爲:https://thrift.apache.org/downloadvim

2. 而後須要編譯安裝該 thrift 工具,安裝教程在此處有詳細說明,本文是依據「Debian/Ubuntu install」的安裝說明進行的,步驟以下:網絡

首先安裝 thrift 編譯須要的基本依賴組件。架構

sudo apt-get install automake bison flex git libboost-all-dev libevent-dev libssl-dev libtool make pkg-config build-essential g++

因爲咱們是利用 thrift 進行 C++ 與 Go 之間的通訊,所以還要安裝 Go 編譯環境。若是沒有安裝 Go 編譯環境,編譯thrift工具時會看到對 Go 的支持爲 NO。具體 Go 編譯環境請自行參考相關安裝教程。socket

 接下來開始配置 thrift 工具:

這裏下載的是0.10.0版本,測試發現0.11.0版本有一些問題,缺乏某些動態庫,另外Python庫的編譯在Ubuntu上也存在頭文件路徑錯誤,而CentOS則沒有這個問題。因此建議使用0.10.0版本。

wget "https://mirrors.tuna.tsinghua.edu.cn/apache/thrift/0.10.0/thrift-0.10.0.tar.gz"
tar zxvf thrift-0.10.0.tar.gz
cd thrift-0.10.0
./configure

若是依賴安裝穩當且配置無誤,則會看到下面輸出:

這裏會列出 thrift 工具構建了哪一種語言支持的庫,其後還有該語言庫相關的詳細說明,以下:

接下來開始編譯和安裝:

make && make install //若是內存夠大、CPU夠多,可以使用 make -j && make install

因爲 configure 配置時沒有指定安裝的路徑,所以這裏的 make install 安裝到系統默認路徑"/usr/local/bin/"下,須要root權限。

須要注意的是,編譯 thrift 時,thrift 可能會經過網絡利用"go get"工具來安裝一些第三方庫,如"golang.org/x/net/context",該庫因爲一些"網絡問題"會安裝失敗,進而致使編譯失敗,本文利用了命令行的"http_proxy"環境變量設置了相關代理,我的用戶請自行準備好代理工具以解決該問題。

安裝完成後,thrift工具就可使用了,以下:

 

3. 編寫 thrift 文件並轉換爲代碼

在安裝完成 thrift 工具以後,咱們須要用 thrift 定義的語法來編寫 thrift 文件,再使用 thrift 工具來轉換 thrift 文件以生成代碼,最後將生成的代碼集成到項目中使用。架構圖以下:

 

下面是詳細步驟:

cd
mkdir thrift
cd thrift/
vim timeRPC.thrift
thrift --gen cpp timeRPC.thrift 

以上命令是在個人機器上我的home目錄下建立了一個叫作thrift的文件夾並在該文件夾下編寫了一個叫作 "timeRPC.thrift" 的 thrift 文件。而後使用 thrift 工具轉換該文件生成了 C++ 的服務端代碼。

這裏的例子很簡單,模仿UNIX的daytime服務,提供「時間問答」,容許客戶端向服務端詢問如今是幾點,服務端會把如今的Unix時間回送給客戶端。timeRPC.thrift文件的內容以下:

service timeServe {
    i32 getCurrtentTime()
}

這裏的service至關於C++的類、Go的包,而getCurrentTime( )是對應的成員/包函數,函數體內是具體的功能實現。

 

若是上面的轉換操做成功,會在當前目錄下生成一個叫作 "gen-cpp" 的文件夾,裏面包含了須要的代碼,以下圖:

總共生成了7個文件,其中最後一個文件是咱們的"main.cpp"文件,咱們須要對它進行適當修改以使用。

先重命名一下,以下圖:

 

三:利用thrift進行C++與Go通訊

在通過前面步驟以後,咱們已經獲得了C++版本的代碼,這些代碼須要分別引入服務端和客戶端以進行通訊使用。

在前面咱們已經獲得了C++服務端的代碼,服務端代碼的源文件被重名成了"main.cpp",代碼內容以下:

 1 // This autogenerated skeleton file illustrates how to build a server.
 2 // You should copy it to another filename to avoid overwriting it.
 3 
 4 #include "timeServe.h"
 5 #include <thrift/protocol/TBinaryProtocol.h>
 6 #include <thrift/server/TSimpleServer.h>
 7 #include <thrift/transport/TServerSocket.h>
 8 #include <thrift/transport/TBufferTransports.h>
 9 
10 using namespace ::apache::thrift;
11 using namespace ::apache::thrift::protocol;
12 using namespace ::apache::thrift::transport;
13 using namespace ::apache::thrift::server;
14 
15 using boost::shared_ptr;
16 
17 
18 class timeServeHandler : virtual public timeServeIf
19 {
20 public:
21     timeServeHandler()
22     {
23         // Your initialization goes here
24     }
25 
26     int32_t getCurrtentTime()
27     {
28         // Your implementation goes here
29         auto t = time(nullptr);
30         printf("getCurrtentTime: %ld\n", t);
31         return t;
32     }
33 
34 };
35 
36 int main(int argc, char **argv)
37 {
38     int port = 9090;
39     shared_ptr<timeServeHandler> handler(new timeServeHandler());
40     shared_ptr<TProcessor> processor(new timeServeProcessor(handler));
41     shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
42     shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
43     shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
44 
45     TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
46     server.serve();
47     return 0;
48

這裏須要修改的是第28-31行,這個函數具體實現就是咱們須要的功能,結果從函數返回值得到。這裏是調用C標準庫的time( )函數來得到一個Unix時間戳,在服務端打印該時間,而且將這個時間戳返回給客戶端。

對它進行編譯:

g++ -std=c++11 -o cpp-server timeRPC_constants.cpp timeRPC_types.cpp timeServe.cpp main.cpp -lthrift

這裏代碼使用了C++11的auto推斷功能,所以使用-std=c++11選項,另外還須要連接thrift庫。結果以下:

C++客戶端代碼以下:

// system
#include <iostream>

// lib
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <boost/shared_ptr.hpp>
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using boost::shared_ptr;

// project
#include "gen-cpp/timeServe.h"


int main() {
    // get socket
    auto p = new TSocket("127.0.0.1", 9090);
    shared_ptr<TTransport> socket(p);

    // choose transport:Socket/Http/File
    auto q = new TBufferedTransport(socket);
    shared_ptr<TTransport> transport(q);

    // serialize:Binary/Compact/JSON
    auto r = new TBinaryProtocol(transport);
    shared_ptr<TProtocol>  protocol(r);

    timeServeClient client(protocol);

    // open connect
    transport->open();
    auto timeNow = client.getCurrtentTime();
    std::cout << timeNow << std::endl;
    transport->close();

    return 0;
}

對它進行編譯:

g++ -std=c++11 -o cpp-client client.cpp gen-cpp/timeServe.cpp  -lthrift

這裏在gen-cpp的上一層目錄中建立了client.cpp文件,代碼中使用了C++11的auto推斷功能和智能指針,所以使用-std=c++11選項,另外一樣須要連接thrift庫。結果以下:

到這裏爲止,已經完成了經過thrift進行C++客戶端和服務端之間的通訊,運行程序測試一下結果,以下:

從上面的截圖能夠看到,運行服務端程序以後,經過客戶端程序去請求,能夠獲得服務端返回的時間戳,而且服務端同時也進行了打印。

 

接下來使用golang語言來實現客戶端。相似C++須要連接thrift庫同樣,golang也須要對應的thrift的package來提供支持。這個包叫作"git.apache.org/thrift.git/lib/go/thrift",能夠經過下面的命令來安裝

go get git.apache.org/thrift.git/lib/go/thrift

須要注意的是,這個地址被牆了,所以須要代理訪問。固然,最終安裝的thrift包可能會編譯出錯。報相似這樣的錯誤:"not enough arguments in call to oprot.Flush",之因此報這個錯是apache更新了thrift的接口,不兼容致使。所以,咱們也能夠不安裝上面的包,而是使用最前面編譯獲得的golang包。以下:

注意拷貝thrift包的時候,要正確建立package的目錄,由於後面生成的服務代碼中導入的包路徑對此有要求。

除了golang的包須要拷貝到開發目錄下,咱們也須要生成特定的服務代碼,以下:

而後再將生成的服務代碼拷貝到go開發環境目錄下,而後建立一個go類型的客戶端,以下:

client.go客戶端的代碼以下:

package main

import (
	"fmt"
	"os"
	"timerpc"

	"git.apache.org/thrift.git/lib/go/thrift"
)

func main() {
	// get socket
	socket, err := thrift.NewTSocket("127.0.0.1:9090")

	// choose transport
	transport := thrift.NewTBufferedTransport(socket, 8192)

	// serialize
	protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()

	client := timerpc.NewTimeServeClientFactory(transport, protocolFactory)

	// open connect
	transport.Open()
	defer socket.Close()

	timeResult, err := client.GetCurrtentTime()
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(2)
	}
	fmt.Println(timeResult)

}

對它進行編譯:

go build client.go

 運行程序測試一下結果,以下:

相關文章
相關標籤/搜索