zoukankan      html  css  js  c++  java
  • .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 接口发送数据。

      

  • 相关阅读:
    wget(转)
    852. Peak Index in a Mountain Array
    617. Merge Two Binary Trees
    814. Binary Tree Pruning
    657. Judge Route Circle
    861. Score After Flipping Matrix
    832. Flipping an Image
    461. Hamming Distance
    654. Maximum Binary Tree
    804. Unique Morse Code Words
  • 原文地址:https://www.cnblogs.com/focus-lei/p/8889389.html
Copyright © 2011-2022 走看看