是否有具有“返回值到服务器”功能的 SignalR 替代方案?

Paw*_*iak 1 c# rpc ipc signalr asp.net-core

我的目标:将数据传递给连接到服务器的特定客户端,并在不调用 Server 方法的情况下获得结果

我尝试使用 SignalR 来做到这一点(因为它对我来说是非常简单的工具),但我无法得到结果(现在我知道为什么了)。我正在研究 ASP.NET Core 3.1。

我的问题:是否有具有“返回值到服务器”功能的 SignalR 替代方案(在目标客户端上使用参数调用方法并获得结果)?

pok*_*oke 5

SignalR 通常用于有多个客户端和客户端连接到的单个服务器的设置中。这使得客户端调用服务器并期望返回结果是一件很正常的事情。由于服务器通常并不真正关心连接了哪些单个客户端,并且由于服务器通常向一组客户端进行广播(例如使用一组),因此通信方向主要用于通知或广播。单目标消息是可能的,但没有用于请求/响应模式的内置机制。

为了使与SignalR这项工作,你需要为客户端调用服务器的方式。所以你需要一个集线器动作来发送响应。

仅此一点并不难,但可能要做的是将客户端调用与集线器接收到的传入结果消息链接起来。为此,您将不得不构建一些东西。

这是一个让您入门的示例实现。这MyRequestClient是一个单例服务,它基本上封装了消息传递,并为您提供了一个异步方法,该方法将调用客户端,并且只有在客户端通过调用集线器上的回调方法响应时才完成:

public class MyRequestClient
{
    private readonly IHubContext<MyHub> _hubContext;
    private ConcurrentDictionary<Guid, object> _pendingTasks = new ConcurrentDictionary<Guid, object>();

    public MyRequestClient(IHubContext<MyHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task<int> Square(string connectionId, int number)
    {
        var requestId = Guid.NewGuid();
        var source = new TaskCompletionSource<int>();
        _pendingTasks[requestId] = source;

        await _hubContext.Clients.Client(connectionId).SendAsync("Square", nameof(MyHub.SquareCallback), requestId, number);

        return await source.Task;
    }

    public void SquareCallback(Guid requestId, int result)
    {
        if (_pendingTasks.TryRemove(requestId, out var obj) && obj is TaskCompletionSource<int> source)
            source.SetResult(result);
    }
}
Run Code Online (Sandbox Code Playgroud)

在集线器中,您然后需要回调操作来调用请求客户端来完成任务:

public class MyHub : Hub
{
    private readonly ILogger<MyHub> _logger;
    private readonly MyRequestClient _requestClient;

    public MyHub(ILogger<MyHub> logger, MyRequestClient requestClient)
    {
        _logger = logger;
        _requestClient = requestClient;
    }

    public Task SquareCallback(Guid requestId, int number)
    {
        _requestClient.SquareCallback(requestId, number);
        return Task.CompletedTask;
    }


    // just for demo purposes
    public Task Start()
    {
        var connectionId = Context.ConnectionId;
        _ = Task.Run(async () =>
        {
            var number = 42;
            _logger.LogInformation("Starting Square: {Number}", number);
            var result = await _requestClient.Square(connectionId, number);
            _logger.LogInformation("Square returned: {Result}", result);
        });
        return Task.CompletedTask;
    }
}
Run Code Online (Sandbox Code Playgroud)

Start枢纽作用仅用于演示目的有办法用一个有效的连接ID开始这个。

在客户端,您需要实现客户端方法并让它在完成后调用指定的回调方法:

connection.on('Square', (callbackMethod, requestId, number) => {
    const result = number * number;
    connection.invoke(callbackMethod, requestId, result);
});
Run Code Online (Sandbox Code Playgroud)

最后,您可以通过调用Start客户端的操作来尝试此操作:

connection.invoke('Start');
Run Code Online (Sandbox Code Playgroud)

当然,这个实现是非常基本的,需要一些东西,比如正确的错误处理和在客户端没有正确响应时支持超时任务。也可以扩展它以支持任意调用,而无需您手动创建所有这些方法(例如,通过在集线器上拥有一个能够完成任何任务的回调方法)。