9

I have the following code which I want to implement as my server. As I understand it is async. and should allow connections from multiple clients...

public void Start()
{          
    TcpListener listener = new TcpListener(IPAddress.Any, 10250);
    listener.Start();
    Console.WriteLine("Listening...");

    while (true)
    {
        IAsyncResult res = listener.BeginAcceptTcpClient(HandleAsyncConnection, listener);
        connectionWaitHandle.WaitOne();
    }
}

private void HandleAsyncConnection(IAsyncResult res)
{
    TcpListener listener = (TcpListener)res.AsyncState;
    TcpClient client = listener.EndAcceptTcpClient(res);
    connectionWaitHandle.Set();

    StringBuilder sb = new StringBuilder();
    var data = new byte[client.ReceiveBufferSize];

    using (NetworkStream ns = client.GetStream())
    {             
        // Test reply
        Byte[] replyData = System.Text.Encoding.ASCII.GetBytes(DateTime.Now.ToString());
        ns.Write(replyData, 0, replyData.Length);
        ns.Flush();
        ns.Close();
    }

    client.Close();
}

I have a test app which simply fires requests to my server. As you can see in the code the server just replies with its date/time. The test app sends say 20 requests which are simply test strings. For each of these requests it opens a socket, sends the data to my server and then closes the socket again.

This works fine with one test app running. However, if I open two test apps the second one cannot connect to the server. I thought because I am handling the request async. and because my test app opens then closes the socket before each call I could handle requests from multiple clients?

pradyunsg
  • 18,287
  • 11
  • 43
  • 96
Remotec
  • 10,304
  • 25
  • 105
  • 147
  • I have some sample code that does exactly what you want but it's on my laptop. I'll post tonight if I remember and if someone hasn't already answered for you. Sorry about the delay. – BenCr Apr 21 '11 at 10:13
  • That would be excellent thanks. – Remotec Apr 21 '11 at 10:17

2 Answers2

20

Edit

If using >=.Net4.5, it's better to use the new network methods that then permit the adoption of async and await. As such, it might be better to follow the example I provided in this post as a starting point.

Original Post

The following code demonstrates how to accept multiple clients asynchronously without spinning off a new thread per connection.

private TcpListener listener;
public void Start()
{          
    listener = new TcpListener(IPAddress.Any, 10250);
    listener.Start();
    Console.WriteLine("Listening...");
    StartAccept();

}
private void StartAccept()
{
    listener.BeginAcceptTcpClient(HandleAsyncConnection, listener);
}
private void HandleAsyncConnection(IAsyncResult res)
{
    StartAccept(); //listen for new connections again
    TcpClient client = listener.EndAcceptTcpClient(res);
    //proceed

}

You can use this pattern for most async operations.

Community
  • 1
  • 1
spender
  • 117,338
  • 33
  • 229
  • 351
  • 1
    Does there need to be a loop inside StartAccept() to keep the app. active until a connection arrives? – Remotec Apr 21 '11 at 10:50
  • I assume this is a Console app then. I'd suggest calling Start from the application's Main method, then preventing the program from closing with a Console.ReadKey or similar. – spender Apr 21 '11 at 12:51
  • This seems to be the code thats giving me the best results so far. – Remotec Apr 21 '11 at 14:55
  • @spender i like this code exaple for its simplicity. but after the `//proceed` can you help with code sample to manage multiple clients. ie sending message to each one uniquely. – Smith Sep 12 '11 at 15:42
  • @smith, handling a roster of clients is probably the most complex part of such an application because it's where there is a requirement to share state between potentially concurrent operations. I'd say that it's well beyond the scope of this answer. The collections in the `System.Collections.Concurrent` namespace can really ease development here, but if you're having a specific problem, I'd suggest opening a new question. – spender Sep 12 '11 at 16:44
  • @spender Such an excellent and efficient code! Saved my hours, my hours are equal to days, if you have limited time, like for most of us. Thank you for the hint as well. – freewill Jan 03 '13 at 22:51
  • Is it needed to call StartAccept again, inside HandleAsyncConnection? – Cesar Nov 20 '13 at 20:56
  • 1
    @Cesar Yes, otherwise the server will accept only a single client. I would advise against such code with >.Net 4.5 and encourage you to adopt the new network methods that allow you to use `async/await`. See my answer here: http://stackoverflow.com/questions/12630827/using-net-4-5-async-feature-for-socket-programming/12631467#12631467 – spender Nov 21 '13 at 00:34
2

The way you did it, there is no benefit compared to using AcceptTcpClient. Simply loop and create a new thread for each accepted connection:

public void Start()
{          
    TcpListener listener = new TcpListener(IPAddress.Any, 10250);
    listener.Start();
    Console.WriteLine("Listening...");
    while (canRun)
    {
       var client = listener.AcceptTcpClient();
       new Thread(ClientThread).Start(client);
    }
}

private void ClientThread(IAsyncResult res)
{
    TcpClient client = (TcpClient)res.AsyncState;

    StringBuilder sb = new StringBuilder();
    var data = new byte[client.ReceiveBufferSize];

    using (NetworkStream ns = client.GetStream())
    {             
        // Test reply
        Byte[] replyData = System.Text.Encoding.ASCII.GetBytes(DateTime.Now.ToString());
        ns.Write(replyData, 0, replyData.Length);
        ns.Flush();
        ns.Close();
    }

    client.Close();
}

A golden rule when using asynchronous methods is to NOT block the operation. Blocking would defeat the purpose of using async ops.

public void Start()
{          
    TcpListener listener = new TcpListener(IPAddress.Any, 10250);
    listener.Start();
    Console.WriteLine("Listening...");
    listener.BeginAcceptTcpClient(OnAccept, listener);
}

private void OnAccept(IAsyncResult res)
{
    TcpListener listener = (TcpListener)res.AsyncState;
    TcpClient client = listener.EndAcceptTcpClient(res);

    StringBuilder sb = new StringBuilder();
    var data = new byte[client.ReceiveBufferSize];

    using (NetworkStream ns = client.GetStream())
    {             
        // Test reply
        Byte[] replyData = System.Text.Encoding.ASCII.GetBytes(DateTime.Now.ToString());
        ns.Write(replyData, 0, replyData.Length);
        ns.Flush();
        ns.Close();
    }

    client.Close();
}
jgauffin
  • 99,844
  • 45
  • 235
  • 372