0

I am trying to make a simple server application to learn the basics of multi threaded server programming in c#. The basic idea is simple: the client connects to the server and sends: "get time" to receive to current time of the server. All of the tcplistener threads and sockets should be running on seperate threads. I am not sure why, but when the application finishes initializing all of the threads, the console app closes.

Here is the server code:

class Program
{
    static public int minPort;
    static public int maxPort;
    static int openPort = 0;
    static byte[] sendData = new byte[1024];
    static TcpListener[] listeners;
    static Thread[] connectionThreads;

    static void Main()
    {
        Console.Write("What do you want your minimum port to be? ");
        minPort = Convert.ToInt32(Console.ReadLine());
        Console.Write("What do you want your maximum port to be? ");
        maxPort = Convert.ToInt32(Console.ReadLine());

        //init
        ThreadStart streamThreadStart = new ThreadStart(DataStream);
        openPort = maxPort - minPort;
        listeners = new TcpListener[maxPort - minPort];
        connectionThreads = new Thread[maxPort - minPort];

        for (int i = 0; i == maxPort - minPort; i++)
        {
            listeners[i] = new TcpListener(IPAddress.Any, minPort + i);
            connectionThreads[i] = new Thread(streamThreadStart);
            Thread.Sleep(10);
            openPort = openPort + 1;
        }
    }

    static void DataStream()
    {
        int port = openPort;
        byte[] receiveData = new byte[1024];
        listeners[openPort].Start();
        Socket socket = listeners[port].AcceptSocket();
        NetworkStream stream = new NetworkStream(socket);
        while (true)
        {
            socket.Receive(receiveData);
            Console.WriteLine("Received: " + BitConverter.ToString(receiveData));
            socket.Send(parseCommand(receiveData));
        }

    }
    static byte[] parseCommand(byte[] input)
    {
        string RCommand = BitConverter.ToString(input);
        string SCommand;
        if (RCommand == "get time")
        {
            SCommand = DateTime.Now.ToUniversalTime().ToString();
        }else
        {
            SCommand = "Unknown Command, please try again";
        }
        byte[] output = Encoding.ASCII.GetBytes(SCommand);
        return output;
    }
}
  • In a console application when main is complete, the application closes as there is no more code to execute. You will need to build a loop in main to keep the console application running. – Ryan Wilson Nov 21 '18 at 17:56
  • You know that one listener socket can service multiple clients and hand off the accepted sockets? There should never be a need to run listeners on multiple ports unless you're doing something **very** odd. E.g. common web servers *only* listen on ports 80 and/or 443. Do you think these servers dealing with thousands of clients only deal with a single client at a time? Lots of research/reading required here. – Damien_The_Unbeliever Nov 21 '18 at 18:06
  • Also, you have to pay attention to the return value from `Receive` and moral equivalents. Because all the TCP gives you is a *stream of bytes*, not messages. It doesn't guarantee to fill your buffer or that what you will receive matches a single buffer passed to a single call of `Send` at the other end. – Damien_The_Unbeliever Nov 21 '18 at 18:11
  • Another option would be to create a [Windows Service](https://learn.microsoft.com/en-us/dotnet/framework/windows-services/), which is designed to run long-term without closing. – BJ Myers Nov 21 '18 at 19:29

3 Answers3

0

In general, best practice is for a console application to offer an "Enter any key to exit" prompt when the user wants to stop the application. But you can always query for a specific key-press to quit, such as 'q'.

Here is some code to get you started:

Console.WriteLine("Press any key to exit..."); Console.ReadKey();

benhorgen
  • 1,928
  • 1
  • 33
  • 38
0

You need to join your threads before exiting.

static public void Main()
{
    /*
        Existing code goes here
    */

    //Before exiting, make sure all child threads are finished
    foreach (var thread in connectionThreads) thread.Join(); 
}

When your program calls Thread.Join, it is telling the operating system that it needn't schedule the main thread for any timeslices until the child thread is finished. This makes it lighter weight than other techniques, such as busywaiting (i.e. running a while loop); while the main thread will still hold onto resources, it won't consume any CPU time.

See also When would I use Thread.Join? and c# Waiting for multiple threads to finish

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • While I do like this answer, since it discusses the correct way to join threads back to the main thread. It does not handle the problem of having a console app that cannot be controlled. Solutions for this are, as suggested the Windows service, or changing the main thread so it also reacts to user input. – Ben Ootjers Nov 21 '18 at 20:58
0

Your code doesn't start new thread! So, i made some changes in your code:

class Program
    {
        static public int minPort;
        static public int maxPort;
        //static int openPort = 0;
        static byte[] sendData = new byte[1024];
        static TcpListener[] listeners;
        static Thread[] connectionThreads;

        static void Main()
        {
            Console.Write("What do you want your minimum port to be? ");
            minPort = Convert.ToInt32(Console.ReadLine());
            Console.Write("What do you want your maximum port to be? ");
            maxPort = Convert.ToInt32(Console.ReadLine());

            //init
           // ThreadStart streamThreadStart = new ThreadStart(DataStream(0));
            //openPort = minPort;
            listeners = new TcpListener[maxPort - minPort];
            connectionThreads = new Thread[maxPort - minPort];

            //for (int i = 0; i == maxPort - minPort; i++) it can't work
            for (int i = 0; i < maxPort - minPort; i++)
            {
                listeners[i] = new TcpListener(IPAddress.Any, minPort + i);
                connectionThreads[i] = new Thread(new ParameterizedThreadStart(DataStream)); //thread with start parameter
                connectionThreads[i].Start(i); // start thread with index
                Thread.Sleep(10);

            }

        }

        static void DataStream(object o)
        {
            int index = (int)o; //get index
            byte[] receiveData = new byte[1024];
            listeners[index].Start(); // start listener with right index
            Socket socket = listeners[index].AcceptSocket();
            NetworkStream stream = new NetworkStream(socket);
            while (true)
            {
                socket.Receive(receiveData);
                Console.WriteLine("Received: " + BitConverter.ToString(receiveData));
                socket.Send(parseCommand(receiveData));
            }

        }
        static byte[] parseCommand(byte[] input)
        {
            string RCommand = BitConverter.ToString(input);
            string SCommand;
            if (RCommand == "get time")
            {
                SCommand = DateTime.Now.ToUniversalTime().ToString();
            }
            else
            {
                SCommand = "Unknown Command, please try again";
            }
            byte[] output = Encoding.ASCII.GetBytes(SCommand);
            return output;
        }
    }