Home About

November 17th, 2008

Building a TCP/IP server using C# - 6

If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!


Dozens of protocols have come and gone (does anyone even remember IPX/SPX?) but both TCP & UDP over IP survive. C# offers a good deal of libraries for handling connections both on the server and client side. Here we look at how to build a simple server in increasing steps of complexity.

Before we get started

This article will show how to build a little TCP server that passes a simple message to each connecting client. The first two examples use synchronous IO, the final one uses asynchronous connects.

Each of the following programs will try to do the following:

  1. Setup the TCP stack
  2. Listen for an incoming TCP connection (localhost , port 2200)
  3. Accept the connection
  4. Display the IP adres of the caller, and the port from the remote system
  5. Send the message “I am a little busy, come back later”
  6. Close the connection
  7. Goto 2

So what local address is it anyway?

Each computer with TCP networking has an assigned network address which is in the form x.x.x.x (10.0.0.6) This is of course IPv4, we will leave IPv6 for another day. If the computer has more than one network card then there will be more than one address.

On top of that there are two more addresses of significance:

  • “0.0.0.0″ – If we set this as the source address, your program will listen on all available network cards in the computer
  • “127.0.0.1″ – This is the local loopback. Any program that binds itself to this is only available for programs on the same computer

To make it possible for multiple programs to use the network interface, each can listen to a 16 bit “port” (0 – 65536, with 0-1024 typically reserved for system services). For our little server we randomly picked a port: 2200.

It is perfectly possible for a program to only bind itself to only one network card (eg. a web server being available only on the extenal interface) or to all at the same time. We will bind to 127.0.0.1 in the examples below. As a result the “server” is only available to programs on the same computer.

Version 1 — TcpListener is slighlty easier to code

To avoid having to delve too deep into the TCP stack , the System.Net.Sockets.TcpClient and System.Net.Sockets.TcpListener classes make available a slightly “easier” interface to sending and receiving data over a network. In addition it is possible to send and receive data through a NetworkStream object. These classes all add some overhead, but since our example is simple we won’t notice:

  • TcpListener allows us to listen for an incoming connection
  • TcpClient handles the actual connection
  • NetworkStream sends and receives the actual data
namespace SimpleServer
{
    class Program
    {
        public static void Main()
        {
            // Data is send in bytes -- so we need to convert a C# string to a Byte[]
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            Byte[] message = encoding.GetBytes("I am a little busy, come back later!");

            try
            {
                // Setup a listener at the local IP adres, port 2200
                IPAddress localAddress = IPAddress.Parse("127.0.0.1");
                TcpListener listener = new TcpListener(localAddress,2200);

                // Start listening, only allow 1 connection to queue at the same time
                listener.Start(1);

                // Start listening for connections.
                while (true)
                {
                    Console.WriteLine("Server is waiting on socket {0}", listener.LocalEndpoint);

                    // The program is suspended while waiting for an incoming connection.
                    // This is a synchronous TCP application
                    TcpClient client = listener.AcceptTcpClient();

                    // Obtain a stream object for reading and writing
                    NetworkStream io = client.GetStream();

                    // An incoming connection needs to be processed.
                    Console.WriteLine("Received Connection from {0}", client.Client.RemoteEndPoint );

                    Console.WriteLine("Sending message..");
                    io.Write(message, 0, message.Length);

                    // End of the incomming connection
                    Console.WriteLine("Ending the connection");
                    client.Close();
                }

            }
            catch (Exception e)
            {
                Console.WriteLine("Caught Exception: {0}", e.ToString());
            }
        }
    }
}

Run the program in the debugger; and when tracing it step by step it will stop at the following line:

TcpClient client = listener.AcceptTcpClient();

We are doing “synchronous” communication — we need to wait for a connection to arrive before the program is allowed to continue.

Fire up telnet (from the command prompt) and connect to our little server. Continue to step through the code as we connect. The following will do the trick:

telnet localhost 2200
Server is waiting on socket 127.0.0.1:2200
Received Connection from 127.0.0.1:4883
Sending message..
Ending the connection
Server is waiting on socket 127.0.0.1:2200

The Telnet window will show the message passed on by the server:

I am a little busy, come back later!
Connection to host lost.

And the program is back at the beginning, rinse and repeat.

Version 2 — Using sockets for our communication

As there is some overhead associated with TcpListener and TcpClient, we need to modify our program to gain that ounce of extra performance. In the following example we drop both these classes and instead directly use the System.Net.Sockets.Socket class.

The actual difference in code is mostly in the setup of the connection:

Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

I might have made fun of other network stacks earlier, but that doesn’t mean we can’t connect to them and if your computer has the support for them its possible to connect to many other kinds of networks.

AddressFamily.InternetWork provides access to IPv4 networks
AddressFamily.InternetWorkV6 provides access to IPv6 networks

And then of course there is AppleTalk, ATM, Banyan, Chaos, DataKit, DecNet, Iso, Osi,….

SocketType.Stream provides reliable two-way communication, For connectionless UDP) you would use SocketType.Dgram. And if you would like get really low access SocketType.Raw (it returns the IP packet including all the headers) might do the trick.

ProtocolType provides the higher level protocol, in our case we stick with Tcp. Other common options would be Udp or Icmp.

// This will bind us to the first IP address found on this computer
IPAddress localAddress = IPAddress.Parse("127.0.0.1");

// Define the address we want to claim: the first IP address found earlier, at port 2200
IPEndPoint ipEndpoint = new IPEndPoint(localAddress, 2200);

// Bind the socket to the end point
listenSocket.Bind(ipEndpoint);

// Start listening, only allow 1 connection to queue at the same time
listenSocket.Listen(1);

Similar to TCPListener we need to bind and listen to port 127.0.0.1:2200, it just requires a few more steps.

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace SimpleServerUsingSockets
{
    class Program
    {
        public static void Main()
        {
            // Q&A: How to convert a C# string to a byte[] array ?
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

            try
            {
                // This will bind us to the first IP address found on this computer
                IPAddress localAddress = IPAddress.Parse("127.0.0.1");

                // Define the kind of socket we want: Internet, Stream, TCP
                Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // Define the address we want to claim: the first IP address found earlier, at port 2200
                IPEndPoint ipEndpoint = new IPEndPoint(localAddress, 2200);

                // Bind the socket to the end point
                listenSocket.Bind(ipEndpoint);

                // Start listening, only allow 1 connection to queue at the same time
                listenSocket.Listen(1);

                // Start listening for connections -- the program loops forever
                while (true)
                {
                    Console.WriteLine("Server is waiting on socket {0}", ipEndpoint);

                    // The program is suspended while waiting for an incoming connection.
                    // This is a synchronous TCP application
                    Socket handler = listenSocket.Accept();

                    // An incoming connection needs to be processed.
                    Console.WriteLine("Received Connection from {0}", handler.RemoteEndPoint);

                    Console.WriteLine("Sending message..");
                    handler.Send(encoding.GetBytes("I am a little busy, come back later!"));

                    // End of the incomming connection
                    Console.WriteLine("Ending the connection");
                    handler.Close();
                }

            }
            catch (Exception e)
            {
                Console.WriteLine("Caught Exception: {0}", e.ToString());
            }
        }
    }
}

Version 3 — We don’t want to wait any longer

The above two examples used synchronous IO. We need to wait for the remote system to make a connection before the program can continue. Of course, in real life we would like to our little server to do much more. For this we need asynchronous IO. Our program will be notified by the operating system every time it needs to do something but it is otherwise free to continue.

The hard work is done by “BeginAccept” and “EndAccept”. In “BeginAccept” we specify a callback function which is called as soon as a connection is made. In the callback function itself we need to use “EndAccept” to retrieve the socket with which to communicate to our client.

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SimpleServer
{
    class Program
    {
        public static void ReceiveCallback(IAsyncResult AsyncCall)
        {
            // Data is send in bytes -- so we need to convert a C# string to a Byte[]
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            Byte[] message = encoding.GetBytes("I am a little busy, come back later!");

            // The original listening socket is returned in the AsyncCall, we need to call "EndAccept" to
            // receive the client socket which we can use to send and receive data.
            Socket listener = (Socket)AsyncCall.AsyncState;
            Socket client = listener.EndAccept(AsyncCall);

            Console.WriteLine("Received Connection from {0}", client.RemoteEndPoint);
            client.Send(message);

            // End of the incoming connection
            Console.WriteLine("Ending the connection");
            client.Close();

            // At the end of the connection, we need to tell the OS that we can receive another call
            listener.BeginAccept(new AsyncCallback(ReceiveCallback), listener);
        }

        public static void Main()
        {
            try
            {

                IPAddress localAddress = IPAddress.Parse("127.0.0.1");

                // Define the kind of socket we want: Internet, Stream, TCP
                Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // Define the address we want to claim: the first IP address found earlier, at port 2200
                IPEndPoint ipEndpoint = new IPEndPoint(localAddress, 2200);

                // Bind the socket to the end point
                listenSocket.Bind(ipEndpoint);

                // Start listening, only allow 1 connection to queue at the same time
                listenSocket.Listen(1);
                listenSocket.BeginAccept(new AsyncCallback(ReceiveCallback), listenSocket);
                Console.WriteLine("Server is waiting on socket {0}", listenSocket.LocalEndPoint);

                // Start being important while the world rotates
                while (true)
                {
                    // Write a message and sleep for 2 seconds
                    Console.WriteLine("Busy Waiting....");
                    Thread.Sleep(2000);
                }

            }
            catch (Exception e)
            {
                Console.WriteLine("Caught Exception: {0}", e.ToString());
            }
        }
    }
}

Now again, using the earlier telnet code example try connecting to this thread a few times.

This is not the only way to free up your programs main thread for more important work. Another approach could have been to stick with a (logically) easier synchronous server but instead create a new thread to run it in.

Image credit: nicolasnova

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Google
  • Reddit

Tags: , , ,

6 Responses to “Building a TCP/IP server using C#”

  1. James Says:

    Thanks you, this was very helpful!

  2. Martijn Says:

    @James

    Thank you for the comment — I appreciate the feedback.

  3. Guy Winslow Says:

    Try using this on a machine with two NIC’s and a different client box, in fact different client box and different operating system as well as same operating system different box. I think you will find local host is locked out of incoming traffic on most boxes. You can’t use “IPAddress.Any” ether becuse the other endpoint dos not now witch nic you are listening on. You could say wall each nic should be part of difrent subnets, but that defets the perpose of using more then one nic on a server to combat botel necks. So the real question is Using todayes server machines, How do you create a server with TcpListener?

  4. Martijn Says:

    @Guy

    Listening on the localhost is a “feature” to make the demo workable and easier to write as each system is guaranteed to have one.

    If you bind to another address (or all) you need to decide which one to use — and that is a configuration issue that goes beyond this demo.

    If you setup a web/ftp server you also need to configure which address you would like to use ; there is no method to advertise to the world which address/port you are using.

    If you would like to know which IP addreses are available on your machine have a look at example #4 of the following post:

    http://www.dijksterhuis.org/converting-an-ip-address-to-a-hostname-and-back-with-c/

    Cheers,
    Martijn

  5. ayeni Says:

    I like your contribution its really very interesting but wilke more on threading

  6. Martijn Says:

    Hi Ayeni,

    Good point. I will see if I can come up with an example that explores a threaded TCP/IP server further.

    Martijn

Leave a Reply


Recent Comments
  • Martin Sykora: Wow, this is a sweet little intro to regex, good blog writing my friend! Keep it up!! I am looking...
  • kotelni: I found IndexOfAll metod call very useful. I like the idea of using yield return. This is slightly better...
  • Bryce: Thanks for the info. I play extensively on Oceanic realms and want to switch over for a bit of playing on TW...
  • Martijn: The plugin you are looking for is the DoFollow plugin by Joost de Valk. http://yoast.com/wordpress/...
  • Rob - Former Fat Guy: Brilliant stuff. I looked at a bunch of plugins to do this before I decided on yours. What...