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

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

上一篇介绍了一些预备知识,包括 JSON-RPC 介绍和实现了 JSON-RPC 的 StreamJsonRpc 介绍,讲到了 StreamJsonRpc 可以通过 .NET 的 Stream 类和 WebSocket 类实现 JSON-RPC 协议的通信。本篇就先选择其中的 Stream 类来讲解,通过具体的示例讲解如何使用 StreamJsonRpc 实现 RPC 调用。

准备工作

先新建两个 Console 应用,分别命名为 StreamSample.Client 和 StreamSample.Server,并均添加 StreamJsonRpc 包引用。

mkdir StreamJsonRpcSamples                              # 创建目录
cd StreamJsonRpcSamples                                 # 进入目录
dotnet new sln -n StreamJsonRpcSamples                  # 新建解决方案
dotnet new console -n StreamSample.Client               # 建新客户端应用
dotnet new console -n StreamSample.Server               # 新建服务端应用
dotnet sln add StreamSample.Client StreamSample.Server  # 将应用添加到解决方案
dotnet add StreamSample.Client package StreamJsonRpc    # 为客户端安装 StreamJsonRpc 包
dotnet add StreamSample.Server package StreamJsonRpc    # 为服务端安装 StreamJsonRpc 包

上篇 提到了实现 JSON-RPC 通讯要经历四个步骤:建立连接、发送请求、接收请求、断开连接,其中发送请求和接收请求可以归为数据通讯,下面按照这几个步骤顺序来逐步讲解。

建立连接

使用 Stream 实现 JSON-RPC 协议的通讯,要求该 Stream 必须是一个全双工 Stream(可同时接收数据和发送数据)或才是一对半双工 Stream(本文不作讨论)。实现了全双工的 Stream 类在 .NET 中有 PipeStreamNetworkStream 等,本示例用的是 NamedPipeClientStream 类和 NamedPipeServerStream,前者用于客户端,后者用于服务端。

先看服务端代码示例:

int clientId = 1;var stream = new NamedPipeServerStream("StringJsonRpc",PipeDirection.InOut,NamedPipeServerStream.MaxAllowedServerInstances,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);Console.WriteLine("等待客户端连接...");
await stream.WaitForConnectionAsync();
Console.WriteLine($"已与客户端 #{clientId} 建立连接");

这里使用了 NamedPipeServerStream 类,其第一个构造参数指定了该 Stream 管道的名称,方便客户端使用该名称查找。其它参数就不解释了,其各自的含义可以在你编写代码时通过智能提示了解。

Stream 实例通过 WaitForConnectionAsync 来等待一个客户端连接。由于该服务端可以连接多个客户端,这里使用自增长的 clientId 来标识区分它们。

再来看客户端代码示例:

var stream = new NamedPipeClientStream(".","StringJsonRpc",PipeDirection.InOut,PipeOptions.Asynchronous);Console.WriteLine("正在连接服务器...");
await stream.ConnectAsync();
Console.WriteLine("已建立连接!");

和服务器类似,客户端使用的是 NamedPipeClientStream 类来建立连接,在其构造参数中需要指定服务端的地址(这里用了.代表本机)和通讯管道的名称。Stream 实例通过 ConnectAsync 方法主动向服务器请求连接。

如果网络是通的,客户端和服务端就能成功建立连接。下面就要实现客户端和服务端之间的数据通讯了,即客户端发送请求和服务端接收并处理请求。

数据通讯

客户端与服务端建立连接后,数据不会无缘无故从一端流到另一端,要实现两端的数据通讯还需要先把通讯管道架设起来,在其两端设定对应的控制和处理程序。工程上这个听起来好像不简单,但对于 StreamJsonRpc 来说是件非常简单的事。最简单的方法是使用 JsonRpc 类的 Attach 静态方法来架设两端的 Stream 管道,该方法返回一个 JsonRpc 实例可以用来控制数据的通讯。

对于服务端,架设管道的同时还要为管道上的请求添加监听和对应的处理程序,比如定义一个名为 GreeterServer 的类来处理“打招呼”的请求:

public class GreeterServer
{public string SayHello(string name){Console.WriteLine($"收到【{name}】的问好,并回复了他");return $"您好,{name}!";}
}

然后实例化该类,把它传给 JsonRpc 类的 Attach 静态方法:

static async Task Main(string[] args)
{..._ = ResponseAsync(stream, clientId);clientId++;
}static Task ResponseAsync(NamedPipeServerStream stream, int clientId)
{var jsonRpc = JsonRpc.Attach(stream, new GreeterServer());return jsonRpc.Completion;
}

这里我们单独定义了一个 ResponseAsync 方法用来处理客户端请求,在 Main 函数中我们不用关心该方法返回的 Task 任务,所以使用了弃元。

对于客户端也是类似的,使用 JsonRpc 类的 Attach 静态方法来完成管道架设,并调用 JsonRpc 实例的 InvokeAsync 方法向服务端发送指定请求。代码示例如下:

...
Console.WriteLine("我是精致码农,开始向服务端问好...");
var jsonRpc = JsonRpc.Attach(stream);
var message = await jsonRpc.InvokeAsync<string>("SayHello", "精致码农");
Console.WriteLine($"来自服务端的响应:{message}");

这样就实现了客户端调用服务端的方法,但客户端需要知道服务端的方法签名。这里只是为示例演示,在实际情况中,客户端和服务端需要先约定好接口,这样客户端就可以面向接口实现强类型编程,不必关心服务端处理程序的具体信息。

注意到没,从建立连接到实现数据通讯,客户端和服务端都是对应的,而且使用的类和方法都是相似的。

断开连接

当客户端或服务器端在不需要发送请求或响应请求时,则可以调用 JsonRpc 实例的 Dispose 方法断开并释放连接。

jsonRpc.Dispose();

如果需要断开连接,一般是由客户端这边发起,比如对于控制台应用按 Ctrl + C 结束任务便会断开与服务端的连接。那服务端如何知道某个客户端断开了连接呢?可以手动等待 JsonRpc 实例的 Completion 任务完成,比如:

static async Task ResponseAsync(NamedPipeServerStream stream, int clientId)
{var jsonRpc = JsonRpc.Attach(stream, new GreeterServer());await jsonRpc.Completion;Console.WriteLine($"客户端 #{clientId} 的已断开连接");jsonRpc.Dispose();await stream.DisposeAsync();
}

这里为了保险起见,我还手动把 stream 也释放掉了。

除了主动断开连接,客户端或服务器抛出未 catch 的异常也会致使连接中断,在实际情况中针对这种异常的连接中断可能需要编写重试机制,这里就不展开讨论了。

完整代码

以上为了讲解方便,代码只贴了与上下文相关的部分,最后我再把完整代码贴一下吧。

服务端 StreamSample.Server 下的 Program.cs:

客户端 StreamSample.Client 下的 Program.cs:

完整代码已放到 GitHub,地址为:

github.com/liamwang/StreamJsonRpcSamples

两个客户端和服务端一起运行的截图:

本篇总结

本文通过一个简单但完整的示例讲解了如何使用 StreamJsonRpc 来实现基于 JSON-RPC 协议的 RPC 调用。由于服务端和客户端都使用的是 StreamJsonRpc 库来实现的,所以在示例中感觉不到 JSON-RPC 协议带来的统一规范,也没看到具体的 JSON 格式的数据。这是因为 StreamJsonRpc 库都已经帮我们封装好了,两端都基于 C#,示例使用的也是简单的 Stream 方式,隐藏了我们不必关心的细节。其实只要符合 JSON-RPC 协议标准,C# 写的服务端也可以由其它语言实现的客户端来调用,反之亦然。

关注我一段时间的朋友都知道,我的文章篇幅一般不会太长,主要是方便大家利用零碎时间把它一次性看完。StreamJsonRpc 的使用远不止本文讲的这些,比如还有基于 WebSocket 进行数据传输的方式。来想通过两篇讲完,但讲了一半就已经超出了预期的篇幅长度。所以我把本文定为[中篇],如果有时间我会继续写[下篇],下篇主要会讲 StreamJsonRpc + WebSocket 的使用,并会尽量以更贴合实际应用场景的示例来讲解。

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

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

相关文章

Istio 中的多集群部署与管理

本文节选自ServiceMesher 社区出品的开源电子书《Istio Handbook——Istio 服务网格进阶实战》&#xff0c;作者钟华&#xff0c;来自腾讯云。Istio 在 1.1 后提供了两类多集群的连通的部署模式&#xff1a;1. 多控制面2. 单控制面&#xff0c;也称为 “共享控制面” 模式多控制…

认证授权方案之授权初识

1.前言回顾&#xff1a;认证授权方案之JwtBearer认证在上一篇中&#xff0c;我们通过JwtBearer的方式认证&#xff0c;了解在认证时&#xff0c;都是基于Claim的&#xff0c;因此我们可以通过用户令牌获取到用户的Claims&#xff0c;在授权过程中对这些Claims进行验证&#xff…

[数据结构]树、森林与二叉树之间的相互转换方法

树、二叉树与森林的相互转换 本文只给出树、森林与二叉树之间的转换方法&#xff0c;而详细的证明过程不在本文讨论范围之内。 树 → 二叉树 在所有兄弟结点之间加一连线。 对每个结点&#xff0c;除了保留与其长子的连线外&#xff0c;去掉该结点与其它孩子的连线。 二叉树 …

Blazor.Server以正确的方式集成Ids4

&#xff08;一个真正的以后端形式来集成认证中心的方案&#xff09;❤本文导读首先特别感谢张善友老师提供技术指导&#xff0c;源于上周我发了一篇文章《[Mvp.Blazor] 集成Ids4&#xff0c;实现统一授权认证》&#xff0c;我本来是想通过像vue框架那样&#xff0c;通过引oidc…

作为一个有理想的程序员,必读的书都有哪些?

很多程序员朋友问我&#xff1a;“哪本最具影响力的书&#xff0c;是每个程序员都应该读的&#xff1f;” 笔者从事软件开发15年&#xff0c;看过的计算机相关的书籍不下百本了&#xff0c;如果非要推荐的话&#xff0c;给大家精选以下10本&#xff0c;希望对大家有所帮助&…

认证授权方案之JwtBearer认证

1.前言回顾&#xff1a;认证方案之初步认识JWT在现代Web应用程序中&#xff0c;即分为前端与后端两大部分。当前前后端的趋势日益剧增&#xff0c;前端设备&#xff08;手机、平板、电脑、及其他设备&#xff09;层出不穷。因此&#xff0c;为了方便满足前端设备与后端进行通讯…

使用过滤器模式,让客户关怀中的代码更加干净整洁

一&#xff1a;实际场景介绍我们在给用户做订单催付通知的时候&#xff0c;会有这样的一种场景&#xff0c;用户在系统后台设置一组可以催付的规则&#xff0c;比如说订单金额大于xx元&#xff0c;非黑名单用户&#xff0c;来自哪个地区&#xff0c;已购买过某个商品&#xff0…

C++实现各种排序以及复杂度,稳定性分析

代码如下: #include<iostream> using namespace std;void Bubble_Sort(int *a, int n) {bool flag;int tmp 0;for (int i n - 1; i > 0; i--){flag false;for (int j 0; j < i; j){if (a[j] > a[j 1]){swap(a[j], a[j 1]);flag true;}}if (!flag) break…

Webapi管理和性能测试工具WebBenchmark

WebBenchmark是一款基于开源通讯组件Beetlex扩展的Webapi管理和性能测试工具&#xff0c;在传统工具中一般管理工具缺乏性能压测能力或有性能压测的缺少管理功能&#xff1b;WebBenchmark的设计目标是就管理和性能测试能力同时具备。接下来介绍一下工具的功能和使用&#xff1a…

Abstract Factory(抽象工厂)--对象创建模式

Abstract Factory &#xff08;抽象工厂&#xff09;–对象创建模式 一、意图 提供一个创建一系列相关或者相互依赖的接口&#xff0c;而无需指定它们具体的类。 二、动机 1.在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象”的创建工 作;同时&#xff0c;由于需求…

Builder(生成器)--对象创建型模式

Builder&#xff08;生成器&#xff09;–对象创建型模式 一、意图 将一个复杂的对象构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 二、动机 1.在软件系统中&#xff0c;有时候面临着“一个复杂对象”的创建工作&#xff0c;其通常由各个部分的子对…

Gartner:缺乏技术人才将影响企业数字化转型

导语大多数公司在数字化转型的阶段对所需的技能方面都处于“盲目”状态。正文随着COVID-19响应加快了数字化转型的速度和规模&#xff0c;缺乏数字化技能可能会危害人才计划不统一的公司。甚至在冠状病毒大流行之前&#xff0c;董事会就将数字/技术中断列为2020年的头等大事&am…

DEBUG org.springframework.web.servlet.DispatcherServlet - Error rendering view [org.thymeleaf.spring

报错信息如下: 报错原因: thymeleaf有一些限制&#xff0c;使用th语言&#xff0c;内容为空就会报错 改成这样解决问题:

Factory Method(工厂方法)--对象创建型模式

Factory Method&#xff08;工厂方法&#xff09;–对象创建型模式 一、意图 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。 二、动机 1.在软件系统中&#xff0c;经常面临着创建对象的工作;由于需求的变化…

全内存的redis用习惯了?那能突破内存限制类redis产品ssdb呢?

首先说一下背景&#xff0c;在双十一的时候&#xff0c;我们系统接受X宝的订单推送&#xff0c;原先的实现方式是使用 redis 的 List 作为推送数据的承载&#xff0c;在非大促的场景下&#xff0c;一切运行正常&#xff0c;内存占用大概3-4G&#xff0c;机器是16G内存。由于提前…

Prototype(原型)--对象创建模式

Prototype&#xff08;原型&#xff09;–对象创建模式 一、意图 用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象。 二、动机 1.在软件系统中&#xff0c;经常面临着“某些结构复杂的对象”的创建工作&#xff1b;由于需求的变化&#xff0c;这些…

认证授权方案之授权揭秘 (上篇)

一、前言回顾&#xff1a;认证授权方案之授权初识从上一节中&#xff0c;我们在对授权系统已经有了初步的认识和使用&#xff0c;可以发现&#xff0c;asp.net core为我们提供的授权策略是一个非常强大丰富且灵活的认证授权方案&#xff0c;能够满足大部分的授权场景。在Config…