CPP Cmake NamedPipe Assignment created to build a client/server desktop application targetted for Windows platform.
Using CMake we create:
-
ServerApp - A NamedPipe, multithreaded server application
-
ClientApp - A NamedPipe, threaded client application
-
CommonLib - A library common to ServerApp and ClientApp with some util functions and classes.
-
nlohmann_json - A third party Json library.
The basis of the ServerApp and ClientApp are from Microsoft's Named Pipe documentation.
Niels Lohmann's Json Library is under MIT License.
The basic requirements for this project are:
-
CMake v3.8+
-
A Visual Studio compiler
-
The client should be able to connect to the server through a NamedPipe
-
The client should be able to make both sync and async calls to the server
-
The client should be able to send trivial data (strings, numbers) to the server
-
The client should be able to create objects on the server (based on req-7 below), retrieve them, their attributes and call methods on them
-
The server should be able to receive both sync/async connection requests from clients
-
The server should be able to store data provided by the client via NamedPipe in a reasonable data structure
-
The server should be able to register a custom class (w/ related functions, attributes) which can be used by the client (see req-4)
-
The server should be able to store the custom objects created by the client for the custom class created in req-7
Following the specifications we created a CMake Project for a server/client application using Named Pipes.
The goal of our application is to be simple and efficient. From our Web Development knowledge, we believe that when talking about simple and efficient servers, one type of service that comes to mind is that of RESTful applications, in simple terms we will make a stateless server, that will treat each request as it's own contained task.
Regarding storage, we decided to have a json file to store our server data, so it persists between sessions.
After researching around, mostly at Microsoft's documentation, about how to use Named Pipes and the usual server and client applications. We decided to take the following approach:
Create a Common Library for both Client and Server applications to have some util functions and to store all the possible commands(requests). Also contain a Address and a UserProfile classes.
Address has Street, City, Country and Postal Code as attributes, all std::strings.
UserProfile has a std::string Name, an int Age, an Address UserAddress and a std::size_t Id.
These classes will be used as the custom object requirement from the task.
We created the following commands, which can be called by the command name or index.
- AsyncHello - A simple Asynchronous command sent. Receives a simple string as response, this is string is changed once the user sends his name.
- SyncedHello - A simple Synchronous command sent. Receives a simple string as response, this is string is changed once the user sends his name.
- SendName - An Asynchronous command sent with an string arg. Receives a response that contains his name.
- SendAge - A Synchronous command sent with an integer arg. Receives a response that contains his age.
- CreateUserProfile - An Asynchronous command that asks the server to create a UserProfile, given all it's parameters. Receives the user in a JSON formated matter as a response.
- UserBirthday - An Asynchronous command that asks the server to run a function on a given UserProfile, UserProfile is selected by it's ID. Receives a response with the new user's age.
- FindUserProfile - An Asynchronous command that asks the server to find a UserProfile by part of his name(Case sensitive). Receives a list of all UserProfiles with the sent part contained in their name.
- GetUserProfile - A Synchronous command that asks the server to return all data of a given UserProfile. Receives the user desired in a JSON formated matter as a response.
Client Main Loop
Wait for user's command
IF Command is "Exit"
Close program
ELSE
IF command has arguments
Wait for user's arguments
// Define Message as command appended with it's arguments separated by '@'
Switch on user's command
// Each command will handle the things differently
// And some are Synchronous, others are Asynchronous
IF command is Synchronous
Connect Pipe
Send Message
Wait for response
Deal with response
Close Pipe
ELSE
Connect Pipe
Send Message
Create Thread
Wait for response
Deal with response
Close Pipe
Server Main Loop
While program isn't terminated
Create a Named Pipe Connection
Wait for a client to connect (blocks program)
When client connected
Create thread to deal with client
Thread Loop
Read clent's request //composed of a command plus a series of arguments separated by '@'
If command exists
Deal with command
Send response
Disconnect Pipe
Close thread
While implementating I realized that I could create a synchronous call (SyncedMessage) to the server using CallNamedPipe, which automatically handles CreateFile, WaitNamedpipe, TransactNamedPipe and CloseHandle in one single call, so instead of making at least 4 calls we do one to CallNamedPipe passing our parameters, like a timeout of 20 seconds, and it handles everything for us.
Once we had a synchronous call, we decided to make the ASynchronous call be a thread that calls our SyncedMessage function. Since it's a thread it will run independently of our client main loop and it won't have problems of handling with Asynchronous Server I/O, because it's a synchronous call to the essence.
.
├── CMakeLists.txt
├── .gitignore
├── README.md
├── Binaries
│ ├── bin
│ | ├── ClientApp.exe
│ │ ├── Common.lib
│ │ └── ServerApp.exe
│ ├── include
│ └── lib
├── ClientApp
│ ├── CMakeLists.txt
│ ├── include
│ └── src
│ └── main.cpp
├── Common
│ ├── CMakeLists.txt
│ ├── include
│ | └── common
│ | ├── address.h
│ | ├── common.h
│ | └── userProfile.h
│ └── src
│ ├── address.cpp
│ ├── common.cpp
│ └── userProfile.cpp
├── Server
│ ├── CMakeLists.txt
│ ├── include
│ └── src
│ └── main.cpp