An application server is a program that provides services to other programs, usually over a network connection.
A simple example of this is a stock quote server. When I worked for a brokerage, we had a quote server that provided stock quotes for the 11 traders in our department. Each trader had a client program running on their machine, and whenever they needed quotes, the client program would contact the quote server and retrieve the necessary data.
In this article I am presenting a framework for writing application servers in C++. First I'll detail how to use the framework, and then I will explain the internal workings of the framework for the more curious readers. Hopefully by the end of this article you will be able to understand this code, and use it in your systems.
Before you can use the framework, you have to install the Expat XML Parser. The framework uses XML to pass data between clients and servers, and utilizes the Expat library to parse this XML. The examples in this article were tested with version 1.95.2 of the parser.
There are four main C++ classes that the framework provides:
The request class represents a request that a client sends to a server. The class has a "name" property, and a collection of "params". You can loosely think of a request as a method call. The "name" property denotes the name of the method, and the "params" collection contains the arguments to the method.
The reply class represents a reply that a server sends to a client. It has a "value" property, and a collection of "errors". You can think of the reply class as a return value to a method call. The "value" property is the actual value returned from the method, and the "errors" collection contains any errors that the method might have generated.
The server class is the workhorse of the framework. You derive from this class to create a new server. It has two methods you are interested in - "handle_request", and "run". The "run" method puts the server into action, and begins accepting connections. The "handle_request" method needs to be overriden in your derived class, and gets called every time a client requests a service.
The last class, client, allows a client program to send a request object to a server. You pass the host and port to the constructor of the class, and then use the "send_request" method to send request objects to the server.
Now that we went over the main C++ classes in the framework, we can take a look at a working example. The following two files implement a minimal server and client:
In simple_quoter_server.cpp we define a C++ class named "simple_quoter_server", and derive it from xmlrpc::server. We override the "handle_request" method, which gets called every time a client makes a request. If the ticker is "RHAT", we return its price in the "value" property of the reply object. If an error occurs, we add an error to the errors collection.
In simple_quoter_client.cpp, we create an instance of the xmlrpc::client class, create and configure a request object, and then send the request to the server via the "send_request" method.
The framework uses XML to pass messages between clients and servers. The very first thing I did before writing any code was define exactly what the XML would look like for requests and replies. I came up with the following:
// // <request> // <name></name> // <params> // <param> // <name></name><value></value> // </param> // </params> // </request> // // // <reply> // <return_value></return_value> // <errors> // <error></error> // </errors> // </reply> //
Next I had to learn how to use the Expat XML Parser. I found a great article on www.xml.com describing how to use expat, and I wrote the following class to encapsulate everything:
The node class represents a node in an XML document. You pass it an XML string via the "load_xml" method, it parses the string, and it creates an in-memory representation of the XML document. It has a collection of children nodes that you can query, so you can "drill-down" into the XML document. If you need the XML string, you can query its "get_xml" method.
Next I created two classes to represent the XML definitions I introduced above:
You'll notice that both classes have "get_xml" and "load_xml" members. The "get_xml" method returns an XML string containing the internal representation of the class, and the "load_xml" string loads that same string back into the class. The result is that these two classes can persist themselves to XML strings, and load these strings later on.
I did this so that the rest of the framework would not have to worry about XML at all. The client and server classes could use the "load_xml" and "get_xml" methods to generate the XML data they needed to send to eachother. You could actually change the implementation of these two methods to not use XML, and the client and server classes would still function properly.
The framework uses the following three C++ classes to pass data back and forth over the network:
You can find an article describing these three classes in the January Issue of the Linux Gazette. The names of the classes have changed, but the implementations are still the same.
The server class, which we will go over in a bit, uses multiple threads when accepting requests from clients. The following classes in the framework provide threading support:
The thread class represents a single thread of execution. The thread_group class represents a concept I borrowed from the Boost.Threads Library. Basically, if you only want to create one thread, you use the thread class. If you want to create more than one thread, you use the thread_group class.
Definitely check out the Boost site when you get a chance. Boost is a group of C++ enthusiasts that create cross-platform libraries. The special thing about it is that some of the Boost members are also C++ Standards Committee members. This basically means that the libraries you find on their site will be of top quality.
The server class is defined and implemented in the following files:
Notice the "accept_thread" class at the top of the *.cpp file. Do you remember how I mentioned before that the "server" class was the workhorse of the framework? Well, I wasn't exactly telling the truth. All the server class does is create a bunch of "accept_thread" objects, and sits back and waits for them to finish.
Since the overloaded "()" operator of the "accept_thread" class performs all of the work, let's take a look at it. Its implementation does the following:
That's basically it. It accepts a socket, handles the request, and closes the socket after sending back the reply.
The client class allows you to send requests to a server. It is defined and implemented in the following files:
The "send_request" method performs the following steps:
In this article I've presented a framework for writing application servers in C++. Hopefully I've explained it clearly enough so that you can use this code in your own systems.
The following archive contains all of the files for this article:
You can extract and build the framework with these commands:
prompt$ gunzip app_server_c++.tar.gz prompt$ tar -xf app_server_c++.tar prompt$ cd app_server_c++ prompt$ make