Creating a Server/Client Application Using Only TCP Protocol


Hello everybody, a few days ago I was thinking of creating a server/client application that manages everything right under a console application to work in only < 10 MB of memory consumption.  I was able to work around with a few protocols handling objects in the .NET Framework and the answer to the problem was the System.Net namespaces collection. But mainly we will only be referring to and talking about the System.Net.Sockets namespace. This namespace provides us with programming objects that can be used to write applications that use native TCP/IP protocols and can be used to write applications that can manage networking capabilities. You can write your application to manage the sockets (Internet Sockets, Windows Sockets, or simply Socks) and handle the requests coming from various clients, and to generate a response to them. 
In this article I will go through TCP, what .NET Framework has in it to work around with a TCP protocol, Transmission control protocol. At the end of this article, you will be able to write web services (or whatever you call a server/client application model) in native .NET's System.Net.Sockets namespace. You won't need any WCF framework any more if you gain enough of an understanding of this namespace. Although WCF also uses the same protocol and the same objects, but only abstracts the underlying threads and process handling so that you can write what you want to perform, rather than managing how it is done.

Transmission control protocol

Let us have an overview of the Transmission Control Protocol itself, it is a part of the internet protocol suite (named by Wikipedia) and is a widely used protocol in the transmission layer of the OSI model. The OSI Model is a very famous model designed for computer networking and how computers communicate with each other using networks. The OSI Model is a standards-based model developed and is being applied to computers. It contains the following 7 layers that control how data is processed:
  1. Physical layer
  2. Data layer
  3. Network layer
  4. Transmission layer
  5. Session layer
  6. Presentation layer
  7. Application layer
These layers work in their own separate abstract environment and do not interfere with other layers. The OSI model ensures that the data transferred is always received on the other end. In this article, as an introduction, I will talk about only the transmission layer. TCP and IP both protocols work to ensure that data is transferred without any problem to the recipient, the actual recipient. But, for our server/client application, we will only talk about the TCP protocol, leaving the IP protocol. Also, note that the IP protocol works in the Network layer, whereas the TCP protocol works in the Transmission layer
All of these layers perform their own tasks in managing various functions in networking. Usually, the TCP protocol is used with the IP protocol to ensure a reliable, structured, and sequential, error-free data transmission. TCP protocol powers our daily use functionalities ranging from World Wide Web (HTTP), File Transfer (FTP), and other communication models and protocols. A few keynotes of TCP protocol are:
  1. Establishing and terminating the connection.
  2. Transferring data
    1. Reliable transfer of data, managing the sequence of bytes, and structure of packets.
    2. Error detection, re-sending the lost packets.
    3. Flow control, estimating the speed at which packets can be sent reliably.
  3. Acknowledgment and other flags.
In the next section, we will learn how to use this protocol and create two sample projects, one that would act as a server and the other that would act as a client. We would then use the client to make requests to the server, the server would perform actions on the data sent with the request and would return a response to the client. The client would show the message sent by the server. Remember, just like other protocols, TCP is just the protocol used to maintain the network connection between two applications, the server, and the client or other similar programs communicating through the network.
This image describes how one application shares its data with other applications. Encoding and the conversion of data based on protocols start at the Application layer, the transport layer plays a vital role in managing the data, the ordering of bytes, and error-control whereas the physical layer manages the bits that are transferred from one application to another. The same happens on the other end but in an opposite manner, bits are converted to packets, an error check-up is run and bytes are ordered so that they can be converted to data. Data is then shared with the user. That's how networking works.
I have skipped naming the layers, instead, I have named them categorically as Application Layer (for Application, Presentation, and Session layer) Transport Layer (because I need to discuss TCP, the Transport Layer must be described clearly), and the Physical Layer (for the Network, Data, and Physical Layers). You must always consider them as a separate layer.  
Clint-server Model
The client-server (or server/client) model has been in the development game for a very long time. The client-server application model provides programmers with an opportunity to build their framework in two versions. One application serves as a server whereas another as a client. 
  1. Server
    The server is the application, program, or computer that provides resources to devices connected through the network.
  2. Client
    The client is the application, program, and computer that relies on servers to get resources. 
In this model, a client may be or may be present on the same machine or location. The client and server communicate with each other over an internet connection or any other computer network that can allow them to share resources. Most commonly, servers are equipped with hardware resources, such as printers and so on in the offices. Servers also have the software applications that are required to run the business. You can send (from a client application) to the server, where the data is processed and a response is generated that is sent back to the client. 

Working of a Client-Server model

A client-server model works in a very simple way, a client application is one that relies on the server for resources, software application, and other hardware components. It can be in a separate and different context or can be installed on the same server for activity. The server application (device, program, or machine) is the one that controls hardware resources, software applications, and other business data and can work with it. The client sends a request to the server and the server may or may not ask for any additional content from the client. It entirely depends on how the server and client are configured. Once the client connects to the server, the server works as the request commands it. Various commands can initiate and trigger various functions on the server. Once the server is done working with the client's data and request, it generates a response. The response may or may not be displayed by the client and the server may or may not send the response and close the connection itself. Once the connection is closed, other traffic can connect and share their data.
For some visual effects, I have created a demonstration of this process,
This image shows how a client and server communicate with each other. The server contains the definitions of the data sources and the source code for all the scripts and other resources used to run the business logic.
This way, a client and server act as a distributed application framework. Users can communicate with the server from any location they are at.
Benefits of the Client-Server model
Now that you are aware of a client-server model, let me point out some benefits of this model (that I found helpful
  1. An entire application model is distributed in two different projects. I can easily configure how each one that shares the data and how each one accepts the data from the network.
  2. I can write the business logic on the server application and execute functions on it. Maintaining the DRY rule of Don't repeat yourself.
  3. I can share the client application with friends and keep my server running. It takes less than 10 MB to run the server and that is great for such small and compact applications. I can for sure add more features, such as asynchronous programming to accept multiple requests from clients if I want to.
  4. Cross-platform data sharing across applications. 
Apart from these goodies, there are quite other good things about building such a framework that distributes the client and server-based logic in two different applications. You can have your server up and running, to listen to clients whenever they want to. Client applications do not need to be running. 
Also, remember that it is not required to build your application in which the server has more resources compared to the client. That is totally wrong, although most commonly apps are built keeping this in mind. But a server does not need to have more resources. For example, you can write all of the logic of your business in the client application, run validation tests and then send the data to the server to just simply store the data in the file system. You don't need to send the data to the server after every keypress, to validate the data and perform other logic, that takes a lot of network traffic also.

Building Client-server application in .NET

The .NET Framework includes a very efficient and robust framework for creating web services, Windows communication foundation. WCF allows you to create a client-server model for an application, using which you can generate a server application. (Usually, a console project is used to create a server instance in WCF, because of its short memory requirements and efficiency. You can also use WPF or WinForms.) You can also create client applications in a variety of ways. 
  1. Console application
    For simpler client application.
  2. Windows Presentation Foundation (or Windows Forms)
    For much complex and GUI oriented applications.
  3. ASP.NET web application
    For applications that can be hosted on the internet, so clients can connect to the applications using the internet. This option allows users with a non-Windows platform to consume the web service, such as from Android devices where the .NET Framework is not present. 
Apart from this, the .NET Framework provides native .NET libraries and assemblies that can be used to create network-oriented applications in the .NET Framework. The System.Net namespace provides us with assemblies that we can use to work around with networking and protocols in .NET applications. There are many namespaces under this namespace and I would talk about only a few. Namespaces that are introduced or talked about in this article are:
  1. System.Net
    Core assembly for networking in .NET Framework. Also will be used in our sample project.
  2. System.Net.Mail
    This namespace holds the objects used to send emails, connect to SMTP servers, and other required operations.
  3. System.Net.Security
    Provides us with an object required to generate SSL based streams.
  4. System.Net.Sockets
    This namespace holds the objects for TCP or UDP clients and listeners and clients for working around with network based on these protocols
We will use the preceding discussed namespaces and create our applications for both server and client use. In both of these scenarios, we would use console projects and write the appropriate code to maintain the server and client applications. 

Sample project

For our sample, we will create a sample project that accepts the data from a client. The client sends the data in JSON format, sending the name, email address, and the message of the client to the server. The server (for this example) sends an email to the person (using his email address).
The client asks for the name, email, and message of the user. You can share this client application with anyone on the network, the server would accept the data and respond as required.  

Writing the server application

Our server application must be hosted to accept new requests from the client. To host our application, we need to provide an address and port that our application would be listening to. We can use a TcpListener object to create an instance of our server. As a parameter to the constructor, we will pass the IP address endpoint that would be used as the hosting address for our server. Clients need to know this address since they will connect to this server application of ours using this address. Note that I am using the loopback address ( and a sample port number (1234) because I will use my own machine to act as a server and client. You can use other IP addresses or ports to configure your own servers and provide access to it for your clients in the network. 
  1. IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, 1234); // Address  
  2. TcpListener listener = new TcpListener(ep); // Instantiate the object  
  3. listener.Start(); // Start listening...  
At this stage, our server would be up and running and would be ready to accept new requests from TCP clients. Note that networks work with bytes and we would be reading the data in the form of bytes. After accepting the bytes, the decoding is done to convert the data to an appropriate type, text, image, or audio. In this sample, I will show text only that is simple enough. You can use other objects (File for example) and convert the data to an image type using the bytes providing a format, "image/jpeg" and so on. 
Next, we listen to a client. Until a client sends a request, we cannot proceed. To accept the data, we need to have a memory location to store the data sent by the client. Also, we need to store the string representation of the message also. Look below at how I did that.
  1. // Just a few variables  
  2. const int bytesize = 1024 * 1024; // Constant, not going to change later  
  3. string message = null;  
  4. byte[] buffer = new byte[bytesize];  
  6. /* Can only proceed from here 
  7.  * if a client makes a request to our application. 
  8.  * Once receives a request, should proceed from this line. */  
  9. var sender = listener.AcceptTcpClient();   
  11. // Reads the bytes from the network   
  12. sender.GetStream().Read(buffer, 0, bytesize);
The preceding code makes our server able to accept a new request and read the data that the client has sent. Until now we have configured the application to be able to start, run, and listen to new requests coming from the server. Our server is currently synchronous, it can only accept one request and would not accept another unless the connection is terminated.
You can use asynchronous programming models to accept multiple requests from multiple clients. In synchronous mode, you cannot accept multiple requests. You need to work with one request, complete the request, and send a response or terminate the connection and then you can move to the next request or client. 
Since I told you that bytes are only sent or received, other data types are just fancy items that we use. We need to now convert these bytes into descriptive data. Since we know our client will transmit string data, we can decode the data into string type. Also, note that our buffer size is very large so that we can accept any amount of data. You can shorten it if you want. This much byte data would always contain null characters where there was no character found. Null characters would take much space on your console. I have written a function for that, to remove the null characters from the string. 
  1. // Pass byte array as parameter  
  2. private static string cleanMessage(byte[] bytes)  
  3. {  
  4.     // Get the string of the message from bytes  
  5.     string message = System.Text.Encoding.Unicode.GetString(bytes);  
  7.     string messageToPrint = null;  
  8.     // Loop through each character in that message  
  9.     foreach (var nullChar in message)  
  10.     {  
  11.         // Only store the characters, that are not null character  
  12.         if (nullChar != '\0')  
  13.         {  
  14.              messageToPrint += nullChar;  
  15.         }  
  16.     }  
  18.     // Return the message without null characters.   
  19.     return messageToPrint;  
  20. }  
We will use this function in our server as well as the client. To remove null characters from the messages sent or received. Apart from this, I have another 2 functions that are used to handle various functions of our server. One to generate and send the response, the other to send the email to the user as a response. 

Sending the email

As for this example, we will use an email as a response to the user. We will send an email to the user to notify him that we have received the data that he sent us. I won't explain how this module is being done, for more information on sending emails in the .NET Framework, please read this article of mine.  
  1. // Send an email to the user also to notify him of the delivery.  
  2. using (SmtpClient client = new SmtpClient("<your-smtp-server>", 25))  
  3. {  
  4.        // A few settings  
  5.        client.EnableSsl = true;  
  6.        client.Credentials = new NetworkCredential("<email>",   
  7.                                          "<password>");  
  9.         client.Send(  
  10.                  new MailMessage("<email-address>", p.Email,  
  11.                         "Thank you for using the Web Service",  
  12.                         string.Format(  
  13.     @"Thank you for using our Web Service, {0}.We have recieved your message, '{1}'.", p.Name, p.Message    
  14.                         )  
  15.                  )  
  16.          );  
  17. }  
This function would send an email to the client at their email address with this message. You can alter this email address or you can change how the server behaves when the client communicates. 

Sending the response

Once everything has been done, it is now time to generate the response to be sent to the client. Similarly, you would send bytes to the client that the application can then convert back to the text or other data that it knows the server sent it. 
We can only send a response to the client if he is still connected. Once the connection is lost, we cannot send a response to the client. Since we have the client connected, we can create a separate function to convert bytes of data and stream it down to the client.
  1. // Sends the message string using the bytes provided and TCP client connected  
  2. private static void sendMessage(byte[] bytes, TcpClient client)  
  3. {  
  4.     // Client must be connected to   
  5.     client.GetStream() // Get the stream and write the bytes to it  
  6.           .Write(bytes, 0,   
  7.           bytes.Length); // Send the stream  
  8. }  
Until now, we have created a server application that can host and accept messages and send responses to them and it can also notify the users using their email addresses.

Complete server program 

The complete server program in our console project is as follows:
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6. using System.Net.Sockets;  
  7. using System.Net;  
  8. using System.Net.Mail;  
  9. using Newtonsoft.Json;  
  11. namespace TcpServer  
  12. {  
  13.     class Program  
  14.     {  
  15.         static void Main(string[] args)  
  16.         {  
  17.             IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, 1234);  
  18.             TcpListener listener = new TcpListener(ep);  
  19.             listener.Start();  
  21.             Console.WriteLine(@"  
  22.             ===================================================  
  23.                    Started listening requests at: {0}:{1}  
  24.             ===================================================",   
  25.             ep.Address, ep.Port);  
  27.             // Run the loop continuously; this is the server.  
  28.             while (true)  
  29.             {  
  30.                 const int bytesize = 1024 * 1024;  
  32.                 string message = null;  
  33.                 byte[] buffer = new byte[bytesize];  
  35.                 var sender = listener.AcceptTcpClient();  
  36.                 sender.GetStream().Read(buffer, 0, bytesize);  
  38.                 // Read the message and perform different actions  
  39.                 message = cleanMessage(buffer);  
  41.                 // Save the data sent by the client;  
  42.                 Person person = JsonConvert.DeserializeObject<Person>(message); // Deserialize  
  44.                 byte[] bytes = System.Text.Encoding.Unicode.GetBytes("Thank you for your message, " + person.Name);  
  45.                 sender.GetStream().Write(bytes, 0, bytes.Length); // Send the response  
  47.                 sendEmail(person);  
  48.             }  
  49.         }  
  51.         private static void sendEmail(Person p)  
  52.         {  
  53.             try  
  54.             {  
  55.                 // Send an email to the user also to notify him of the delivery.  
  56.                 using (SmtpClient client = new SmtpClient("<your-smtp>", 25))  
  57.                 {  
  58.                     client.EnableSsl = true;  
  59.                     client.Credentials = new NetworkCredential("<email>", "<pass>");  
  61.                     client.Send(  
  62.                         new MailMessage("<email-from>", p.Email,  
  63.                             "Thank you for using the Web Service",  
  64.                             string.Format(  
  65.     @"Thank you for using our Web Service, {0}.We have recieved your message, '{1}'.", p.Name, p.Message   
  66.                             )  
  67.                         )  
  68.                     );  
  69.                 }  
  71.                 Console.WriteLine("Email sent to " + p.Email); // Email sent successfully  
  72.             }  
  73.             catch (Exception e)  
  74.             {  
  75.                 Console.WriteLine(e.Message);  
  76.             }  
  77.         }  
  79.         private static string cleanMessage(byte[] bytes)  
  80.         {  
  81.             string message = System.Text.Encoding.Unicode.GetString(bytes);  
  83.             string messageToPrint = null;  
  84.             foreach (var nullChar in message)  
  85.             {  
  86.                 if (nullChar != '\0')  
  87.                 {  
  88.                     messageToPrint += nullChar;  
  89.                 }  
  90.             }  
  91.             return messageToPrint;  
  92.         }  
  94.         // Sends the message string using the bytes provided.  
  95.         private static void sendMessage(byte[] bytes, TcpClient client)  
  96.         {  
  97.             client.GetStream()  
  98.                 .Write(bytes, 0,   
  99.                 bytes.Length); // Send the stream  
  100.         }  
  101.     }  
  103.     class Person  
  104.     {  
  105.         public string Name { getset; }  
  106.         public string Email { getset; }  
  107.         public string Message { getset; }  
  108.     }  
  109. }  
Our application, once running looks like this:
Note: Most of the lines are fancy text in our console. Do not consider them the required fields. I will explain the person's class in the client application.  I have also used the Newtonsoft.Json library to convert the data from JSON to our object, I will explain that in a minute. Keep reading! 

Writing the client application

Now that our server is up and running, we now need to create an application that would act as our client. The client would make a connection to our server, send some data to it then wait for the server to send a response back to our client application. In this application, we will ask the client for their name, email address, and a message. We will then submit these details as members of a Person object. The person's objects would be used on the server and appropriate functions would be triggered. 
Our person object has the following 3 members:
  1. class Person  
  2. {  
  3.     public string Name { getset; } // Name  
  4.     public string Email { getset; } // Email address  
  5.     public string Message { getset; } // Some message text  
  6. }  
We will use this object on our server. I did not explain it before, now I will because I will be using these members to generate a request. Our application asks for user's names, email addresses, and the message that they have. Then it would send that message to the server and the server would send an email to them as a response.
  1. // Get the details from the user and store them.  
  2. Person person = new Person();  
  4. Console.Write("Enter your name: ");  
  5. person.Name = Console.ReadLine();  
  6. Console.Write("Enter your email address: ");  
  7. person.Email = Console.ReadLine();  
  8. Console.Write("Enter your message: ");  
  9. person.Message = Console.ReadLine();  
  11. // Send the message  
  12. byte[] bytes = sendMessage(System.Text.Encoding.Unicode.GetBytes(person.ToJSON()));  
In the client, I also created a function to send the message to the server. In this function, I connect to the server using an address and port that the server is running at. Then, it would send the data to the server in bytes. The server would respond to the request in the same function. That is why the function has a return type of byte array. 
  1. private static byte[] sendMessage(byte[] messageBytes)  
  2. {  
  3.     const int bytesize = 1024 * 1024;  
  4.     try // Try connecting and send the message bytes  
  5.     {  
  6.          System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient("", 1234); // Create a new connection  
  7.          NetworkStream stream = client.GetStream();  
  9.          stream.Write(messageBytes, 0, messageBytes.Length); // Write the bytes  
  10.          Console.WriteLine("================================");  
  11.          Console.WriteLine("=   Connected to the server    =");  
  12.          Console.WriteLine("================================");  
  13.          Console.WriteLine("Waiting for response...");  
  15.          messageBytes = new byte[bytesize]; // Clear the message   
  17.          // Receive the stream of bytes  
  18.          stream.Read(messageBytes, 0, messageBytes.Length);  
  20.          // Clean up  
  21.          stream.Dispose();  
  22.          client.Close();  
  23.     }  
  24.     catch (Exception e) // Catch exceptions  
  25.     {  
  26.         Console.WriteLine(e.Message);  
  27.     }  
  29.     return messageBytes; // Return response  
  30. }  
In the preceding code, our client application attempts to connect to the server and sends the request to the server with the data provided. The server reads the data, works, and then provides a response string, in bytes form. We return those bytes so that the application can clean up our data and show it on the screen. Also, you should use it in a try-catch block because maybe the server is not available. Instead of breaking the application, you should simply show that server is down and close the application. 
Upon running our application, we get this screen.
Now upon submitting, the application would connect and send the data to the server. What the server has for us is not visible in this program. Instead, we need to move to our server application to see what is going on there.
While running the application, I was not connected to the internet so I was provided with that error message. Upon connecting, I was able to get the second line of success message.  
After this line of code, the server would send back the response to the client. The client application would be able to show the following message on the console screen to the user:
So, great until now. Server and client applications are doing their work now.

Points of Interest 

In this article, I showed how to create a web service or a server/client application using native .NET Framework libraries and assemblies. You can create various types of projects that use a centralized server and distributed client applications.
For security purposes, you should always consider using a SslStream object from the System.Net.Security namespace. Also remember, this type of application framework is synchronous. Only one client can connect at a time. If another client must connect, it would wait until the server is released for further requests and processes. You can use async/await operators to convert the current model to an asynchronous one. I would also post the source code on MSDN galleries for you to download and work with. 
Initially, I said that I want to create a program that runs under 10 MB of RAM consumption, after running this application set (of both server and client) we can see that they both run under 5 MB of RAM consumption. Later when uploading starts and email is being sent, it might increase to a 10 MB memory benchmark, but that still is what we were looking for. :)
TcpClient is the client program running and asking the user for input. Whereas vshost32.exe (if you have some experience with Visual Studio) is the program that is attached as a debugger to run the TcpServer. 
I hope I have helped you with this article. :) I will be sharing more code for this framework type in the future.  

Similar Articles