C#使用远程服务调用框架Apache Thrift

作者:天方 时间:2023-05-07 01:05:01 

Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。和其它RPC框架相比,它主要具有如下连个特点:

  • 高性能。 它采用的是二进制序列化,并且用的是长连接。比传统的使用XML,SOAP,JSON等短连接的解决方案要快得多。

  • 多语言支持。 它对提供了对C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk等多种常用语言的支持

正因为如此,Thrift对于高并发、大数据量和多语言的环境是非常有优势的,本文就简单的介绍一下如何使用它。

使用Thrift和传统的Corba之类的RPC框架并无太大差异,主要分为如下三个部分:

  • 使用IDL定义提供的服务:主要用于服务接口和数据传输对象(DTO)并且有一套自己的语法, Thrift中命名为thirft文件,并且有一套自己的语法。

  • 将IDL编译成框架代码

  • 客户端使用框架代码调用远程服务

  • 服务端使用框架代码实现接口,并提供服务

和传统RPC框架一样,Thrift框架提供数据传输的服务,服务端和客户端只需要关注业务即可;这一系列流程并没有多大新颖的地方。

准备工作:

需要在Thrift官网下载两个文件:

  • IDL编译工具。官方本身提供了Windows的exe版本:thrift-0.9.2.tar.gz

  • Thrift类库。Thrift底层框架,提供了底层的数据传输服务。

下载完后,由于Thrift类库是以源码形式提供的,因此需要自己编译。为了简单,这里以C#为例,打开 "\lib\csharp\src\Thrift.sln" 工程文件,直接编译即可生成Thrift.dll文件。官方说.net版本.net 3.5及以上即可。

示例项目:

Thrift本身提供了多多种语言的支持,这里示例项目仍以官网的CSharp Tutorial为例来介绍一下如何在C#中使用Thrift。

首先下载接口定义文件 tutorial.thrift 和shared.thrift 。官方的文档中加了一大堆注释,实际上还是比较简单的,主体部分如下:

service Calculator extends shared.SharedService
{
  void ping(),
  i32 add(1:i32 num1, 2:i32 num2),
  i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
  oneway void zip()
}

主要就是定义了一个这样的接口,提供了四个接口。以及一些相关的结构体的定义。定义好接口后,调用Thrift.exe将其编译成C#的代码:

thrift -r --gen csharp tutorial.thrift

编译完后,会生成一个gen-csharp文件夹,里面就是相关的结构体和接口的定义,和一些框架的实现,具体的后续再讲。这个文件夹里的文件大部分都是服务端和客户端都要用的,为了简单起见,大可以建立一个ShareProject(低版本的VS不支持的话可以建立一个独立的类库项目),以方便客户端和服务端引用。

客户端:

首先还是来看看客户端代码(运行这段代码需要加上前面编译的Thrift.dll和编译idl文件生成的一堆C#代码):

using System;
using Thrift;
using Thrift.Protocol;
using Thrift.Server;
using Thrift.Transport;

namespace CSharpTutorial
{
   public class CSharpClient
   {
       public static void Main()
       {
           try
           {
               TTransport transport = new TSocket("localhost", 9090);
               TProtocol protocol = new TBinaryProtocol(transport);
               Calculator.Client client = new Calculator.Client(protocol);

transport.Open();
               try
               {
                   client.ping();
                   Console.WriteLine("ping()");

int sum = client.add(1, 1);
                   Console.WriteLine("1+1={0}", sum);

Work work = new Work();

work.Op = Operation.DIVIDE;
                   work.Num1 = 1;
                   work.Num2 = 0;
                   try
                   {
                       int quotient = client.calculate(1, work);
                       Console.WriteLine("Whoa we can divide by 0");
                   }
                   catch (InvalidOperation io)
                   {
                       Console.WriteLine("Invalid operation: " + io.Why);
                   }

work.Op = Operation.SUBTRACT;
                   work.Num1 = 15;
                   work.Num2 = 10;
                   try
                   {
                       int diff = client.calculate(1, work);
                       Console.WriteLine("15-10={0}", diff);
                   }
                   catch (InvalidOperation io)
                   {
                       Console.WriteLine("Invalid operation: " + io.Why);
                   }

SharedStruct log = client.getStruct(1);
                   Console.WriteLine("Check log: {0}", log.Value);

}
               finally
               {
                   transport.Close();
               }
           }
           catch (TApplicationException x)
           {
               Console.WriteLine(x.StackTrace);
           }

}
   }
}

官方的例子简化一下后如下:

using (TTransport transport = new TSocket("localhost", 9090))
   using (TProtocol protocol = new TBinaryProtocol(transport))
   using (Calculator.Client client = new Calculator.Client(protocol))
   {
       transport.Open();
       int sum = client.add(1, 1);
       Console.WriteLine("1+1={0}", sum);
   }

主要就设计到了三个类:TTransport、TProtocol和Calculator.Client,其中TTransport、Tprotocol是用于传输控制的,和业务无关,是在Thrift.dll中定义的。而Calculator.Client则是客户端的Proxy,用于执行RPC,和业务相关,是由idl文件编译生成。客户端无需编写代码,即可拥有RPC能力。(这个特效基本上所有RPC框架都具有)

服务端:

服务端示例代码如下: 

using System;
using System.Collections.Generic;
using Thrift.Server;
using Thrift.Transport;

namespace CSharpTutorial
{
   public class CalculatorHandler : Calculator.Iface
   {
       Dictionary<int, SharedStruct> log;

public CalculatorHandler()
       {
           log = new Dictionary<int, SharedStruct>();
       }

public void ping()
       {
           Console.WriteLine("ping()");
       }

public int add(int n1, int n2)
       {
           Console.WriteLine("add({0},{1})", n1, n2);
           return n1 + n2;
       }

public int calculate(int logid, Work work)
       {
           Console.WriteLine("calculate({0}, [{1},{2},{3}])", logid, work.Op, work.Num1, work.Num2);
           int val = 0;
           switch (work.Op)
           {
               case Operation.ADD:
                   val = work.Num1 + work.Num2;
                   break;

case Operation.SUBTRACT:
                   val = work.Num1 - work.Num2;
                   break;

case Operation.MULTIPLY:
                   val = work.Num1 * work.Num2;
                   break;

case Operation.DIVIDE:
                   if (work.Num2 == 0)
                   {
                       InvalidOperation io = new InvalidOperation();
                       io.What = (int)work.Op;
                       io.Why = "Cannot divide by 0";
                       throw io;
                   }
                   val = work.Num1 / work.Num2;
                   break;

default:
                   {
                       InvalidOperation io = new InvalidOperation();
                       io.What = (int)work.Op;
                       io.Why = "Unknown operation";
                       throw io;
                   }
           }

SharedStruct entry = new SharedStruct();
           entry.Key = logid;
           entry.Value = val.ToString();
           log[logid] = entry;

return val;
       }

public SharedStruct getStruct(int key)
       {
           Console.WriteLine("getStruct({0})", key);
           return log[key];
       }

public void zip()
       {
           Console.WriteLine("zip()");
       }
   }

public class CSharpServer
   {
       public static void Main()
       {
           try
           {
               CalculatorHandler handler = new CalculatorHandler();
               Calculator.Processor processor = new Calculator.Processor(handler);
               TServerTransport serverTransport = new TServerSocket(9090);
               TServer server = new TSimpleServer(processor, serverTransport);

// Use this for a multithreaded server
               // server = new TThreadPoolServer(processor, serverTransport);

Console.WriteLine("Starting the server...");
               server.Serve();
           }
           catch (Exception x)
           {
               Console.WriteLine(x.StackTrace);
           }
           Console.WriteLine("done.");
       }
   }
}

官方代码基本上包括两个部分:接口实现和服务的启动,简化后如下:

public class CalculatorHandler : Calculator.Iface
   {
       public void ping()
       {
       }

public int add(int n1, int n2)
       {
           return n1 + n2;
       }

public int calculate(int logid, Work work)
       {
           throw new InvalidOperation();
       }

public SharedStruct getStruct(int key)
       {
           return new SharedStruct();
       }

public void zip()
       {
       }
   }

接口实现依赖于IDL生成的Calculator.Iface接口,这里我就实现了一个add接口。再来看看服务端 

static void Main(string[] args)
   {
       CalculatorHandler handler = new CalculatorHandler();
       Calculator.Processor processor = new Calculator.Processor(handler);

TServerTransport serverTransport = new TServerSocket(9090);
       TServer server = new TSimpleServer(processor, serverTransport);

Console.WriteLine("Starting the server...");
       server.Serve();
   }

服务端主要的功能就是将刚才的接口作为服务加载起来,并启动服务,也是比较常规的做法,没有太多需要介绍的地方

从这个Demo来看,主要过程和其它RPC框架的流程倒也没有太大区别。一个比较出彩的地方是Thrift是提供了原生的.net运行库的(前面编译的Thrift.dll),作为一个跨语言的框架不用Pinvoke非常给力,给部署带来不少方便。本来高性能才是它的主要特点,后续有空再测试一下。

最后,本文只是一个简单的入门体验,如果需要更多信息,请参看Thrift的官方网站。

来源:https://www.cnblogs.com/TianFang/p/4355280.html

标签:C#,远程,服务,调用,框架,Apache,Thrift
0
投稿

猜你喜欢

  • C++实现leetcode(3.最长无重复字符的子串)

    2023-06-25 03:17:22
  • Java开发必备的三大修饰符

    2021-10-19 10:11:01
  • Android 自定义按钮点击事件和长按事件对比

    2023-06-04 00:57:14
  • java LRU(Least Recently Used )详解及实例代码

    2022-10-08 10:42:43
  • java 实现约瑟夫环的实例代码

    2022-06-24 16:26:11
  • Spring Boot使用Allatori代码混淆的方法

    2023-11-24 16:34:55
  • C#实现终止正在执行的线程

    2022-05-06 22:34:22
  • Android自定义顶部导航栏控件实例代码

    2022-02-11 13:43:16
  • Android编程实现XML解析与保存的三种方法详解

    2022-04-15 00:39:01
  • C#使用Data Annotations进行手动数据验证

    2022-11-19 11:23:15
  • Android模仿知乎的回答详情页的动画效果

    2022-12-25 06:08:57
  • c# SqlDataAdapter中的Fill是怎么实现的

    2021-07-16 08:30:04
  • Android 一些常用的混淆Proguard

    2021-10-13 06:46:29
  • 使用vscode搭建javaweb项目的详细步骤

    2022-08-05 01:12:43
  • Unity的IPostprocessBuild实用案例深入解析

    2023-05-29 05:54:49
  • Springboot打包部署代码实例

    2022-08-15 14:41:46
  • 一文教会你使用jmap和MAT进行堆内存溢出分析

    2023-11-06 08:01:57
  • Unity3D动态对象优化代码分享

    2023-12-22 09:38:24
  • 简单好用的Adapter---ArrayAdapter详解

    2022-09-08 08:33:05
  • Java利用POI读取、写入Excel的方法指南

    2023-11-23 15:39:23
  • asp之家 软件编程 m.aspxhome.com