代码之家  ›  专栏  ›  技术社区  ›  Blair Holloway

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

  •  2
  • Blair Holloway  · 技术社区  · 15 年前

    我的一个朋友来找我时遇到了一个问题:当在连接的服务器端使用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。

    提供了堆栈溢出搜索 回答-因为没有调用socket.receive,所以socket没有检测到断开连接。很公平。我们可以解决这个问题:

    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)调用block,它将正确检测断开连接。但是,这确实会产生为每个客户机创建和维护线程的开销。

    我觉得我缺少了一些秘密的、令人敬畏的答案,这些答案可以保持原始代码的清晰性,但避免使用额外的线程来执行异步读取。尽管如此,networkstream类可能从未设计用于这种用途。有人能发光吗?

    更新: 我只是想澄清一下,我有兴趣看看.NET框架是否有一个解决方案涵盖了网络流的这种使用(即轮询 避免阻塞)-显然可以这样做;网络流可以很容易地包装在提供功能的支持类中。框架基本上要求你使用线程来避免在网络流上阻塞,这看起来很奇怪。阅读,或者偷看套接字本身来检查断开连接,就像是一个bug。或者可能缺少特征。;)

    2 回复  |  直到 15 年前
        1
  •  1
  •   feroze    15 年前

    服务器是否希望通过同一连接发送多个对象?如果是这样的话,我看不到这段代码是如何工作的,因为没有发送分隔符来表示第一个对象从何处开始,下一个对象从何处结束。

    如果只有一个对象被发送,连接在之后关闭,那么原始代码就可以工作了。

    必须启动网络操作才能查明连接是否仍处于活动状态。我要做的是,我不直接从网络流反序列化,而是缓冲到一个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);
                }
            }
    
        2
  •  1
  •   sth    14 年前

    是的,但是如果你在拿到尺寸之前失去了连接怎么办?即在下一行之前:

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

    ReadInt32() 将无限期阻塞线程。

    推荐文章