sockets - 使用 NetworkStream 类时检测客户端 TCP 断开连接

标签 sockets tcpclient networkstream disconnect

我的一个 friend 向我提出了一个问题:在连接的服务器端使用 NetworkStream 类时,如果客户端断开连接,NetworkStream 无法检测到它。

精简后,他的 C# 代码如下所示:

List<TcpClient> connections = new List<TcpClient>();
TcpListener listener = new TcpListener(7777);
listener.Start();

while(true)
{
    if (listener.Pending())
    {
        connections.Add(listener.AcceptTcpClient());
    }
    TcpClient deadClient = null;
    foreach (TcpClient client in connections)
    {
        if (!client.Connected)
        {
            deadClient = client;
            break;
        }
        NetworkStream ns = client.GetStream();
        if (ns.DataAvailable)
        {
            BinaryFormatter bf = new BinaryFormatter();
            object o = bf.Deserialize(ns);
            ReceiveMyObject(o);
        }
    }
    if (deadClient != null)
    {
        deadClient.Close();
        connections.Remove(deadClient);
    }
    Thread.Sleep(0);
}

该代码有效,客户端可以成功连接,服务器可以读取发送给它的数据。但是,如果远程客户端调用 tcpClient.Close(),服务器不会检测到断开连接 - client.Connected 仍为 true,而 ns.DataAvailable 为 false。

对 Stack Overflow 的搜索提供了一个答案 - 由于未调用 Socket.Receive,因此套接字未检测到断开连接。很公平。我们可以解决这个问题:

foreach (TcpClient client in connections)
{
    client.ReceiveTimeout = 0;
    if (client.Client.Poll(0, SelectMode.SelectRead))
    {
        int bytesPeeked = 0;
        byte[] buffer = new byte[1];
        bytesPeeked = client.Client.Receive(buffer, SocketFlags.Peek);
        if (bytesPeeked == 0)
        {
            deadClient = client;
            break;
        }
        else
        {
            NetworkStream ns = client.GetStream();
            if (ns.DataAvailable)
            {
                BinaryFormatter bf = new BinaryFormatter();
                object o = bf.Deserialize(ns);
                ReceiveMyObject(o);
            }
        }
    }
}

(为了简洁起见,我省略了异常处理代码。)

这段代码可以工作,但是,我不会称这个解决方案“优雅”。我知道的另一个优雅的解决方案是为每个 TcpClient 生成一个线程,并允许 BinaryFormatter.Deserialize(née NetworkStream.Read)调用阻塞,这将正确检测断开连接。不过,这确实会产生为每个客户端创建和维护线程的开销。

我感觉我错过了一些 secret 的、很棒的答案,它可以保留原始代码的清晰度,但避免使用额外的线程来执行异步读取。不过,也许 NetworkStream 类从来就不是为这种用途而设计的。谁能解释一下吗?

更新:只是想澄清一下,我有兴趣看看 .NET 框架是否有一个解决方案涵盖 NetworkStream 的这种使用(即轮询避免阻塞) ) - 显然这是可以做到的; NetworkStream 可以轻松地包装在提供该功能的支持类中。看起来很奇怪的是,该框架本质上要求您使用线程来避免 NetworkStream.Read 上的阻塞,或者查看套接字本身以检查断开连接 - 几乎就像它是一个错误一样。或者可能缺乏某项功能。 ;)

最佳答案

服务器是否期望通过同一连接发送多个对象?如果是的话,我不知道这段代码将如何工作,因为没有发送任何分隔符来表示第一个对象开始和下一个对象结束的位置。

如果只发送一个对象并且连接随后关闭,则原始代码将起作用。

必须启动网络操作才能查明连接是否仍然处于事件状态。我要做的是,不是直接从网络流反序列化,而是缓冲到 MemoryStream 中。这将使我能够检测到连接何时丢失。我还会使用消息框架来分隔流上的多个响应。

        MemoryStream ms = new MemoryStream();

        NetworkStream ns = client.GetStream();
        BinaryReader br = new BinaryReader(ns);

        // message framing. First, read the #bytes to expect.
        int objectSize = br.ReadInt32();

        if (objectSize == 0)
              break; // client disconnected

        byte [] buffer = new byte[objectSize];
        int index = 0;

        int read = ns.Read(buffer, index, Math.Min(objectSize, 1024);
        while (read > 0)
        {
             objectSize -= read;
             index += read;
             read = ns.Read(buffer, index, Math.Min(objectSize, 1024);
        }

        if (objectSize > 0)
        {
             // client aborted connection in the middle of stream;
             break;
        } 
        else
        {
            BinaryFormatter bf = new BinaryFormatter();
            using(MemoryStream ms = new MemoryStream(buffer))
            {
                 object o = bf.Deserialize(ns);
                 ReceiveMyObject(o);
            }
        }

关于sockets - 使用 NetworkStream 类时检测客户端 TCP 断开连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1895832/

相关文章:

c# - TCP 套接字/NetworkStream 意外失败

ruby - 如何在 Ruby 中建立启用 SSL 的 TCP/IP 连接

C# TcpClient ReadAsync 读取大消息

C# - 使用 StreamReader 读取 HTTP 请求

javascript - 为什么我的 nodejs 代码中的第一个和第二个套接字请求之间存在差异?

c# - 用于电子邮件发送的波兰字符 MIME 格式的问题

sockets - Windows服务中的Indy UDP Server无法监听

c - 即使以二进制模式写入后,文件也被损坏

c++ - 捕获和播放 MJPEG - 使用 OpenCV 和 ffmpeg 通过 UDP 传输网络视频流

c# - 套接字关闭时读取网络流