WPF/WinForms 客户端通过 gRPC 与后端通信:从 Proto 契约到流式调用的完整指南
2026/8/24 20:50:43 网站建设 项目流程

WPF/WinForms 客户端通过 gRPC 与后端通信:从 Proto 契约到流式调用的完整指南

引言

做过桌面客户端的同学,以前和后端通信基本都是 HTTP + JSON(REST)。但当接口变多、字段变多、对性能有要求时,REST 的痛点就来了:字段冗余、没有强类型契约、序列化开销大。

gRPC解决了这些问题:用 Protobuf 做序列化(体积小、快),用.proto文件做强类型契约(前后端字段不会对不上),还天然支持双向流。

本文面向 WPF/WinForms 开发者,讲清客户端用 gRPC 通信需要掌握的全部知识点。

环境:.NET 6/8,NuGet 包Grpc.Net.Client+Google.Protobuf+Grpc.Tools

一、什么是 gRPC 和 Proto 契约

gRPC 是 Google 开源的 RPC 框架,基于 HTTP/2,默认用Protobuf做序列化。对比 REST:

维度REST (HTTP + JSON)gRPC
契约无强类型,靠文档约定.proto强类型契约
序列化JSON 文本,体积大Protobuf 二进制,体积小、快
通信模式基本是一问一答支持单向、服务端流、客户端流、双向流
传输HTTP/1.1HTTP/2(多路复用)

核心是.proto文件——它同时定义了"有哪些服务"和"消息长什么样":

syntax = "proto3"; package greet; // 定义服务 service Greeter { // 一元调用 rpc SayHello (HelloRequest) returns (HelloReply); // 服务端流式 rpc SayHelloStream (HelloRequest) returns (stream HelloReply); } // 定义消息 message HelloRequest { string name = 1; } message HelloReply { string message = 1; }

二、生成强类型客户端

.proto文件要通过Grpc.Tools在编译时自动生成 C# 客户端代码。

2.1 引入包与配置

在客户端项目的.csproj里:

<ItemGroup><PackageReferenceInclude="Grpc.Net.Client"Version="2.*"/><PackageReferenceInclude="Google.Protobuf"Version="3.*"/><PackageReferenceInclude="Grpc.Tools"Version="2.*"PrivateAssets="All"/></ItemGroup><ItemGroup><!-- 指定 proto 文件,GrpcServices=Client 表示生成客户端 --><ProtobufInclude="Protos\greet.proto"GrpcServices="Client"/></ItemGroup>

编译后,会生成一个Greeter.GreeterClient类,直接new就能用。

关键点:proto 文件是前后端共享的契约源,最好放在公共仓库/子模块里,由后端维护,客户端引用同一份,避免字段对不上。

三、建立连接:GrpcChannel

3.1 创建 Channel 和 Client

usingGrpc.Net.Client;usingGrpc.Core;// 创建通道(Channel 是重资源,要复用,不要每次调用都新建)usingvarchannel=GrpcChannel.ForAddress("https://localhost:5001");// 创建客户端varclient=newGreeter.GreeterClient(channel);// 一元调用varreply=awaitclient.SayHelloAsync(newHelloRequest{Name="张三"});Console.WriteLine(reply.Message);

3.2 Channel 的生命周期(重要!)

GrpcChannel底层是一个HttpClient连接复用是 gRPC 性能的关键:

  1. 不要每次调用都new GrpcChannel,否则连接池失效、性能骤降。
  2. 在应用生命周期内全局复用一个 Channel,用单例或依赖注入管理。
  3. 程序退出前正确Dispose

用 DI 管理最优雅(需引用Grpc.Net.ClientFactory):

// 程序启动时注册(.NET 6+ 的 Host 里)builder.Services.AddGrpcClient<Greeter.GreeterClient>(o=>{o.Address=newUri("https://localhost:5001");});

之后在窗体里直接注入Greeter.GreeterClient使用即可。

四、四种通信模式

gRPC 支持四种调用模式,这是它比 REST 强的地方。

4.1 一元调用(Unary):一问一答

最常见的场景,上面已演示。

4.2 服务端流(Server Streaming):一问多答

适合"实时推送":比如日志流、行情推送、大文件分块下载。

usingvarcall=client.SayHelloStream(newHelloRequest{Name="张三"});// 逐条读取服务端推送的消息awaitforeach(varreplyincall.ResponseStream.ReadAllAsync()){Console.WriteLine(reply.Message);// 想中途取消,可以 break}

4.3 客户端流(Client Streaming):多问一答

适合"批量上传":比如批量导入数据、上传文件。

usingvarcall=client.Upload();// 逐条写入foreach(varitemindataList){awaitcall.RequestStream.WriteAsync(item);}// 通知服务端"发送完毕",然后接收最终响应awaitcall.RequestStream.CompleteAsync();varresponse=awaitcall.ResponseAsync;

4.4 双向流(Bidirectional Streaming):边发边收

适合聊天、实时协作等复杂交互,两端都可随时发和收。

usingvarcall=client.Chat();// 一个任务负责收varreadTask=Task.Run(async()=>{awaitforeach(varmsgincall.ResponseStream.ReadAllAsync())Console.WriteLine("收到: "+msg.Text);});// 一个任务负责发foreach(vartextinmessagesToSend){awaitcall.RequestStream.WriteAsync(newChatMsg{Text=text});}awaitcall.RequestStream.CompleteAsync();awaitreadTask;

五、认证、超时与取消

生产环境三件套,缺一不可。

5.1 认证:传 Token

Metadata携带认证信息(如 JWT):

varheaders=newMetadata{{"Authorization","Bearer "+accessToken}};varreply=awaitclient.SayHelloAsync(newHelloRequest{Name="张三"},headers);

5.2 超时与取消:Deadline + CancellationToken

gRPC 默认没有超时,网络抖动会让调用一直挂起,必须设置超时:

// 方式一:CancellationTokenSource 定时取消usingvarcts=newCancellationTokenSource(TimeSpan.FromSeconds(5));varreply=awaitclient.SayHelloAsync(newHelloRequest{Name="张三"},cancellationToken:cts.Token);// 方式二:deadline(绝对时间点)varreply2=awaitclient.SayHelloAsync(newHelloRequest{Name="张三"},deadline:DateTime.UtcNow.AddSeconds(5));

5.3 错误处理

gRPC 异常是RpcException,通过StatusCode区分:

try{varreply=awaitclient.SayHelloAsync(req);}catch(RpcExceptionex)when(ex.StatusCode==StatusCode.DeadlineExceeded){MessageBox.Show("请求超时,请重试");}catch(RpcExceptionex)when(ex.StatusCode==StatusCode.Unauthenticated){MessageBox.Show("登录已过期");}

六、进阶:拦截器与重试

拦截器(Interceptor)可以统一处理日志、注入 Token、统计耗时,避免在每个调用点重复写:

publicclassLoggingInterceptor:Interceptor{publicoverrideAsyncUnaryCall<TResponse>AsyncUnaryCall<TRequest,TResponse>(TRequestrequest,ClientInterceptorContext<TRequest,TResponse>context,AsyncUnaryCallContinuation<TRequest,TResponse>continuation){varcall=continuation(request,context);// 拿到响应后记录日志vartask=call.ResponseAsync.ContinueWith(t=>{Log.Info($"{context.Method}调用完成");});returncall;}}// 使用拦截器varinvoker=channel.Intercept(newLoggingInterceptor());varclient=newGreeter.GreeterClient(invoker);

七、与 WPF/WinForms 的 UI 结合

gRPC 调用是异步的,桌面端的关键是:别卡 UI 线程,await 后安全更新界面

// WinForms:async void 事件处理器privateasyncvoidbtnCall_Click(objectsender,EventArgse){btnCall.Enabled=false;// 防重复点击try{varreply=await_client.SayHelloAsync(newHelloRequest{Name=textBox1.Text},deadline:DateTime.UtcNow.AddSeconds(5));// await 之后自动回到 UI 线程,可直接更新控件label1.Text=reply.Message;}catch(RpcExceptionex){MessageBox.Show("调用失败:"+ex.StatusCode);}finally{btnCall.Enabled=true;}}

要点:

  1. async/awaitawait之后天然回到 UI 线程,不需要Invoke(WPF 的SynchronizationContext会处理好)。
  2. 服务端流式推送的await foreach里更新 UI 同样安全,因为每次迭代都回到 UI 上下文。
  3. 页面/窗体关闭时,记得CancellationTokenSource取消进行中的流,避免资源泄漏。

总结

WPF/WinForms 客户端用 gRPC 通信,掌握这 7 点就够用了:

  1. .proto文件做强类型契约,前后端共享同一份。
  2. Grpc.Tools编译时自动生成客户端
  3. GrpcChannel是重资源,全局复用,用 DI 管理生命周期。
  4. 按场景选四种通信模式:一元、服务端流、客户端流、双向流。
  5. 必须设超时/取消,用Metadata传 Token 认证,RpcException做错误处理。
  6. 拦截器统一处理日志、Token 注入。
  7. 结合桌面 UI 用async/awaitawait后安全更新界面,退出前取消流。

gRPC + Protobuf 带来的强类型、高性能、流式能力,会让你的桌面客户端和后端协作更顺畅。如果你还在纠结"REST 还是 gRPC",下一篇我可以专门写两者的选型对比与迁移实践。


本文为原创,转载注明出处。觉得有用就点赞、收藏、关注,感谢支持!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询