zoukankan      html  css  js  c++  java
  • 利用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

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

  • 相关阅读:
    Linux下sed,awk,grep,cut,find学习笔记
    Python文件处理(1)
    KMP详解
    Java引用详解
    解决安卓中页脚被输入法顶起的问题
    解决swfupload上传控件文件名中文乱码问题 三种方法 flash及最新版本11.8.800.168
    null id in entry (don't flush the Session after an exception occurs)
    HQL中的Like查询需要注意的地方
    spring mvc controller间跳转 重定向 传参
    node to traverse cannot be null!
  • 原文地址:https://www.cnblogs.com/pluse/p/7761365.html
Copyright © 2011-2022 走看看