目錄
Thrift學習總結
Apahce Thrift是FaceBook實現的一種高效的、支持多種語言的遠程服務調用的框架。本文結合網絡上的資源對從C#開發人員的角度簡單介紹Apache Thrift的架構、開發,並且針對不同的傳輸協議和服務類型給出相應的C#實例,同時簡單介紹Thrift異步客戶端的實現。
前言
Thrift是一款由Fackbook開發的可伸縮、跨語言的服務開發框架,該框架已經開源並且加入的Apache項目。Thrift主要功能是:通過自定義的Interface Definition Language(IDL),可以創建基於RPC的客戶端和服務端的服務代碼。數據和服務代碼的生成是通過Thrift內置的代碼生成器來實現的。Thrift 的跨語言性體現在,它可以生成C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml , Delphi等語言的代碼,且它們之間可以進行透明的通信。
Thrift代碼生成器windows版下載地址
http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.2/thrift-0.9.2.exe
Thrift源碼下載地址
http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.2/thrift-0.9.2.tar.gz
准備工作
本文先舉個栗子來簡單說明Thrift的使用以及如何構建Thrift服務。首先需要下載Thrift的代碼生成器和源碼,如上連接。
有了代碼生成工具,還需要Thrift類庫文件,以便我們調用。打開下載好的源碼,找到thrift-0.9.2\lib\csharp\src下的解決方案,
然后用VS2013打開,可以看見如下結構
一個簡單的小程序
1、准備工作
做完了前期准備工作就可以正式開始編寫一個小程序,感受thrift。創建一個簡單的Hello服務,根據thrift的語法規范編寫腳本文件Hello.thrift,代碼如下:
清單1.Hello.Thrift
namespace csharp HelloThrift.Interface
service HelloService{
string HelloString(1:string para)
i32 HelloInt(1:i32 para)
bool HelloBoolean(1:bool para)
void HelloVoid()
string HelloNull()
}
然后打開cmd切換到thrift代碼生成工具的存放目錄,在命令行中輸入如下命令thrift -gen csharp Hello.thrift
代碼生成工具會自動在當前目錄下把定義好的接口腳本生成C#代碼,生成后的代碼目錄如下
打開目錄會看到生成的服務定義文件
其中定義了服務HelloService的五個方法,每個方法包含一個方法名,參數列表和返回類型。每個參數包括參數序號,參數類型以及參數名。Thrift 是對 IDL(Interface Definition Language)描述性語言的一種具體實現。因此,以上的服務描述文件使用 IDL語法編寫.使用Thrift 工具編譯 Hello.thrift,就會生成相應的 HelloService.cs文件。該文件包含了在 Hello.thrift 文件中描述的服務 HelloService 的接口定義,即HelloService.Iface 接口,以及服務調用的底層通信細節,包括客戶端的調用邏輯HelloService.Client 以及服務器端的處理邏輯HelloService.Processor,用於構建客戶端和服務器端的功能。
2小試牛刀
做好准備工作以后就可以開始感受thrift的魅力。打開VS新建一個空白解決方案命名為HelloThrift。在解決方案根目錄下創建一個lib文件夾,將准備工作中生成的Thrift.dll文件放入lib文件夾中。在解決方案分中建立兩個控制台程序和一個類庫,控制台程序分別命名為HelloThrift.Client和HelloThrift.Server,類庫命名為Thrift.Interface。Client、Server和Interface分別引用lib文件夾中的Thrift.dll文件,將准備工作中生成的HelloService文件導入到Interface類庫中。Client和Server分別引用Interface。具體結果如下圖所示。
完成引用導入工作以后,在服務端創建一個類命名為MyHelloService,實現HelloService.Iface接口,代碼如下:
清單2.MyHelloService
using System;
using HelloThrift.Interface;
namespace HelloThrift.Server
{
public class MyHelloService : HelloService.Iface
{
/// <summary>
/// 只有一個參數返回值為字符串類型的方法
/// </summary>
/// <param name="para">string類型參數</param>
/// <returns>返回值為string類型</returns>
public string HelloString(string para)
{
Console.WriteLine("客戶端調用了HelloString方法");
return para;
}
/// <summary>
/// 只有一個參數,返回值為int類型的方法
/// </summary>
/// <param name="para"></param>
/// <returns>返回值為int類型</returns>
public int HelloInt(int para)
{
Console.WriteLine("客戶端調用了HelloInt方法");
return para;
}
/// <summary>
/// 只有一個bool類型參數,返回值為bool類型的方法
/// </summary>
/// <param name="para"></param>
/// <returns>返回值為bool類型</returns>
public bool HelloBoolean(bool para)
{
Console.WriteLine("客戶端調用了HelloBoolean方法");
return para;
}
/// <summary>
/// 返回執行為空的方法
/// </summary>
public void HelloVoid()
{
Console.WriteLine("客戶端調用了HelloVoid方法");
Console.WriteLine("HelloWorld");
}
/// <summary>
/// 無參數,返回值為null的方法
/// </summary>
/// <returns>返回值為null</returns>
public string HelloNull()
{
Console.WriteLine("客戶端調用了HelloNull方法");
return null;
}
}
}
創建服務器端實現代碼,將MyHelloService 作為具體的處理器傳遞給 Thrift 服務器,代碼如下:
清單3.HelloThrift.Server
using System;
using HelloThrift.Interface;
using Thrift;
using Thrift.Protocol;
using Thrift.Server;
using Thrift.Transport;
namespace HelloThrift.Server
{
class Program
{
/// <summary>
///啟動服務端
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
try
{
//設置服務端口為8080
TServerSocket serverTransport = new TServerSocket(8080);
//設置傳輸協議工廠
TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
//關聯處理器與服務的實現
TProcessor processor = new HelloService.Processor(new MyHelloService());
//創建服務端對象
TServer server = new TThreadPoolServer(processor, serverTransport, new TTransportFactory(), factory);
Console.WriteLine("服務端正在監聽8080端口");
}
catch (TTransportException ex)//捕獲異常信息
{
//打印異常信息
Console.WriteLine(ex.Message);
}
}
}
}
創建客戶端實現代碼,調用HelloThrift.client訪問服務端的邏輯實現,代碼如下:
清單3.HelloThrift.Client
using System;
using HelloThrift.Interface;
using Thrift.Protocol;
using Thrift.Transport;
namespace HelloThrift.Client
{
class Program
{
static void Main(string[] args)
{
try
{
//設置服務端端口號和地址
TTransport transport=new TSocket("localhost",8080);
transport.Open();
//設置傳輸協議為二進制傳輸協議
TProtocol protocol=new TBinaryProtocol(transport);
//創建客戶端對象
HelloService.Client client=new HelloService.Client(protocol);
//調用服務端的方法
Console.WriteLine(client.HelloString("HelloThrift"));
Console.ReadKey();
}
catch (TTransportException e)
{
Console.WriteLine(e.Message);
}
}
}
}
上面的代碼調用了服務端的HelloString方法,服務端也會返回傳入的傳輸值,客戶端將服務端返回的數據打印出來。好了完成了代碼以后,修改一下程序的啟動項順序,右鍵選擇解決防范,啟動項目,選擇多啟動項目,將Client和Server設置為啟動。F5啟動程序,見證奇跡的時刻到了,那么到底客戶端會不會打印出來結果呢?
客戶端調用結果
服務端顯示請求結果
關於其他的方法本文就不再演示了,調用起來都是一樣,只有那個返回值為null的方法客戶端會拋異常,因為C#不知道這個是什么玩意。其他的方法返回值都是正常。下面簡單討論一下框架的通信原理。(就是復制粘貼)
深入挖掘
那么問題來了,挖掘技術哪家強,中國北京找知網。
Thrift 包含一個完整的堆棧結構用於構建客戶端和服務器端。下圖描繪了 Thrift 的整體架構。
1. 架構圖
如圖所示,圖中黃色部分是用戶實現的業務邏輯,褐色部分是根據 Thrift 定義的服務接口描述文件生成的客戶端和服務器端代碼框架,紅色部分是根據 Thrift 文件生成代碼實現數據的讀寫操作。紅色部分以下是 Thrift 的傳輸體系、協議以及底層 I/O 通信,使用 Thrift 可以很方便的定義一個服務並且選擇不同的傳輸協議和傳輸層而不用重新生成代碼。
Thrift 服務器包含用於綁定協議和傳輸層的基礎架構,它提供阻塞、非阻塞、單線程和多線程的模式運行在服務器上,但是由於C#語言的限制,C#實現的服務端沒有非阻塞模式,可以配合winform/webform調用。
服務端和客戶端具體的調用流程如下:
該圖所示是HelloServiceServer啟動的過程以及服務被客戶端調用時,服務器的響應過程。從圖中我們可以看到,程序調用了TThreadPoolServer的server方法后,server進入阻塞監聽狀態,其阻塞在TServerSocket的accept方法上。當接收到來自客戶端的消息后,服務器發起一個新線程處理這個消息請求,原線程再次進入阻塞狀態。在新線程中,服務器通過TBinaryProtocol協議讀取消息內容,調用HelloServiceImpl的helloVoid方法,並將結果寫入helloVoid_result中傳回客戶端。
該圖所示是HelloServiceClient調用服務的過程以及接收到服務器端的返回值后處理結果的過程。從圖中我們可以看到,程序調用了Hello.Client的helloVoid方法,在helloVoid方法中,通過send_helloVoid方法發送對服務的調用請求,通過recv_helloVoid方法接收服務處理請求后返回的結果。
2數據類型
Thrift 腳本可定義的數據類型包括以下幾種類型:
基本類型:
bool:布爾值,true 或 false,對應 C#的 bool
byte:8 位有符號整數,對應 C#的 byte
i16:16 位有符號整數,對應 C#的 short
i32:32 位有符號整數,對應 C#的 int
i64:64 位有符號整數,對應 C#的 long
double:64 位浮點數,對應 C#的 double
string:未知編碼文本或二進制字符串,對應 C#的 string
結構體類型:
struct:定義公共的對象,類似於 C 語言中的結構體定義,在 C#中是一個實體類
容器類型:
list:對應 C#的 List<T> 有序集合
set:對應 C#的 HashSet<T>無序但是不能重復的集合
map:對應 C#的 Dictionary<TKey,TValue>鍵值對集合,鍵不能重復
異常類型:
exception:對應 C#的 Exception
服務類型:
service:對應服務的類
3協議
Thrift可以讓用戶選擇客戶端與服務端之間傳輸通信協議的類別,在傳輸協議上總體划分為文本(text)和二進制(binary)傳輸協議,為節約帶寬,提高傳輸效率,一般情況下使用二進制類型的傳輸協議為多數,有時還會使用基於文本類型的協議,這需要根據項目/產品中的實際需求。常用協議有以下幾種:BinaryProtocol——二進制編碼格式進行數據傳輸使,TCompactProtocol——高效率的、密集的二進制編碼格式進行數據傳輸,TJSONProtocol——使用JSON 的數據編碼協議進行數據傳輸。
4傳輸層
TSocket —— 使用阻塞式 I/O 進行傳輸,是最常見的模式,由於C#語言的限制無法使用非阻塞同步傳輸和非阻塞異步傳輸的方式
5服務端類型
TSimpleServer —— 單線程服務器端使用標准的阻塞式 I/O,TThreadPoolServer —— 多線程服務器端使用標准的阻塞式 I/O。由於C#語言的限制無法使用非阻塞的多線程服務端。一般開發使用阻塞式多線程服務端即可。再來一發
通過小試牛刀初步感受了thrift框架的魅力,通過簡單的么分析了解了thrift框架的基本原理,那么如何結合三層架構進行復雜的應用呢?那么我們就再來一發,使用三層架構+thrift+web form進行CRUD,這次客戶端不再是控制台程序而是web應用程序進行調用。
首先還是做好准備工作,定義好此次案例需要的腳本,定義好的腳本如下所示:
清單4.User.Thrift
上面的腳本定義了一個名為User的實體,這個實體有分別有五個屬性,服務定義了7個操作方法。由於thrift框架沒有對datetime類型數據的定義,只能用string類型進行代替。那么根據要求創建好實體類和服務接口,在解決方案中導入需要的各種文件並創建好三層架構。具體創建后程序結構如下圖所示,由於篇幅原因在此就不在贅訴如何導入文件以及創建三層框架,詳細的過程請參考源代碼,本例着重介紹如何在web客戶端調用服務端的方法對遠程的數據進行操作。
搭建好框架以后,需要在客戶端配置文件中指定服務端的地址和端口號,具體配置如下
<appSettings>
<!--服務端端口號-->
<add key="port" value="8080"/>
<!--服務端地址-->
<add key ="host" value="localhost"/>
</appSettings>
制定好配置文件信息以后,在ThriftClinetFactory類中定義好客戶端實現在代碼,如下所示
using System;
using System.Configuration;
using Thrift.Protocol;
using Thrift.Transport;
using ThriftApp.Common;
namespace ThriftApp.WebClient
{
public static class ThriftClinetFactory
{
public static UserService.Client CreateThriftClient()
{
//獲取配置文件中的主機地址
string hostName = ConfigurationManager.AppSettings["host"];
//獲取服務端端口號
int port = Convert.ToInt32(ConfigurationManager.AppSettings["port"]);
//設置傳輸端口和服務器地址
TTransport transport = new TSocket(hostName, port);
//設置傳輸協議為二進制協議
TProtocol protocol = new TBinaryProtocol(transport);
//創建客戶端對象並綁定傳輸協議
UserService.Client client = new UserService.Client(protocol);
//打開傳輸端口
transport.Open();
//返回客戶端對象
return client;
}
}
}
那么定義好客戶端創建的工廠類以后,我們就可以直接通過工廠來創建客戶端對象,對服務端的方法進行調用。在web服務端創建一個webform頁面,命名為Test,在前台頁面中拖入一個Repeater控件,並定義好模板,具體代碼如下
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="ThriftApp.WebClient.Test"%>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<script src="bootstrap/js/jquery-2.1.3.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate>
<table class="table table-bordered">
<tr>
<th>編號</th>
<th>用戶名</th>
<th>密碼</th>
<th>郵箱</th>
<th>注冊時間</th>
</tr>
</HeaderTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
<ItemTemplate>
<tr>
<td><%#Eval("Id") %></td>
<td><%#Eval("UserName") %></td>
<td><%#Eval("UserPass") %></td>
<td><%#Eval("Email") %></td>
<td><%#Eval("RegTime") %></td>
</tr>
</ItemTemplate>
</asp:Repeater>
</div>
</form>
</body>
</html>
在后台頁面綁定數據源,具體代碼如下:
//創建客戶端對象
using (var client = ThriftClinetFactory.CreateThriftClient())
{
//獲取所有用戶信息,指定數據源
Repeater1.DataSource = client.GetAllUser();
//綁定數據
Repeater1.DataBind();
}
設置一下解決方案的啟動項,將web客戶端和服務端同時設置為啟動項,F5啟動以后就會看到如下結果
更具具體的增刪改查操作,請參考源碼。另附無刷新CRUD結果圖
本文主要參考引用的資料
Apache Thrift - 可伸縮的跨語言服務開發框架http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/