Как буферизировать сообщения на сигнальном концентраторе и отправлять их, когда появляется нужный клиент? - PullRequest
1 голос
/ 17 апреля 2019

У меня есть два типа клиентов, соединяющих мой сервер signalR (ASP.NET Core). Некоторые из них являются отправителями, а некоторые - получателями. Мне нужно направлять сообщения от отправителей к получателям, что не является проблемой, но когда нет получателей, мне нужно каким-то образом буферизовать сообщения и не потерять их (вероятно, лучшим является ConcurrentQueue в каком-то классе синглтонов), но когда При первом подключении приемника буфер сообщений должен начать отключение. Какой лучший подход для этого?

Я создал синглтон-класс, который окружает коллекцию ConcurrentQueue, и я помещаю туда сообщения в очередь. Также у меня есть отдельный одноэлементный класс, в котором хранится коллекция идентификаторов соединений получателей. И я реализовал событие во втором классе, которое запускает событие, когда первый получатель подключается после того, как список получателей был пуст, но, возможно, это не очень хороший подход, я не знаю, как использовать id в Hub, потому что существует более одного экземпляра сигнального концентратора. Второй подход заключается в том, чтобы пометить класс персистентности как контроллер и внедрить в этот класс ContextHub и буфер сообщений, а также удалить буфер очереди и напрямую отправлять сообщения получателям.

1 Ответ

0 голосов
/ 11 июля 2019

Если я правильно понял, вы хотите отложить отправку сообщений SignalR, используя что-то вроде синхронизированного вызова в некотором IHostedService. Вот чего мне удалось достичь.

  • Ваш подход, который заключается в использовании ConcurrentQueue, который содержит вызываемые делегаты Action для обработки одновременных вызовов концентратора, является правильным. Как вы упоминаете, он должен вводиться как синглтон.

Итак, класс Queues:

public class Queues {
    public ConcurrentQueue<Action<IHubContext<MyHub, IMyEvents>>> MessagesQueue { get; set; }
}
  • Теперь нам нужно захватить ConnectionId звонящего, чтобы звонок мог получить ответ позже. SendMessage поставить в очередь необходимый делегат действия для выполнения вызова экземпляра концентратора в качестве параметра.

В качестве примера SendMessage вызовет ответ обратно вызывающей стороне, а BroadcastMessage отправит сообщение всем клиентам.

Использование вместо этого захваченного экземпляра концентратора приведет к исключению, поскольку концентратор будет быстро удален. Вот почему это будет введено позже в другом классе. Посмотрите на SendMessage_BAD

Вот класс MyHub и соответствующий интерфейс IMyEvents:

public interface IMyEvents {
    void ReceiveMessage(string myMessage);
}

public class MyHub : Hub<IMyEvents> {
    Queues queues;

    public MyHub(Queues queues) {
        this.queues = queues;
    }

    public void SendMessage(string message) {
        var callerId = Context.ConnectionId;
        queues.MessagesQueue.Enqueue(hub => hub.Clients.Client(callerId).ReceiveMessage(message));
    }

    // This will crash
    public void SendMessage_BAD(string message) {
        this.callerId = Context.ConnectionId;
        queues.MessagesQueue.Enqueue(_ => this.Clients.Client(callerId).ReceiveMessage(message));
    }

    public void BroadcastMessage(string message) {
        queues.MessagesQueue.Enqueue(hub => hub.Clients.All.ReceiveMessage(message));
    }
}
  • Теперь, используя наивный подход, этот код вызовет отправку сообщения отложенным способом. (На работе таймер обеспечивает регулярную каденцию, а класс IHostedService, но его здесь нет). Этот класс должен вводиться как синглтон.

Здесь класс DeferredMessageSender:

public class DeferredMessageSender {
    Queues queues;
    IHubContext<MyHub, IMyEvents> hub;

    public DeferredMessageSender(Queues queues, IHubContext<MyHub, IMyEvents> hub) {
        this.queues = queues;
        this.hub = hub;
    }

    public void GlobalSend() {
        while(queues.MessagesQueue.TryDequeue(out var evt)) {
            evt.Invoke(hub);
        }
    }
}

Надеюсь, это поможет.

...