TcpClient; NetworkStream; ReadAsync; C#
Asked Answered
M

1

5

Please excuse my lack of knowledge regarding Tasks and Async.

Using the TcpClient class I am creating a connection with an available server:

void async RunClientAsync()
{
    TcpClient client = new TcpClient();
    try
    {
        await client.ConnectAsync(IPAddress.Parse("1.1.1.1"), 8889);
        Task.Start(() => ReadClientAsync(client));
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

// -----

void async ReadClientAsync(TcpClient client)
{
    byte[] bf = new byte[2048];
    try
    {
        while(true)
        {
            int br = await client.NetworkStream().ReadAsync();
            if (br > 0) 
            {
                HandleInboundData(bf, br);
            }
        }
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }
}

The helper methods HandleException(Exception ex) and HandleInboundData(byte[] buffer, int length) will perform the assumed tasks.

The connection to the server will be in perpetuity and the data received from the server will be of unknown length and frequency, the idea being to throw a task out there that receives and processes the inbound data only when data is available.

ReadClientAsync(TcpClient client) is an obvious fail because ReadAsync will always return 0 bytes if there is no data available.

How should I approach writing ReadClientAsync using async / task to prevent the busy-looping situation? I've used BeginRead / EndRead in these situations before, which has worked fine. Would that be the solution in this particular case?

Thank you,

Merissameristem answered 8/1, 2016 at 20:38 Comment(2)
Thank you for the comment. This is from the MSDN entry for ReadAsync: "A task that represents the asynchronous read operation. The value of the TResult parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached." I understood end of stream to mean that there was no data in the stream to read.Merissameristem
Understood. Thank you Luaan. I believe I understand where the issue is.Merissameristem
D
10

No, that's not how TCP works.

NetworkStream is considered to be in an "end of stream" state when the other side has initiated (possible one-way) shutdown. That's when ReadAsync (or Read, for that matter) returns zero - not in any other case.

The MSDN documentation can be easily misunderstood - mainly because you're looking at the wrong piece of documentation. NetworkStream doesn't override ReadAsync (there's no reason to do so), so you're actually looking at the documentation for the generic Stream.ReadAsync. In contrast, the documentation for NetworkStream.Read says:

This method reads data into the buffer parameter and returns the number of bytes successfully read. If no data is available for reading, the Read method returns 0. The Read operation reads as much data as is available, up to the number of bytes specified by the size parameter. If the remote host shuts down the connection, and all available data has been received, the Read method completes immediately and return zero bytes.

Note the final sentence, which tells you what it actually means for a NetworkStream to be "end of stream". This is how TCP connections are closed.

Your response to this should usually be shutting down the connection from the other side as well - return out of your helper method and clean up the socket. In any case, do not repeat the while (true) again - you're just going to get an infinite loop that eats 100% of your CPU.

If you want a few pointers on how to handle C# asynchronous sockets with await, have a look at my sample at https://github.com/Luaancz/Networking/tree/master/Networking%20Part%202. Note the disclaimers - this is in no way production ready. But it does solve a few of the very common mistakes people make when implementing TCP communication.

Dahl answered 8/1, 2016 at 21:10 Comment(2)
How would you time out so ReadAsync or WriteAsync or ConnectAsync timeout after 10 seconds. In other words, they either complete successfully or timeout after 10 seconds?Brevity
@Brevity Pass a CancellationToken with CancelAfter set; you can combine it with existing an CancellationToken via CancellationTokenSource.CreateLinkedTokenSource. Now your clients better send keep alive packets if they have nothing else to send or kiss them goodbye :pFiliano

© 2022 - 2024 — McMap. All rights reserved.