【问题标题】:NetworkStream callback called multiple times on server when client closes connection当客户端关闭连接时,在服务器上多次调用 NetworkStream 回调
【发布时间】:2015-09-10 10:39:20
【问题描述】:

我正在编写一个模拟器来测试对象的(反)序列化并通过 TCP 发送它们。 我在 C# 中使用 TcpClient、TcpListener 和 NetworkStreams 进行通信。 我想打开一个连接,发送多条消息并在一段时间后关闭连接。 建立连接并双向发送数据包工作正常。 但是,当我关闭客户端上的连接(调用 stream.Close() 和 tcpClient.Close())时,服务器的 BeginRead 回调会被一次又一次地调用。

客户端代码:

private TcpClient client;

public void StartClient()
{
    this.client = new TcpClient(this.serverAddress, this.port);
}

public void Send(byte[] data)
{
    var stream = this.client.GetStream();
    stream.Write(data, 0, data.Length);
}

public void StopClient()
{
    this.client.GetStream().Close();
    this.tcpClient.Close();
}

服务器代码:

private readonly AutoResetEvent autoResetEvent= new AutoResetEvent(false);

private TcpListener tcpListener;

public event Action<byte[]> MessageReceived;

public void StartListening()
{
    this.tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1337);
    this.tcpListener.Start();
    this.tcpListener.BeginAcceptTcpClient(this.ClientConnecting, this.tcpListener);
}

private void ClientConnecting(IAsyncResult result)
{
    var listener = (TcpListener)result.AsyncState;

    this.tcpClient = listener.EndAcceptTcpClient(result);
    this.Listen();
}

private void Listen()
{
    var stream = this.tcpClient.GetStream();
    var buffer = new byte[256];
    while (stream.CanRead)
    {
        try
        {
            stream.BeginRead(buffer, 0, buffer.Length, this.DataReceiveCallback, new ReceiveState { Buffer = buffer, Stream = stream });
        }
        catch
        {
            this.StopListening();
            break;
        }

        this.autoResetEvent.WaitOne();
    }
}

public void StopListening()
{
    var stream = this.tcpClient.GetStream();
    stream.Close();

    this.tcpClient.Close();
    this.tcpClient = null;
}

private void DataReceiveCallback(IAsyncResult ar)
{
    var state = (ReceiveState)ar.AsyncState;
    var stream = state.Stream;

    if (stream.CanRead)
    {
        var buffer = state.Buffer;
        var numberOfBytesRead = stream.EndRead(ar);

        if (this.MessageReceived != null)
        {
            if (!stream.DataAvailable)
            {
                this.MessageReceived(buffer.Take(numberOfBytesRead).ToArray());
            }
            else
            {
                var receivedBytes = new List<byte>(buffer);
                while (stream.DataAvailable)
                {
                    numberOfBytesRead = stream.Read(buffer, 0, buffer.Length);
                    receivedBytes.AddRange(buffer.Take(numberOfBytesRead));
                }
                this.MessageReceived(receivedBytes.ToArray());
            }
        }
    }

    this.autoResetEvent.Set();
}

当我在客户端调用 StopClient() 时,正在调用服务器上的回调 (DataReceiveCallback)。 stream.CanRead 返回 true,但有 0 个字节要读取。 stream.DataAvailable 也是假的。

但是,除此之外,我看不出在服务器端,客户端关闭了连接。并且可以在连接仍然打开时从客户端接收 0 字节。

现在发生的事情是,在客户端关闭连接后,DataReceiveCallback 被一次又一次地调用,总是有 0 个字节要读取。

如果客户端关闭连接,我想要做的是关闭服务器上的连接。我该怎么做才能做到这一点?

注意:我知道这个例子缺乏适当的错误处理,你不应该在阅读后开始同步阅读,直到stream.Read() 返回 0。但是,这符合我现在的需要,因为我直接控制所有正在发送的数据。

【问题讨论】:

标签: c# asynchronous tcp tcpclient networkstream


【解决方案1】:

您正在循环调用 BeginRead。如果流完成,则读取立即以 0 字节完成并继续循环。

您使用异步 IO 毫无意义,因为您正在阻塞等待 IO 完成的线程。毫无疑问,您是从糟糕的 MSDN 示例中复制的(实际上并不了解它的作用)。

扔掉它并使用正常循环进行同步读取。当 read 调用返回 0 时,您将中断循环。

另外请注意,TCP 不支持您现在假设的任何方式的消息。删除CanRead 和DataAvailable 的所有用法。只要您根据这些属性进行思考,您就很可能做错了。

【讨论】:

  • 我仍然不明白为什么回调会被一次又一次地调用,因为通常它只有在有可用数据时才会生效。服务器在自己的线程中运行。我将异步方法与 AutoReset 事件结合使用,因为我希望线程被阻塞,直到有新数据可用。使用 NetworkStream.Read() 如果没有可用数据,它不会阻塞但会立即返回 0 字节。我必须一次又一次地调用 Read() 来检查是否有可用的数据,但我想避免忙于等待。
  • 不,NetworkStream.Read 阻塞直到至少有一个字节。如果它返回 0,则连接已被远程端关闭。使用 NetworkStream.Read。不需要您精心设计的方案。
  • 你完全正确。我误解了 NetworkStream.Read 的文档。谢谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多