【问题标题】:Reading from closed NetworkStream doesn't cause any exception从关闭的 NetworkStream 读取不会导致任何异常
【发布时间】:2013-12-11 17:14:08
【问题描述】:

我正在尝试创建一个相当简单的客户端-服务器应用程序,但对于通信,我想使用二进制序列化对象。通信本身似乎很好,但是当我在客户端关闭流时,服务器并没有真正注意到它并继续尝试读取流。

服务器端(Server 类,在单独的线程中执行):

监听连接

listener = new TcpListener(IPAddress.Parse("127.0.0.1"), this.Port);
listener.Start();
while (!interrupted)
{
    Console.WriteLine("Waiting for client");
    TcpClient client = listener.AcceptTcpClient();
    AddClient(client);
    Console.WriteLine("Client connected");
}

添加客户端:

    public void AddClient(TcpClient socket)
    {
        Client client = new Client(this, socket);
        this.clients.Add(client);
        client.Start();
    }

监听消息(在客户端类的深处):

BinaryFormatter deserializer = new BinaryFormatter();
while (!interrupted)
{
    System.Diagnostics.Debug.WriteLine("Waiting for the message...");
    AbstractMessage msg = (AbstractMessage)deserializer.Deserialize(stream);
    System.Diagnostics.Debug.WriteLine("Message arrived: " + msg.GetType());
    raiseEvent(msg);
}

单元测试:

Server server = new Server(6666);
server.Start();

Thread.Sleep(500);

TcpClient client = new TcpClient("127.0.0.1", 6666);
var message = new IntroductionMessage();
byte[] arr = message.Serialize();

client.GetStream().Write(arr, 0, arr.Length);

Thread.Sleep(500);

Assert.AreEqual(1, server.Clients.Count);

client.GetStream().Close();
client.Close();

Thread.Sleep(1000);

Assert.AreEqual(0, server.Clients.Count);

server.Stop();

所以消息被正确读取,但是,当我关闭流时,deserializer.Deserialize(stream) 似乎没有抛出任何异常......所以不应该以这种方式阅读,还是应该关闭客户以不同的方式?

【问题讨论】:

  • 服务器从哪里获取流? Server 对象内的 TcpListener 实例?
  • 现在,我更新了问题

标签: c#


【解决方案1】:

假设服务器中用于反序列化消息的流是NetworkStream(这是TcpClient.GetStream()返回的流的类型),你应该做两件事:

  1. 为“连接结束”定义特定消息。当服务器收到并反序列化此消息时,退出 while 循环。为了使这个工作,客户端显然需要在关闭它的 TcpClient 连接之前发送这样的消息。 (您可能会选择以类似方式工作的不同机制——但为什么不使用您已有的消息机制...)

  2. NetworkStream上设置一个ReadTimeout,这样万一连接丢失并且客户端无法发送“连接结束”消息,服务器将达到超时并意识到客户端“已死”。

您的服务器中用于侦听客户端消息的代码应如下所示:

//
// Time-out after 1 minute after receiving last message
//
stream.ReadTimeOut = 60 * 1000;

BinaryFormatter deserializer = new BinaryFormatter();

try
{
    while (!interrupted)
    {
        System.Diagnostics.Debug.WriteLine("Waiting for the message...");
        AbstractMessage msg = (AbstractMessage)deserializer.Deserialize(stream);
        System.Diagnostics.Debug.WriteLine("Message arrived: " + msg.GetType());

        //
        // Exit while-loop when receiving a "Connection ends"  message.
        // Adapt this if condition to whatever is appropriate for
        // your AbstractMessage type.
        //
        if (msg == ConnectionEndsMessage) break;

        raiseEvent(msg);
    }
}
catch (IOException ex)
{
    ... handle timeout and other IOExceptions here...
}

【讨论】:

  • @Kamil:根据消息的原始大小和使用软件的(网络)环境,您将不得不选择一个相当大的超时时间(以适应糟糕的酒店互联网连接或者甚至是粗糙的调制解调器连接,咳咳……)。在我的示例中,我选择了一分钟超时。如果您的程序仅在 LAN(或低延迟 Internet 连接上)运行,您可以选择小于 30 秒的超时。
  • 在任何情况下,不要只依赖超时并实现专用的“连接结束”消息。这将极大地帮助您解决不清楚问题是客户端或服务器中的错误,还是互联网/网络连接问题......
猜你喜欢
  • 1970-01-01
  • 2018-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-29
  • 1970-01-01
相关资源
最近更新 更多