Search code examples
c#.net-coretcpclient

TcpClient/NetworkStream read write simoultanously on same instance


I need to communicate with a piece of hardware that only accepts 1 TCP connection. But I cannot find any good example where the same NetworkStream is used for reading and writing simoultanously.

The MSDN documentation says the following:

Read and write operations can be performed simultaneously on an instance of the NetworkStream class without the need for synchronization. As long as there is one unique thread for the write operations and one unique thread for the read operations, there will be no cross-interference between read and write threads and no synchronization is required.

However, I am unable to find an example that shows how to use the same instance of a NetworkStream in different threads to read and write.

I am unsure whether I should:

  • simply use the NetworkStream directly for reading with myNetworkStream.ReadAsync(), or use a StreamReader,
  • simply use the NetworkStream directly for writing with myNetworkStream.WriteAsync(), or use a StreamWriter,
  • directly start a new thread with a while(true) for reading upon instantiation of the TcpClient,
  • keep a global reference to my NetworkStream instance so that I can write whenever...

I'm far from being an expert when it comes to TCP connections/network communication, so there are definitely things I don't fully understand...

Thanks in advance :)


Solution

  • Remember with I/O bound tasks, there is no thread.

    The paragraph you posted doesn't really deal with tasks, for example:

    var client = new TcpClient();
    await client.ConnectAsync("example.com", 80);
    var stream = client.GetStream();
    
    // start reading from the socket
    var rBuf = new byte[2048];
    var tRecv = stream.ReadAsync(rBuf, 0, rBuf.Length);
    
    // send data and wait for it to complete
    var sBuf = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
    await stream.WriteAsync(sBuf, 0, sBuf.Length);
    
    // now await for the data
    var bytesRecv = await tRecv;
    
    Console.WriteLine($"Received {bytesRecv} bytes");
    

    There isn't two simultaneous threads doing something at the same time when dealing with the underlying stream.

    What the paragraph is talking about is doing something like this:

    var client = new TcpClient();
    await client.ConnectAsync("example.com", 80);
    var stream = client.GetStream();
    
    new Thread(() =>
    {
        var rBuf = new byte[2048];
        var bytesRecv = stream.Read(rBuf, 0, rBuf.Length);
        Console.WriteLine($"Received {bytesRecv} bytes");
    }).Start();
    
    Thread.Sleep(500); // ensure the above thread has started
    
    new Thread(() =>
    {
        var sBuf = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
        stream.Write(sBuf, 0, sBuf.Length);
    }).Start();
    
    Console.ReadKey(true);
    

    In the above example, you have two distinct threads both doing something at the same time with the underlying stream and nothing "blows up".

    As far as what pattern you should use... You have a few possibilities. You have first example above with tasks (modern approach), or you could do the threaded while(true) method using blocking calls ("old-school" approach). Or the Begin... and End... methods (between old-school and modern).

    What's best? You choose. Personally I like having events fired off when data is available, so I tend to use the BeginConnect/EndConnect, BeginReceive/EndReceive, etc methods.

    Sometimes though, I like to do a while(true) using ReadAsync() but using a CancellationToken to kill the loop.

    I do not suggest using the while(true) with blocking calls. With having the wonderful tools that the TPL gives us, we shouldn't have to start generic threads anymore to accomplish anything.