Я написал службу, используя PollingDuplexHttpBinding
, у которой есть клиент Silverllight, который ее использует. Моя служба в основном имеет заданное количество клиентов, отправляющих свои данные (довольно часто, каждую секунду, и данные довольно большие, каждый вызов составляет около 5 КБ) в службу, а также прослушивает новые данные, отправленные другими клиентами в службу для направляться к ним, что очень похоже на архитектуру чата.
Проблема, которую я замечаю, заключается в том, что когда клиенты подключаются к службе через Интернет, через несколько минут ответ службы становится медленным, а ответы становятся запаздывающими. Я пришел к выводу, что когда достигается пропускная способность хостов службы (скорость загрузки через Интернет, на сервере только около 15 КБ / с), сообщения, отправленные другими клиентами, буферизуются и обрабатываются соответствующим образом, когда есть доступная пропускная способность. Мне интересно, как именно я могу ограничить размер этого буфера, который служба использует для хранения полученных сообщений от клиентов? Не так важно, чтобы мои клиенты получали все данные, а скорее, чтобы они получали последние данные, отправленные другими, поэтому подключение в реальном времени — это то, что я ищу за счет гарантированной доставки.
Короче говоря, я хочу иметь возможность очищать свою очередь/буфер в службе всякий раз, когда она заполняется или достигается определенный предел, и снова начинать заполнять ее полученными вызовами, чтобы избавиться от задержки. Как мне это сделать? Является ли свойство MaxBufferSize
тем, что мне нужно уменьшить на стороне службы, а также на стороне клиента? Или мне нужно закодировать эту функциональность в моем сервисе? Любые идеи?
Спасибо.
ИЗМЕНИТЬ:
Вот моя сервисная архитектура:
//the service
[ServiceContract(Namespace = "", CallbackContract = typeof(INewsNotification))]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
public class NewsService
{
private static Dictionary<IChatNotification, string> clients = new Dictionary<IChatNotification, string>();
private ReaderWriterLockSlim subscribersLock = new ReaderWriterLockSlim();
[OperationContract(IsOneWay = true)]
public void PublishNotifications(byte[] data)
{
try
{
subscribersLock.EnterReadLock();
List<INewsNotification> removeList = new List<INewsNotification>();
lock (clients)
{
foreach (var subscriber in clients)
{
if (OperationContext.Current.GetCallbackChannel<IChatNotification>() == subscriber.Key)
{
continue;
}
try
{
subscriber.Key.BeginOnNotificationSend(data, GetCurrentUser(), onNotifyCompletedNotificationSend, subscriber.Key);
}
catch (CommunicationObjectAbortedException)
{
removeList.Add(subscriber.Key);
}
catch (CommunicationException)
{
removeList.Add(subscriber.Key);
}
catch (ObjectDisposedException)
{
removeList.Add(subscriber.Key);
}
}
}
foreach (var item in removeList)
{
clients.Remove(item);
}
}
finally
{
subscribersLock.ExitReadLock();
}
}
}
//the callback contract
[ServiceContract]
public interface INewsNotification
{
[OperationContract(IsOneWay = true, AsyncPattern = true)]
IAsyncResult BeginOnNotificationSend(byte[] data, string username, AsyncCallback callback, object asyncState);
void EndOnNotificationSend(IAsyncResult result);
}
Конфиг службы:
<system.serviceModel>
<extensions>
<bindingExtensions>
<add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceThrottling maxConcurrentSessions="2147483647" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<pollingDuplex>
<binding name="myPollingDuplex" duplexMode="SingleMessagePerPoll"
maxOutputDelay="00:00:00" inactivityTimeout="02:00:00"
serverPollTimeout="00:55:00" sendTimeout="02:00:00" openTimeout="02:00:00"
maxBufferSize="10000" maxReceivedMessageSize="10000" maxBufferPoolSize="1000"/>
</pollingDuplex>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="NewsNotificationService.Web.NewsService">
<endpoint address="" binding="pollingDuplex" bindingConfiguration="myPollingDuplex" contract="NewsNotificationService.Web.NewsService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<directoryBrowse enabled="true" />
</system.webServer>
</configuration>
Клиент будет вызывать службу обычно между периодами 500-1000 мс, например:
_client.PublishNotificationAsync(byte[] data);
и обратный вызов уведомит клиента об уведомлениях, отправленных другими клиентами:
void client_NotifyNewsReceived(object sender, NewsServiceProxy.OnNewsSendReceivedEventArgs e)
{
e.Usernamer//WHich client published the data
e.data//contents of the notification
}
Итак, подведем итог: когда количество клиентов увеличивается, а скорость загрузки узлов службы через Интернет ограничена, сообщения, отправляемые службой подписчикам, где-то буферизуются и обрабатываются в очереди, что и вызывает проблему. Я не знаю, где буферизуются эти сообщения. В локальной сети сервис работает нормально, потому что сервер имеет скорость загрузки, равную его скорости загрузки (для 100 КБ/с входящих вызовов он отправляет 100 КБ/с уведомлений). Где буферизуются эти сообщения? И как мне очистить этот буфер?
Я сделал что-то экспериментальное, чтобы попытаться проверить, буферизуются ли сообщения в службе, я попытался вызвать этот метод на клиенте, но он всегда возвращает 0, даже когда один клиент все еще находится в процессе получения уведомлений, которые кто-то другой отправил 4- 5 минут назад:
[OperationContract(IsOneWay = false)]
public int GetQueuedMessages()
{
return OperationContext.Current.OutgoingMessageHeaders.Count();
}