Есть ли альтернатива SignalR с функциональностью «возвращаемое значение серверу»? - PullRequest
0 голосов
/ 14 апреля 2020

Моя цель: Передать данные сделать указать c клиент, который подключен к серверу и получить результаты без вызова метода сервера .

Я пытался использовать SignalR для этого (потому что это очень простой инструмент для меня), но я не могу получить результаты (теперь я знаю почему). Я работаю над ASP. NET Core 3.1.

Мой вопрос: Есть ли альтернатива SignalR с функциональностью "возвращаемое значение на сервер" (вызов метода с параметрами на целевом клиенте и получить результаты)?

1 Ответ

1 голос
/ 14 апреля 2020

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);
    }
}

In концентратор, тогда вам нужно действие обратного вызова для вызова клиента запроса для завершения задачи:

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;
    }
}

Действие концентратора Start предназначено только для демонстрационных целей, чтобы иметь возможность запустить его с действительным соединением id.

На клиенте вам необходимо реализовать метод client и заставить его вызвать указанный метод обратного вызова, как только это будет сделано:

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

Наконец, вы можете попробовать это, вызвав Start действие клиента:

connection.invoke('Start');

Конечно, эта реализация очень проста c и потребует нескольких вещей, таких как правильная обработка ошибок и поддержка тайм-аута задач, если клиент этого не сделал. не отвечает должным образом. Также было бы возможно расширить это для поддержки произвольных вызовов, без необходимости создавать все эти методы вручную (например, с помощью одного метода обратного вызова в концентраторе, который может выполнить любую задачу).

...