Thrift安裝與伺服器、客戶端的編寫執行演示(windows版本)

韓師學子--胖佳發表於2019-02-25

           Thrift安裝與伺服器、客戶端的編寫執行演示(windows版本)



第一部分: thrift的安裝使用
1.1 簡介
         thrift是一個軟體框架,用來進行可擴充套件且跨語言的服務的開發。它結合了功能強大的軟體堆疊和程式碼生成引擎,以構建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些程式語言間無縫結合的、高效的服務。

1.2 下載

https://download.csdn.net/download/xiao__jia__jia/10974167

 在windows的系統的高階環境-》環境變數的Path配置放置的目錄

執行cmd,檢視是否成功
 

​​​​​​​thrift -version

第二部分:Thrift語法

 2.1 型別

  Thrift型別系統包括預定義基本型別,使用者自定義結構體,容器型別,異常和服務定義。

 2.1.1 基本型別

  • bool: 布林值 (true or false), one byte

  • byte: 有符號位元組

  • i16: 16位有符號整型

  • i32: 32位有符號整型

  • i64: 64位有符號整型

  • double: 64位浮點型

  • string: Encoding agnostic text or binary string

Note that: Thrift不支援無符號整型,因為Thrift目標語言沒有無符號整型,無法轉換。

 2.1.2 容器(Containers)

  Thrift容器與流行程式語言的容器型別相對應,採用Java泛型風格。它有3種可用容器型別:

  • list<t1>: 元素型別為t1的有序表,容許元素重複。(有序表ordered list不知道如何理解?排序的?c++的vector不排序)

  • set<t1>:元素型別為t1的無序表,不容許元素重複。

  • map<t1,t2>: 鍵型別為t1,值型別為t2的kv對,鍵不容許重複。

  容器中元素型別可以是除了service外的任何合法Thrift型別(包括結構體和異常)。

 2.1.3 結構體和異常(Structs and Exceptions)

  Thrift結構體在概念上類似於(similar to)C語言結構體型別--將相關屬性封裝在一起的簡便方式。Thrift結構體將會被轉換成面嚮物件語言的類。

  異常在語法和功能上類似於(equivalent to)結構體,差別是異常使用關鍵字exception而不是struct宣告。但它在語義上不同於結構體:當定義一個RPC服務時,開發者可能需要宣告一個遠端方法丟擲一個異常。

  結構體和異常的宣告將在下一節介紹。

 2.1.4 服務(Services)

  服務的定義方法在語義(semantically)上等同於面嚮物件語言中的介面。Thrift編譯器會產生執行這些介面的client和server stub。具體參見下一節。

 2.2 型別定義(Typedef)

  Thrift支援C/C++型別定義。

   typedef i32 MyInteger // a
   typedef T ReT // b

  說明:a.  末尾沒有逗號。b.   struct也可以使用typedef。

 2.3 列舉(Enums)

  很多語言都有列舉,意義都一樣。比如,當定義一個訊息型別時,它只能是預定義的值列表中的一個,可以用列舉實現。
 

enum TweetType {
    TWEET,       // (1)
  RETWEET = 2, // (2)
    DM = 0xa,    // (3)
  REPLY        //  (4)
}                

struct Tweet {
    1: required i32 userId;
    2: required string userName;
    3: required string text;
    4: optional Location loc;
    5: optional TweetType tweetType = TweetType.TWEET; // (5)
    16: optional string language = "english"
}

 

說明:

  (1).  編譯器預設從0開始賦值

  (2).  可以賦予某個常量某個整數

  (3).  允許常量是十六進位制整數

  (4).  末尾沒有分號

  (5).  給常量賦預設值時,使用常量的全稱

  注意,不同於protocal buffer,thrift不支援列舉類巢狀,列舉常量必須是32位的正整數

 2.4 註釋(Comment)

  Thrift支援shell風格, C多行風格和Java/C++單行風格。
 

/*
 * This is a multi-line comment.
 * Just like in C.
 */

// C++/Java style single-line comments work just as well.

 

2.5 名字空間(Namespace)

  Thrift中的名稱空間類似於C++中的namespace和java中的package,它們提供了一種組織(隔離)程式碼的簡便方式。名字空間也可以用於解決型別定義中的名字衝突。

  由於每種語言均有自己的名稱空間定義方式(如python中有module), thrift允許開發者針對特定語言定義namespace:



namespace cpp com.example.project  // (1)
namespace java com.example.project // (2)
namespace php com.example.project  

  (1). 轉化成namespace com { namespace example { namespace project {

  (2).  轉換成package com.example.project

 2.6 Includes

  便於管理、重用和提高模組性/組織性,我們常常分割Thrift定義在不同的檔案中。包含檔案搜尋方式與c++一樣。Thrift允許檔案包含其它thrift檔案,使用者需要使用thrift檔名作為字首訪問被包含的物件,如:



include "tweet.thrift"           // (1)
...
struct TweetSearchResult {
    1: tweet.Tweet tweet; // (2)
}

 

說明:

  (1).  thrift檔名要用雙引號包含,末尾沒有逗號或者分號

  (2).  注意tweet字首

 2.7 常量(Constant)

  Thrift允許定義跨語言使用的常量,複雜的型別和結構體可使用JSON形式表示。



const i32 INT_CONST = 1234;    // (1)

 說明:

  (1) 分號可有可無。支援16進位制。

 2.8 結構體定義(Defining Struct)

  struct是Thrift IDL中的基本組成塊,由域組成,每個域有唯一整數識別符號,型別,名字和可選的預設引數組成。如定義一個類似於Twitter服務:

struct Tweet {
    1: required i32 userId;                  // (1)
    2: required string userName;             // (2)
    3: required string text;
    4: optional Location loc;                // (3)
    16: optional string language = "english" // (4)
}

struct Location {                            // (5)
    1: required double latitude;
    2: required double longitude;
}

(1) 每個域有一個唯一的正整數識別符號;

 (2) 每個域可標識為required或optional;

 (3) 結構體可以包含其它結構體

 (4) 域可有預設值,與required或optional無關。

 (5) Thrift檔案可以定義多個結構體,並在同一檔案中引用,也可加入檔案限定詞在其它Thrift檔案中引用。

  如上所見,訊息定義中的每個域都有一個唯一數字標籤,這些數字標籤在傳輸時用來確定域,一旦使用訊息型別,標籤不可改變。(隨著專案的進展,可以要變更Thrift檔案,最好不要改變原有的數字標籤)

  規範的struct定義中的每個域均會使用required或者 optional關鍵字進行標識。如果required標識的域沒有賦值,Thrift將給予提示;如果optional標識的域沒有賦值,該域將不會被 序列化傳輸;如果某個optional標識域有預設值而使用者沒有重新賦值,則該域的值一直為預設值;如果某個optional標識域有預設值或者使用者已經重新賦值,而不設定它的__isset為true,也不會被序列化傳輸。(不被序列化傳輸的後果是什麼?為空為零?還是預設值,下次試試)

  與services不同,結構體不支援繼承。

2.9 服務定義(Defining Services)

  在流行的序列化/反序列化框架(如protocal buffer)中,Thrift是少有的提供多語言間RPC服務的框架。這是Thrift的一大特色。

  Thrift編譯器會根據選擇的目標語言為server產生服務介面程式碼,為client產生stubs。
 

service Twitter {
    // A method definition looks like C code. It has a return type, arguments,
    // and optionally a list of exceptions that it may throw. Note that argument
    // lists and exception list are specified using the exact same syntax as
    // field lists in structs.
    void ping(),                                    // (1)
    bool postTweet(1:Tweet tweet);                  // (2)
    TweetSearchResult searchTweets(1:string query); // (3)

    // The 'oneway' modifier indicates that the client only makes a request and
    // does not wait for any response at all. Oneway methods MUST be void.
    oneway void zip()                               // (4)
}

(1) 有點亂,介面支援以逗號和分號結束;

 (2) 引數可以是基本型別和結構體;(引數是cosnt的,轉換為c++語言是const&)

 (3) 返回值同引數一樣;

 (4) 返回值是void,注意oneway;

Note that:引數列表的定義與結構體一樣。服務支援繼承。 

 

第三部分:伺服器和客戶端的編寫與執行(java實現)

3.1 編寫thrift檔案

TestThrift.thrift

namespace java com.xiaomi.wanjia.thrift 
struct Blog { 
1:string topic 
2:binary content 
3:i64 createTime 
4:string id 
5:string ipAddress 
6:map<string,string> props 
} 
service ThriftCase { 
i32 testCase1(1:i32 num1, 2:i32 num2,3:string num3) 
list<string> testCase2(1:map<string,string> num1) 
void testCase3() 
void testCase4(1:list<Blog> blog)
}

3.2 生成程式碼 (以java語言為例),在放置thrift.exe的目錄上輸入cmd,進入終端,輸入下面的命令,在目中下生成相應的類

thrift -gen java TestThrift.thrift



3.3 建立工程

在idea下新建一個java的 maven project,將生成的程式碼整個資料夾(com/xiaomi/winwill/service/*)拷貝到工程的src目錄下。

pom.xml

 <dependency>
            <groupId>thrift</groupId>
            <artifactId>libthrift</artifactId>
            <version>0.5.0</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.7</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.5</version>
            <scope>test</scope>
        </dependency>

3.4 實現介面

TestThrift.thrift檔案中定義類一個service(ThriftCase),生成java程式碼之後將會變成一個介面,這些介面的作用是實現跨平臺通訊,但是真正的業務邏輯並未實現,所以,要做什麼?怎麼做?這些詳細的設計應該由我們自己來實現。在工程的com.xiaomi.winwill.service包中建立一個類:Business.java,該類實現ThriftCase的Iface介面:

Business

package com.xiaomi.wanjia.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.xiaomi.wanjia.thrift.Blog;
import org.apache.thrift.TException;
import com.xiaomi.wanjia.thrift.ThriftCase;
public class Business implements ThriftCase.Iface {
    private Set<String> keySet;
    @Override
    public int testCase1(int num1, int num2, String num3) throws TException {
        System.out.println("#1: ");
        if(num3.equals("+")){
            System.out.println(num1+num3+num2+"="+(num1+num2));
            return num1 + num2;
        } else if(num3.equals("-")){
            System.out.println(num1+num3+num2+"="+(num1-num2));
            return num1 - num2;
        } else if(num3.equals("*")){
            System.out.println(num1+num3+num2+"="+(num1*num2));
            return num1 * num2;
        } else if(num3.equals("/")){
            if(num2 == 0){ System.out.println("error, can not divided by zero!");
            return 0;
            } else{
                System.out.println(num1+num3+num2+"="+(num1/num2));
                return num1 / num2;
            }
        }
        return 0;
    }
    @Override
    public List<String> testCase2(Map<String, String> num1) throws TException {
        System.out.println("#2: ");
        List<String> res = new ArrayList<String>();
        keySet = num1.keySet();
        for(String str:keySet){
            res.add(str);
        }
        System.out.println(res);
        return res;
    }
    @Override
    public void testCase3() throws TException {
        System.out.println("#3: ");
        System.out.println("Output nothing!");
    }
    @Override public void testCase4(List<Blog> blog) throws TException {
        System.out.println("#4: ");
        for (Blog blog2 : blog) {
            System.out.println("id: "+blog2.getId());
            System.out.println("ipAddress: "+blog2.getIpAddress());
            System.out.println("createTime: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(blog2.getCreateTime()));
            System.out.println("topic: "+blog2.getTopic());
        }
    }
}
Client

package com.xiaomi.wanjia.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import com.xiaomi.wanjia.thrift.Blog;
import com.xiaomi.wanjia.thrift.ThriftCase;

public class Client {

    public static void main(String[] args) throws TException {
        Map<String, String> map = new HashMap<String, String>();
        List<Blog> blogs = new ArrayList<Blog>();
        TTransport transport = new TSocket("localhost", 7912);
        TProtocol protocol = new TBinaryProtocol(transport);
        ThriftCase.Client client = new ThriftCase.Client(protocol);
        Blog blog = new Blog(); blog.createTime = System.currentTimeMillis();
        blog.id = "SCUQIFUGUANG";
        blog.ipAddress = "127.0.0.1";
        blog.topic = "this is blog topic"; blogs.add(blog);
        transport.open();
        map.put("MyBlog", "http://blog.163.com/scuqifuguang@126/");
        System.out.println("Client calling");
        client.testCase1(10, 21, "+");
        client.testCase2(map);
        client.testCase3();
        client.testCase4(blogs);
        transport.close();
    }
}
Server
package com.xiaomi.wanjia.service;

import java.io.IOException;
import java.net.ServerSocket;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import com.xiaomi.wanjia.thrift.ThriftCase;
public class Server {
    public static void main(String[] args) throws TTransportException, IOException {
        ServerSocket socket = new ServerSocket(7912);
        TServerSocket serverTransport = new TServerSocket(socket);
        ThriftCase.Processor processor = new ThriftCase.Processor(new Business());
        TServer server = new TSimpleServer(processor, serverTransport);
        System.out.println("Running server...");
        server.serve();
    }
}

最終的專案結構


 

3.8 執行

首先執行Server:

然後執行Client:

 最後再看看Server的Console視窗:

從執行結果可以看出,Client端已經成功地與伺服器通訊,並且遠端呼叫了伺服器的四個TestCase方法。

 

相關文章