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

      

  • 相关阅读:
    C++逐行读取文本文件的正确做法
    <Android Framework 之路>Android5.1 Camera Framework(一)
    zeromq-4.1.2在windows下的编译
    Duilib应用修改程序图标方法
    gdal集成kml库的做法
    使用DWR实现JS调用服务端Java代码
    DirectUI界面编程(六)实现右键弹出菜单
    如何设计系统的错误码及错误信息
    TCP协议格式
    UDP协议
  • 原文地址:https://www.cnblogs.com/focus-lei/p/8889389.html
Copyright © 2020-2023  润新知