C# 保证消息传递

C# 保证消息传递,c#,signalr,rabbitmq,C#,Signalr,Rabbitmq,我正在研究应用程序(托管(web)和安装的windows客户端)之间实时消息的体系结构设计。由于windows客户端安装在客户场所,因此我们无法控制防火墙,即打开任何端口 因此,我考虑使用signar通过http over websocket或回退技术发送实例通知。我们的windows客户端当前使用.Net 4.0框架 我做了一些关于通过信号器保证消息传递的研究,人们建议用GUID确认消息,但不确定我如何实现这个想法。另外,当客户端未连接时,我需要在RabbitMQ上对消息进行排队,onConn

我正在研究应用程序(托管(web)和安装的windows客户端)之间实时消息的体系结构设计。由于windows客户端安装在客户场所,因此我们无法控制防火墙,即打开任何端口

因此,我考虑使用signar通过http over websocket或回退技术发送实例通知。我们的windows客户端当前使用.Net 4.0框架

我做了一些关于通过信号器保证消息传递的研究,人们建议用GUID确认消息,但不确定我如何实现这个想法。另外,当客户端未连接时,我需要在RabbitMQ上对消息进行排队,onConnected只需从队列发送所有消息

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; }
    }
}
namespace SignalRHub.Hubs
{
[授权]
公共类聊天室:聊天室
{
公共void发送(字符串who、字符串数据)
{
字符串名称=Context.User.Identity.name;
列表组=新列表();
组。添加(名称);
加上(世卫组织);
消息消息=新消息()
{
messageId=Guid.NewGuid(),
数据=数据
};
Clients.Groups(Groups).addNewMessageToPage(名称,JsonConvert.SerializeObject(消息));
}
公共无效确认服务器(Guid消息ID)
{
//处理消息确认
var msgGuid=messageId;
}
已连接的公用覆盖任务()
{
字符串名称=Context.User.Identity.name;
添加(Context.ConnectionId,name);
返回base.OnConnected();
}
}
公共类消息
{
公共Guid消息ID{get;set;}
公共字符串数据{get;set;}
}
}

请告知?

我通过几个步骤就做到了这一点

  • 在具有Id的存储库中持久化消息
  • 在发送/连接事件中,获取所有未处理的消息并发送它们
  • 在客户端,向服务器发送确认并从存储库中删除消息
  • 根据设计,signar不能保证客户端只接收一次消息,因为即使您实现了这样的协议,您也可能会丢失ack消息,并且仍然会获得两次消息

    第一种方法:服务器为所有未处理的消息发送事件触发器addNewMessages。可以执行此操作,但如果从未调用发送事件

    //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; }
    }
    
    //只是一个示例repo:应该是持久的、线程安全的。。。
    公共静态类MessageRepository
    {
    public static List UnprocessedMessages=new List();
    公共静态无效添加消息(消息消息消息)
    {
    未处理的消息。添加(消息);
    }
    公共静态列表GetMessagesBySuer(字符串颁发者)
    {
    返回未处理的消息。其中(m=>m.Issuer.Equals(Issuer)).ToList();
    }
    公共静态无效删除(Guid id)
    {
    if(UnprocessedMessages.Any(m=>m.messageId.Equals(id)))
    {
    var message=UnprocessedMessages.FirstOrDefault(m=>m.messageId.Equals(id));
    未处理的消息。删除(消息);
    }
    }
    }
    [授权]
    公共类聊天室:聊天室
    {
    公共void发送(字符串who、字符串数据)
    {
    字符串名称=Context.User.Identity.name;
    列表组=新列表();
    组。添加(名称);
    加上(世卫组织);
    消息消息=新消息()
    {
    messageId=Guid.NewGuid(),
    数据=数据,
    发卡机构=名称,
    接收者=谁,
    CreationDate=DateTime.UtcNow
    };
    MessageRepository.AddMessage(message);
    var unProcessedMessages=MessageRepository.GetMessagesByIssuer(name).OrderBy(m=>m.CreationDate.ToList();
    未处理的消息。ForEach(m=>
    {
    Clients.Groups(Groups.addNewMessageToPage(名称,JsonConvert.SerializeObject(m));
    });
    }
    公共无效确认服务器(Guid消息ID)
    {
    //处理消息确认
    var msgGuid=messageId;
    MessageRepository.Remove(msgGuid);
    }
    已连接的公用覆盖任务()
    {
    字符串名称=Context.User.Identity.name;
    添加(Context.ConnectionId,name);
    返回base.OnConnected();
    }
    }
    公共类消息
    {
    公共Guid消息ID{get;set;}
    公共字符串数据{get;set;}
    公共字符串颁发者{get;set;}
    公共字符串接收器{get;set;}
    公共日期时间创建日期{get;set;}
    }
    
    这里有第二种方法(我认为是更好的方法)。您有一个C#客户端单例,它带有一个计时器,定期检查您的存储库,然后发送未处理的消息。您还应该删除过期邮件

    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));
                }
            });
    
    
        }
    }
    
    公共类呈现监视器
    {
    私人定时器;
    //我们计划多久检查一次商店中的连接是否有效
    私有只读时间跨度_presenceCheckInterval=TimeSpan.FromSeconds(10);
    公众参与监察
    {
    }
    公共无效开始监视()
    {
    如果(_timer==null)
    {
    _计时器=新计时器(=>
    {
    尝试
    {
    检查();
    }
    捕获(例外情况除外)
    {
    //不要添加后台线程,这会杀死整个进程
    Trace.TraceError(例如消息);
    }
    },
    无效的
    时间跨度0,
    _存在检查间隔);
    }
    }
    私人作废检查()
    {
    var context=GlobalHost.ConnectionManager.GetHubContext();
    var messages=MessageRepository.GetAllMessages();
    messages.ForEach(m=>
    {
    if(上下文!=null)
    {
    列表组=新列表();
    组。添加(m.Issuer);
    组。添加(m.Receiver);
    上下文客户端