Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Simple, Robust and Expandable Winsock Server for Multiple Clients with Easy to Add New Services

4.93/5 (23 votes)
4 Sep 2006CPOL11 min read 1   1.7K  
How to build a simple, robust and easily expandable server for multiple clients

Introduction

I made this client server project trying to get a job in the Novosibirsk (Russia) branch of a US company. They have acknowledged that my project is the best they ever received and so I believe it will be useful for The Code Project members.

Another reason for me to believe that the project may be useful is that before implementing it, I studied the solution that Code Project suggests and probably for the first time in my life, I did not find on this wonderful site the solution that could be the starting point for my project and simple enough to implement in a week or so.

The main goal of this project was to make a simple, robust, small, easily expandable and easy to implement server for multiple clients. The server is only about 100 KB, implemented as a Microsoft Windows service, self-installable and does not use MFC. The client is MFC dialog based but it does not use MFC socket classes.

Tasks

As an example of the user services (I will call them "tasks" to avoid confusion with Microsoft Windows service applications), I used:

  • Getting the server time
  • Getting from the server the list of files and directories in the specified directory and all its subdirectories. The file sizes are also indicated.

Adding New Tasks

To add a new task, it is sufficient to write one new class to store and show the data in the client (e.g. see CTimeTask), one class to get the data in the sever (e.g. see CTimeTskN) and add three XML like tags to CVMprotocolCr class. All these classes know nothing about the network and sockets.

The working threads that provide the connection and data transfer (see for example LStnrThrdN.cpp from vmSrvr) in their turn know nothing about the tasks. They interact with the tasks via the abstract class pointer that is supplied by the task class fabric (e.g. see CTskFbrckN). The working network threads basically use one virtual function from the abstract task class with the sense "Do it". So you do not need to modify the working threads.

You should also add one line that creates your new task in the task fabric for the client (CTskFbrck) and the server (CTskFbrckN).

There is no need to modify other classes, except for the client interface, where all that we need is to add one new button and may be the command string if required.

Server

Server is implemented as a Microsoft Windows service. To install, use cmd file or type in the command line, vmSrvr.exe -i 5105, where 5105 is the port (you may use any port instead of 5105). If you don't indicate the port the default port is 5105. After the installation the service starts immediately. It also starts automatically on Microsoft Windows startup.

To uninstall use cmd file or type in the command line: vmSrvr.exe -u.

Server Reliability

To make the server reliable, all sockets are used in asynchronous mode so that the server never hangs forever. To avoid processor overload, the working thread waits with the timeout. To wait for the new connection, the port availability for the data receiving and sending the function, select is used with the corresponding timeout so the server response is quick.

To avoid hanging due to lengthy socket closing, I use the socket in the abortive connection mode setting the corresponding variable in the linger structure (e.g. see the thread LstnrThrdN in LStnrThrdN.h).

The main server loop (see ServiceMain in the file yvrrn.cpp) is in the try block so that the server may restart itself in case of a fatal error.

Clearing the Server Resources

Most resources are wrapped in the safer classes and are automatically released in the destructor upon exit from the functions or destroying the object. Most of the safer classes are in vmSafeWinSock.h (common project folder).

When the client disconnects or closes, it sends to the server the command and the corresponding thread responsible for communication with the client and exits, clearing all its resources.

However, even if the client is terminated ungracefully, e.g. by killing the process or turning off the computer power, the server disconnects and clears the resources within about 15 seconds.

To find when the client disconnects, the poorly documented trick is used. If we use select to find out when the port is available for reading, the function recv always returns not zero bytes if the connection is present. In the case of a broken connection, it returns immediately with zero bytes (see CCnctnVMN::Recieve() function and the comments inside). This solution was tested in the following regular real life conditions. The client and server computers were in different networks that had no direct connections. In fact I used two independent Internet providers that compete with each other. Therefore computers had no direct connections and used independent switches and other devices to connect to the Internet. Both client and server worked under Windows XP. The client was killed in the Windows Task Manger. The server cleared the resources in about 15 seconds after this event as expected in an absolutely regular manner. Naturally, it is always possible to develop the firewall that cheats the server, imitating the connection even after the client crashes. This means that only in this case you need the custom solution but it is, I believe, out of the scope of the present article.

The main server loop checks once in 5 seconds, which connection threads have exited using their handles stored in CThrdPullVMN (see CVMObsrvr::Obsrv() function and closes the handles of the dead thread.

Server Synchronization

To simplify, I did not use the I/O Completion Ports, Microsoft Windows events and callbacks to synchronize. Instead I made the threads that send and receive the data to the client so independent of each other and main program that they almost don't need any synchronization. They also use the select function to see when the data is available or the port is easy to send.

Actually the server uses just one event to stop all the threads and one critical section to check whether the threads are alive. The threads periodically check the stop event about once in 5 seconds and exit if the stop event is set. The stopping event is used to close all the threads gracefully when the service is stopped by the Services Control Manager (see ServiceMain in the file yvrrn.cpp)

The List of the Server Classes and Main Functions

All functions and classes have comments and I believe it would be easy to understand the code. The most convenient staring point to study the service is the file YVRRN.CPP. Some small and easy to understand helper classes are omitted from the list.

  • yvrrn.cpp is the main starting file for the service. In this file you find two main functions that start the service ServiceMain and main.
  • CVMObsrvr is responsible for the starting, stopping and clearing of the working service set. The most important function is CVMObsrvr::Obsrv(). This in particular starts the listener thread that listens for the new client connection.
  • LStnrThrdN.cpp has the class CLStnrThrdN that starts the listener thread function. LstnrThrdN. LstnrThrdN listens for the new client connection and for each client, starts the thread function WrkTrhdVMN. All these functions are in the same LStnrThrdN.cpp file.
  • CCnctnVMN is responsible for sending the data to the client and receiving it. It knows nothing about the data it handles.
  • CFileiSzTskN is the task sample. The class is responsible for getting the file names and sizes in the given directory and all its subdirectories. It uses CCnctnVMN to send the data to the client and knows nothing about the sending process. It sends the data in the form of a table in the HTML file.
  • CInstlUninstl is responsible for the service installs and uninstalls.
  • CIntrprttr analyzes the string sent by the client and extracts the command ID and the command string.
  • CLstnrTpsprtN is essentially the structure to transfer the data to the listener thread function via its argument.
  • CThrdPullVMN is a class that holds all working thread handles to close the handles of the dead (exited) threads and to monitor the thread stopping after the stop event is set.
  • CTrdClssCr: The pointer to this abstract class is used in the working thread WrkTrhdVMN to do the particular job for the client. WrkTrhdVMN knows nothing about what type of task it performs. This pointer is supplied by the task fabric (CTskFbrckN).
  • CTimeTskN is another task sample. It is more simple than CFileiSzTskN. CTimeTskN gets the server time to return it to the client. It uses CCnctnVMN to send the data to the client and knows nothing about the sending process.
  • CTskFbrckN is task fabric. It gives the pointer of the abstract class CTrdClssC to the working thread (WrkTrhdVMN in LStnrThrdN.cpp), which knows nothing about what type of task it performs for the client.
  • CVMprotocol contains the helper functions to check the start and the end tag presence for the command, received from the client.
  • CVMprotocolCr is common for the client and the server. It contains the start / end XML like tags set for client commands and the server data as well as the helper functions to use them.
  • CWrkThrdPsprtN is essentially a structure to transfer the data to the working thread WrkTrhdVMN.

Client

The client application is intended to work with the vmSrvr service installed on a remote server. It communicates with the server via the network with TCP/IP protocol. You should know the port that vmSrvr listens to before using this client. The default port is 5105.

To simulate client server on the same computer, you may use localhost as the computer name or 127.0.0.1 as IP.

Any new task interrupts the previous task.

Once connected, the client stays connected until you explicitly ask to disconnect. According to the specification you also have to explicitly disconnect before connecting to the new server name or IP (you may easily change this behaviour).

The most convenient starting points to study the client are the files CvmClntHlpr.cpp (function CvmClntHlpr::DoCmmnd) and CvmClntHlpr.h.

All functions and classes are supplied with comments, so I believe there should be no problem in understanding the code.

The List of the Client Classes and Main Functions

A few small or easy to understand classes are omitted from the list:

  • CvmClntHlpr is the main class that launches the client. The most important function is DoCmmnd. It connects if it is not connected and does the job.
  • CVMThrd launches the thread VmThrdFnctn that is responsible for data exchange with the server. VmThrdFnctn is in the file VMThrd.cpp.
  • CCnctnVM is responsible for sending commands to the server and receiving data. It knows nothing about the data it handles.
  • CDoItVM is the main class that the working thread uses to send commands and receive data.
  • CDscnnctVM is the disconnection task that has the same parent class as the file and time sample tasks. It sends the disconnection command to the server. It is the simplest task example.
  • CFileSzTsk is the task sample. It collects the information about the directories, files and its sizes that the server sends to the client. It also shows the collected data.
  • CIntrprttr analyzes the data that the server sends to the client. Its main job is to find the start and the end XML tag of the data.
  • CTimeTask: Another task sample that is more simple than CFileSzTsk. It gets the server time and shows it. It uses CCnctnVM to receive the data from the server. It knows nothing about the sending process and the network.
  • STskOnly: The structure with the "read only" information to the thread like port and IP address.
  • CTskFbrck is task fabric. It gives the pointer of the abstract class CTrdClssCr to the working thread, which knows nothing about what type of the task it performs.
  • CVMprotocolCr is common for the client and the server. It contains the start / end tags set for client commands and the server data as well as the helper functions to use them.

Project

The project was compiled under Microsoft Visual Studio 2005.

To compile you should set both for the client and the server the common directory as the additional include directory (C/C++ / General).

You should also set "not using precompiled headers" for both projects (C/C++ / Create/Use Precompiled Headers).

The most convenient starting points to study the client are the files CvmClntHlpr.cpp (function CvmClntHlpr::DoCmmnd) and CvmClntHlpr.h.

The most convenient starting point to study the service is the file YVRRN.CPP.

The ready to test precompiled executables are in the release directories of the project.

Points of Interest

  • Click here to read about a poorly documented trick.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)