Как обеспечить гарантированную доставку сообщений с помощью SignalR?
Я разрабатываю клиент-серверное приложение реального времени с использованием С# и SignalR. Мне нужно отправлять сообщения клиенту как можно быстрее.
Мой код на сервере:
for (int i = 0; i < totalRecords; i++)
{
hubContext.Clients.Client(clientList[c].Key).addMessage(
serverId, RecordsList[i].type + RecordsList[i].value);
Thread.Sleep(50);
}
Если есть задержкa > 50 мс, все работает идеально, но если нет задержки или задержки, то меньше 50 мс, некоторые сообщения отсутствуют.
Мне нужно отправлять сообщения как можно быстрее без задержки.
Думаю, мне нужно проверить, получено ли сообщение и только после отправки другого.
Как это сделать правильно?
Ответы
Ответ 1
SignalR не гарантирует доставку сообщений. Поскольку SignalR не блокируется при вызове методов клиента, вы можете быстро вызвать методы клиента, как вы обнаружили. К сожалению, клиент не всегда может получать сообщения сразу после отправки, поэтому SignalR должен буферизовать сообщения.
Вообще говоря, SignalR будет поддерживать до 1000 сообщений на клиента. Как только клиент отстает более чем на 1000 сообщений, он начнет пропускать сообщения. Этот DefaultMessageBufferSize 1000 может быть увеличен, но это увеличит использование памяти SignalR, и оно по-прежнему не гарантирует доставки сообщений.
http://www.asp.net/signalr/overview/signalr-20/performance-and-scaling/signalr-performance#tuning
Если вы хотите гарантировать доставку сообщений, вам придется самостоятельно их подписать. Вы можете, как вы сказали, отправить сообщение только после того, как было подтверждено предыдущее сообщение. Вы также можете ACK несколько сообщений одновременно, если ожидание ACK для каждого сообщения слишком медленное.
Ответ 2
Недавно я столкнулся с той же проблемой. Ответ, который я придумал, заключается в создании системы очереди сообщений.
Вместо того, чтобы сразу отправлять сообщения, оставьте их в очереди и отправьте фоновый поток/таймер. Чтобы сделать его общим, вы можете передать новый идентификатор GUID с каждым отправным сообщением. Затем вы сохранили бы это сообщение (id и связанные с ним данные) в списке.
Вот пример кода. lock
следует заменить на ReaderWriterLockSlim
или что-то еще более эффективное, но это дает вам представление о том, с чего начать.
В целом это не очень эффективно, это быстрый проект того, как можно обрабатывать доставку сообщений, которую нужно заказать.
void Main()
{
using(var mq = new MessageQueue())
{
var m1 = new Message((m)=>{
Console.WriteLine("a");
});
var m2 = new Message((m)=>{
Console.WriteLine("b");
});
mq.QueueMessage(m1);
mq.QueueMessage(m2);
var r = new Random();
while(mq.StillWaiting)
{
Thread.Sleep(1);
var rnum = r.Next(0,100);
if(rnum > 75)
{
var nm = mq.NextMessage;
if(nm != null)
mq.ResponseReceived(nm.messageId);
}
}
}
}
// Define other methods and classes here
public class MessageQueue : IDisposable{
public List<Message> Messages {get;set;}
public bool StillWaiting {
get{
lock(Messages)
return Messages.Count > 0;
}
}
public Message NextMessage{
get{
lock(Messages)
return Messages.FirstOrDefault();
}
}
private Timer _sendTimer;
private List<Guid> responses;
public MessageQueue()
{
Messages = new List<Message>();
responses = new List<Guid>();
_sendTimer = new Timer(timerTick,this,10,10);
}
public void ResponseReceived(Guid id)
{
lock(Messages)
{
if(responses.Contains(id) == false)
responses.Add(id);
}
}
public void QueueMessage(Message m)
{
lock(Messages)
Messages.Add(m);
}
public void timerTick(object state)
{
lock(Messages)
{
foreach(var m in Messages.ToArray())
{
Console.WriteLine("Checking " + m.messageId);
if(m.SendCount == 0 || responses.Contains(m.messageId) == false)
{
m.Send();
return;
}
Console.WriteLine("Got response from " + m.messageId);
Messages.Remove(m);
responses.Remove(m.messageId);
}
}
}
public void Dispose()
{
_sendTimer.Dispose();
}
}
public class Message
{
public Guid messageId;
public int SendCount {get;private set;}
private Action<Message> sendAction;
public Message(Action<Message> a)
{
SendCount = 0;
messageId = Guid.NewGuid();
sendAction = a;
}
public void Send()
{
sendAction.Invoke(this);
SendCount++;
}
}