处理阻塞的.NET套接字超时

4

使用Accept方法创建的TcpClient实例用于管理客户端连接。当我需要终止服务器线程时,问题就出现了,因为它被阻塞在接收调用上。

所以我设置了TcpClient ReceiveTimeout,以便每隔n毫秒循环一次以测试退出条件。结果是,Receive操作引发异常(SocketException),错误代码为SocketError.TimedOut。好吧,我在想...

问题在于属性Socket.Connected返回false,但正如MSDN文档中所述:

Connected属性的值反映了最近一次操作时连接的状态。如果您需要确定连接的当前状态,请进行非阻塞、零字节的Send调用。如果调用成功返回或抛出WAEWOULDBLOCK错误代码(10035),则套接字仍处于连接状态;否则,套接字不再连接。

所以,我按照说明做了:

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) {
             // ...
        }
}

但是,即使执行了异步发送操作,属性 mSocket.Connected 总是为false,导致外部循环终止(其他线程调用Disconnect方法来终止服务器线程)。

我错过了什么吗?

2个回答

3
问题在于如果超时发生,TcpClient 将会断开连接。因此,您的方法不起作用。 请使用异步读/写函数或使用 select。 使用异步函数调用的最简单方法可能是这样的:
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>
...

恭喜您的第一个答案!但这正是我想要避免的。为了使用非阻塞套接字,我将重构服务器逻辑,并使每个网络操作异步进行,而不需要生成额外的线程。“问题在于如果超时发生,则TcpClient会断开连接”:这是在哪里写的?在stdc非阻塞套接字中,在超时(使用select实现)之后不会断开连接。 - Luca
我发布的代码使用了异步的BeginRead/EndRead函数。但是它使用WaitOne来阻塞。所以你不需要改变你的服务器逻辑。BeginRead不会阻塞。WaitOne会在有数据或者指定的超时时间到达之前一直阻塞。可以使用!result.IsCompleted来检查是否超时或者有数据可用。 - pitt7
我不知道“如果超时,则TcpClient会断开连接”在哪里写的。但是我遇到了完全相同的问题。如果Read由于超时返回,则Connected为False。我对此没有进行太多研究,只是接受在C#中设置超时值并经常遇到此超时不是预期行为。 - pitt7
我同意。然而,一个更简单的方法是在处理网络通信时避免在服务器线程上进行连续循环,从而省去了超时功能的需要。 - Luca
1
我遇到了同样的问题并尝试了这个解决方案。然而,如果没有接收到任何字节,WaitOne会在指定的超时时间后退出,但EndRead会阻塞直到至少接收到一个字节或连接关闭。 - Tarnschaf
在调用stream.EndRead之前,检查result.IsCompleted是否可行? - jm.

0
你应该查看你链接的 Socket.Connected MSDN 页面上的 C# 示例。它有一个显著不同的方法来确定套接字是否仍然连接。
// .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);

我有相同的行为。发送例程不会抛出异常,而且Connected属性仍然设置为false。稍后的例程调用(发送/接收)的行为与galbarm所描述的相同。 - Luca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接