【问题标题】:How to get a simple stream of string using ServiceStack Grpc?如何使用 ServiceStack Grpc 获取简单的字符串流?
【发布时间】:2020-06-15 16:36:15
【问题描述】:

为了让基本的字符串“流”在 C# 中工作而与 ServiceStack 库斗争了一段时间。

简而言之,我正在尝试从“原生”gRPC 复制基本示例。

原始缓冲区

service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (stream HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

服务器

public override async Task SayHello(HelloRequest request, IServerStreamWriter<HelloReply> responseStream,                                            ServerCallContext context)
    {
        foreach (var x in Enumerable.Range(1, 10))
        {
            await responseStream.WriteAsync(new HelloReply
            {
                Message = $"Hello {request.Name} {x}"
            });

            await Task.Delay(200);
        }
    }

客户

var replies = client.SayHello(new HelloRequest { Name = "Laurent" });

await foreach (var reply in replies.ResponseStream.ReadAllAsync())
{
    Console.WriteLine(reply.Message);
}

然后使用 ServiceStack 库,我无法正确完成服务器部分。我总是收到一条消息,告诉我我的函数“SayHello”未定义。

告诉我,谢谢!

【问题讨论】:

    标签: c# servicestack grpc


    【解决方案1】:

    ServiceStack gRPC 实现采用了可以从 gRPC 端点调用的code-first implementation where your existing ServiceStack Services

    因此,与其手动创建.proto 文件,不如使用standard Request / Response DTOsService 实现为普通的请求/回复 gRPC 服务创建服务。

    对于Server Stream gRPC Services,除了从ServiceStack 的Service 基类继承之外,您还需要实现IStreamService 接口。

    文档中的Implementing Server Stream Services 中介绍了一个示例:

    public class StreamFileService : Service, IStreamService<StreamFiles,FileContent>
    {
        public async IAsyncEnumerable<FileContent> Stream(StreamFiles request, 
            CancellationToken cancel = default)
        {
            var i = 0;
            var paths = request.Paths ?? TypeConstants.EmptyStringList;
            while (!cancel.IsCancellationRequested)
            {
                var file = VirtualFileSources.GetFile(paths[i]);
                var bytes = file?.GetBytesContentsAsBytes();
                var to = file != null
                    ? new FileContent {
                        Name = file.Name,
                        Type = MimeTypes.GetMimeType(file.Extension),
                        Body = bytes,
                        Length = bytes.Length,
                    }
                    : new FileContent {
                        Name = paths[i],
                        ResponseStatus = new ResponseStatus {
                            ErrorCode = nameof(HttpStatusCode.NotFound),
                            Message = "File does not exist",
                        }
                    };
    
                yield return to;
    
                if (++i >= paths.Count)
                    yield break;
            }
        }
    }
    

    您还需要在 RegisterServices 中注册您的 Stream Service 实现:

    Plugins.Add(new GrpcFeature(App) {
        RegisterServices = {
            typeof(StreamFileService)
        }
    });
    

    如果您使用smart C# generic gRPC Service Client,则可以完全避免使用.proto 描述符和protoc 生成的类,因为您可以在ServiceModel project 中重用服务器DTO,以启用无需代码生成的端到端API:

    var request = new StreamFiles {
        Paths = new List<string> {
            "/js/ss-utils.js",
            "/js/hot-loader.js",
            "/js/not-exists.js",
            "/js/hot-fileloader.js",
        }
    };
    
    var files = new List<FileContent>();
    await foreach (var file in client.StreamAsync(request))
    {
        files.Add(file);
    }
    

    您可以使用C# Add ServiceStack Reference 在客户端上生成您的 C# DTO,而不是共享您的 ServiceModel.dll

    对于protoc generated clients,您可以使用x dotnet toolGenerate protoc Dart gRPC Client

    $ x proto-dart https://todoworld.servicestack.net -out lib
    

    您可以在哪里使用serverStreamFiles API 存根来调用服务器流服务:

    var stream = client.serverStreamFiles(StreamFiles()..paths.addAll([
      '/js/ss-utils.js',
      '/js/hot-loader.js',
      '/js/hot-fileloader.js',
    ]));
    
    await for (var file in stream) {
      var text = utf8.decode(file.body);
      print('FILE ${file.name} (${file.length}): ${text.substring(0, text.length < 50 ? text.length : 50)} ...');
    }
    

    todo-world/clients 存储库包含许多不同语言的 gRPC 测试示例。

    【讨论】:

    • 嗨,Mytz,谢谢您的回答。对于我在 C# 中使用 Grpc 的经验来说,这有点过于复杂,但我终于能够让它发挥作用。正如您所建议的,我最终删除了原始文件并使用 Service Stack ToDoWorld 示例中的第二个项目(C# 通用)中的示例。
    【解决方案2】:

    最后,如果这可以帮助其他人,这就是我最终做的一个简单的流 POC。也没有更多的原型文件!

    客户端程序.cs:

            private async static Task GetBotStream()
            {
                var res = client.StreamAsync(new BotStreamRequest { });
    
                await foreach (var textReceived in res)
                {
                    Console.WriteLine(textReceived.Result);
                }
            }
    
    

    客户端 dtos.cs

       [DataContract]
        public partial class BotStreamRequest : IReturn<BotStreamReply>
        {
        }
    
        [DataContract]
        public partial class BotStreamReply
        {
            [DataMember(Order = 1)]
            public virtual string Result { get; set; }
    
            [DataMember(Order = 2)]
            public virtual ResponseStatus ResponseStatus { get; set; }
        }
    

    服务器程序.cs

            public async IAsyncEnumerable<BotStreamReply> Stream(BotStreamRequest request, [EnumeratorCancellation]CancellationToken cancel = default)
            {
                foreach (var x in Enumerable.Range(1, 10))
                {
                    yield return new BotStreamReply { Result = $"My stream {x}" };
                }
            }
    

    【讨论】:

    • 您能否举例说明如何从请求中调用 Stream 方法?它有点不清楚请求如何触发流,以便请求导致用户收到超过 1 个响应,即您是否需要在 botstreamrequest 类中等待,或者它是如何转换的?任何帮助都会很棒
    猜你喜欢
    • 2018-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 2015-03-14
    相关资源
    最近更新 更多