.Net 8.0 下的新RPC,IceRPC之试试的新玩法打洞

news/2024/10/15 10:21:07

作者引言

很高兴啊,我们来到了IceRPC之试试的新玩法"打洞",让防火墙哭去吧

试试RPCs的新玩法"打洞"

比较典型的玩法:RPC数据流从客户端流向服务端,现在来尝试用IceRPC来玩一个新的花样"打洞"。

概述

对于 IceRPC,客户端是发起连接的实体, 而服务器是接受连接的实体。

建立连接后,通常会从客户端到服务端生成RPCs通道:

  1. 客户端创建请求,并将该请求发送到服务器
  2. 服务端接受此请求,并将此请求发送到已提供的"服务实现"
  3. 服务端返回响应,IceRPC 将此响应带回客户端

A RPC from client to server

IceRPC 提供的几乎所有示例,都是这个客户端到服务器的模式。尽管如此,我们可以使用 IceRPC 试试另一种发送方式。

获取调用器invoker

使用IceRPC,需要一个调用器invoker来发送请求,并接收相应的响应。 IceRPC(C#)提供了 ClientConnection 和 ConnectionCache 类, 用来建立网络连接的两个"终端"调用器invoker。 当使用这些调用器之一发送请求时, 请求会从底层连接的客户端,传输到服务器端。

"终端"调用器是实际发送请求,并接收响应的调用器invoker。 相比之下,管道Pipeline和拦截器interceptors是非终端调用器:他们处理请求和响应,但需要实际的调用者,来完成这项工作。

服务端到客户端调用所需的调用器invokerIConnectionContext.Invoker。 从传入请求中检索连接上下文。 如下所示:


// In a dispatcher implementation
public ValueTask<OutgoingResponse> DispatchAsync(IncomingRequest request,CancellationToken cancellationToken)
{// The invoker represents the connection over which we received this request.IInvoker invoker = request.ConnectionContext.Invoker;...
}

如果正在使用 Slice 实施 IceRPC 服务, 需要在调度管道中,安装调度信息中间件UseDispatchInformation(),以将此连接上下文公开为 IDispatchInformationFeature 的一部分。 如下所示:


// Router setup in composition root / main Program
Router router = new Router().UseDispatchInformation().Map<IGreeterService>(new Chatbot());// Slice Service implementation
public ValueTask<string> GreetAsync(string name,IFeatureCollection features,CancellationToken cancellationToken)
{IDispatchInformationFeature? dispatchInfo = features.Get<IDispatchInformationFeature>();Debug.Assert(dispatchInfo is not null); // installed by the DispatchInformation middleware// The invoker represents the connection over which we received this Greet request.IInvoker invoker = dispatchInfo.ConnectionContext.Invoker;...
}

然后,一旦有了终端调用器,就可以使用该调用器发送请求。 如果使用 Slice,将使用此调用器构建 Slice 代理,然后使用此代理调用操作。 如下所示:


IInvoker invoker = ...; // some invoker
var alarm = new AlarmProxy(invoker); // use Alarm's default path.
await alarm.SomeOpAsync();

推送通知用例

使用IceRPC开发推送通知功能: 当服务端中发生某些事件时(从服务端"推送push"),客户端希望收到来自服务端的通知。 它不想定期发送请求,来检查是否发生此事件("拉取pull")。

服务器无法打开与客户端的连接(由于防火墙或其他网络限制), 因此,我们希望使用现有的客户端到服务器连接来执行这些通知。

Push notification example

我们可以使用以下 Slice 接口对这种交互进行模拟:


// Implemented by a service in the client
interface Alarm {abnormalTemperature(value: float32) -> string
}// Implemented by a service in the server
interface Sensor {getCurrentTemperature() -> float32// Monitors the current temperature and calls `abnormalTemperature` on Alarm when the// current temperature moves outside [low..high].monitorTemperature(low: float32, high: float32)
}

并实现传感器Sensor服务如下:


// Implementation of Sensor service
public ValueTask MonitorTemperatureAsync(float low,float high,IFeatureCollection features,CancellationToken cancellationToken)
{IDispatchInformationFeature? dispatchInfo = features.Get<IDispatchInformationFeature>();Debug.Assert(dispatchInfo is not null); // installed by DispatchInformation middleware// We use Alarm's default path for this proxy.var alarm = new AlarmProxy(dispatchInfo.ConnectionContext.Invoker);// We enqueue the information and monitor the temperature in a separate task._monitor.Add(low, high, alarm);
}

在客户端中,我们实现警报Alarm服务,将其映射到路由器Router内,然后在客户端连接的选项中设置调度程序ClientConnection:


// Client side
Router router = new Router.Map<IAlarmService>(new PopupAlarm()); // use Alarm's default path.await using var connection = new ClientConnection(new ClientConnectionOptions{Dispatcher = router, // client-side dispatcherServerAddress = new ServerAddress(new Uri("icerpc://..."))});// Use connection as usual to create a SensorProxy and call MonitorTemperatureAsync.[SliceService]
internal partial class PopupAlarm : IAlarmService
{public ValueTask<string> AbnormalTemperatureAsync(float value,IFeatureCollection features,CancellationToken cancellationToken){// Show a popup with the abnormal temperature....return new("Roger"); // acknowledge alarm}
}

底层调用器invoker

连接上下文提供的调用器是绑定到特定网络连接的"原始"调用器。 如果网络连接因任何原因关闭,则该调用器将不再可用。 当使用此类调用器时,需要自己处理此类连接故障。 ClientConnectionConnectionCache更易于使用,因为它们可以根据需要,重建底层连接。

替代方案:字节流拉动Stream pulls

基于 HTTP 的 RPC 框架,比如 gRPC,不能让 RPC 反过来推送,这里还有一个替代方案!

如果无法将通知推送到客户端,可以从客户端拉取这些通知。 它在网络上使用大约相同数量的字节,但要不优雅。 如下所示:


// Implemented by a service in the server
interface Sensor {getCurrentTemperature() -> float32// Monitors the current temperature and streams back any value outside// the acceptable range.monitorTemperature(low: float32, high: float32) -> stream float32
}

通过这种方法,客户端会迭代 monitorTemperature 返回的流:每个新值都是一个新通知。

独特优势

RPC可以得到另一方式的回应。 该回应告诉调用者caller,请求已由客户端中的服务,成功发送和处理。

如果只是将回应流stream responses回您的客户端, 服务器不用任何确认: 它就知道产生了回应流,也可能知道该流已成功通过了网络,但它不知道客户端是否成功接收并处理了该流。

本质上,流响应类似于从服务器到客户端的单向请: 语法不同,但没有功能差异。另一方面,流响应无法模拟从服务器到客户端的双向 RPC。

云路由用例(分布式)

从服务器到客户端制作 RPC 的另一个用例是通过云服务路由, 如图所示 Thermostat【git源码】例子。

这个例子是现实世界中,常见难题的一个简化版本: 比如你有一个客户端应用 (像手机应用app) 需要连接一个设备 (比如恒温器thermostat)。 该设备位于防火墙后面,不接受传入连接。 如何建立彼此通信呢?

解决方案:引入客户端和设备都连接的中介服务器。 该服务器通常部署在"云中",并将请求从客户端路由到设备(反之亦然,如果需要)。 从客户端到设备的请求,通过客户端到服务器的连接,然后通过服务器到设备的连接。 这种方式对 IceRPC 来说,非常好处理:

Thermostat example

通过此示例,客户端可以更改恒温器thermostat上的设定点,并等待恒温器的确认:例如"确定"或故障—,因为指定的设定点太低:

Thermostat client

恒温器Thermostat示例在服务器 DeviceConnection 类中实现自己的终端调用器


/// <summary>Represents the server-side of the connection from the device to this server. This
/// connection remains valid across re-connections from the device.</summary>
internal class DeviceConnection : IInvoker
{private volatile IInvoker? _invoker;public async Task<IncomingResponse> InvokeAsync(OutgoingRequest request,CancellationToken cancellationToken = default){if (_invoker is IInvoker invoker){try{return await invoker.InvokeAsync(request, cancellationToken);}catch (ObjectDisposedException){// throw NotFound below}}throw new DispatchException(StatusCode.NotFound, "The device is not connected.");}/// <summary>Sets the invoker that represents the latest connection from the device.</summary>internal void SetInvoker(IInvoker invoker) => _invoker = invoker;
}

设备连接:从设备到服务器的最新连接。 拥有一个,在连接中留下来的终端调用器非常有用: 它允许恒温器服务器创建管道和代理,无需每次设备重新连接时,重新创建。

结论

反向调用生成 RPC 是一个强大的功能,是 IceRPC 与其他 RPC 框架区分的主要功能。 可以利用此功能构建,具有有意义的语义的网络应用程序,而且这些应用程序可以在防火墙上开心地正常工作"打洞"。

作者结语

  • 一直做,不停做,才能提升速度
  • 翻译的不好,请手下留情,谢谢
  • 如果对我有点小兴趣,如可加我哦,一起探讨人生,探讨道的世界
  • 觉得还不错的话,点个
    image

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

快捷键ctrl+`打不开vscode终端

分析 毫无疑问,是热键冲突了。 目前没有很好的热键检测手段,包括OpenArk也检测不到这个热键冲突,说实话只能借助百度,自己找那真是大海捞针。 像这种冲突,一般是全局快捷键,也就是后台的应用也能使用的快捷键,比如截图啊之类的。因为一般的快捷键是前台时才可用的。 解决…

SpringBoot 打包所有依赖

SpringBoot 项目打包的时候可以通过插件 spring-boot-maven-plugin 来 repackage 项目,使得打的包中包含所有依赖,可以直接运行。例如: <plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin&…

简单解决version GLIBC_2.34 not found,version GLIBC_2.25 not found

简单解决version GLIBC_2.34 not found,version GLIBC_2.25 not found 无需手动下载安装包编译 前言 很多博客都是要手动下载安装包进行编译升级,但这样很容易导致系统崩溃,本博文提供一个简单的方法,参考自博客1,博客2. 检查版本 strings /usr/lib64/libc.so.6 |grep GLI…

简单解决version `GLIBC_2

简单解决version GLIBC_2.34 not found,version GLIBC_2.25 not found 无需手动下载安装包编译 前言 很多博客都是要手动下载安装包进行编译升级,但这样很容易导致系统崩溃,本博文提供一个简单的方法,参考自博客1,博客2. 检查版本 strings /usr/lib64/libc.so.6 |grep GLI…

初三奥赛模拟测试5

初三奥赛模拟测试5点击查看快读快写代码 #include <cstdio>using namespace std; // orz laofudasuan // modifiednamespace io {const int SIZE = (1 << 21) + 1;char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;…

栈_单向链表

利用单向链表设计一个栈,实现“后进先出”的功能 ​ 栈内存自顶向下进行递增,其实栈和顺序表以及链式表都一样,都属于线性结构,存储的数据的逻辑关系也是一对一的。 ​ 栈的一端是封闭的,数据的插入与删除只能在栈的另一端进行,也就是栈遵循“*后进先出*”的原则。也被成…

【排课小工具】面向对象分析探索领域模型

用户向系统中输入课表模板、课程信息以及教师责任信息,系统以某种格式输出每个班级的课表。该用例中的主要参与者包括用户以及系统,除了上述两个主要参与者外,我们从该用例中抽取出可能有价值的名词:课表模板、课程、教师职责、班级以及课表。现在我们只知道下面图示的关系…

【Qt 专栏】Qt Creator 的 git 配置 上传到gitee

1.进入到Qt项目文件夹内,打开 “Git Bash Here” 2.初始化,在“Git Bash Here”中输入 git init 3.加入所有文件,在“Git Bash Here”中输入 git add . (需要注意,git add 后面还有一个点) 4.添加备注,git commit -m "备份" 5.推送本地仓库到gitee(需要事…