阅读本文大概须要 11 分钟。git
上一篇介绍了一些预备知识,包括 JSON-RPC 介绍和实现了 JSON-RPC 的 StreamJsonRpc 介绍,讲到了 StreamJsonRpc 能够经过 .NET 的 Stream 类和 WebSocket 类实现 JSON-RPC 协议的通讯。本篇就先选择其中的 Stream 类来说解,经过具体的示例讲解如何使用 StreamJsonRpc 实现 RPC 调用。github
先新建两个 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 通信要经历四个步骤:创建链接、发送请求、接收请求、断开链接,其中发送请求和接收请求能够归为数据通信,下面按照这几个步骤顺序来逐步讲解。json
使用 Stream 实现 JSON-RPC 协议的通信,要求该 Stream 必须是一个全双工 Stream(可同时接收数据和发送数据)或是一对半双工 Stream(本文不做讨论)。实现了全双工的 Stream 类在 .NET 中有 PipeStream
、NetworkStream
等,本示例用的是 NamedPipeClientStream
类和 NamedPipeServerStream
,前者用于客户端,后者用于服务端。bash
先看服务端代码示例:服务器
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
来标识区分它们。async
再来看客户端代码示例:函数
var stream = new NamedPipeClientStream(".", "StringJsonRpc", PipeDirection.InOut, PipeOptions.Asynchronous); Console.WriteLine("正在链接服务器..."); await stream.ConnectAsync(); Console.WriteLine("已创建链接!");
和服务器相似,客户端使用的是 NamedPipeClientStream
类来创建链接,在其构造参数中须要指定服务端的地址(这里用了.
表明本机)和通信管道的名称。Stream 实例经过 ConnectAsync
方法主动向服务器请求链接。spa
若是网络是通的,客户端和服务端就能成功创建链接。下面就要实现客户端和服务端之间的数据通信了,即客户端发送请求和服务端接收并处理请求。
客户端与服务端创建链接后,数据不会平白无故从一端流到另外一端,要实现两端的数据通信还须要先把通信管道架设起来,在其两端设定对应的控制和处理程序。工程上这个听起来好像不简单,但对于 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:
class Program { static async Task Main(string[] args) { int clientId = 1; while (true) { var stream = new NamedPipeServerStream("StringJsonRpc", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); Console.WriteLine("等待客户端链接..."); await stream.WaitForConnectionAsync(); Console.WriteLine($"已与客户端 #{clientId} 创建链接"); _ = ResponseAsync(stream, clientId); clientId++; } } 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(); } } public class GreeterServer { public string SayHello(string name) { Console.WriteLine($"收到【{name}】的问好,并回复了他"); return $"您好,{name}!"; } }
客户端 StreamSample.Client 下的 Program.cs:
class Program { static async Task Main(string[] args) { var stream = new NamedPipeClientStream(".", "StringJsonRpc", PipeDirection.InOut, PipeOptions.Asynchronous); Console.WriteLine("正在链接服务器..."); await stream.ConnectAsync(); Console.WriteLine("已创建链接!"); Console.WriteLine("我是精致码农,开始向服务端问好..."); var jsonRpc = JsonRpc.Attach(stream); var message = await jsonRpc.InvokeAsync<string>("SayHello", "精致码农"); Console.WriteLine($"来自服务端的响应:{message}"); Console.ReadKey(); } }
完整代码已放到 GitHub,地址为:
github.com/liamwang/StreamJsonRpcSamples
两个客户端和服务端一块儿运行的截图:
本文经过一个简单但完整的示例讲解了如何使用 StreamJsonRpc 来实现基于 JSON-RPC 协议的 RPC 调用。因为服务端和客户端都使用的是 StreamJsonRpc 库来实现的,因此在示例中感受不到 JSON-RPC 协议带来的统一规范,也没看到具体的 JSON 格式的数据。这是由于 StreamJsonRpc 库都已经帮咱们封装好了,两端都基于 C#,示例使用的也是简单的 Stream 方式,隐藏了咱们没必要关心的细节。其实只要符合 JSON-RPC 协议标准,C# 写的服务端也能够由其它语言实现的客户端来调用,反之亦然。
关注我一段时间的朋友都知道,个人文章篇幅通常不会太长,主要是方便你们利用零碎时间把它一次性看完。StreamJsonRpc 的使用远不止本文讲的这些,好比还有基于 WebSocket 进行数据传输的方式。来想经过两篇讲完,但讲了一半就已经超出了预期的篇幅长度。因此我把本文定为[中篇],若是有时间我会继续写[下篇],下篇主要会讲 StreamJsonRpc + WebSocket 的使用,并会尽可能以更贴合实际应用场景的示例来说解。