I have a data collection system that passes data from the collecting computer(server) to the plotting computer (client) over a TCP connection. The code works fine the first time a collection is run. The code continues to work fine on subsequent runs if the system is not left idle for more than three minutes. If the system remains idle for more than three minutes the transfer hangs for the first 12 seconds or so (long enough to cause the collection buffers to overflow).
At the start of every collection the client goes into a loop for 30 seconds where it checks every 1 ms to see if _clientStream.DataAvailable = true. That loop looks similar to this (some error checking code removed):
public bool WaitForData(int maxWaitInMs)
{
DateTime start_time = DateTime.Now;
while (!_clientStream.DataAvailable )
{
Thread.Sleep(DATA_WAIT_DELAY); //1 ms
TimeSpan elapsed_time = DateTime.Now - start_time;
if (elapsed_time.TotalMilliseconds > maxWaitInMs)
{
return false;
}
}
return true;
}
The server side is just doing a simple write of fixed length data
_dataTcpServer.SendData(packet_buffer, OFFSET, packet.Size());
When the problem occurs, I can tell from the debugger that the server calls SendData once, returns gets the next packet of data calls the send method again and then hangs up for approx 12 seconds on the SendData call. The client in the meantime never sees DataAvailable go true until the 12 seconds elapse. The Send timeout was left at the default so it should be infinite. The 12 second time does seem to be related somewhat to the amount of data sent.
One thing I did differently in this system was instead of creating the TcpClient when the client app starts, I new up a TcpClient when the client needs to Connect. On Disconnect I dispose of the Stream retrieved via the TcpCLient.GetStream and I close the TcpClient.
I’m just hoping for some insight into why being idle for three minutes has this effect (both systems are running XP).
EDIT – Problem Solved but understanding is proving elusive
On the server I was using a separate thread that employed TcpListener.AcceptTcpClient() to allow a client to connect. AcceptTcpClient() returns a TcpClient object. I would periodically check that TcpClient’s IsConnected property so I would know if the client disconnected (i.e. the measurement was over) and then I would pend again on AcceptTcpClient() and await the start of the next measurement. The problem was that even though the client’s TcpClient disconnected, the server’s TcpClient.IsConnected always returned true.
I solved the problem by having the client send a message (not over TCP) to the server and then the server forces a disconnect on its end. Now everything works but I’m convinced I’m not using this API properly. While I can see the methods for doing the accept asynchronously I don’t see how the server is supposed to find out when the client disconnects and I don’t see why the server’s TcpClient.IsConnected property would return true when there is no longer a valid connection.
Also I was late to notice the ExclusiveAddressUse property so I never set it to true. The system acted like if another client connection was made within a few minutes, the same connection was reused and the server could send data to the client (i.e. it was as if the client had never disconnected). If more than three minutes passed since the disconnect, the new client connection was was accepted (even though as far as I can tell my code never got back to the AcceptTcpClient() line), but it was no longer the same connection the server was sending on.
While I still don’t understand why my client could connect when my server wasn’t pending on AcceptTcpClient I think I do understand why Connected returns true even though the client disconnected. I dug into the underlying Socket docs on MSDN and saw this:
Since the client knows to quit because the server stopped sending data the last send prior to the client quitting was sucessful. I think Connected is a terrible name for this property. It should be WasConnected or WasConnectedAtLastSend, while more verbose it would have saved me a lot of heartache (and so would have reading the MSDN docs sooner).