tags:

views:

587

answers:

2

Hi!

I've implemented a solution that uses asynchronous sockets to push information from a server to all connected Silverlight clients.

I'm having a few problems with the Push Server and need some clarification since this is the first time I'm working with Sockets and in an fully asynchronous environment.

Right now the Push Server accepts new socket connections and adds them to a Generic List. The Generic List is of a custom Type that I've implemented (named AsyncClientConnection) used to manage a single asynchronous socket connection.

The Push Server has a Timer and when a specified amount of time has passed it opens a file and sends the contents to all connected sockets (in the Generic List). It's simulating what is going to happen in the future, when the server will be receiving raw byte data from a physical device. While the server is sending the data to the client it cleans up any disconnected/disposed clients.

My problem is with the Generic List, it's not thread safe and isn't working well in this asynchronous scenario. When a new socket connects to the server, a new AsyncClientConnection is created for it and is added to the generic list; but if the server is in the middle of pushing the file to the connected clients it's using the generic list so when the new AsyncClientConnection is added to the list, the list is modified,and an exception occurs for obvious reasons.

I've never seriously used threads before (I've only just tried them out to make sure that I understood the theory of them), and have never used asynchronous sockets before, so I'm a little lost when it comes to the tools out there that I can use to fix the problem.

I'm even questioning if there's a different way to maintain the AsyncClientConnection types all together.

Any suggestions would be great!

Thanks a lot,

-Frinny

+1  A: 

Originally, I solved my problem by synchronizing access to the resource used to maintain the connections. I locked this resource (the Generic List) whenever I was adding a new connection to it. I also locked the resource whenever I was enumerating through it while pushing data to the connected sockets.

In C# the keyword I would have used is lock; since I'm working with VB.NET I used the SyncLock keyword. The lock, or SyncLock, keyword prevents multiple threads from accessing a thread resource. This works as long as every piece of code locks the resource before using it.

This solution works; however, as Doug has pointed out, new connections have to wait for the server to finish pushing the data to the connected clients. As the frequency at which the server pushed the data increased, and the number of connected clients increased, I started to notice that new clients were taking a while to connect.

This problem wasn't that bad in my scenario because I was able to open 45 connections before I started noticing any server connection lag time. Even then it was a matter of seconds before the connection initiated. This could be bad in other scenarios though, and so I reconsidered my design which lead me to my current solution. This solution does not involve enumerating through the list of connected clients in order to push the data. This means the only time I need to lock (or SyncLock) the list of connected AsyncClientConnections is when new AsyncClientConnections are added or AsyncClientConnections no longer connected are removed. In other words, incoming connections do not have to wait to connect while the data is being pushed.

My current solution has 4 components:

  • AsyncClientConnection: a class that manages the asynchronous socket connection logic. It is responsible for sending (pushing) the data to the connected client. This class contains a reference to a DataGetter.
  • PusherServer: a class that manages the connected AsyncClientConnections. It accepts incoming asynchronous socket connections and creates new AsyncClientConnections that manage these sockets. It also removes any AsyncClientConnections that are no longer connected.
  • DataGetter: a class used to retrieve the data that is to be sent to the connected clients. The data retrieved is a picture file. The DataGetter raises a DataRetrieved event every time it's finished retrieving a picture.
  • TAsyncClientEventArgs: a class that inherits from EventArgs. It's used to transfer the data retrieved by the DataGetter class when it raises the DataRetrieved event.

The PusherServer contains an instance of the DataGetter and a list of AsyncClientConnections. When a new socket connection is made to the PusherServer, it creates a new AsyncClientConnection and passes it a reference to the socket connection and a reference to the DataGetter.

When a new AsyncClientConnection is created, it specifies a method that is to be used to handle the DataGetter's DataRetrieved event.

Now, when the DataGetter instance raises the DataRetrieved event all connected AsyncClientConnections' push the data retrieved to the clients. There is no more enumerating through the list of AsyncClientConnections, no need to lock the list of AsyncClientConnections while pushing data, and the connections are established more quickly and smoother than the first solution.

Hope this helps others facing the same problem.

-Frinny

Frinavale
A: 

One problem with your solution is that you'll hold a lock for the entirety of the push operation. If this takes a long time, new clients will be blocked until this happens. As you increase the frequency of data being pushed, this could effectively block out new clients.

I'd suggest that you lock the list, make a copy, release the lock, then enumerate the copy to send data. This will likely perform better under load. (Remember, you are copying the list, not the underlying objects in the list)

Doug
Thanks for your reply Doug!I started experiencing what you've described when I increased the frequency of pushing and increased the number of sockets connected to the server.The problem still happens even with your suggested solution. I locked the list, copy the list to a "working" array, released the lock and then push the data to the connected sockets based on what was in the working array.This does not help. New connections are still blocked from for about the same amount of time as they were blocked from without copying the list.Do you have any other suggestions?Thanks -Frinny
Frinavale
I've come up with a better solution to this problem. The answer's been edited to outline what I've done.
Frinavale