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.