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
I intentionally choose the buffer to be that small to test/learn about the behaviour if received data doesn't fit into the buffer.
The code works so far, in that it keeps receiving and that sending is possible from another thread.
When starting to receive, the client doesn't notice when the server shuts down, while waiting for data.
Questions
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?
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?Using a
while(true)
loop somehow feels wrong, althoughReceiveAsync
seems to block the thread and therefor no CPU cycles are getting wasted. What would be a more TPL-like approach to this?Microsoft's documentation is mentioning
socket.Available
, which (according to the doc) should be used especially in conjunction with theReceiveAsync
method. But replacing thewhile(true)
withwhile(socket.Available != 0)
will prematuraly end my receiving thread, when no data is available. So how would one use that properly?How should I handle socket errors? E.g. when the server side closes the connection or something else occurs while receiving?
How should I properly handle/implement timeouts?
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