问题背景:我有两个系统A和B,WEB客户端调用系统A的API接口,B系统和硬件进行socket连接交互。A系统也可以直接调用B系统的api接口。但是B不能调用A系统。
A系统是主系统,
B系统是子服务,硬件和B系统是一对一socket消息推送。
此时我想让硬件推送的设备状态由B系统再推送到A系统。
通过 B系统做为一个桥梁,最终实现 A<--B<--硬件。
此时你会说,直接用消息队列mq不就可以了吗?是的,这个队列可以解决目前的问题,但是对于小团队项目,研发人员不多的情况下。每增加一个中间件或者应用软件,都会增加维护的成本。况且,AB两个系统都增加了SignalR服务端。所以,用现有的基础设施,增加自己想要的功能,让一个工具发挥到极致,才是用兵之道。
画个图说明一下:
实现代码很简单:
系统AB的SignalR的服务端写法都是一样的,详细见微软文档:
https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-7.0&tabs=visual-studio
我写了一个简单的SignalR的服务端demo如下:
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
/// <summary>
/// 失去连接
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 连接上
/// </summary>
/// <returns></returns>
public override Task OnConnectedAsync()
{
return base.OnConnectedAsync();
}
/// <summary>
/// 发送消息
/// </summary>
/// <returns></returns>
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
必须将 SignalR 服务器配置为将 SignalR 请求传递给 SignalR。将以下突出显示的代码添加到 Program.cs
文件。
using SignalRChat.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");
app.Run();
服务端大致就是这个写法,不写太具体了。具体的去参看微软文档吧。
👆上面核心的代码就这3行:
using SignalRChat.Hubs;
builder.Services.AddSignalR();
app.MapHub<ChatHub>("/chatHub");
重点来了,我在微软文档没有找到 netcore api 的SignalR 客户端的demo。只找到了 wpf 、winfom 这样的客户端demo。很显然,这需要做相关改造工作。
我要实现一个 netcore api 的SignalR 客户端的demo。
在Program.cs里面写上如下代码:
当然,你也可以抽离出来。
//连接的时候传递参数Param
var param = new Dictionary<string, string> {
{ "SubscribeSIP", "1" }
};
HubConnection connection = new HubConnectionBuilder()
.WithUrl("你的服务端连接地址")
, option => { option.Headers = param; })
.WithAutomaticReconnect(new RandomRetryPolicy())
.Build();
//连接的函数
ConnectWithRetryAsync(connection, CancellationToken.None);
//监听服务端推来的消息
connection.On<string, string>("Notification", async (user, message) =>
{
await clientCallBack.Notification(user, message);
Console.WriteLine(message);
});
//调用服务端的 SendMessage函数
connection.InvokeAsync("SendMessage","autoChang","hello");
/// <summary>
/// SignalR Cline首次连接,如SignalR Server不在线会持续连接
/// </summary>
/// <param name="connection"></param>
/// <param name="token"></param>
/// <returns></returns>
public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token)
{
while (true)
{
try
{
Console.WriteLine(" SignalR 首次连接中....");
await connection.StartAsync(token);
if (connection.State == HubConnectionState.Connected)
{
Console.WriteLine(" SignalR 首次连接成功....");
return true;
}
}
catch when (token.IsCancellationRequested)
{
return false;
}
catch
{
if (connection.State == HubConnectionState.Disconnected)
{
Console.WriteLine("SignalR 首次连接失败,5秒之后重试");
await Task.Delay(5000);
}
}
}
}
/// <summary>
/// 连接成功之后,再次断开的重试连接策略
/// </summary>
public class RandomRetryPolicy : IRetryPolicy
{
private readonly Random _random = new Random();
public TimeSpan? NextRetryDelay(RetryContext retryContext)
{
var time = _random.NextDouble() * 10;
Console.WriteLine($" SignalR 在{time}秒后重试连接");
return TimeSpan.FromSeconds(time);
}
}
客户端可以携带参数,
var param = new Dictionary<string, string> {
{ "SubscribeSIP", "1" }
};
服务端收到参数后,进行解析
/// <summary>
/// 连接上
/// </summary>
/// <returns></returns>
public override Task OnConnectedAsync()
{
var isSubscribeSIP = Context.GetHttpContext().Request.Headers["SubscribeSIP"].ObjToInt() == 1 ? true : false;
var identifier = Context.UserIdentifier;
_logger.LogInformation($"用户:{identifier}->连接服务成功,是否订阅SIP设备信息:{isSubscribeSIP}");
return base.OnConnectedAsync();
}
当消息推送的时候,可以指定给订阅者推送相关信息:
/// <summary>
/// 消息通知,给订阅sip设备者,发送设备信息
/// </summary>
/// <param name="deviceInfoDto"></param>
public async void AddMessageToSipDeviceInfo(WSsipDeviceInfoDto deviceInfoDto)
{
var clients = SignalRClientList.Where(d => d.IsSubscribeSIP).ToList();
if (clients == null) return;
foreach (var client in clients)
{
deviceInfoDto.SignalrUserId = client.Signalr_userId;
client.ConnectionIds.ForEach(x => _hubContext.Clients.Client(x).SendAsync(SignalREvent.SipDeviceInfo, deviceInfoDto));
}
}
以上是技术要领,学会思想。实践很快的。更多的建议是去看微软文档,文档写的很明白。无非就是wpf winfom 客户端 如何转换成 netcore api的客户端的思想。更多是生命周期的注册,如何实例化等。
好了,今天的分享就到这里。