.net core下使用Thrift

     因網站組(.net)與遊戲服務端(c++)原來使用REST API通信效率稍顯低下,準備下期重構時改用rpc方式,經比較Thrift和gRPC二者的優劣(參照網上的對比結果),最終決定使用Thrift。html

     首先下載Thrift代碼生成器,編寫根據Thrift的語法規範(可參看https://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html)編寫腳本文件OrderService.thrift,以下:c++

namespace csharp Qka.Contract

service OrderService{

    InvokeResult Create(1:Order order)

    Order Get(1:i32 orderId)

    list<Order> GetListByUserId(1:i32 userId,2:bool isPaid)

    InvokeResult Delete(1:i32 orderId)
}

enum ResponseCode {  
  SUCCESS = 0,  
  FAILED = 1,  
}

struct Order {
    1: required i32 OrderId;
    2: required i32 SkuId;
    3: required i32 Amount;
    4: optional string Remark;
}

struct InvokeResult {
    1: required ResponseCode code;
    2: optional string Message;
}

在Thrift代碼生成器的目錄下執行命令:./thrift.exe -gen csharp OrderService.thrift,發現同目錄下多了一個gen-csharp文件夾,生成的代碼放在這個文件夾裏面。apache

新建一個.net core的解決方案,結構以下:緩存

三個項目均添加apache-thrift-netcore的nuget包(這裏服務端寄宿在asp.net core程序的緣由是由於咱們採用微服務的模式,每一塊業務的UI、Rest API、RPC Server所有放在一塊),將剛剛生成的代碼文件拷至Qka.Contract項目裏,其餘兩個項目添加Qka.Contract項目的引用。服務器

在Qka.WebServer中實現服務接口:網絡

public class OrderServiceImpl : Iface
    {
        public InvokeResult Create(Order order)
        {
            return new InvokeResult
            {
                Code = ResponseCode.SUCCESS,
                Message = $"訂單{order.OrderId}建立成功!"
            };
        }

        public InvokeResult Delete(int orderId)
        {
            return new InvokeResult
            {
                Code = ResponseCode.SUCCESS,
                Message = $"訂單{orderId}刪除成功成功!"
            };
        }

        public Order Get(int orderId)
        {
            return new Order
            {
                OrderId = 1,
                SkuId = 1,
                Amount = 2,
                Remark = "黃金萬兩"
            };
        }

        public List<Order> GetListByUserId(int userId, bool isPaid)
        {
            return new List<Order>
            {
                new Order
                {
                    OrderId = 1,
                    SkuId = 1,
                    Amount = 10000,
                    Remark = "黃金萬兩"
                },
                new Order
                {
                    OrderId = 2,
                    SkuId = 2,
                    Amount = 100,
                    Remark = "白銀百兩"
                },
            };
        }
    }

編寫ApplicationExtenssion,代碼以下:app

public static class ApplicationExtenssion
    {
        public static IApplicationBuilder UseThriftServer(this IApplicationBuilder appBuilder)
        {
            var orderService = new OrderServiceImpl();
            Processor processor = new Processor(orderService);
            TServerTransport transport = new TServerSocket(8800);
            TServer server = new TThreadPoolServer(processor, transport);

            var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider;

            var lifeTime = services.GetService<IApplicationLifetime>();
            lifeTime.ApplicationStarted.Register(() =>
            {
                server.Serve();
            });
            lifeTime.ApplicationStopped.Register(() =>
            {
                server.Stop();
                transport.Close();
            });

            return appBuilder;
        }
    }

上面的代碼用的是TThreadPoolServer,網上的代碼均採用TSimpleServer,經過反編譯比較TSimpleServer、TThreadedServer、TThreadPoolServer,發現TSimpleServer只能同時響應一個客戶端,TThreadedServer則維護了一個clientQueue,clientQueue最大值是100,TThreadPoolServer則用的是用線程池響應多個客戶請求,生產環境毫不能用TSimpleServer。asp.net

在Startup.cs文件的Configure方法中添加:socket

app.UseThriftServer();

服務端代碼大功告成,再來編寫客戶端調用代碼:ide

    class Program
    {
        static void Main(string[] args)
        {
            TTransport transport = new TSocket("localhost", 8800);
            TProtocol protocol = new TBinaryProtocol(transport);
            var client = new OrderService.Client(protocol);
            transport.Open();

            var createResult = client.Create(new Order
            {
                OrderId = 100,
                SkuId = 2,
                Amount = 3,
                Remark = "測試建立訂單"
            });
            var order = client.Get(10);
            var list = client.GetListByUserId(10, true);
            var deleteResult = client.Delete(20);

            transport.Close();

            Console.ReadKey();
        }
    }

下面這段話引自https://www.cnblogs.com/cyfonly/p/6059374.html,解釋上面代碼中爲何採用TSocket和TBinaryProtocol:

Thrift 支持多種傳輸協議,用戶能夠根據實際需求選擇合適的類型。Thrift 傳輸協議上整體可劃分爲文本 (text) 和二進制 (binary) 傳輸協議兩大類,通常在生產環境中使用二進制類型的傳輸協議爲多數(相對於文本和 JSON 具備更高的傳輸效率)。經常使用的協議包含:
TBinaryProtocol:是Thrift的默認協議,使用二進制編碼格式進行數據傳輸,基本上直接發送原始數據
TCompactProtocol:壓縮的、密集的數據傳輸協議,基於Variable-length quantity的zigzag 編碼格式
TJSONProtocol:以JSON (JavaScript Object Notation)數據編碼協議進行數據傳輸
TDebugProtocol:經常用以編碼人員測試,以文本的形式展示方便閱讀
關於以上幾種類型的傳輸協議,若是想更深刻更具體的瞭解其實現及工做原理,能夠參考站外相關文章《thrift源碼研究》。

傳輸方式
與傳輸協議同樣,Thrift 也支持幾種不一樣的傳輸方式。
1. TSocket:阻塞型 socket,用於客戶端,採用系統函數 read 和 write 進行讀寫數據。
2. TServerSocket:非阻塞型 socket,用於服務器端,accecpt 到的 socket 類型都是 TSocket(即阻塞型 socket)。
3. TBufferedTransport 和 TFramedTransport 都是有緩存的,均繼承TBufferBase,調用下一層 TTransport 類進行讀寫操做嗎,結構極爲類似。其中 TFramedTransport 以幀爲傳輸單位,幀結構爲:4個字節(int32_t)+傳輸字節串,頭4個字節是存儲後面字節串的長度,該字節串纔是正確須要傳輸的數據,所以 TFramedTransport 每傳一幀要比 TBufferedTransport 和 TSocket 多傳4個字節。
4. TMemoryBuffer 繼承 TBufferBase,用於程序內部通訊用,不涉及任何網絡I/O,可用於三種模式:(1)OBSERVE模式,不可寫數據到緩存;(2)TAKE_OWNERSHIP模式,需負責釋放緩存;(3)COPY模式,拷貝外面的內存塊到TMemoryBuffer。
5. TFileTransport 直接繼承 TTransport,用於寫數據到文件。對事件的形式寫數據,主線程負責將事件入列,寫線程將事件入列,並將事件裏的數據寫入磁盤。這裏面用到了兩個隊列,類型爲 TFileTransportBuffer,一個用於主線程寫事件,另外一個用於寫線程讀事件,這就避免了線程競爭。在讀完隊列事件後,就會進行隊列交換,因爲由兩個指針指向這兩個隊列,交換隻要交換指針便可。它還支持以 chunk(塊)的形式寫數據到文件。
6. TFDTransport 是很是簡單地寫數據到文件和從文件讀數據,它的 write 和 read 函數都是直接調用系統函數 write 和 read 進行寫和讀文件。
7. TSimpleFileTransport 直接繼承 TFDTransport,沒有添加任何成員函數和成員變量,不一樣的是構造函數的參數和在 TSimpleFileTransport 構造函數裏對父類進行了初始化(打開指定文件並將fd傳給父類和設置父類的close_policy爲CLOSE_ON_DESTROY)。
8. TZlibTransport 跟 TBufferedTransport 和 TFramedTransport同樣,調用下一層 TTransport 類進行讀寫操做。它採用<zlib.h>提供的 zlib 壓縮和解壓縮庫函數來進行壓解縮,寫時先壓縮再調用底層 TTransport 類發送數據,讀時先調用 TTransport 類接收數據再進行解壓,最後供上層處理。
9. TSSLSocket 繼承 TSocket,阻塞型 socket,用於客戶端。採用 openssl 的接口進行讀寫數據。checkHandshake()函數調用 SSL_set_fd 將 fd 和 ssl 綁定在一塊兒,以後就能夠經過 ssl 的 SSL_read和SSL_write 接口進行讀寫網絡數據。
10. TSSLServerSocket 繼承 TServerSocket,非阻塞型 socket, 用於服務器端。accecpt 到的 socket 類型都是 TSSLSocket 類型。
11. THttpClient 和 THttpServer 是基於 Http1.1 協議的繼承 Transport 類型,均繼承 THttpTransport,其中 THttpClient 用於客戶端,THttpServer 用於服務器端。二者都調用下一層 TTransport 類進行讀寫操做,均用到TMemoryBuffer 做爲讀寫緩存,只有調用 flush() 函數纔會將真正調用網絡 I/O 接口發送數據。
相關文章
相關標籤/搜索