9

I've been making a server. I am using TcpListener.AcceptTcpClientAsync() in an async method, but I have no idea how to actually make it work. My code right now is:

private static async void StartServer()
{
    Console.WriteLine("S: Server started on port {0}", WebVars.ServerPort);
    var listener = new TcpListener(WebVars.LocalIp, WebVars.ServerPort);
    listener.Start();
    var client = await listener.AcceptTcpClientAsync();
}

How do I process the client? Do I just continue coding and it will automagically make new threads of the same method or do I need to do some magic method that will do it for me?

Edit: current code:

private static Task HandleClientAsync(TcpClient client)
{
    var stream = client.GetStream();
    // do stuff
}
/// <summary>
/// Method to be used on seperate thread.
/// </summary>
private static async void RunServerAsync()
{
    while (true)
    {
        Console.WriteLine("S: Server started on port {0}", WebVars.ServerPort);
        var listener = new TcpListener(WebVars.LocalIp, WebVars.ServerPort);
        listener.Start();
        var client = await listener.AcceptTcpClientAsync();
        await HandleClientAsync(client);
    }
}
Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
Ilan
  • 513
  • 2
  • 8
  • 24
  • 1
    Please, do not include information about a language used in a question title unless it wouldn't make sense without it. Tags serve this purpose. – Ondrej Janacek Mar 05 '14 at 07:03
  • This is related: http://stackoverflow.com/questions/21013751/what-is-the-async-await-equivalent-of-a-threadpool-server – noseratio Mar 05 '14 at 08:07

2 Answers2

9

// all credit should go to c# 7.0 in a nutshell (Joseph Albahari & Ben Albahari)

    async void RunServerAsync()
    {
        var listner = new TcpListener(IPAddress.Any, 9999);
        listner.Start();
        try
        {
            while (true)
                await Accept(await listner.AcceptTcpClientAsync());
        }
        finally { listner.Stop(); }
    }


    const int packet_length = 2;  // user defined packet length

    async Task Accept(TcpClient client)
    {
        await Task.Yield();
        try
        {
            using(client)
            using(NetworkStream n = client.GetStream())
            {
                byte[] data = new byte[packet_length];
                int bytesRead = 0;
                int chunkSize = 1;

                while (bytesRead < data.Length && chunkSize > 0)
                    bytesRead += chunkSize = 
                        await n.ReadAsync(data, bytesRead, data.Length - bytesRead);

                // get data
                string str = Encoding.Default.GetString(data);
                Console.WriteLine("[server] received : {0}", str);

                // To do
                // ...

                // send the result to client
                string send_str = "server_send_test";
                byte[] send_data = Encoding.ASCII.GetBytes(send_str);
                await n.WriteAsync(send_data, 0, send_data.Length);

            }
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
sailfish009
  • 2,561
  • 1
  • 24
  • 31
5

Nothing will magically create dedicated threads for you, although there are some threads used for IO completion which can come into play, particularly if you don't have a synchronization context that you need to return to.

You should decide whether you want your StartServer method to actually complete when it's accepted a single connection, or keep looping until you've been told to shut down.

Either way, you clearly need to decide how to handle the client too. Either you could start a new thread and use synchronous methods, or you could just use asynchronous IO to handle everything in the same thread. For example, to dump the incoming data to a file:

private Task HandleClientAsync(TcpClient client)
{
    // Note: this uses a *synchronous* call to create the file; not ideal.
    using (var output = File.Create("client.data"))
    {
        using (var input = client.GetStream())
        {
            // Could use CopyToAsync... this is just demo code really.

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = await input.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await output.WriteAsync(buffer, 0, bytesRead);
            }
        }
    }
}

(That's assuming the client will just terminate the connection when it's finished writing the data.) Aside from the File.Create call, this is all asynchronous - so there's no need to create a separate thread for it.

This is just an example, of course - real connection handling would usually be more complicated. If your real handling needs anything more compute-intensive, you may well want to consider using Task.Run to use the thread pool... that way it won't interfere with accepting more connections.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I want the server to continue looping. It'll be in a `while` loop, and get the incoming string, process it, then return another string or file. – Ilan Mar 05 '14 at 06:55
  • @Ilan321: Right - so you need to add that `while` loop to your `StartServer` method (which I'd change to `RunServerAsync`, and make it return a `Task` so that whatever's calling it can see when it's completed). You might also want to look at the demo HTTP RPC handler I wrote for an "introducing async" talk: https://github.com/jskeet/DemoCode/blob/master/AsyncIntro/Code/AsyncHttpService/HttpJsonRpcHandler.cs (Not production code, but it might be useful to you.) – Jon Skeet Mar 05 '14 at 06:57
  • I'm going to edit my question and put my current code. Will it work? – Ilan Mar 05 '14 at 07:00
  • 1
    @Ilan321: You don't want to create a new listener in the loop - you only need the `AcceptTcpClientAsync` call (and the call to start handling it) in the loop. And you *don't* want to await the result of `HandleClientAsync` - otherwise you can only handle one request at a time. You may well want to create a collection of "requests in progress" (represented by tasks) so that you can tell when everything's finished. Again, see the code I linked to. – Jon Skeet Mar 05 '14 at 07:03
  • Jon , looking at`await input.ReadAsync` , does it await _during fetching information from input (e.g. while fetching , there is no thread involved)_ OR does it await until information available (waiting , no block) ? – Royi Namir Oct 30 '14 at 10:13
  • @RoyiNamir: I'm not sure what you mean. Assuming the data isn't immediately available, the call will return an uncompleted task when it hits the `await` expression, and the thread can do something else. When data becomes available, the async method will continue where it left off, on an appropriate thread. (In a console app, this will be a thread pool thread.) – Jon Skeet Oct 30 '14 at 10:15