I'm writing a simulator to test (de-)serialization of objects and sending them over TCP.
I use TcpClient, TcpListener and NetworkStreams in C# to communicate.
I want to open a connection, send multiple messages and after some time close the connection.
Establishing a connection and sending packets in both directions works fine.
However when i close the connection on the client (call stream.Close()
and tcpClient.Close()
) the server's callback for BeginRead is called again and again.
The client code:
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();
}
The server code:
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();
}
When I call StopClient()
on the client, the callback (DataReceiveCallback
) on the server is being invoked. stream.CanRead
returns true, but there are 0 bytes to read. Also stream.DataAvailable
is false.
However, apart from that, I see no way to know on the server side, that the client closed the connection. And it is possible to receive 0 bytes from the client when the connection is still open.
What happens as af now, is that after closing the connection on the client, DataReceiveCallback
is called again and again, always with 0 bytes to read.
What I want to do is close the connection on the server if the client closes the connection. What can I do to achieve this?
Note: I know that this example lacks proper error handling and you shouldn't start reading synchronously once you read something until the stream.Read()
returns 0. However, this suits my needs for now as I directly control all the data being sendt.
You are calling BeginRead in a loop. If the stream is done the read completes immediately with 0 bytes and the loop continues.
Your use of async IO makes no sense because you are blocking a thread waiting for the IO to complete. Undoubtedly, you have copied that from bad MSDN samples (without understanding what this does, really).
Throw that away and use a normal loop for reading synchronously. When the read call returns 0 you break the loop.
Also note, that TCP does not support messages in any way which you seem to assume right now. Delete all usages of
CanRead
andDataAvailable
. As long as you are thinking in terms of these properties you are most likely doing it wrong.