Skip to content

003: Listening for Connections

Shane DeSeranno edited this page Feb 13, 2019 · 4 revisions

In this section, I'm going to create a class to hold the 'Server' logic. This is responsible for listening for connections on a given port and accepting these connections. It will also need to poll each client on a regular basis. And, finally, it'll need a way to tear down and clean up connections.

A quick .NET Core specific feature that I'd like to add and call out here is the Logging and Configuration features. In order to be able to use these, you need to edit the 'project.json' of the SSHServer project and add the following to the 'dependencies' section:

        "Microsoft.Extensions.Configuration": "1.1.0",
        "Microsoft.Extensions.Configuration.Binder": "1.1.0",
        "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
        "Microsoft.Extensions.Configuration.Json": "1.1.0",
        "Microsoft.Extensions.Logging": "1.1.0",
        "Microsoft.Extensions.Logging.Console": "1.1.0",

So, create a new class called 'Server' at the root of the project and add the following:

        private IConfigurationRoot m_Configuration;
        private LoggerFactory m_LoggerFactory;
        private ILogger m_Logger;

        public Server()
        {
            m_Configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("sshserver.json", optional: false)
                .Build();

            m_LoggerFactory = new LoggerFactory();
            m_LoggerFactory.AddConsole(m_Configuration.GetSection("Logging"));
            m_Logger = m_LoggerFactory.CreateLogger("SSHServer");
        }

This code will load the configuration file 'sshserver.json'. We can use the m_Configuration member variable later to access values (like the port number). It also creates an ILogger we can use to log to the console in a standard way. Before continuing we need to create a 'sshserver.json' as it is required and set the content to:

{
    "Logging": {
        "IncludeScopes": true,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "port": 22
}

Great, now we have our configuration and logging setup and we need to add 3 Methods: Start, Poll, and Stop

        public void Start()
        {
            // Ensure we are stopped before we start listening
            Stop();

            m_Logger.LogInformation("Starting up...");

            // Create a listener on the required port
            int port = m_Configuration.GetValue<int>("port", DefaultPort);
            m_Listener = new TcpListener(IPAddress.Any, port);
            m_Listener.Start(ConnectionBacklog);

            m_Logger.LogInformation($"Listening on port: {port}");
        }

        public void Poll()
        {
            // Check for new connections
            while (m_Listener.Pending())
            {
                Task<Socket> acceptTask = m_Listener.AcceptSocketAsync();
                acceptTask.Wait();

                Socket socket = acceptTask.Result;
                m_Logger.LogDebug($"New Client: {socket.RemoteEndPoint.ToString()}");

                // TODO: Create and add client list
            }

            // TODO: Poll each client for activity

            // TODO: Remove all disconnected clients
        }

        public void Stop()
        {
            if (m_Listener != null)
            {
                m_Logger.LogInformation("Shutting down...");

                // TODO: Disconnect clients and clear clients

                m_Listener.Stop();
                m_Listener = null;

                m_Logger.LogInformation("Stopped!");
            }
        }

And now, we just need to go setup the Main method to call these:

    public class Program
    {
        private static bool s_IsRunning = true;
        public static void Main(string[] args)
        {
            Console.CancelKeyPress += Console_CancelKeyPress;

            Server server = new Server();
            server.Start();

            while (s_IsRunning)
            {
                server.Poll();
                System.Threading.Thread.Sleep(25);
            }

            server.Stop();
        }

        private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = true;
            s_IsRunning = false;
        }
    }

This code monitors for CTRL-C being pressed, and politely shuts the server down if it detects this. The Main calls Start(), then runs Poll() every 25 milliseconds, and Stop() before closing. If you run it now and use telnet to connect to your machine using port 22, you will see:

info: SSHServer[0]
      Starting up...
info: SSHServer[0]
      Listening on port: 22
dbug: SSHServer[0]
      New Client: 127.0.0.1:6867

And that brings us to the end of this section! See tag Listening_For_Connections for the source at this point.

Next up, we need to manage the Client Connections to ensure each one is tracked.