• 利用thrift rpc进行C++与Go的通信


    一:什么是rpc

     rpc通俗来理解就是远程调用函数,相对于本地调用来说,只需要在主调函数中调用被掉函数即可,代码如下:

     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,这本书基本上是系统层网络编程人员必读书籍,但是读过这本书的人都知道,网络的细节很多,也较复杂,如果每个项目都需要亲自写这些底层实现,那无疑大大延缓了项目开发进度,而且很多上层开发人员不懂得这些细节。解决办法就是造轮子,以库的形式封装这些底层细节,屏蔽掉底层,让其他开发人员可以简单直接使用。

    二:thrift的安装

    thrift 就是前面提到的一种rpc库。它是跨语言的,并且是C/S模式。thrift 的安装与使用过程在其官网是有说明的:https://thrift.apache.org/

    本文安装过程基于官网教程,安装环境为:Ubuntu 16.04 x64,下面是具体过程:

    1. 首先需要下载 thrift 工具,下载地址为:https://thrift.apache.org/download

    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 编译环境请自行参考相关安装教程。

     接下来开始配置 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
    ", 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

     运行程序测试一下结果,如下:

  • 相关阅读:
    cocos2dx-lua牧场小游戏(一)
    【足迹C++primer】40、动态数组
    C++ 函数
    TCP closing a connection
    POJ 1410 Intersection(计算几何)
    使用OGG&quot;Loading data from file to Replicat&quot;的方法应该注意的问题:replicat进程是前台进程
    PHP 文件操作类(创建文件并写入) 生成日志
    Android横屏竖屏设置
    Jetty 9.3庆祝20周年生日快乐,并添加HTTP/2支持
    【转】有效修改max open files/ulimit -n
  • 原文地址:https://www.cnblogs.com/pluse/p/7761365.html
Copyright © 2020-2023  润新知