.net core下使用Thrift

     因网站组(.net)与游戏服务端(c++)原来使用REST API通讯效率稍显低下,准备下期重构时改用rpc方式,经比较Thrift和gRPC两者的优劣(参照网上的对比结果),最终决定使用Thrift。

     首先下载Thrift代码生成器,编写根据Thrift的语法规范(可参看https://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html)编写脚本文件OrderService.thrift,如下:

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文件夹,生成的代码放在这个文件夹里面。

新建一个.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,代码如下:

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。

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

app.UseThriftServer();

服务端代码大功告成,再来编写客户端调用代码:

    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 接口发送数据。

  

 

posted @ 2018-04-20 11:37  focus-lei  阅读(4323)  评论(1编辑  收藏  举报