Search code examples
c#.netasync-awaittcpclienttcplistener

Problems with asynchronous functions with TcpListener and TcpClient, function not waiting on await keyword


I am new to asynchronous socket programming, and I am having problems with my asynchronous functions.

I am trying to create a chat program that uses Windows Forms for the client, and a console application for the server.

Here is the code for handling connections on my server:

public async void StartServer()
{
    TcpListener listener = new TcpListener(_ip, _port);
    listener.Start();
    Console.WriteLine("Server is running on IP: {0} Port: {1}", _ip.ToString(), _port);
    while (true)
    {
        try
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            HandleConnections(client);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}
private async void HandleConnections(TcpClient client)
{
    NetworkStream stream = client.GetStream();
    byte[] buffer = new byte[256];
    string message = null;
    int x;
    while(stream.DataAvailable)
    {
        x = await stream.ReadAsync(buffer, 0, buffer.Length);
        message += Encoding.ASCII.GetString(buffer);
    }
    message = message.Replace('\0', ' ');
    message = message.Trim();
    Console.WriteLine("Message Recieved: " + message);
    byte[] bytes = Encoding.ASCII.GetBytes(message);
    await stream.WriteAsync(bytes, 0, bytes.Length);
    stream.Close();
}

And here is the code for the client program connecting to the server:

private async void ConnectButton_Click(object sender, EventArgs e)
{
    IPAddress address = IPAddress.Parse(IPInput.Text);
    client = new TcpClient();
    await client.ConnectAsync(address, 12345);
    NetworkStream stream = client.GetStream();
    string message = UsernameInput.Text + " Connected!";
    Task<int> sendTask = SendMessage(stream, message);
    int sendComp = await sendTask;
    Task<string> recieveTask = RecieveMessage(stream);
    string recieved = await recieveTask;
    stream.Close();
    ChatText.AppendText(recieved);
}
private async Task<int> SendMessage(NetworkStream stream, string message)
{
    byte[] bytes = Encoding.ASCII.GetBytes(message + "\r\n");
    await stream.WriteAsync(bytes, 0, bytes.Length);
    return 1;
}
private async Task<string> RecieveMessage(NetworkStream stream)
{
    byte[] buffer = new byte[256];
    string message = null;
    int x;
    while (stream.DataAvailable)
    {
        x = await stream.ReadAsync(buffer, 0, buffer.Length);
        message += Encoding.ASCII.GetString(buffer);
    }
    return message;
}

The first problem that I am having is when I run the client program and click the ConnectButton, the message gets sent to the server program which outputs Message Recieved: user Connected!, but then the client program encounters a null reference exception on the line ChatText.AppendText(recieved); saying that the recieved variable is null. It seems that the line string recieved = await recieveTask; is not waiting for the task to finish executing, and it jumps to the next line without assigning a value to recieved. If I put a breakpoint at the top of the private async Task<string> RecieveMessage(NetworkStream stream) function and step through it, then the recieved variable gets it's value and the code will complete successfully, but without the breakpoint I get the null reference exception.

The next issue that I am having, is if I leave the server running and open the client again and try connecting, the server gets a null reference exception on the line message = message.Replace('\0', ' ');. The first time I run with the client, the server receives the message successfully, but the second time, it doesn't get any data from the stream and leaves the variable null, resulting in a null reference exception.

I apologize if my code is garbage, I have been reading the MSDN documentation for hours and am unable to come up with a solution, and I feel like I am doing this completely wrong. So my questions are as follows:

What is causing these errors that I am encountering? And am I approaching this problem the right way?


Solution

  • Both of your issues are not related to asynchronous functions, and actually both issues are because of the same problem:

    while (stream.DataAvailable)
    {
        // read stream here
    }
    

    If data is not yet available to read from the stream - both of your ReceiveMessage and HandleConnections functions just skip reading stream at all. What you should do instead (in your case) is:

    do
    {
        // read your stream here
    } while (stream.DataAvailable);
    

    Then first Read (or ReadAsync) will wait until first data chunk arrives, and only after first chunk will check if more data is already available.

    Also note that you use large buffer (256 bytes) while client\server send short messages (like "Client received: xxx"), which means most of the buffer is empty and when you convert it to string via Encoding.ASCII.GetString - you get a lot of whitespace at the end ("Client received: xxx ... ").