1
\$\begingroup\$

I am having a hard time to come up with a simple TCP client, that should use one socket and two threads (one for sending and one for receiving).

As using TPL tasks is the way asynchrony should be handled in C#, I am trying to use the *Async methods provided by the SocketTaskExtensions class.

This is what my class looks so far:

public class TcpConnector
{
    private readonly Socket socket;

    public bool IsConnected => this.socket.Connected;

    public TcpConnector()
    {
        this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

    public async Task ConnectAsync(IPEndPoint endpoint)
    {
        await this.socket.ConnectAsync(endpoint);
    }

    public async Task<int> SendAsync(string message)
    {
        if (!this.IsConnected)
        {
            throw new SocketException((int) SocketError.NotConnected);
        }

        ArraySegment<byte> buffer = Encoding.UTF8.GetBytes(message);

        return await this.socket.SendAsync(buffer, SocketFlags.None);
    }

    public async Task ReceiveAsync()
    {
        if (!this.IsConnected)
        {
            throw new SocketException((int) SocketError.NotConnected);
        }

        var segment = new ArraySegment<byte>(new byte[4]);

        while (true)
        {
            var receiveTask = this.socket.ReceiveAsync(segment, SocketFlags.None);
            receiveTask.Wait();

            var message = Encoding.UTF8.GetString(segment.Array, 0, receiveTask.Result);          
        } 
    }
}

And this is one of my unit tests:

[Fact]
[Trait("Category", "UnitTest")]
public async Task Should_Successfully_ReceiveAsync()
{
    // Arrange
    var client = new TcpConnector();
    var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);

    // Act
    await client.ConnectAsync(endpoint);

    var receiveTask = client.ReceiveAsync();
    await receiveTask; 

    // Assert
    ...
}

Notes

  1. I intentionally choose the buffer to be that small to test/learn about the behaviour if received data doesn't fit into the buffer.

  2. The code works so far, in that it keeps receiving and that sending is possible from another thread.

  3. When starting to receive, the client doesn't notice when the server shuts down, while waiting for data.

Questions

  1. From what I understand is that a TCP socket provides a way of a bidirectional and asynchronous communication. Which means a client should be able to send and receive at the same time. Is that assumption correct?

  2. How can I properly use ArraySegment here? Should the data to be send and received be hold in one (private) ArraySegment<byte> buffer? What are the benefits of using this struct over a plain byte buffer?

  3. Using a while(true) loop somehow feels wrong, although ReceiveAsync seems to block the thread and therefor no CPU cycles are getting wasted. What would be a more TPL-like approach to this?

  4. Microsoft's documentation is mentioning socket.Available, which (according to the doc) should be used especially in conjunction with the ReceiveAsync method. But replacing the while(true) with while(socket.Available != 0) will prematuraly end my receiving thread, when no data is available. So how would one use that properly?

  5. How should I handle socket errors? E.g. when the server side closes the connection or something else occurs while receiving?

  6. How should I properly handle/implement timeouts?

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Your code so far is not really useful. Method ReceiveAsync for instance, is only half-implemented. Could you complete the implementation of your methods? \$\endgroup\$ – dfhwze Jul 12 '19 at 4:41
  • 2
    \$\begingroup\$ ReceiveAsync is unclear and impossible to review, because we can't see how you want to handle the result, and what to do after a single received operation is completed. You have to show a full implementation and a working use case. \$\endgroup\$ – user73941 Jul 12 '19 at 5:01
  • 1
    \$\begingroup\$ Thanks for replying and not voting down. I will invest more time and come up with a full implementation. \$\endgroup\$ – Matthias Güntert Jul 12 '19 at 6:52