.NET Socket Bugs & Gotchas
Here are a some bugs and subtleties in the .NET System.Sockets.Net library (or documentation) that have burned me while doing C# development. Network programming isn't easy, but Microsoft's poor implementation and documentation of these classes make it even harder.
- TcpClient.Close() doesn't close the socket connection.
- This is apparently by design, though you won't find that anywhere in the documentation (until Whidbey and version 2.0 of the .NET Framework). When you call TcpClient.GetStream(), the returned NetworkStream becomes the owner of the underlying socket. To close the socket, call TcpClient.GetStream().Close(). TcpClient.Close() should work if you never called GetStream(), but I wouldn't count on that behavior continuing in Whidbey. In fact, I'm not sure what the .NET 2.0 version of TcpClient.Close() does. Isn't the socket the only resource that needs to be disposed of?
- Socket.Available and NetworkStream.DataAvailable don't throw exceptions when the socket connection dies.
- The documentation wrongly says that these properties will throw a SocketException if “the remote host shuts down or closes the connection.” In fact, the documentation of the Winsock function ioctlsocket(sock, FIONREAD, &argp) (which these properties wrap) says that an error is returned (and thus an exception thrown) only in a few rare cases (namely WSANOTINITIALISED 10093 if WSAStartup was never called, WSAENETDOWN 10050 if the network is down or the network stack is full, WSAEINPROGRESS 10036 if another blocking operation is in progress, WSAENOTSOCK 10038 if the file descriptor is not a socket, and WSAEFAULT 10014 if argp is an invalid pointer) and not if the socket simply disconnects. Also, you shouldn't rely on the accuracy of the number returned from Socket.Available (see Microsoft Knowledge Base Article #192599 for details).
- Socket.Connected doesn't check that the socket is currently connected, only that it was as of the last IO operation.
- That is, this propery never tells you anything you didn't already know. If your socket never connected, it's not connected. If your last call to send or receive threw something other than WSAEWOULDBLOCK 10035, the socket isn't connected. Etc. The .NET 2.0 documentation suggests a dance involving messing with the socket's blocking mode and sending zero bytes over it, but that doesn't work either.
The only reliable way to detect a network disconnection is by reading or writing to the Socket (or TcpClient, or whatever). By default, those are blocking operations. You'll need to either use the asynchronous BeginReceive and BeginSend or BeginRead and BeginWrite (but be sure to check for exceptions thrown by the corresponding End function); make your socket non-blocking, call Socket.Poll or Socket.Select before sending or receiving, or do your socket operations on their own thread. Again, network programming is not a trivial task. I hope this had made it a little easier.
If you've had any other problems with these socket libraries, or have any other suggestions for working around them, let me know. For other problems with .NET, check out this helpful list of .NET bugs. If ever a .NET function doesn't do what you expect, look through its source with the .NET Reflector, which I used to help solve these socket problems.