天天看點

thrift實作一個用戶端和服務端c++ 語言的RPC連接配接例子

       thrift是FaceBook開源的一款跨語言的RPC(Remote Procedure Call:遠端過程調用)架構,其具體過程為:RPC采用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機調用程序發送一個有程序參數的調用資訊到服務程序,然後等待應答資訊。在伺服器端,程序保持睡眠狀态直到調用資訊到達為止。當一個調用資訊到達,伺服器獲得程序參數,計算結果,發送答複資訊,然後等待下一個調用資訊,最後,用戶端調用程序接收答複資訊,獲得程序結果,然後調用執行繼續進行【摘自百度百科】。

       好了,不了解的人可能還是一臉懵213,此篇文章的目的也不是在于深究thrift的底層原理,這裡我們隻要學會thrift的用法就好!首先我們還是根據RPC過程描述對thrift的具體操作有個總體認識,如下圖所示:

thrift實作一個用戶端和服務端c++ 語言的RPC連接配接例子

       我們的目的是實作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;

}
           

四、源碼下載下傳

點選下載下傳