Using .Net 4.5 Async Feature for Socket Programming

Using .Net 4.5 Async Feature for Socket Programming

...because you're so determined, I put together a very simple example of how to write an echo server to get you on your way. Anything received gets echoed back to the client. The server will stay running for 60s. Try telnetting to it on localhost port 6666. Take time to understand exactly what's going on here.

void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
TcpListener listener = new TcpListener(IPAddress.Any, 6666);
try
{
listener.Start();
//just fire and forget. We break from the "forgotten" async loops
//in AcceptClientsAsync using a CancellationToken from `cts`
AcceptClientsAsync(listener, cts.Token);
Thread.Sleep(60000); //block here to hold open the server
}
finally
{
cts.Cancel();
listener.Stop();
}
}

async Task AcceptClientsAsync(TcpListener listener, CancellationToken ct)
{
var clientCounter = 0;
while (!ct.IsCancellationRequested)
{
TcpClient client = await listener.AcceptTcpClientAsync()
.ConfigureAwait(false);
clientCounter++;
//once again, just fire and forget, and use the CancellationToken
//to signal to the "forgotten" async invocation.
EchoAsync(client, clientCounter, ct);
}

}
async Task EchoAsync(TcpClient client,
int clientIndex,
CancellationToken ct)
{
Console.WriteLine("New client ({0}) connected", clientIndex);
using (client)
{
var buf = new byte[4096];
var stream = client.GetStream();
while (!ct.IsCancellationRequested)
{
//under some circumstances, it's not possible to detect
//a client disconnecting if there's no data being sent
//so it's a good idea to give them a timeout to ensure that
//we clean them up.
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
var amountReadTask = stream.ReadAsync(buf, 0, buf.Length, ct);
var completedTask = await Task.WhenAny(timeoutTask, amountReadTask)
.ConfigureAwait(false);
if (completedTask == timeoutTask)
{
var msg = Encoding.ASCII.GetBytes("Client timed out");
await stream.WriteAsync(msg, 0, msg.Length);
break;
}
//now we know that the amountTask is complete so
//we can ask for its Result without blocking
var amountRead = amountReadTask.Result;
if (amountRead == 0) break; //end of stream.
await stream.WriteAsync(buf, 0, amountRead, ct)
.ConfigureAwait(false);
}
}
Console.WriteLine("Client ({0}) disconnected", clientIndex);
}

C# Socket performance with .Net 4.5 Async vs [...]Async vs Begin[...]

What better choice to have performance with thousands of simultaneous
connections?

...

A curiosity regarding the Begin[...]. If I have a MMORPG server where
one connection interacting with each other for position update,
animation, effects (basic MMORPG mechanism), in numbers, which would
be "heavily loaded servers"? 200~300 simultaneous connections?

On the server side, you may benefit equally well from using any asynchronous socket APIs, either Begin/End-style APM ones, event-based EAP ones or Task-based TAP ones. That's because you'll be blocking fewer threads, as opposed to using the synchronous APIs. So, more thread will be available to concurrently serve other incoming requests to your server, thus increasing its scalability.

Most likely, your won't see any performance advantage of using TAP socket APIs over their APM or EAP analogues. However, the TAP API pattern is so much easier to develop with than APM or EAP. When used with async/await, it produces shorter, more readable and less error-prone code. You get natural pseudo-linear code flow, which is not otherwise possible with APM callbacks or EAP event handlers. If you're unable find a proper Task-based socket API, you can always make one yourself from a Begin/End APM API with Task.FromAsync (or from an EAP API, check "A reusable pattern to convert event into task").

When it comes to a client side UI app, the scalability is not that important, but there's another benefit from the TAP pattern. With little efforts, it helps making your UI responsive, because you won't be blocking the UI thread (what usually happens while waiting for the result of a synchronous call). This is not specific to Task-based Socket API, it applies to any Task-based API, e.g, Task.Delay() or Stream.ReadAsync().

For some good reading materials on asynchronous programming in C#, check the async/await tag wiki:

https://stackoverflow.com/tags/async-await/info

Best practices for working with sockets on a .NET 4.5 based game server?

It seems kind of strange to use TCP for real-time high performance data exchange (although in some situations and depending on your network skills, you can make a case for it). Since reliability is not a core requirement but rather getting real-time world data, it makes much more sense to use a UDP connection (e.g. using Lidgren), specially when you're in a LAN setup.

Finally, here is an interesting article on the difference between TCP and UDP from a Game point of view.

Is there a TPL (Async/Await) based socket abstraction?

There's no TAP-based raw socket API, no. I believe that the BCL team took a look at the Socket class - which already supports a complete synchronous API and two complete asynchronous APIs - and decided that adding a third complete asynchronous API would just be too much.

It's easy enough to use Task.Factory.FromAsync to wrap the existing Begin/End methods. Personally, I like to do this with extension methods so they can be called more naturally.

But first you should take a step back and see if there's any way to avoid using raw sockets. In particular, see if SignalR is a possibility. Writing correct raw socket code is extremely difficult, and using a higher-level abstraction would be much easier.

What is the current standard of practice to implement an async socket client?

There is no current standard or common practice at the moment. You have a number of choices, each with advantages and disadvantages:

  1. Wrap the TAP methods into Tasks and use async/await.
    • Advantages: Pretty straightforward to do.
    • Disadvantages: It's low-level; you have to manage all the various async operations yourself within an "infinite" loop as well as handle state management for each connection.
  2. Wrap the Socket *Async methods into Tasks and use async/await.
    • Advantages: Speed. This is the fastest and most scalable option.
    • Disadvantages: You still have low-level "infinite" loops and state management, where the code is more complex than option (1).
  3. Convert socket completions to Rx events.
    • Advantages: You can encapsulate the "infinite" loops and treat the completions as a stream of events.
    • Disadvantages: There's a hefty learning curve to Rx, and this is not a simple use case. Managing the state of each connection can get complex.
  4. Convert socket completions to TPL Dataflow.
    • Advantages: (same as Rx): encapsulating the loops and getting a stream of data.
    • Disadvantages: The learning curve is easier than Rx, but you still have some complex state management for each connection.
  5. Use an existing library such as my Nito.Async library, which provides EAP socket classes.
    • Advantages: Very easy to use; everything is an event and there's no multithreading concerns. Also, the tricker parts of state management are done for you.
    • Disadvantages: Doesn't scale as well as the lower-level solutions.

For your situation (a few hundred messages per second on less than a hundred sockets), I would recommend using my Nito.Async library. It's the easiest one of these options to get working.

Regarding your protocol, you'll have to parse out the \ns by hand and do your own buffering. (That's true for all the choices above).

Does .NET 4.5's async feature work with MySql and other databases too?

The asynchronous methods for all drivers are defined in DbDataReader, eg DbDataReader.ReadAsync. It is up to the specific drivers to override these methods with specific implementations to take advantage of the asynchronous characteristics of each database and use eg. a naturally asynchronous operation instead of a synchronous operation wrapped in a thread.

That said, MySQL Connector/Net 6.8 adds support for asynchronous operations in Entity Framework 6 but the MySqlDataReader class does NOT provide a ReadAsync method. This is because Connector uses an old architecture (pre-2.0), implementing the IDataReader interface instead of deriving from the generic DbDataReader class introduced in .NET 2.0.



Related Topics



Leave a reply



Submit