Handling timeout on blocking .NET socket

2020-06-25 04:29发布

问题:

A TcpClient instance, created with the Accept method, is used for manage a client connection. The problem arise when I need to terminate the server thread, since it is blocked on a receive call.

So I setup a the TcpClient ReceiveTimeout in order to loop every n milliseconds to test the exit condition. The result is that the Receive operation raise an exception (SocketException) having the error code SocketError.TimedOut. Good I was thinking...

The problem is that the property Socket.Connected returns false, but as stated in the MSDN documentation:

The value of the Connected property reflects the state of the connection as of the most recent operation. If you need to determine the current state of the connection, make a nonblocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected.

So, I do what states:

try {
     // Receive operation on socket stream
     // Send operation on socket stream
} catch (SocketException e) {
    if (e.SocketErrorCode == SocketError.TimedOut) {
    try {
        IAsyncResult asyncResult;
        int sResult;

        asyncResult = mSocket.Client.BeginSend(new byte[] {}, 0, 0, SocketFlags.None, delegate(IAsyncResult result) { }, null);
        sResult = mSocket.Client.EndSend(asyncResult);
        Debug.Assert(asyncResult.IsCompleted == true);

        if (mSocket.Connected == false)
            throw new Exception("not more connected");  // Always thrown
    } catch (Exception e) {
             // ...
        }
}

But, even if the aynch Send operation is executed, the property mSocket.Connected is always false, causing the outer loop to terminate (other threads calls Disconnect method to terminate the server thread).

What am I missing?

回答1:

The problem is if the timeout occurs the TcpClient gets disconnected. So your method won't work. Use the async read/write functions or use select.

The probably easiest way with async function call is like this:

byte[] data = new byte[4096];
IASyncResult result = stream.BeginRead(data, 0, data.Length, null, null);
result.AsyncWaitHandle.WaitOne(<timeout value in ms>);
int bytes = stream.EndRead(result);

if (!result.IsCompleted)
  <timed out>
else
  <read data>
...


回答2:

You should look at the C# example on the Socket.Connected MSDN page you linked to. It has a significantly different implementation of a method to determine whether the socket is still connected.

// .Connect throws an exception if unsuccessful
client.Connect(anEndPoint);

// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
    byte [] tmp = new byte[1];

    client.Blocking = false;
    client.Send(tmp, 0, 0);
    Console.WriteLine("Connected!");
}
catch (SocketException e) 
{
    // 10035 == WSAEWOULDBLOCK
    if (e.NativeErrorCode.Equals(10035))
        Console.WriteLine("Still Connected, but the Send would block");
    else
    {
        Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode);
    }
}
finally
{
    client.Blocking = blockingState;
}

Console.WriteLine("Connected: {0}", client.Connected);