OpenRobot.Framework.Communication
1.0.0
See the version list below for details.
dotnet add package OpenRobot.Framework.Communication --version 1.0.0
NuGet\Install-Package OpenRobot.Framework.Communication -Version 1.0.0
<PackageReference Include="OpenRobot.Framework.Communication" Version="1.0.0" />
<PackageVersion Include="OpenRobot.Framework.Communication" Version="1.0.0" />
<PackageReference Include="OpenRobot.Framework.Communication" />
paket add OpenRobot.Framework.Communication --version 1.0.0
#r "nuget: OpenRobot.Framework.Communication, 1.0.0"
#:package OpenRobot.Framework.Communication@1.0.0
#addin nuget:?package=OpenRobot.Framework.Communication&version=1.0.0
#tool nuget:?package=OpenRobot.Framework.Communication&version=1.0.0
OpenRobot.Framework.Communication
MQTT 连接与 IPC 跨进程通讯通用库(net8.0,单包内含 Mqtt / Ipc 两个独立命名空间)。
- MQTT:连接控制 + 自动重连 + 重连后重订阅 + 按序补发;订阅以
(Topic, Handler)批量注册,收包经Channel消费队列派发;QoS / Retain / MQTT5 ResponseTopic 与 CorrelationData 可配。 - IPC:一个服务端 + 多个客户端。服务端按握手
ClientId维护会话表,定向发送 / 广播 / 单会话断开互不影响;Client 与 Session 接口同构,双向「单向发送(不等待返回)」与「请求-响应(带超时)」都可用;传输可在命名管道 / TCP 之间切换。 - 协议栈类型(MQTTnet)不出现在公共 API:换掉 MQTTnet 不影响调用方。
- 日志全部经
ICommunicationLogger,宿主可实现后接管输出(也支持直接用Microsoft.Extensions.Logging)。 - 无副作用:不写环境变量、不落盘、不生成持久状态;凭据只读,全部由宿主提供。
安装
<PackageReference Include="OpenRobot.Framework.Communication" Version="1.0.0" />
包由本仓库产出:dotnet pack common/OpenRobot.Framework.Communication/OpenRobot.Framework.Communication.csproj -c Release,
产物为 OpenRobot.Framework.Communication.1.0.0.nupkg(lib/net8.0 单个程序集)与配套 .snupkg。
依赖:
MQTTnet4.3.7.1207(仅内部使用)Microsoft.Extensions.Logging.Abstractions8.0.2Microsoft.Extensions.DependencyInjection.Abstractions8.0.2
快速开始
MQTT
using OpenRobot.Framework.Communication;
using OpenRobot.Framework.Communication.Mqtt;
var mqtt = new MqttClient(new MqttOptions
{
Name = "primary",
Broker = "127.0.0.1",
Port = 1883,
Logger = new ConsoleCommunicationLogger(), // 或实现 ICommunicationLogger
});
mqtt.AddSubscriptions(
// 处理完要回执:返回值非 null 即回执(带 CancellationToken 的重载用 (m, ct) => ...)
new MqttSubscription("ota/down/#", HandleOtaDownAsync),
new MqttSubscription("config/changed", msg => Console.WriteLine(msg.Payload)));
await mqtt.StartAsync(); // 非阻塞:见下方「StartAsync 不等首连」
await Task.Delay(500);
await mqtt.PublishAsync("ota/up", new { deviceId = "d1", state = "ok" });
static Task<string?> HandleOtaDownAsync(MqttMessage msg, CancellationToken ct)
=> Task.FromResult<string?>("{\"accepted\":true}");
IPC 服务端(一个端点,多个客户端)
using OpenRobot.Framework.Communication.Ipc;
await using var service = new IpcService(new IpcOptions { Name = "hub", PipePrefix = "EdgeControl" });
service.On("telemetry.report", inbound =>
{
// inbound.SourceId 是发来这条消息的客户端 ClientId
Console.WriteLine($"{inbound.SourceId}: {inbound.Message.Payload}");
return Task.CompletedTask;
});
// 需要强类型载荷时显式指定泛型参数(否则 (payload, ctx) 双参 lambda 无法推断 T)
service.On<TelemetryReport>("telemetry.report", report => HandleReportAsync(report));
service.OnRequest("api.proxy.request", async ctx =>
{
if (!await IsAuthorizedAsync(ctx.SourceId)) { await ctx.ReplyErrorAsync("unauthorized"); return; }
await ctx.ReplyAsync(new { ok = true });
});
service.ClientConnected += (_, e) => Console.WriteLine($"上线: {e.Session.ClientId} @ {e.Session.RemoteEndpoint}");
await service.StartAsync(); // 返回时已可连接
await service.SendToAsync("upgrader", "ota.dispatch", new { url = "https://..." });
await service.BroadcastAsync("config.changed", new { revision = 42 });
var reply = await service.RequestAsync("upgrader", "api.proxy.request", new { path = "/x" },
TimeSpan.FromSeconds(30));
IPC 客户端(独立进程)
await using var client = new IpcClient(new IpcOptions
{
Name = "upgrader",
ClientId = "upgrader", // 服务端按它区分会话
PipePrefix = "EdgeControl",
DefaultRequestTimeout = TimeSpan.FromSeconds(60),
});
client.On<OtaDispatch>("ota.dispatch", async task => await RunAsync(task)); // 不关心来源
await client.StartAsync();
// 客户端也能反向往服务端发请求(双向对称)
var cfg = await client.RequestAsync("config.get", new { key = "revision" });
API 速查
IMqttClient
| 成员 | 说明 |
|---|---|
Name |
连接名(MqttOptions.Name,多连接区分日志用) |
IsConnected / State |
是否已连接 / MqttConnectionState(Disconnected、Connecting、Connected、Reconnecting、Failed、Stopped) |
ConnectionStateChanged |
状态转换事件,一次转换触发一次 |
StartAsync(ct) |
非阻塞启动 + 后台自动重连循环,幂等 |
ConnectAsync(ct) |
单次连接(不等自动重连);失败会抛 |
StopAsync() |
停止自动重连并断开,保留订阅注册,可再次 StartAsync |
AddSubscriptions(...) |
批量注册订阅,按 TopicFilter 去重(同 filter 只保留首个) |
SubscribeAsync(filter, ct) |
只确保订阅、不挂处理逻辑 |
UnsubscribeAsync(filter, ct) |
从注册表摘除 + 移出重订阅清单(不发 UNSUBSCRIBE,见「已知限制」) |
AddInterceptor(i) |
注册收包前置拦截器(入队前按序同步执行) |
PublishAsync(topic, payload[, options]) |
未连接或发布失败时入补发队列,重连后按序补发 |
MqttSubscription 四个构造重载:Func<MqttMessage, CancellationToken, Task<string?>> 与 Func<MqttMessage, Task<string?>>(返回值非 null 且消息带 ResponseTopic 时自动回执)、Func<MqttMessage, Task>、Action<MqttMessage>(后两者不回复)。
带
Task<string?>的两个重载必须都在:只有三参版时,一参 lambdam => Task.FromResult<string?>(json)会因元数对不上而静默绑定到「不回执」的Func<MqttMessage, Task>(Task<string?>可隐式转Task),回执凭空消失且不报错。
IIpcService(服务端)
| 成员 | 说明 |
|---|---|
Name / IsListening / ClientIds |
端点名 / 是否监听中 / 在线客户端 ID 快照 |
ClientConnected / ClientDisconnected |
会话建立 / 断开(含被新连接顶掉,每会话恰好一次) |
StartAsync(ct) / StopAsync() |
返回时端点已可连接;停止会断开全部会话(保留处理器,可重启) |
GetSession(id) / GetSessions() |
按 ClientId 取会话 / 取全部会话快照 |
SendToAsync(id, ...) |
定向单向发送;客户端不在线返回 false(不缓存) |
BroadcastAsync(...) |
广播给全部在线会话;离线跳过,单会话失败不影响其余 |
RequestAsync(id, ...) |
定向请求-响应;不在线或超时返回 null(不抛) |
On / OnRequest(继承 IIpcMessageHandlers) |
服务级处理器,对所有会话生效(含之后才连上的客户端);会话级同名处理器优先 |
IIpcClient / IIpcSession(共用 IIpcEndpoint)
| 成员 | 说明 |
|---|---|
Name |
Client 侧 = 自己的 ClientId;Session 侧 = 该客户端的 ClientId |
IsConnected / State / ConnectionStateChanged |
连接状态与状态事件(客户端每次重连成功都会触发) |
SendAsync(message) / SendAsync<T>(type, payload) |
单向发送:仅入队即返回(ValueTask),队列满丢最旧 + Warn;未连接时保留、连上后按序发出 |
RequestAsync(...) |
请求-响应;超时或失败返回 null(不抛) |
On / OnRequest |
注册处理器(Session 侧仅对该客户端生效,且优先于服务级) |
ClientId / ReconnectAttempts |
仅 IIpcClient:握手标识 / 当前连续重连次数 |
RemoteEndpoint / ConnectedAt / Hello |
仅 IIpcSession:对端地址 / 握手完成时间 / 握手载荷 |
处理器内可用 IpcInboundMessage.SourceId 判断来源;请求处理器用 IpcRequestContext.ReplyAsync / ReplyErrorAsync 回传(ReplyErrorAsync 回 {"success":false,"error":...},请求方快速失败而非等超时)。
__ipc.前缀由库独占(握手帧__ipc.hello),业务消息类型不得占用。
DI 集成
services.AddOpenRobotCommunication(o =>
{
o.Logger = myLogger; // 全局日志(可选)
o.AddMqtt("primary", m => { m.Broker = "broker.local"; m.LogTag = "Mqtt.primary"; });
o.AddIpcService("hub", i => { i.Name = "hub"; i.PipePrefix = "EdgeControl"; i.MaxClients = 32; });
o.AddIpcClient("upgrader", i => { i.Name = "upgrader"; i.ClientId = "upgrader"; });
});
var client = sp.GetRequiredKeyedService<IMqttClient>("primary");
await client.StartAsync();
- 每个 MQTT 连接 = keyed singleton
IMqttClient;每个 IPC 端点 = keyed singletonIIpcClient/IIpcService。 - 不自动连接 / 监听:注册只构造实例,由宿主在适当时机调用
StartAsync。 - 日志优先级:
MqttOptions/IpcOptions.Logger>CommunicationOptions.Logger> 容器的ILoggerFactory> 静默。 - 容器必须异步释放:三个服务都是
IAsyncDisposable且不实现IDisposable,同步ServiceProvider.Dispose()会抛InvalidOperationException("type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.")。用await using var sp = services.BuildServiceProvider();。
配置项
MqttOptions
| 项 | 默认 | 说明 |
|---|---|---|
Name |
"default" |
连接名(日志 + MqttMessage.ClientName) |
LogTag |
null → "Mqtt" |
日志 tag |
Broker / Port |
"" / 1883 |
broker 地址与端口 |
ClientId / Username / Password |
null |
显式凭据(非空即生效,优先于 CredentialProvider) |
KeepAliveSeconds |
60 |
心跳间隔 |
ConnectTimeoutMs |
10000 |
连接建立超时 |
UseTls / AllowUntrustedCertificate |
false / false |
TLS 开关与跳过证书校验(内网用,生产应关闭) |
DefaultQuality |
AtMostOnce |
发布默认 QoS |
CleanSession |
true |
不依赖 broker 持久会话,重连靠客户端侧重订阅 + 补发 |
ReconnectDelayMs |
5000 |
重连检测间隔 |
MaxReconnectAttempts |
0 |
0 = 不限;达上限置 Failed 并停止重连 |
PendingPublishCapacity |
10000 |
补发队列上限(超限丢最旧 + Warn) |
PendingPublishMaxRetry |
3 |
单条最大补发次数(超限丢弃 + Error) |
DispatcherQueueCapacity |
10000 |
消费队列上限(满丢最旧 + Warn) |
DispatcherConsumerCount |
1 |
1 保证同连接内按到达顺序处理;>1 提速但放弃顺序保证 |
CredentialProvider |
null |
IMqttCredentialProvider,显式凭据未配置时调用 |
Logger |
null |
日志实现(null 静默) |
IpcOptions(Service 与 Client 共用)
| 项 | 默认 | 说明 |
|---|---|---|
Name |
"" |
Service 为监听名;Client 为默认标识名(见 ClientId) |
LogTag |
null → "Ipc" |
日志 tag |
Transport |
NamedPipe |
NamedPipe(Windows 管道 / Linux UDS)或 Tcp |
PipePrefix |
"OpenRobot" |
管道名 / socket 文件前缀 |
TcpHost / TcpPort |
"127.0.0.1" / 0 |
Transport = Tcp 时生效 |
ClientId |
null |
Client 角色专用:为空取 Name,仍为空则进程内生成 Guid |
ReconnectDelayMs |
3000 |
Client 重连间隔 |
ConnectTimeoutMs |
10000 |
连接建立超时 |
HandshakeTimeoutMs |
10000 |
Service 等待 hello 首帧的时限,超时关连接 |
MaxClients |
128 |
Service 最大并发会话数(超出拒绝新连接 + Warn)。命名管道下实际封顶 252 |
OutboundQueueCapacity |
10000 |
单向发送队列上限(满丢最旧 + Warn) |
DefaultRequestTimeout |
30s |
RequestAsync 未显式指定超时时的默认值 |
MaxFrameBytes |
64 MB |
单帧最大字节数(防御异常长度) |
Json |
null → IpcJson.Default |
载荷序列化配置(冻结为只读,运行期不可改) |
TransportOverride |
null |
自定义 IIpcTransport(测试 / 自定义传输;非 null 时忽略上面四个传输项) |
Logger |
null |
日志实现(null 静默) |
写失败一律拆连接、不在原连接上重试:写失败可能已在线上留下半截帧,重试会把新帧追加进半截帧导致对端静默解析错乱。在途消息保留,由下一条连接优先补发。
平台说明
- Windows 命名管道:端点名为
{PipePrefix}.{Name}(如EdgeControl.hub),保留原大小写;管道名不区分大小写。同一服务端的管道实例数受系统上限 254 约束(库会为「在途 Accept」与「待拒绝连接」各留 1 个实例,故会话数实际封顶 252)。需要更多并发会话请改用Transport = Tcp。 - Linux / macOS:
System.IO.Pipes自动映射为 Unix Domain Socket,端点名为/tmp/{prefix}.{name}.sock(转小写——UDS 区分大小写,且路径总长受系统上限约 108 字节约束)。PipePrefix与Name应保持简短,过长会导致连接失败。 - TCP:跨机器可用,但本期为明文传输,无 TLS。仅建议在受信内网使用;需要跨不可信网络时应在链路上自行加隧道 / 加密,或等待后续 TLS 扩展点。
- 传输可测性:
IIpcTransport是接缝,InMemoryTransport用于单进程内的确定性测试(TransportOverride注入)。
已知限制
UnsubscribeAsync不发 UNSUBSCRIBE:内部 broker 接缝(IMqttBrokerConnection)只提供 Connect / Disconnect / Publish / Subscribe,没有 Unsubscribe。因此"注销订阅"只做「从注册表摘除 + 移出重订阅清单」——broker 可能仍在投递,但注册表中已无匹配项,派发器只记 Warn 而不调用任何处理器。需要真实退订需先扩接缝。MqttClient.StartAsync不等首次连接:它只启动后台重连循环,首连失败不向上抛。await StartAsync()返回后IsConnected仍可能为 false。需要「连上再继续」请用ConnectAsync,或订阅ConnectionStateChanged等Connected。宿主的启动就绪判断必须基于State/ConnectionStateChanged。- 容器需异步释放:见「DI 集成」一节。
- MQTT 消息顺序仅在
DispatcherConsumerCount = 1(默认)时保证;提高并发数即放弃该保证。 - IPC 单向发送不保证送达:
SendAsync只入队即返回,队列满时丢最旧(记 Warn);需要确认送达请用RequestAsync。 - IPC 服务端不缓存离线消息:
SendToAsync对离线客户端返回false;BroadcastAsync跳过离线会话。 - IPC 握手不可省略:客户端连接后第一帧必须是
__ipc.hello,首帧非 hello /ClientId为空 / 帧非法 / 超HandshakeTimeoutMs的连接一律被关闭且不建会话。 - 凭据只读:库不生成、不持久化、不写环境变量。
ClientId三级回退中最后一级是进程内随机 Guid,多实例场景应显式配置ClientId。 MqttClient公开构造只有(MqttOptions, IMqttCredentialProvider?):协议栈工厂与 logger 的注入构造为internal(供同程序集与测试使用),宿主无法替换协议栈实现。
版本历史
1.0.0(首次发布)
MQTT
- 连接控制:
StartAsync(非阻塞 + 自动重连)/ConnectAsync/StopAsync,显式状态机与ConnectionStateChanged事件 - 断线自动重连,重连后重订阅全部 filter(幂等,不重复 SUBSCRIBE)+ 按序补发(上限 / 重试次数可配)
- 订阅以
(Topic, Handler)批量注册(TopicFilter去重),收包经Channel消费队列派发,命中全部匹配订阅(按 filter 明确度降序) - 处理器返回值非 null 且消息带
ResponseTopic时自动回执(带CorrelationData);抛异常自动回错误载荷 IMqttMessageInterceptor收包前置钩子(存档 / ACK 等)- QoS / Retain / ResponseTopic / CorrelationData 透传;
IMqttCredentialProvider只读凭据接入
IPC
- 一个服务端 + 多个客户端:按握手
ClientId建会话表,定向发送 / 广播 / 请求-响应 / 单会话断开互不影响;重复ClientId以新连接为准 IIpcClient与IIpcSession同构(共用IIpcEndpoint),双向通讯天然对称- 单向发送走
Channel待发队列(不等待写出);请求-响应带超时(超时返回null,不抛) - 客户端断线自动重连,每次重连重发握手帧
- 传输可切命名管道(Windows)/ UDS(Linux、macOS)/ TCP(明文),
IIpcTransport为可替换接缝 - 处理器支持服务级(对所有会话生效)/ 会话级(优先),4 种注册重载(含强类型载荷)
通用
ICommunicationLogger日志抽象(含ConsoleCommunicationLogger/MicrosoftCommunicationLogger/NullCommunicationLogger)AddOpenRobotCommunication()DI 扩展(keyed singleton,不自动连接)- 公共 API 不含 MQTTnet / 源项目类型
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- MQTTnet (>= 4.3.7.1207)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.