.NET 开源项目 StreamJsonRpc 介绍[下篇]

阅读本文大概需要 9 分钟。

大家好,这是 .NET 开源项目 StreamJsonRpc 介绍的最后一篇。上篇介绍了一些预备知识,包括 JSON-RPC 协议介绍,StreamJsonRpc 是一个实现了 JSON-RPC 协议的库,它基于 Stream、WebSocket 和自定义的全双工管道传输。中篇通过示例讲解了 StreamJsonRpc 如何使用全双工的 Stream 作为传输管道实现 RPC 通讯。本篇(下篇)将继续通过示例讲解如何基于 WebSocket 传输管道实现 RPC 通讯。

准备工作

为了示例的完整性,本文示例继续在中篇创建的示例基础上进行。该示例的 GitHub 地址为:

github.com/liamwang/StreamJsonRpcSamples

我们继续添加三个项目,一个是名为 WebSocketSample.Client 的 Console 应用,一个是名为 WebSocketSample.Server 的 ASP.NET Core 应用,还有一个名为 Contract 的契约类库(和 gRPC 类似)。

你可以直接复制并执行下面的命令一键完成大部分准备工作:

dotnet new console -n WebSocketSample.Client # 建新客户端应用
dotnet new webapi -n WebSocketSample.Server # 新建服务端应用
dotnet new classlib -n Contract # 新建契约类库
dotnet sln add WebSocketSample.Client WebSocketSample.Server Contract # 将项目添加到解决方案
dotnet add WebSocketSample.Client package StreamJsonRpc # 为客户端安装 StreamJsonRpc 包
dotnet add WebSocketSample.Server package StreamJsonRpc # 为服务端安装 StreamJsonRpc 包
dotnet add WebSocketSample.Client reference Contract # 添加客户端引用 Common 引用
dotnet add WebSocketSample.Server reference Contract # 添加服务端引用 Common 引用

为了把重点放在实现上,这次我们依然以一个简单的功能作为示例。该示例实现客户端向服务端发送一个问候数据,然后服务端响应一个消息。为了更贴合实际的场景,这次使用强类型进行操作。为此,我们在 Contract 项目中添加三个类用来约定客户端和服务端通讯的数据结构和接口。

用于客户端发送的数据的 HelloRequest 类:

public class HelloRequest
{public string Name { get; set; }
}

用于服务端响应的数据的 HelloResponse 类:

public class HelloResponse
{public string Message { get; set; }
}

用于约定服务端和客户端行为的 IGreeter 接口:

public interface IGreeter
{Task<HelloResponse> SayHelloAsync(HelloRequest request);
}

接下来和中篇一样,通过建立连接、发送请求、接收请求、断开连接这四个步骤演示和讲解一个完整的基于 WebSocket 的 RPC 通讯示例。

建立连接

上一篇讲到要实现 JSON-RPC 协议的通讯,要求传输管道必须是全双工的。而 WebSocket 就是标准的全双工通讯,所以自然可以用来实现 JSON-RPC 协议的通讯。.NET 本身就有现成的 WebSocket 实现,所以在建立连接阶段和 StreamJsonRpc 没有关系。我们只需要把 WebSocket 通讯管道架设好,然后再使用 StreamJsonRpc 来发送和接收请求即可。

客户端使用 WebSocket 建立连接比较简单,使用 ClientWebSocket 来实现,代码如下:

using (var webSocket = new ClientWebSocket())
{Console.WriteLine("正在与服务端建立连接...");var uri = new Uri("ws://localhost:5000/rpc/greeter");await webSocket.ConnectAsync(uri, CancellationToken.None);Console.WriteLine("已建立连接");
}

服务端建立 WebSocket 连接最简单的方法就是使用 ASP.NET Core,借助 Kestrel 和 ASP.NET Core 的中间件机制可以轻松搭建基于 WebSocket 的 RPC 服务。只要简单的封装还可以实现同一套代码同时提供 RPC 服务和 Web API 服务。

首先在服务端项目的 Startup.cs 类的 Configure 方法中引入 WebSocket 中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseRouting();app.UseWebSockets(); // 增加此行,引入 WebSocket 中间件app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}

再新建一个 Controller 并定义一个 Action 用来路由映射 WebSocket 请求:

public class RpcController : ControllerBase
{...[Route("/rpc/greeter")]public async Task<IActionResult> Greeter(){if (!HttpContext.WebSockets.IsWebSocketRequest){return new BadRequestResult();}var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();...}
}

这里的 Greeter 提供的服务既能接收 HTTP 请求也能接收 WebSocket 请求。HttpContext 中的 WebSockets 属性是一个 WebSocketManager 对象,它可以用来判断当前请求是否为一个 WebSocket 请求,也可以用来等待和接收 WebSocket 连接,即上面代码中的 AcceptWebSocketAsync 方法。另外客户端的 WebSocket 的 Uri 路径需要与 Router 指定的路径对应。

连接已经建立,现在到了 StreamJsonRpc 发挥作用的时候了。

发送请求

客户端通过 WebSocket 发送请求的方式和前一篇讲的 Stream 方式是一样的。还记得前一篇讲到的 JsonRpc 类的 Attach 静态方法吗?它告诉 StreamJsonRpc 如何传输数据,并返回一个用于调用 RPC 的客户端,它除了可以接收 Stream 参数外还有多个重载方法。比如:

public static T Attach<T>(Stream stream);
public static T Attach<T>(IJsonRpcMessageHandler handler);

第二个重载方法可以实现更灵活的 Attach 方式,你可以 Attach 一个交由 WebSocket 传输数据的管道,也可以 Attach 给一个自定义实现的 TCP 全双工传输管道(此方式本文不讲,但文末会直接给出示例)。现在我们需要一个实现了 IJsonRpcMessageHandler 接口的处理程序,StreamJsonRpc 已经实现好了,它是 WebSocketMessageHandler 类。通过 Attach 该实例,可以拿到一个用于调用 RPC 服务的对象。代码示例如下:

Console.WriteLine("开始向服务端发送消息...");
var messageHandler = new WebSocketMessageHandler(webSocket);
var greeterClient = JsonRpc.Attach<IGreeter>(messageHandler);
var request = new HelloRequest { Name = "精致码农" };
var response = await greeterClient.SayHelloAsync(request);
Console.WriteLine($"收到来自服务端的响应:{response.Message}");

你会发现,定义客户端和服务端契约的好处是可以实现强类型编程。接下来看服务端如何接收并处理客户端发送的消息。

接收请求

和前一篇一样,我们先定义一个 GreeterServer 类用来处理接收到的客户端消息。

public class GreeterServer : IGreeter
{private readonly ILogger<GreeterServer> _logger;public GreeterServer(ILogger<GreeterServer> logger){_logger = logger;}public Task<HelloResponse> SayHelloAsync(HelloRequest request){_logger.LogInformation("收到并回复了客户端消息");return Task.FromResult(new HelloResponse{Message = $"您好, {request.Name}!"});}
}

同样,WebSocket 服务端也需要使用 Attach 来告诉 StreamJsonRpc 数据如何通讯,而且使用的也是 WebSocketMessageHandler 类,方法与客户端类似。在前一篇中,我们 Attach 一个 Stream 调用的方法是:

public static JsonRpc Attach(Stream stream, object? target = null);

同理,我们推测应该也有一个这样的静态重载方法:

public static JsonRpc Attach(IJsonRpcMessageHandler handler, object? target = null);

可惜,StreamJsonRpc 并没有提供这个静态方法。既然 Attach 方法返回的是一个 JsonRpc 对象,那我们是否可以直接实例化该对象呢?查看该类的定义,我们发现是可以的,而且有我们需要的构造函数:

public JsonRpc(IJsonRpcMessageHandler messageHandler, object? target);

接下来就简单了,一切和前一篇的 Stream 示例都差不多。在 RpcController 的 Greeter Action 中实例化一个 JsonRpc,然后开启消息监听。

public class RpcController : ControllerBase
{private readonly ILogger<RpcController> _logger;private readonly GreeterServer _greeterServer;public RpcController(ILogger<RpcController> logger, GreeterServer greeterServer){_logger = logger;_greeterServer = greeterServer;}[Route("/rpc/greeter")]public async Task<IActionResult> Greeter(){if (!HttpContext.WebSockets.IsWebSocketRequest){return new BadRequestResult();}_logger.LogInformation("等待客户端连接...");var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();_logger.LogInformation("已与客户端建立连接");var handler = new WebSocketMessageHandler(socket);using (var jsonRpc = new JsonRpc(handler, _greeterServer)){_logger.LogInformation("开始监听客户端消息...");jsonRpc.StartListening();await jsonRpc.Completion;_logger.LogInformation("客户端断开了连接");}return new EmptyResult();}
}

看起来和我们平时写 Web API 差不多,区别仅仅是对请求的处理方式。但需要注意的是,WebSocket 是长连接,如果客户端没有事情可以处理了,最好主动断开与服务端的连接。如果客户客户没有断开连接,执行的上下文就会停在 await jsonRpc.Completion 处。

断开连接

通常断开连接是由客户端主动发起的,所以服务端不需要做什么处理。服务端响应完消息后,只需使用 jsonRpc.Completion 等待客户端断开连接即可,上一节的代码示例中已经包含了这部分代码,就不再累述了。如果特殊情况下服务端需要断开连接,调用 JsonRpc 对象的 Dispose 方法即可。

不管是 Stream 还是 WebSocket,其客户端对象都提供了 Close 或 Dispose 方法,连接会随着对象的释放自动断开。但最好还是主动调用 Close 方法断开连接,以确保服务端收到断开的请求。对于 ClientWebSocket,需要调用 CloseAsync 方法。客户端完整示例代码如下:

static async Task Main(string[] args)
{using (var webSocket = new ClientWebSocket()){Console.WriteLine("正在与服务端建立连接...");var uri = new Uri("ws://localhost:5000/rpc/greeter");await webSocket.ConnectAsync(uri, CancellationToken.None);Console.WriteLine("已建立连接");Console.WriteLine("开始向服务端发送消息...");var messageHandler = new WebSocketMessageHandler(webSocket);var greeterClient = JsonRpc.Attach<IGreeter>(messageHandler);var request = new HelloRequest { Name = "精致码农" };var response = await greeterClient.SayHelloAsync(request);Console.WriteLine($"收到来自服务端的响应:{response.Message}");Console.WriteLine("正在断开连接...");await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "断开连接", CancellationToken.None);Console.WriteLine("已断开连接");}Console.ReadKey();
}

在实际项目中可能还需要因异常而断开连接的情况做处理,比如网络不稳定可能导致连接中断,这种情况可能需要加入重试机制。

运行示例

由于服务端使用的是 ASP.NET Core 模板,VS 默认使用 IIS Express 启动,启动后会自动打开网页,这样看不到 Console 的日志信息。所以需要把服务端项目 WebSocketSample.Server 的启动方式改成自启动。

另外,为了更方便地同时运行客户端和服务端应用,可以把解决方案设置成多启动。右键解决方案,选择“Properties”,把对应的项目设置“Start”即可。

如果你用的是 VS Code,也是支持多启动调试的,具体方法你自行 Google。如果你用的是 dotnet run 命令运行项目可忽略以上设置。

项目运行后的截图如下:

你也可以自定义实现 TCP 全双工通讯管道,但比较复杂而且也很少这么做,所以就略过不讲了。但我在 GitHub 的示例代码也放了一个自定义全双工管道实现的示例,感兴趣的话你可以克隆下来研究一下。

该示例运行截图:

本篇总结

本文通过示例演示了如何使用 StreamJsonRpc 基于 WebSocket 数据传输实现 JSON-RPC 协议的 RPC 通讯。其中客户端和服务端有共同的契约部分,实现了强类型编程。通过示例我们也清楚了 StreamJsonRpc 这个库为了实现 RPC 通讯做了哪些工作,其实它就是在现有传输管道(Stream、WebSocket 和 自定义 TCP 连接)上进行数据通讯。正如前一篇所说,由于 StreamJsonRpc 把大部分我们不必要知道的细节做了封装,所以在示例中感觉不到 JSON-RPC 协议带来的统一规范,也没看到具体的 JSON 格式的数据。其实只要遵循了 JSON-RPC 协议实现的客户端或服务端,不管是用什么语言实现,都是可以互相通讯的。

希望这三篇关于 StreamJsonRpc 的介绍能让你有所收获,如果你在工作中计划使用 StreamJsonRpc,这几篇文章包括示例代码应该有值得参考的地方。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308758.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core Blazor 初探之 Blazor Server

上周初步对Blazor WebAssembly进行了初步的探索(ASP.NET Core Blazor 初探之 Blazor WebAssembly)。这次来看看Blazor Server该怎么玩。Blazor ServerBlazor 技术又分两种&#xff1a;Blazor WebAssemblyBlazor ServerBlazor WebAssembly上次已经介绍过了&#xff0c;这次主要来…

[SpringSecurity]web权限方案_用户认证_设置用户名密码

设置登陆的用户名和密码 第一种方式&#xff1a;通过配置文件 spring.security.user.nameatguigu spring.security.user.passwordatguigu第二种方式&#xff1a;通过配置类 package com.atguigu.securitydemo1.config;import org.springframework.context.annotation.Bean; i…

[SpringSecurity]web权限方案_用户认证_查询数据库完成认证

#mysql 数据库连接 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.datasource.urljdbc:mysql://localhost:3306/demo?serverTimezoneUTC spring.datasource.usernameroot spring.datasource.passwordrootpackage com.atguigu.securitydemo1.config;i…

.Net Core 2.2升级3.1的避坑指南

写在前面微软在更新.Net Core版本的时候&#xff0c;动作往往很大&#xff0c;使得每次更新版本的时候都得小心翼翼&#xff0c;坑实在是太多。往往是悄咪咪的移除了某项功能或者组件&#xff0c;或者不在支持XX方法&#xff0c;这就很花时间去找回需要的东西了&#xff0c;下面…

[SpringSecurity]web权限方案_用户认证_自定义用户登录页面

在配置类中实现相关的配置 Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin() //自定义自己编写的登陆页面.loginPage("/login.html") //登陆页面设置.loginProcessingUrl("/user/login") //登陆访问路径.defa…

Asp.Net Core Blazor之容器部署

写在前面Docker作为开源的应用容器引擎&#xff0c;可以让我们很轻松的构建一个轻量级、易移植的容器&#xff0c;通过Docker方式进行持续交付、测试和部署&#xff0c;都是极为方便的&#xff0c;并且对于我们开发来说&#xff0c;最直观的优点还是解决了日常开发中的环境配置…

[SpringSecurity]web权限方案_用户授权_基于权限访问控制_基于角色访问控制_hasAuthority和hasAnyAuthority_hasRole和hasAnyRole

基于角色或权限进行访问控制 hasAuthority 方法 如果当前的主体具有指定的权限&#xff0c;则返回 true,否则返回 false 在配置类设置当前访问地址有哪些 Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin() //自定义自己编写的登…

.Net Core WebAPI + Axios +Vue 实现下载与下载进度条

写在前面老板说&#xff1a;系统很慢&#xff0c;下载半个小时无法下载&#xff0c;是否考虑先压缩再给用户下载&#xff1f;本来是已经压缩过了&#xff0c;不过第一反应应该是用户下的数量多&#xff0c;导致压缩包很大&#xff0c;然后自己测试发现&#xff0c;只是等待的时…

[SpringSecurity]web权限方案_用户授权_自定义403页面

自定义403页面 unauth.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><h1>没有访问权限</h1></body> </html>配置类…

三分钟Docker-环境搭建篇

如题目显示&#xff0c;三分钟让你学会在windows上安装docker环境&#xff0c;开启docker之旅的第一步。安装前要求Windows 10 64位&#xff1a;专业版&#xff0c;企业版或教育版&#xff08;内部版本16299或更高版本&#xff09;。必须启用Hyper-V控制面板->程序和功能-&g…

[SpringSecurity]web权限方案_用户注销

用户注销 在配置类中添加退出映射地址 //退出http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();测试 修改配置类&#xff0c;登陆成功之后跳转到成功页面 在成功页面添加超链接&#xff0c;写设置退出路径 success.htm…

骚年快答 | 为何微服务项目都使用单体代码仓库?

【答疑解惑】| 作者 / Edison Zhou这是恰童鞋骚年的第265篇原创内容之前在学习微软的示例eShopOnContainers时发现它使用的是单体代码仓库库&#xff0c;之后又发现大家在进行微服务项目开发时也都在使用单体代码仓库。问题来了&#xff0c;为啥要微服务项目都要使用单体仓库&a…

[SpringSecurity]web权限方案_自动登陆_原理分析和具体实现

自动登陆 1.cookie技术 2.安全框架机制实现自动登陆 这里我们使用安全框架机制实现自动登陆技术 实现原理 具体实现 第一步 创建数据库 CREATE TABLE persistent_logins (username varchar(64) NOT NULL,series varchar(64) NOT NULL,token varchar(64) NOT NULL,last_us…

[SpringSecurity]web权限方案_CSRF功能

CSRF CSRF功能默认是已经打开了&#xff01; 具体过程可以阅读CsrfFilter这个过滤器的源码 CSRF 理解 在登录页面添加一个隐藏域 <input type"hidden"th:if"${_csrf}!null"th:value"${_csrf.token}"name"_csrf "/>关闭安全…

[SpringSecurity]web权限方案_用户授权_注解使用

注解使用 Secured 判断用户是否具有角色&#xff0c;可以访问方法&#xff0c;另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。 使用注解先要开启注解功能&#xff01; 启动类(配置类)开启注解 EnableGlobalMethodSecurity(securedEnable true) 在controller的…

我和ABP vNext 的故事

Abp VNext是Abp的.NET Core 版本&#xff0c;但它不仅仅只是代码重写了。Abp团队在过去多年社区和商业版本的反馈上做了很多的改进。包括性能、底层的框架设计&#xff0c;它融合了更多优雅的设计实践。不管你是自己需要快速上手项目、或者是公司的研发团队没有足够的能力去完整…

微软为 Visual Studio 推出新的 Razor 编辑器

随着 Visual Studio 最新版本的发布&#xff0c;微软推出了一款新的 Razor 编辑器&#xff0c;用于使用 MVC、Razor Pages 和 Blazor 进行本地开发。该工具目前还处于实验状态。Razor 是一种基于 HTML 和 C# 的模板语言&#xff0c;可以用来为 .NET Web 应用程序创建动态内容。…

禁用了云服务器的网卡怎么办?

点击上方关注“汪宇杰博客” ^_^导语我们平时管理云服务器时&#xff0c;难免误操作把网卡给禁用了&#xff0c;于是再也无法远程连接了。这时候怎么办呢&#xff1f;如果有虚拟机快照&#xff0c;能够恢复到上一个良好的时刻&#xff0c;但通常会损失这个时间段内的数据和应用…

[SpringBoot2]@MatrixVariableUrlPathHelper

场景 页面开发&#xff0c;cookie禁用了&#xff0c;session里面的内容怎么使用&#xff1a; session.set(a,b)—>jessionid—>cookie—>每次发请求携带 此时cookie禁用了&#xff0c;我们要怎么得到session里面的内容呢&#xff1f; url重写&#xff1a;/abc;jse…

WebBenchmark之动态数据测试

对于很多WebApi管理工具来说&#xff0c;针对接口的性能测试都拿固定的数据进行一个循环式的测试&#xff1b;这种测试只能确保当前数据下的有效性&#xff0c;但在性能测试中往往需要压测不同的数据分布&#xff0c;这样能够更准确地反映在不同数据下系统的处理能力。WebBench…