因網站組(.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 接口發送數據。