I've written a service using PollingDuplexHttpBinding
which has a Silverllight client that consumes it. My Service basically has a given number of clients sending their data (quite often, every second, and the data is quite large, each call is around 5KB) to the service, and also listening for new data sent by other clients to the service to be routed to them, very similar to a chat room architecture.
The problem I'm noticing is that when clients connect to the service over the internet, after a few minutes the service's response becomes slow and the replies become lagged. I've come to the conclusion that when the service hosts upload capacity is reached (internet upload speed, on the server its about 15KB/s only), the messages sent by other clients are buffered and processed accordingly when there is bandwidth available. I'm wondering how exactly can I limit the seize of this buffer that the service uses to store the received messages from the clients? It's not so critical that my clients get all the data, but rather that they get the latest data sent by others, so real time connectivity is what i'm looking for at the cost of guaranteed delivery.
In short I want to be able to clean my queue/buffer at the service whenever it fills up, or a certain cap is reached and start filling it again with the received calls to get rid of the delay. How do I do this? Is the MaxBufferSize
property what I need to decrease on the service side as well as on the client side? Or do I need to code this functionality in my service? Any ideas?
Thanks.
EDIT:
Here is my service architecture:
//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);
}
The service config:
<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>
The client will call the service typically between 500ms-1000ms periods like this:
_client.PublishNotificationAsync(byte[] data);
and the callback will notify the client of notifications sent by other clients:
void client_NotifyNewsReceived(object sender, NewsServiceProxy.OnNewsSendReceivedEventArgs e)
{
e.Usernamer//WHich client published the data
e.data//contents of the notification
}
So to recap, when the number of clienst increase, and the service hosts upload speed over the internet is limited, the messages sent out by the service to the subscribers get buffered somewhere and get processed in a queue, which is whats causing the problem, I don't know where these messages are buffered. In a LAN the service works fine because the server has an upload speed equal to the its download speed (for 100KB/s of incoming calls, it sends out 100KB/s of notifications). Where are these messages getting buffered? And how can I clear this buffer?
I did something experimental to try and see if the messages are buffered at the service, I tried calling this method on the client, but it always returns 0, even when one client is still in the process of receiving notifications that somebody else sent 4-5 minutes ago:
[OperationContract(IsOneWay = false)]
public int GetQueuedMessages()
{
return OperationContext.Current.OutgoingMessageHeaders.Count();
}
MaxBufferSize
isn't going to help you with this problem. You're going to have to code it yourself I dont know of any existing solution/framework. It does however sound like an interesting problem. You could start by maintaining aQueue<Message>
for each connected client and when pushing to this queue (or when the client calls dequeue) you can re-evaluate whatMessage
should be sent.UPDATE: Firstly, I would forget about trying to do this from the client side and in the configuration, you're going to have to code this yourself
Here I can see where you are sending to your clients:
so instead of asynchronously pushing these notifications to each client you should push them into a
Queue<byte[]>
. Each client connection will have its own queue and you should probably construct a class dedicated to each client connection:note this code won't compile out of the box and likely has some logic errors, use only as a guide
Imagine a scenario where one message is pushed to the client and while sending 9 more messages call
ClientConnection.Enqueue
. When the first message has finished it checks the queue which should contain only the last (9th message)