thrift实现一个客户端和服务端c++ 语言的RPC连接例子

       thrift是FaceBook开源的一款跨语言的RPC(Remote Procedure Call:远程过程调用)框架,其具体过程为:RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行【摘自百度百科】。

       好了,不了解的人可能还是一脸懵213,此篇文章的目的也不是在于深究thrift的底层原理,这里我们只要学会thrift的用法就好!首先我们还是根据RPC过程描述对thrift的具体操作有个总体认识,如下图所示:

       我们的目的是实现Client客户端和Server服务端的通信传输,有了thrift框架的好处就是,客户机调用进程发送一个有进程参数的调用信息到服务进程而无需关注底层的网络传输,这个调用信息通过thrift框架定义的一个.thrift文件定义,.thrift文件定义一个简单的中的数据类型服务接口,以作为输入文件,可以通过特定的命令使用编译器生成代码用来方便地生成RPC客户端和服务器通信的特定语言的代码,以下将从.thrift文件构造,Client和Server代码生成和Client和Server代码编写三个部分构成。

一、thrift文件

1.thrift文件构成

       前面已经提到,thrift文件定义了数据类型和服务接口,如下面的一个thrift文件:

//文件名称:uploadImage.thrift
/*
定义数据类型,其中每个结构会在*_types.h中单独生成一个类
*/
struct ImageFile
{
1: list<byte> data
}

/*
定义服务接口:
同样每一个服务也会单独生成一个类,如service ××× 表示生成的服务器端处理×××.cpp,其中包含的函数即为服务器和客户端交互的函数
*/
service serviceName{  
  string uploadImage(1: ImageFile img, 2: string indexName, 3: string imgExt),  
}
  • 数据类型:通常封装在一个struct的结构体中,实际上在thrift转换成客户端和服务端代码时,相应的struct会生成相应的的class类,所以struct结构体的作用即为封装需要传输的属性变量。
  • 服务接口:thrift文件定义了一个service标识的函数接口,服务由一组命名函数组成,每个函数具有一个参数列表和一个返回类型。

2.thrift常用的数据类型 点击进入官方文档

     Thrift类型系统包括预定义基本类型,用户自定义结构体,容器类型,异常和服务定义,这里简单介绍几种:

(1) 基本类型

bool: A boolean value (true or false)布尔类型
byte: An 8-bit signed integer 8位带符号整数
i16: A 16-bit signed integer   16位带符号整数
i32: A 32-bit signed integer    32位带符号整数
i64: A 64-bit signed integer    64位带符号整数
double: A 64-bit floating point number   64位浮点数
string: A text string encoded using UTF-8 encoding UTF-8 string类型

注意,thrift不支持无符号整型,因为很多目标语言不存在无符号整型(如java)。

(2) 容器类型

     Thrift容器与类型密切相关,它与当前流行编程语言提供的容器类型相对应,Thrift提供了3种容器类型:

list<T>: 有序元素集合, 根据语言不同转换为相对应的数据结构,如对于c++程序转化为vector,Java程序转化为ArrayList等.
set<T>: 无序的元素集合. Translates to an STL set, Java HashSet, 
map<KEY,VALUE>: 键值对映射. Translates to an STL map, Java HashMap, 

容器中的元素类型可以是除了service意外的任何合法thrift类型(包括结构体和异常)

 (3)  结构体和异常

      Thrift结构体定义了一个共同的对象——它们基本上等同于面向对象语言中的类,但是没有继承。结构有一组强类型字段,每个字段都有唯一的名称标识符。字段中可能有各种注释(数字字段ID、可选默认值等)。异常在语法和功能上类似于结构体,只不过异常使用关键字exception而不是struct关键字声明。但它在语义上不同于结构体—当定义一个RPC服务时,开发者可能需要声明一个远程方法抛出一个异常。

(4)  服务

      服务的定义方法在语法上等同于面向对象语言中定义接口,Thrift编译器会在client和server中产生实现这些函数的接口。

二、Client和Server代码生成

1.生成指令

thrift编译器用于将thrift文件生成为源代码,由不同的客户端库和编写的服务器使用。从thrift文件运行生成源指令:

thrift  --gen  <language>  <Thrift  filename>

要从Thrift文件和其包含的所有其他Thrift文件中递归生成源代码,请运行:

thrift  -r  --gen  <language>  <Thrift  filename>

thrift -r --gen cpp  -o  ./   *.thrift   //./表示文件生成的位置,cpp表示c++代码, *.thrift表示相应的thrift文件

2.生成文件

(1) 生成服务端代码:执行完上面命令会在文件生成目录下生成gen-cpp文件夹,其中生成相应的:

* _constants.h    * _constants.cpp 
* _types.h        * _types.cpp
serviceName.h     serviceName.cpp
*.skeleton.cpp

      *.skeleton.cpp就是c++服务器main函数入口,其中 serviceNameHandler 类虚继承了 serviceNameIf类并在其中实现了相关处理函数,值得注意的时如果函数有返回值,则函数参数列表的第一个参数为函数的输出值,如定义的函数void uploadImage(std::string& url, const ImageFile& img, const std::string& indexName, const std::string& imgExt) {};红色string url为返回值。

(2) 生成客户端代码:拷贝服务端中生成的gen文件出了*.skeleton.cpp的其他所有文件,并单独建立一个cpp文件写的客户端main函数的cpp生成client客户端对象,直接调用处理函数即可完成客户端的请求提交,服务端接收客户端的输入请求继续执行。

三、Client和Server代码编写

        此服务端客户端的功能是从客户端读取一张图片的二进制流上传到服务器并存储在服务器的指定目录,生成服务器的url访问地址。

1、Server代码编写

        服务器端的功能是存储客户端的二进制流在服务器的指定目录,并生成服务器的url访问地址

// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.

#include "ImageUploadService.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <iostream>
#include <fstream>
#include<sys/stat.h>
#include<sys/types.h>
#include <iostream>
#include <string>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;
using namespace std;

static string  getCurrentTimeStr(string indexName)
{
    time_t t = time(NULL);
    char ch[180] = {0};
    strftime(ch, sizeof(ch) - 1, "/%Y/%m/%d/%H/%M/%S", localtime(&t));     //年-月-日 时-分-秒
    return "/home/ubuntu/Desktop/imageupload/" + indexName + ch;//修改为自己要存的位置
}

//循环创建目录
void mkdirs(char *muldir)
{
    int i,len;
    char str[512];
    strncpy(str, muldir, 512);
    len=strlen(str);
    for( i=0; i<len; i++ )
    {
        if( str[i]=='/' )
        {
            str[i] = '\0';
            if( access(str,0)!=0 )
            {
                std::cout << "mkdir" << str << std::endl;
                int flag = mkdir( str, 0777 );
                if(flag == -1)
                {
                    printf("mkdir failed..\n");
                }
            }
            str[i]='/';
        }
    }
    if( len>0 && access(str,0)!=0 )
    {
        std::cout << "mkdir" << str << std::endl;
        mkdir( str, 0777 );
    }

    return;
}

//服务响应函数
class ImageUploadServiceHandler : virtual public ImageUploadServiceIf {
 public:
  ImageUploadServiceHandler() {
    // Your initialization goes here
  }

  void uploadImage(std::string& url, const ImageFile& img, const std::string& indexName, const std::string& imgExt) {
      // Your implementation goes here
          string baseDir = getCurrentTimeStr(indexName);
          std::cout<<baseDir<<endl;

          mkdirs((char *)baseDir.c_str());
          std::stringstream ss;
          ss << baseDir << "/" << (rand() % 1000000) << "_up." << imgExt;

          url = ss.str();

          FILE *fid = fopen(url.c_str(), "wb");
          if(fid == NULL)
          {
              std::cout << "Open Image Error !" << std::endl;
              url = "";
              return;
          }
          for(int i = 0 ; i < img.data.size(); i ++)
          {
              char tmp = img.data[i];
              fwrite(&tmp, sizeof(tmp), 1, fid);
          }

          fclose(fid);

          printf("uploadImage\n");

          url = "http://192.168.110.1" + url.substr();//获得服务器中图片的url地址
          std::cout << url << std::endl;
  }

};

int main(int argc, char **argv) {
  int port = 9556;//服务端口号
  boost::shared_ptr<ImageUploadServiceHandler> handler(new ImageUploadServiceHandler());
  boost::shared_ptr<TProcessor> processor(new ImageUploadServiceProcessor(handler));
  boost::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
  boost::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
  boost::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
  std::cout << "Start Image upload Service @ 9556" << std::endl;
  TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
  server.serve();
  return 0;
}

2、Client代码编写

        客户端是实现的功能是用二进制流读取本地一张图像存储在定义的struct ImageFile中的list<byte>  data中,然后通过thrift上传到服务端:值得注意的是对于客户端的函数参数的输入应保持和服务端的参数列表一致,如参数没有确定的输入值,输入为空即可;服务端和客户端的端口号要保持一致


#include "ImageUploadService.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/protocol/TBinaryProtocol.h>

#include <iostream>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using namespace cv;
using namespace std;

using boost::shared_ptr;

int getImageInfo(string imagePath, ImageFile& img, string& imgExt)
{
    char imgExtIndex = 46;//.对应的ASCII码值
    int index = imagePath.find_last_of(imgExtIndex);
    imgExt = imagePath.substr(index +1);//获取图片后缀名即图片类型,此方法不准确,应该读取图片前几个字节进行判断

    //cout<<endl<<imgExt<<endl;

    FILE *infile;
    infile = fopen(imagePath.c_str(),"rb");//二进制文件读取突图片数据
    if(!infile)
    {
        printf("open failed..\n");
        return -1;

    }

    int ch = fgetc(infile);
    while(EOF != ch)
    {
        img.data.push_back(ch);
        ch = fgetc(infile);
    }

    fclose(infile);

    return 0 ;
}

int main()
{

    boost::shared_ptr<TSocket> socket(new TSocket("localhost",9556));
    boost::shared_ptr<TTransport> transportClient(new TBufferedTransport(socket));
    boost::shared_ptr<TProtocol> protocolClient(new TBinaryProtocol(transportClient));

    ImageUploadServiceClient client(protocolClient);
    transportClient->open();
    /*
     *  void uploadImage(string& url, const ImageFile& img, string& indexName, string& imgExt)
     * 读取本地文件二进制内容上传到服务器,服务器返回http url,
     * ImageFile& img   图像数据
     *string urlPath    url路径
     * string& indexName  indexName 图像编号
     * string& imgExt   imgExt 图像扩展名
     *
     * */
    //参数列表
    string imagePath = "/home/ubuntu/FaceRecognition/demo.png";
    ImageFile img;
    string indexName = "123";
    string imgExt;
    getImageInfo(imagePath, img, imgExt);

    string urlPath;

    client.uploadImage(urlPath,img,indexName,imgExt);//设置空的urlPath保持和服务端参数一致

    transportClient->close();

    return 0;

}

四、源码下载

点击下载

  • 1
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值