views:

669

answers:

1

I have a simple program that uses TcpClient and SslStream to write data to a socket.

To test it I ran the program over night so my program would open the connection, write nothing for a long time, so the firewall or remote server would close the connection. This morning I took a look at TCPView and verified the connection was closed and then told my program to Write to the socket.

No exception was thrown on Write. However, the next Write did thrown this exception as expected: “System.IO.IOException: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host”

I can see why TcpClient.Connected could return True even if it really wasn’t connected, but why would Write not throw an exception on a connection that was actually closed (as verified in TCPView)?

+3  A: 

That's because of the way TCP/IP protocol works. When you call TcpClient.Write data is being sent to the server, and the function returns success without waiting for a server response.

In the meantime the server returns an error. Tcp/IP stack on your side notices that and next time you try to write it will throw an exception

Michał Piaskowski
Are there any best practices to determine if the connection is really open? It seems that the only way would be to write 2 times, and if the 2nd one threw an exception, write some real data (that would be 3 times total). I would assume there's a better way.
Brandon
If you need transaction support (i.e. KNOW that the other side has gotten your data, and how much of that data) you have to implement that in your protocol above TCP. In addition, it's common to implement a heartbeat/ping-pong protocol on top of long lived connections to try to keep firewalls from dropping that connection.
nos
@nos - Thanks for the info. The problem is that I don't have control over the server, and there is no really acknowledgment mechanism (just a raw socket). I suppose I might keep the data I wrote last and if the next write throws an exception, re-write that data, but that isn't a very good solution because the next write could happen 10 hours later.
Brandon
You can use the socket KeepAlive option, but AFAIR you can't set that on TcpClient. You'll have to use System.Net.Sockets.Socket and NetworkStream.
Michał Piaskowski
In .NET 2.0 you can access the underlying socket using the Client property. Maybe I can try client.Client.SetSocketOption.
Brandon
One problem with keeping the last data written generally, is that it might not be the 2. write that fails. It might be the 5th. or 12th. It'll depend on too many variables to handle it properly (e.g. in a congested situation TCP might bundle together a lot of write() calls from the application and the applicaton might not get the error until later (remember , TCP is a stream - 1 write call might not map to only 1 packet sent).
nos
Thanks nos, I didn't think of that. It seems that because the server that I'm talking to has no feedback whatsoever there isn't any easy way guarantee my data gets through. However, all this was surprising to me since I thought TCP guarantees delivery (unlike UDP), so I thought an exception would be thrown the first time I write, not the 2nd or 5th or 12th time.
Brandon
TCP is kind of a leaky abstraction. TCP can't guarantee that data is delivered . It tries very very hard though. One obvious situation where TCP cannot guarantee delivery is if the PC at the other end is nuked. There's more subtle situation as well though :-) If your main problem is a silly firewall/ timing out the connection - enabling TCP Keepalive with a low value for sending them(e.g. every 10 minutes) would likely fix your issues. Or have your app close the connection if you havn't had anything to send for say, 10 minutes, set up the connection again when you have more data to send.
nos
Nos that sounds like a good idea. I realized a good way to re-create this problem is to just unplug your network cable and then try to write to the socket. It has the same effect. I will take your advice and close the socket every once and a while. Thanks for all the help!
Brandon