首页 文章

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

提问于
浏览
2

我的一个朋友遇到了一个问题:当在连接的服务器端使用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éeNetworkStream.Read)调用阻塞,这将正确检测断开连接 . 但是,这确实存在为每个客户端创建和维护线程的开销 .

我觉得我错过了一些秘密的,令人敬畏的答案,它将保留原始代码的清晰度,但避免使用额外的线程来执行异步读取 . 或许,NetworkStream类可能从未设计过这种用法 . 任何人都能解释一下吗?

Update: 只是想澄清我有兴趣看看.NET框架是否有一个涵盖NetworkStream使用的解决方案(即轮询和避免阻塞) - 显然它可以完成; NetworkStream可以很容易地包含在提供功能的支持类中 . 框架本质上要求你使用线程来避免阻塞NetworkStream.Read,或者查看套接字本身来检查断开连接 - 这几乎就像是一个bug . 或者可能缺乏某项功能 . ;)

2 回答

  • 1

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

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

    必须启动网络操作才能确定连接是否仍处于活动状态 . 我要做的是,不是直接从网络流反序列化,而是缓冲到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);
                }
            }
    
  • 1

    是的,但如果你在获得尺寸之前丢失连接怎么办?即在下一行之前:

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

    ReadInt32() 将无限期地阻止该线程 .

相关问题