How to buffer messages on signal hub and send them when the right client appears?

风格不统一 提交于 2019-12-08 07:52:45

问题


I hawe two type of clients connecting my signalR server (ASP.NET Core). Some of them are senders, and some of them are receivers. I need to route messages from senders to the receivers, which is not a problem, but when there is no receivers, I need to somehow buffer messages and not lose them (probably the best is ConcurrentQueue in some kind of a singleton class) but when the first receiver connect, the message buffer needs to start dequeue. Which is the best approach for this?

I created singleton class that wraps arround ConcurrentQueue collection and I enqueue and dequeue messages there. Also I have a separate singleton class which persist collection of the receivers connectionIDs. And I implemented event in this second class that fires event when first receiver connects after the list of receivers was empty but maybe this is not a good approach, I don't know how to use id in Hub, because there is more than one instance of a signalR hub. Second approach is to mark persistance class as controller and inject the ContextHub and message buffer in this class and dequeue buffer from there and directly send messages to the receivers???


回答1:


If I understood well, you want to defer SignalR message sending by using something like a synchronized call in some IHostedService. Here is what I managed to achieve so far.

  • Your approach that consists in using a ConcurrentQueue that contains invokable Action delegates to handle the concurrent hub calls is the right one. As you mention, it has to be injected as a singleton.

So here the Queues class:

public class Queues {
    public ConcurrentQueue<Action<IHubContext<MyHub, IMyEvents>>> MessagesQueue { get; set; }
}
  • Now we need to capture the ConnectionId of the caller so a call can get an answer later. SendMessage enqueue the necessary action delegate to perform a call against a hub instance as a parameter.

As an example SendMessage will trigger an answer back to the caller, and BroadcastMessage will send a message to all clients.

Using a captured hub instance instead would lead to an exception here because the hub will be disposed, quickly. That's why it would be injected later in another class. Have a look on SendMessage_BAD

Here is the MyHub class and the corresponding IMyEvents interface:

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));
    }
}
  • Now, using a naive approach, this code will trigger the message sending a deferred way. (At work, a timer ensure a regular cadence, and the class is an IHostedService but it is does not appear here). This class has to be injected as a singleton.

Here the DeferredMessageSender class:

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

Hope it helps.



来源:https://stackoverflow.com/questions/55735378/how-to-buffer-messages-on-signal-hub-and-send-them-when-the-right-client-appears

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!