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.
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 (
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 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:
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
CVMObsrvr::Obsrv() function and closes the handles of the dead thread.
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
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 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
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 (
CTimeTskN is another task sample. It is more simple than
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
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
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.
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.
I am working in the Russian branch of an international company in Novosibirsk (Russia) as the software engineer ("Software Engineer V") and the leader of the small team.
Now I believe that my level is so high that in C++ and MS Windows the specific "technology" does not important to me. It is probably enough to say that in my last shareware project (Your Voice Reminder, year 2006) I substituted my own clocks instead of the regular Microsoft in the MS Windows taskbar and implemented my own code to record and clear the voice messages including trimming the pause at the end. So now theses clocks may say date and time with your own voice. Also when I made a (limited time) test client / server project for a company to get the job (year 2006), I made the best project (as they estimated it) though I had no previous experience in the client /server programming.
The most large software that I made myself from the scratch had more than 60,000 lines of code and more than 300 C++ classes.
During seven years I was the manager of the successful software project in my own small software company (less than 5 people) where I was also the executive manger. The corresponding software was purchased by many Russian phone companies.
I know English and a little French.
I worked in USA (H1b visa only) and have the valid USA social security number.
Before 1993 I was a scientist and have Ph.D. in physics and mathematics.
After 1993 when the salary of the scientists in Russia dropped below the survival level (less than $100 /months) I became the Software Engineer.
Worked across the whole software -- in virtually all aspects of software design and implementation. I also worked with cross functional teams and as the project manager.
Besides C++ I also developed in Java.