Share via


Creating a server/client application using native .NET TCP library

Source code for this article is available at MSDN Galleries

Introduction

Hello everybody, a few days ago I was thinking of creating a server/client application that manages everything right under console application to work in only <10 MB memory consumption.  I was able to work around with a few protocol handling objects in .NET framework, and the answer to problem was 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 different 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 TCP protocol; Transmission control protocol. At the end of this article, you will be able to write web services (or what ever you call a server/client application model) in native .NET's System.Net.Sockets namespace. You won't be needing any WCF framework any more if you gain enough understanding of this namespace. Although WCF also uses the same protocol, 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 performed.

Transmission control protocol

Let us have an overview of Transmission control protocol itself, it is a part of Internet protocol suite and is a widely used protocol in the transmission layer of the OSI model. OSI Model is a very famous model designed for computer networking and how computers communicate with each other using computer networks. OSI Model is a standard based model developed and is being applied to computers. It contains 7 layers that control how data is converted into bytes, shared across network and converted back from bytes, 

  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. OSI model ensures that the data transferred is always received on the other end error-free and ordering is performed, if there are errors re-transmission is performed (I won't talk about hacking attempts of networks such as TCP hijacking or denial or service). In this article, as an introduction I will talk about only transmission layer. TCP and IP both protocols work together to ensure that data is transferred without any problem to the recipient, actual recipient. But, for our server/client application we will talk about only TCP protocol, leaving IP protocol. Also, note that IP protocol works in Network layer, where as TCP protocol works in Transmission layer
 
All of these layers perform their own tasks in managing different functions in networking. Usually TCP protocol is used with IP protocol to ensure a reliable, ordered, 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 key notes of TCP protocol are, 

  1. Establishing and terminating 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. Acknowledgement 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 other that would act as a client. We would then use client to make requests to the server, server would perform actions on the data sent with the request and would return a response to the client. 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, server and client or other similar programs communicating through network. 

This image describes how one application shares its data with other applications. Encoding and conversion of data based on protocols starts at Application layer, transport layer plays a vital role in managing the data, ordering of bytes and error-control whereas physical layer manages the bits that are transferred from one application to another. Same happens on other end but in an opposite manner, bits are converted to packets, error check up is ran 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 have to discuss TCP, transport layer must be described clearly) and Physical layer (for Network, Data and Physical layer). You must always consider them as a separate layer. 

Client-server Model

Client-server (or Server/client) model has been in the development game for a very long time. Client-server application model provides programmers with an opportunity to build their framework in two different versions. One application serves as a server while other as a client. 

  1. Server
    Server is the application, program or computer device that provides resources to devices connected through network. 
  2. Client
    Client is the application, program and computer device that relies on servers to get resources. 

In this model, a client may be or may be present on the same machine or location. Client and server communicate with each other over internet connection, or any other computer network that can allow them to share resources. Mostly, servers are equipped with hardware resources, such as printers etc. 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. Server application (device, program or machine) is the one that controls hardware resources, software applications and other business data and can work with it. Client sends a request to the server, server may or may not ask for any additional content from the client. It entirely depends on how server and client are configured. Once client connects to the server, server works as the request commands it. Different commands can initiate and trigger different functions on the server. Once 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 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 demonstrates how a client and server communicate with each other. Server contains the definitions of data sources, source code for all 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 server from any location they are at.

Benefits of 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. Entire application model is distributed in two different projects. I can easily configure how each one shares the data and how each one accepts the data from network. 
  2. I can write the business logic on server application, and execute functions on it. Maintaining the DRY rule; 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; 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 of 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 when ever they want to. Client applications do not need to be running. 
 
Also remember that it is not required to build your application in which server has more resources as compared to client. That is totally wrong, although mostly apps are built keeping this in mind. But, a server does not need to be having 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 server to just simply store the data in file system. You don't need to send the data to server after every key press, to validate the data and perform other logic, that takes a lot of network traffic also. 

Building Client-server application in .NET

.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 application, using which you can generate server application (usually a Console project is used to create a server instance in WCF, because of its short memory benchmark and efficiency. You can also use WPF or WinForms) and you can create client applications in a variety of ways. 

.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 application, using which you can generate server application (usually a Console project is used to create a server instance in WCF, because of its short memory benchmark and efficiency. You can also use WPF or WinForms) and you can 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 internet, so clients can connect to the applications using internet. This option allows users with non-Windows platform to consume the web service, such as from Android devices where .NET framework is not present. 

Apart from this, .NET framework provides you with native .NET libraries and assemblies that can be used to create network-oriented applications in .NET framework. System.Net namespace provides us with assemblies that we can use to work around with networking and protocols in .NET applications. There are a lot of 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 object required to generate SSL based streams. 
  4. System.Net.Sockets
    This namespace holds the objects for TCP or UDP clients; listeners and clients for working around with network based on these protocols. 

We will use the above discussed namespaces and create our applications for both server and client use. In both of these scenarios, we would use Console projects and write 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. Client sends the data in JSON format, sending the name, email address and the message of client to the server. Server (for this example) sends an email to the person (using his email address).
 
Client asks for name, email and message by user. You can share this client application to anyone on the network, server would accept the data and respond as required.  

Writing the server application

Our server application requires to be hosted, in order to accept new requests from the client. To host our application, we need to be providing an address and port that our application would be listening at. We can use TcpListener object to create an instance of our server. As a parameter to the constructor we will pass the IP address end point that would be used as the hosting address for our server. Clients need to know this address, as they will connect to this server application of ours using this address. Note that I would use loop back address (127.0.0.1) and a sample port number (1234) because I am going to use my own machine to act as server and client. You can use different IP addresses or ports to configure your own servers and provide access to it for your clients in network. 

IPEndPoint ep = new  IPEndPoint(IPAddress.Loopback, 1234); // Address  
TcpListener listener = new  TcpListener(ep); // Instantiate the object   
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 form of bytes. After accepting the bytes, decoding is performed to convert the data to appropriate type; text, image or audio. In this sample, I will demonstrate text only, which is simple enough. You can use other objects (File for example) and convert the data to image type using the bytes providing a format, "image/jpeg" etc. 
 
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 client. Also, we need to store the string representation of the message also. Look below how I did that, 

// Just a few variables  
const int  bytesize = 1024 * 1024; // Constant, not going to change later  
string message = null;  
byte[] buffer = new  byte[bytesize];  
   
/* Can only proceed from here 
 * if a client makes a request to our application. 
 * Once receives a request, should proceed from this line. */ 
var sender = listener.AcceptTcpClient();   
   
// Reads the bytes from the network  
sender.GetStream().Read(buffer, 0, bytesize);

The above code makes our server able to accept a new request, and read the data that client has sent us. Until now we have configured over application to be able to start, run and listen to new requests coming from server. Our server is currently synchronously, it can only accept one request and would not accept the rest unless 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 have to work with one request, complete the request 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 a descriptive data. Since we know our client is only going to 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 a lot of space on your console. I have written a function for that, to remove the null characters from the string. 

// Pass byte array as parameter  
private static  string cleanMessage(byte[] bytes)  
{  
    // Get the string of the message from bytes  
    string message = System.Text.Encoding.Unicode.GetString(bytes);  
   
    string messageToPrint = null;  
    // Loop through each character in that message  
    foreach (var nullChar in message)  
    {  
        // Only store the characters, that are not null character  
        if (nullChar != '\0')  
        {  
             messageToPrint += nullChar;  
        }  
    }  
       
    // Return the message without null characters.   
    return messageToPrint;  
}

We will use this function in our server as well as client. To remove null characters from the messages sent or received. Apart from this, I have another 2 functions that are used to handle different functions of our server. One to generate and send the response, other to send the email to user as a response. 

Sending the email

As for this example, we will use 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 .NET framework, please read this article of mine. 

// Send an email to user also to notify him of the delivery.  
using (SmtpClient client = new SmtpClient("<your-smtp-server>", 25))  
{  
    // A few settings  
    client.EnableSsl = true;  
    client.Credentials = new  NetworkCredential("<email>", "<password>");  // Authentication
   
    client.Send(  // Send the message
        new MailMessage("<email-address>", p.Email,  // Email addresses
            "Thank you for using the Web Service",  // Subject
            string.Format(  // Body construction
@"Thank you for  using our Web Service, {0}.   
 
We have recieved your message, '{1}'.", p.Name, p.Message  
            )  
        )  
    );  
}

This function would send email to the client at their email address with this message. You can alter this email address or you can change how server behaves when client communicates. 

Sending the response

Once everything has been done, it is now time to generate the response to send to client. Similarly, you would send bytes to the client, which application can then convert back to text or other data that it knows server sent it. 
 
We can only send response to client, if he is still connected. Once connection is lost, we cannot send response to the client. Since we have client connected, we can create a separate function to convert bytes of data and stream it down to the client.

// Sends the message string using the bytes provided and TCP client connected  
private static  void sendMessage(byte[] bytes, TcpClient client)  
{  
    // Client must be connected to   
    client.GetStream() // Get the stream and write the bytes to it   
          .Write(bytes, 0,   
          bytes.Length); // Send the stream  
}

Until now, we have created a server application, that can host, accept messages and send response to them also it can notify the users using their email addresses.

Complete server program 

Complete server program in our console project is as follows,

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Net.Sockets;  
using System.Net;  
using System.Net.Mail;  
using Newtonsoft.Json;  
   
namespace TcpServer  
{  
    class Program  
    {  
        static void  Main(string[] args)  
        {  
            IPEndPoint ep = new  IPEndPoint(IPAddress.Loopback, 1234);  
            TcpListener listener = new  TcpListener(ep);  
            listener.Start();  
   
            Console.WriteLine(@"  
            ===================================================  
                   Started listening requests at: {0}:{1}  
            ===================================================",   
            ep.Address, ep.Port);  
   
            // Run the loop continously; this is the server.  
            while (true)  
            {  
                const int  bytesize = 1024 * 1024;  
   
                string message = null;  
                byte[] buffer = new  byte[bytesize];  
   
                var sender = listener.AcceptTcpClient();  
                sender.GetStream().Read(buffer, 0, bytesize);  
   
                // Read the message, and perform different actions  
                message = cleanMessage(buffer);  
   
                // Save the data sent by the client;  
                Person person = JsonConvert.DeserializeObject<Person>(message); // Deserialize  
   
                byte[] bytes = System.Text.Encoding.Unicode.GetBytes("Thank you for your message, " + person.Name);   
                sender.GetStream().Write(bytes, 0, bytes.Length); // Send the response  
   
                sendEmail(person);  
            }  
        }  
   
        private static  void sendEmail(Person p)  
        {  
            try 
            {  
                // Send an email to user also to notify him of the delivery.  
                using (SmtpClient client = new SmtpClient("<smtp-server>", 25))  
                {  
                    client.EnableSsl = true;  
                    client.Credentials = new  NetworkCredential("<email-address>", "<pass>");   
   
                    client.Send(  
                        new MailMessage("<your-email>", p.Email,  
                            "Thank you for using the Web Service",  
                            string.Format(  
    @"Thank you for  using our Web Service, {0}.   
   
We have recieved your message, '{1}'.", p.Name, p.Message  
                            )  
                        )  
                    );  
                }  
   
                Console.WriteLine("Email sent to " + p.Email); // Email sent successfully  
            }  
            catch (Exception e)  
            {  
                Console.WriteLine(e.Message);  
            }  
        }  
   
        private static  string cleanMessage(byte[] bytes)  
        {  
            string message = System.Text.Encoding.Unicode.GetString(bytes);  
   
            string messageToPrint = null;  
            foreach (var nullChar in message)  
            {  
                if (nullChar != '\0')  
                {  
                    messageToPrint += nullChar;  
                }  
            }  
            return messageToPrint;  
        }  
   
        // Sends the message string using the bytes provided.  
        private static  void sendMessage(byte[] bytes, TcpClient client)  
        {  
            client.GetStream()  
                .Write(bytes, 0,   
                bytes.Length); // Send the stream  
        }  
    }  
   
    class Person  
    {  
        public string  Name { get; set; }  
        public string  Email { get; set; }  
        public string  Message { get; set; }  
    }  
}

Our application, once running looks like this,

Note: Most of the lines are fancy text in our console. Do not consider them required fields. I will explain the Person class in client application. I have also used Newtonsoft.Json library to convert 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. Client would make a connection to our server, send some data to it, wait for server to send a response back to our client application. In this application, we will ask 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 server, and appropriate functions would be triggered. 
 
Our person object, has 3 members and is defined as 

class Person  
{  
    public string  Name { get; set; } // Name  
    public string  Email { get; set; } // Email address  
    public string  Message { get; set; } // Some message text  
 
    // Create the JSON representation of object
    public string  ToJSON()
    {
        string str = "{";
        str += "'name': '" + Name;
        str += "','email': '"  + Email;
        str += "','message': '"  + Message;
 
        str += "'}";
        return str;
    }
}

Since this is a short object, we can use concatenation. But for complex objects, you should always consider serializing the object using JsonConvert.SerializeObject() function of JSON.NET library, or you should use StringBuilder object to get a lengthy string serializationg of the object. You should never concatenate strings for long processes. Strings are immutable and thus takes a lot of cycles in only memory managements. Also concatenated strings are more likely to have invalid schema too. You should (for your own application) change the function's body statements to the following and return jsonNotation to your program.

string jsonNotation = JsonConvert.SerializeObject(person);  // Person person = new Person();

We will use this object on our server. I did not explain it before, now I am because I will be using these members to generate a request. Our application asks for users' names, email addresses and their message what they have. Then it would send that message to server and server would send an email to them as a response.

// Get the details from the user, and store them.  
Person person = new  Person();  
   
Console.Write("Enter your name: ");  
person.Name = Console.ReadLine();  
Console.Write("Enter your email address: ");  
person.Email = Console.ReadLine();  
Console.Write("Enter your message: ");  
person.Message = Console.ReadLine();  
   
// Send the message  
byte[] bytes = sendMessage(System.Text.Encoding.Unicode.GetBytes(person.ToJSON()));

Notice the function ToJSON being used, it converts the object in JSON notation object of string type and this string would be sent on network to the server. In the client, I also created a function, to send the message to server. In this function, I connect to the server using address and port that server is running at. Then, it would send the data to server in bytes, server would respond to the request in the same function that is why function has a return type of byte array. 

private static  byte[] sendMessage(byte[] messageBytes)  
{  
    const int  bytesize = 1024 * 1024;  
    try // Try connecting and send the message bytes   
    {  
         System.Net.Sockets.TcpClient client = new  System.Net.Sockets.TcpClient("127.0.0.1", 1234); // Create a new connection  
         NetworkStream stream = client.GetStream();  
   
         stream.Write(messageBytes, 0, messageBytes.Length); // Write the bytes  
         Console.WriteLine("================================");  
         Console.WriteLine("=   Connected to the server    =");  
         Console.WriteLine("================================");  
         Console.WriteLine("Waiting for response...");  
   
         messageBytes = new  byte[bytesize]; // Clear the message   
   
         // Receive the stream of bytes  
         stream.Read(messageBytes, 0, messageBytes.Length);  
   
         // Clean up  
         stream.Dispose();  
         client.Close();  
    }  
    catch (Exception e) // Catch exceptions  
    {  
        Console.WriteLine(e.Message);  
    }  
   
    return messageBytes; // Return response  
}

In the above code our client application attempts to connect to the server and sends the request to the server with the data provided. Server reads the data, works and then provides with a response string, in bytes form. We return those bytes so that application can clean up our data and show it on screen. Also, you should use it in try catch block because maybe 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 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 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, server would send back the response to client. Client application would be able to show the following message on console screen to the user,

byte[] bytes = System.Text.Encoding.Unicode.GetBytes("Thank you for your message, " + person.Name);   
sender.GetStream().Write(bytes, 0, bytes.Length); // Send the response

So, great until now. Server and client applications are doing their work now. *Mobile phone vibrates* Oh, an email was received at the same time. 

Points of Interest

In this article, I demonstrated how you can create a web service or a server/client application using native .NET framework libraries and assemblies. You can create different types of projects that use a centralized server, and distributed client applications. Good thing of using native objects is that we can create a framework for our need keeping memory usage in mind. This uses only 10 MB at most
 
For security purposes, you should always consider using SslStream object from System.Net.Security namespace.

// Accepts Stream object as parameter in constructor
using (SslStream secureStream = new SslStream(client.GetStream())) {
   // Work with the stream
}

Once this stream has been instantiated, you should use this stream to work around. 

Also remember, this type of application framework is synchronous. Only one client can connect at a time. If another client has to connect, it would wait until server is released and is ready to accept further TCP clients for further requests and processes. You can use async/await operators to convert the current model to asynchronous one. I would also post the source code on MSDN galleries for you to download and work with. 

Entire application (of both, client and server application running) runs under 5 MB as of now, Network requests and other may upgrade the memory benchmark to 10 MB. Still, we were able to create a web service of only 10 MB. 

TcpClient is the client application that is running and asking user for information. vshost32.exe (if you have any knowledge or experience of Visual Studio) is the debugger attached for TcpServer application. 
 
I hope, I have helped you out with this article. :) I will be sharing more code for this framework type in future.