客户端关闭连接时在服务器上多次调用 NetworkStream 回调
NetworkStream callback called multiple times on server when client closes connection
我正在编写一个模拟器来测试对象的(反)序列化并通过 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
returns 正确,但有 0 个字节要读取。 stream.DataAvailable
也是假的。
但是,除此之外,我看不出有什么办法可以在服务器端知道客户端关闭了连接。并且当连接仍然打开时,有可能从客户端接收到 0 个字节。
af 现在发生的情况是,在客户端关闭连接后,DataReceiveCallback
被一次又一次地调用,总是读取 0 个字节。
我想做的是如果客户端关闭连接,则关闭服务器上的连接。我该怎么做才能实现这一目标?
注意:我知道这个例子缺乏适当的错误处理,你不应该在你阅读了一些内容后开始同步阅读,直到 stream.Read()
returns 0。但是,这适合我现在的需要,因为我直接控制所有正在发送的数据。
您正在循环调用 BeginRead。如果流完成,读取立即完成并返回 0 字节并继续循环。
您对异步 IO 的使用毫无意义,因为您正在阻塞等待 IO 完成的线程。毫无疑问,您是从错误的 MSDN 示例中复制的(实际上并没有理解它的作用)。
扔掉它并使用普通循环同步读取。当读取调用 returns 0 时,你打破了循环。
另请注意,TCP 不以您现在假设的任何方式支持消息。删除 CanRead
和 DataAvailable
的所有用法。只要您考虑这些属性,您就很可能做错了。
我正在编写一个模拟器来测试对象的(反)序列化并通过 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
returns 正确,但有 0 个字节要读取。 stream.DataAvailable
也是假的。
但是,除此之外,我看不出有什么办法可以在服务器端知道客户端关闭了连接。并且当连接仍然打开时,有可能从客户端接收到 0 个字节。
af 现在发生的情况是,在客户端关闭连接后,DataReceiveCallback
被一次又一次地调用,总是读取 0 个字节。
我想做的是如果客户端关闭连接,则关闭服务器上的连接。我该怎么做才能实现这一目标?
注意:我知道这个例子缺乏适当的错误处理,你不应该在你阅读了一些内容后开始同步阅读,直到 stream.Read()
returns 0。但是,这适合我现在的需要,因为我直接控制所有正在发送的数据。
您正在循环调用 BeginRead。如果流完成,读取立即完成并返回 0 字节并继续循环。
您对异步 IO 的使用毫无意义,因为您正在阻塞等待 IO 完成的线程。毫无疑问,您是从错误的 MSDN 示例中复制的(实际上并没有理解它的作用)。
扔掉它并使用普通循环同步读取。当读取调用 returns 0 时,你打破了循环。
另请注意,TCP 不以您现在假设的任何方式支持消息。删除 CanRead
和 DataAvailable
的所有用法。只要您考虑这些属性,您就很可能做错了。