Guaranteed message delivery

I am considering an architecture for realtime messaging between applications (hosting (website) and installed windows clients). Since the Windows client is installed on the client premises, we will not control the firewall, that is, open any ports.

So I thought about using SignalR to send notifications of this using http over websocket or return technology. Our Windows client is currently using the .Net 4.0 Framework.

I've done some research on guaranteed message delivery via signalr and people have suggested confirming the message with a GUID, but not sure how I can implement this idea. Also when the client is not connected I need to enqueue a message to RabbitMQ and onConnected just send all messages from the queue.

namespace SignalRHub.Hubs
{
    [Authorize]
    public class ChatHub : Hub
    {
        public void Send(string who, string data)
        {
            string name = Context.User.Identity.Name;

            List<string> groups = new List<string>();
            groups.Add(name);
            groups.Add(who);

            Message message = new Message()
            {
                messageId = Guid.NewGuid(),
                data = data
            };

            Clients.Groups(groups).addNewMessageToPage(name, JsonConvert.SerializeObject(message));
        }

        public void AcknowledgeServer(Guid messageId)
        {
            // Process the message acknowledge
            var msgGuid = messageId;
        }

        public override Task OnConnected()
        {
            string name = Context.User.Identity.Name;
            Groups.Add(Context.ConnectionId, name);

            return base.OnConnected();
        }
    }

    public class Message
    {
        public Guid messageId { get; set; }
        public String data { get; set; }
    }
}

      

Please inform?

+3


source to share


1 answer


I managed to do it in a few steps.

  • messages are saved in the repository with an identifier
  • in the send / connect event, get all unprocessed messages and send them
  • on the client side, send confirmation to the server and remove messages from the repository

By design, signalR cannot guarantee that the client will receive the message and receive it only once, because even if you implement such a protocol, you can lose the ack message and still receive the message twice.

The first approach is to fire the addNewMessages server send event for all unprocessed messages. Could do the trick, but what if the dispatch event is never called ...



//just a sample repo : should be persisted, thread safe ...
public static class MessageRepository
{
    public static List<Message> UnprocessedMessages = new List<Message>();

    public static void AddMessage(Message msg)
    {
        UnprocessedMessages.Add(msg);
    }

    public static List<Message> GetMessagesByIssuer(string issuer)
    {
        return UnprocessedMessages.Where(m => m.Issuer.Equals(issuer)).ToList();
    }

    public static void Remove(Guid id)
    {
        if (UnprocessedMessages.Any(m => m.messageId.Equals(id)))
        {
            var message = UnprocessedMessages.FirstOrDefault(m => m.messageId.Equals(id));
            UnprocessedMessages.Remove(message);
        }
    }
}


[Authorize]
public class ChatHub : Hub
{
    public void Send(string who, string data)
    {
        string name = Context.User.Identity.Name;

        List<string> groups = new List<string>();
        groups.Add(name);
        groups.Add(who);

        Message message = new Message()
        {
            messageId = Guid.NewGuid(),
            data = data,
            Issuer = name,
            Receiver = who,
            CreationDate = DateTime.UtcNow
        };

        MessageRepository.AddMessage(message);
        var unProcessedMessages = MessageRepository.GetMessagesByIssuer(name).OrderBy(m => m.CreationDate).ToList();

        unProcessedMessages.ForEach(m =>
        {
            Clients.Groups(groups).addNewMessageToPage(name, JsonConvert.SerializeObject(m));
        });


    }

    public void AcknowledgeServer(Guid messageId)
    {
        // Process the message acknowledge
        var msgGuid = messageId;
        MessageRepository.Remove(msgGuid);
    }

    public override Task OnConnected()
    {
        string name = Context.User.Identity.Name;
        Groups.Add(Context.ConnectionId, name);

        return base.OnConnected();
    }
}

public class Message
{
    public Guid messageId { get; set; }
    public String data { get; set; }
    public String Issuer { get; set; }
    public String Receiver { get; set; }
    public DateTime CreationDate { get; set; }
}

      

Here's the second approach (best in my opinion). You have a C # client single with a timer that checks your repository periodically and then sends the raw messages. You should also delete expired messages.

public class PresenceMonitor
{
    private Timer _timer;

    // How often we plan to check if the connections in our store are valid
    private readonly TimeSpan _presenceCheckInterval = TimeSpan.FromSeconds(10);


    public PresenceMonitor()
    {

    }

    public void StartMonitoring()
    {
        if (_timer == null)
        {
            _timer = new Timer(_ =>
            {
                try
                {
                    Check();
                }
                catch (Exception ex)
                {
                    // Don't throw on background threads, it'll kill the entire process
                    Trace.TraceError(ex.Message);
                }
            },
            null,
            TimeSpan.Zero,
            _presenceCheckInterval);
        }
    }

    private void Check()
    {
        var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
        var messages = MessageRepository.GetAllMessages();
        messages.ForEach(m =>
        {
            if (context != null)
            {
                List<string> groups = new List<string>();
                groups.Add(m.Issuer);
                groups.Add(m.Receiver);

                context.Clients.Groups(groups).addNewMessageToPage(m.Issuer, JsonConvert.SerializeObject(m));
            }
        });


    }
}

      

And in your class, startuc

run Monitor

.

0


source







All Articles