35

This is my Server App:

public static void Main()
{
    try
    {
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");

        Console.WriteLine("Starting TCP listener...");

        TcpListener listener = new TcpListener(ipAddress, 500);

        listener.Start();

        while (true)
        {
            Console.WriteLine("Server is listening on " + listener.LocalEndpoint);

            Console.WriteLine("Waiting for a connection...");

            Socket client = listener.AcceptSocket();

            Console.WriteLine("Connection accepted.");

            Console.WriteLine("Reading data...");

            byte[] data = new byte[100];
            int size = client.Receive(data);
            Console.WriteLine("Recieved data: ");
            for (int i = 0; i < size; i++)
                Console.Write(Convert.ToChar(data[i]));

            Console.WriteLine();

            client.Close();
        }

        listener.Stop();
    }
    catch (Exception e)
    {
        Console.WriteLine("Error: " + e.StackTrace);
        Console.ReadLine();
    }
}

As you can see , it always listens while working , but I would like to specify that I want the app be able to listen and to have multiple connections support in the same time.

How could I modify this to constantly listen while also accepting the multiple connections?

Community
  • 1
  • 1
keeehlan
  • 7,874
  • 16
  • 56
  • 104
  • 1
    does specifying `127.0.0.1` limit the listening socket to only work on the local machine? Will devices on the public network be able to access this listening socket? – Zapnologica Oct 13 '17 at 06:34
  • Why do you use `new byte[100];` array? Is this data size that sent from client? – barteloma Apr 26 '19 at 11:05
  • @barteloma This was so long ago that I don't remember, but it's probably just a magic number. – keeehlan Apr 27 '19 at 01:20

3 Answers3

60
  1. The socket on which you want to listen for incoming connections is commonly referred to as the listening socket.

  2. When the listening socket acknowledges an incoming connection, a socket that commonly referred to as a child socket is created that effectively represents the remote endpoint.

  3. In order to handle multiple client connections simultaneously, you will need to spawn a new thread for each child socket on which the server will receive and handle data.
    Doing so will allow for the listening socket to accept and handle multiple connections as the thread on which you are listening will no longer be blocking or waiting while you wait for the incoming data.

while (true)
{
   Socket client = listener.AcceptSocket();
   Console.WriteLine("Connection accepted.");
    
   var childSocketThread = new Thread(() =>
   {
       byte[] data = new byte[100];
       int size = client.Receive(data);
       Console.WriteLine("Recieved data: ");
       
       for (int i = 0; i < size; i++)
       {
           Console.Write(Convert.ToChar(data[i]));
       }

       Console.WriteLine();
    
       client.Close();
    });

    childSocketThread.Start();
}
Community
  • 1
  • 1
User 12345678
  • 7,714
  • 2
  • 28
  • 46
  • 1
    how to set it up asynchronousely (without spinning up a new thread for every client?) – SHM Aug 31 '16 at 18:34
  • 1
    @MrDysprosium scalability obviously. – SHM Mar 10 '17 at 15:37
  • 1
    You say obviously, so maybe I'm very low on the scale of understanding this topic... But why is async more scalable than thread? – MrDysprosium Mar 10 '17 at 15:39
  • 1
    @MrDysprosium because threads are expensive to create and manage for OS, thus spinning up a thread for each client is expensive. in addition to that the thread you create for each client will be doing nothing most of them but waiting to get some thing from client. – SHM Mar 18 '17 at 09:58
  • 2
    @SHM not to be the guy that replies 2 years later, but in order to have an asynchronous process going, at some point a thread had to be established, and therefore, someone had to spool a thread. Even over the wire from another machine, a thread is opened to await response. I don't understand how you would establish an asynchronous process otherwise (e.g. Tasks are still threads). – jkrieg Aug 22 '18 at 20:29
  • 1
    @jkrieg Tasks are not threads. A task runs on a thread. You can have asynchronous communication without having 1 thread per connection. Spawning one thread per connection is a very naive approach as most threads will be idle. Using await on tasks that read from a file or a network socket is also not spawning a new thread. The current thread is simply moving on to something else until the device driver performs the requested activity (e.g. by issuing an interrupt to the CPU) – Orestis P. Sep 05 '18 at 19:32
  • @OrestisP.You're right, Tasks are not threads, but they do run on separate threads. More specifically, the task does not run directly on the current application's thread (or process loop, what have you). To run asynchronously means a separation of process order, and that means a different process staging. In the end, you still have multiple threads for the scope of the original question. – jkrieg Sep 07 '18 at 21:03
  • Why do you use new byte[100]; array? Is this data size that sent from client? – barteloma Apr 26 '19 at 11:05
5

I had a similar problem today, and solved it like this:

while (listen) // <--- boolean flag to exit loop
{
   if (listener.Pending())
   {
      Thread tmp_thread = new Thread(new ThreadStart(() =>
      {
         string msg = null;

         TcpClient clt = listener.AcceptTcpClient();

         using (NetworkStream ns = clt.GetStream())
         using (StreamReader sr = new StreamReader(ns))
         {
            msg = sr.ReadToEnd();
         }

         Console.WriteLine("Received new message (" + msg.Length + " bytes):\n" + msg);
      }
      tmp_thread.Start();
   }
   else
   {
       Thread.Sleep(100); //<--- timeout
   }
}

My loop did not get stuck on waiting for a connection and it did accept multiple connections.


EDIT: The following code snippet is the async-equivalent using Tasks instead of Threads. Please note that the code contains C#-8 constructs.

private static TcpListener listener = .....;
private static bool listen = true; // <--- boolean flag to exit loop


private static async Task HandleClient(TcpClient clt)
{
    using NetworkStream ns = clt.GetStream();
    using StreamReader sr = new StreamReader(ns);
    string msg = await sr.ReadToEndAsync();

    Console.WriteLine($"Received new message ({msg.Length} bytes):\n{msg}");
}

public static async void Main()
{
    while (listen)
        if (listener.Pending())
            await HandleClient(await listener.AcceptTcpClientAsync());
        else
            await Task.Delay(100); //<--- timeout
}
unknown6656
  • 2,765
  • 2
  • 36
  • 52
  • Generally, I also use this solution when want to manage the listening from outside, with the possibility to stop it properly. The only issue I face is that using the sleep command generates too much lag if set to high, and consumes cpu if set lower. Is there any idea to solve this? – Cpt Balu Dec 07 '19 at 20:45
  • I believe the best solution would be to use async/await combined with "Task.Delay(...)". I will try to elaborate further when I have access to my computer. – unknown6656 Dec 08 '19 at 09:08
  • Great, let's see how it works. My other idea is to keep using AcceptTcpClient withouth nesting into sleep-wait loop, and start this in an other separate thread, then forcibly terminate it when want to finish listening. It throws then an exception which must be handled. Ugly, but seems to be the most efficient in matter of speed, Just wanted to know if there's any similar but nicer solution. – Cpt Balu Dec 11 '19 at 11:03
  • I updated my answer to contain the async-code using "Task.Delay(...)". Async programming using Tasks is considered to be the "successor" of Thread-based code constructs. – unknown6656 Dec 13 '19 at 11:32
  • The best way is just to await listener.AcceptTcpClientAsync in a while loop. If you want to be able to close the listening socket from outside, then just use a cancelation token to close the socket and catch the socket exception that AcceptTcpClientAsync() throws – Rowan Smith Jan 13 '20 at 04:06
2

Basic idea is, there is listener socket always listening on a given IP and port number. When ever there is connection request, listener accept the connection and remote end point is taken with tcpclient object till connection is closed or lost.