Click here to Skip to main content
12,700,281 members (35,778 online)
Click here to Skip to main content
Add your own
alternative version

Stats

17.5K views
219 downloads
38 bookmarked
Posted

Build a Server Application using Application Library

, 11 Apr 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This article presents the new version of proposed library ‘Application’ to boost.org, and shows how you can build a long time (e.g. Server Application) running applications

Introduction

This article presents the new version of proposed library ‘Application’ to boost.org (0.4.6), and shows how you can build long time running applications (e.g. Server Application).

Basic Setup

  1. The first step is to download the last version of boost and build it.

    The build process of boost is not very easy, but a good documentation is provided, refer to this link.

    Or you can check this good article to help with build.

  2. The second step is to download:

    You can unzip each of it on your ‘c:\’ or other directory. We will configure Visual Studio Project to find these directories.

Caution

The Boost.Application is not yet an official Boost C++ library. It wasn't reviewed and can't be downloaded from www.boost.org. This beta is available to boost community to know the real interest and get comments for refinement. The intention is to submit the library for formal review, if the community thinks that it is interesting.

Boost.Application Resources

You can download the library from GitHub here.

An online documentation (under construction) is available here.

Motivation

Sometimes, we need to run application for long time (e.g. Server Application). We need a mechanism to extend this application on runtime, and we need some facilities to access paths, ensure single instance instantiation on system, manage and catch signals and so on.

This work is recurrent each time that we need build and deploy an application for a particular system, and the way to do this, change a lot on each of these systems.

For instance, on Windows side, we have ‘services’ and on Unix (POSIX) side, we have daemons that are used to build long-running executable applications (e.g. server) and these two APIs have no similarity.

Thus, in this scenario, it is so difficult for the developer to have his/her application running on Windows or on POSIX as server without a lot of work. The work is harder if we need to run the same application in both systems.

Other problem is raised when user wants to provide a way to extend your application using a plug-in mechanism. Like Service/Daemon, the shared modules (DSO) manipulation changes a lot on Windows and POSIX.

Obtaining simple things like paths, arguments, manipulate signal can be annoying, since it also does not have a common interface to do this on both systems.

Application

The ‘application’ library provides a cross-platform C++ way to do a service/daemon, provides a mechanism to help with a plug-in and provide a way to do annoying things using ‘aspects’ and other things.

Our Sample

Sometimes, a common task executes or checks something at specified intervals of time (e.g. unix cron). This can be known as time-based job (task) scheduler.

We will use ‘application’ to build a server application that processes a timer event as tasks on plug-ins (our jobs). Thus our application will have a timer that will expire and trigger tasks that are on a plug-in

At the end, if you are on Windows, you will have a service that loads a ‘.dll’ on runtime, and if you are on POSIX, you will have a Daemon that loads a ‘.so’ on runtime and trigger a task of it.

When our timer (asio) expires, we will scan a folder (filesystem), and look for plug-ins (application) on it, and then execute the job inside of the plug-ins.

Sample Business Logic and Used Boost Libraries

Or sample will contact a web service (api.openweathermap.org) to get weather information.

We are build a server for Windows, then we will use Boot.Program_Options to handle service installation, see details below on ‘Project Sample and Development environment’.

The Web API returns a ‘json’ response, then we will use Boost.Property_Tree and Boost.Foreach to handle this response.

We will compare the “humidity” value that Web API returns and log it on text file if it is less than 80%.

The plugin uses a ‘syncBoost.Asio sample client to connect on web API. User can implement ‘async‘ plugin as exercise, if desired.

We will need to use Boost.Filesystem, Boost.Bind, Boost.Function, Boost.System to do some things.

So, in our sample, we will use the following Boost Libraries:

  • Boost.Asio
  • Boost.Property_Tree
  • Boost.Foreach
  • Boost.Program_Options
  • Boost.Filesystem
  • Boost.Bind
  • Boost.Function
  • Boost.System

Project Sample and Development Environment

I will use Windows and Visual Studio as a development platform due to its popularity. But as I said earlier, the code compiles on POSIX generating a daemon without modifications.

In the download link, you will find a Visual Studio 2013 project. If you use POSIX, you will need to create your own make/jam file to compile the project. If you have any difficulties in this process, please enter in contact then I can help you.

Note that you need to change the paths of project to reflect your environment; in my case, I have this:

C/C++ | General

Linker | General

1. Basic

Boost.Application requires a ‘Functor’ class that will be used to model the concept of an “application”, thus, before your ‘main’ function, you need to add your ‘functor’ class, like this:

// our server class
class time_based_job : boost::noncopyable
{
public:
 
   int operator()()
   {
      return 0;
   }
};
 
// main
int main(int argc, char *argv[])
{  
   time_based_job app; 
 
   return 0;
}

2. Context and Aspects

Boost.Application has a concept of ‘aspects’ to allow easy extension, customization and make things more flexible.

Any C++ class can be an ‘aspect’, and you will use an ‘application context’ to have access to applications ‘aspects’, thus this takes us to another concept: the ‘application context’.

Note that Boost.Application provides a lot of ready-to-use aspects, and we will use some of these ‘aspects’ in our sample.

The ‘application context’ is like a container that you can add your ‘application aspects’. The ‘application context’ can be accessed using 2 ways:

  1. As function parameter
  2. As a global object (singleton)

The user is free to choose the best option to your application design. Here we will use ‘global_context’, so let’s add it to our source file.

// we will use global_context for our sample, we can access global_context in 
// any place of our application
inline global_context_ptr this_application_cxt() {
   return global_context::get();
}
 
// our server class
class time_based_job : boost::noncopyable
{
public:
 
   int operator()()
   {
      return 0;
   }
};
 
// main
int main(int argc, char *argv[])
{  
   time_based_job app; 
 
   // create a global context for application (our aspect_map), 
   // from this point it is available to be used on any part of our application.
   global_context::create();
   boost::shared_ptr<void> global_context_destroy(nullptr, [&](void*) {
      // this code will be executed at the scope exit
      // destroy our global_context and ends our application
      global_context::destroy();
   });
 
   // 1
 
   return 0;
}

The ‘global_context::create’ will create our context, and we use a trick to delete our context when main ends (exit of scope). To do this, we use ‘shared_ptr’.

3. Ready to Use Aspects

Now, we will add 2 ‘ready to use’ aspects that we will need to our ‘application context’. The first aspect will help us to handle ‘args’ and another is to help us with ‘paths’.

Add after: ‘// 1’ ;

this_application_cxt()->insert<args>(
      boost::make_shared<args>(argc, argv));

this_application_cxt()->insert<path>(
     boost::make_shared<path_default_behaviour>(argc, argv));

// 2

4. Launch Application

Boost.Application supports basically 2 flavors of applications that can be:

  • Common Application

    This kind of application is a usual Interactive Terminal application.

  • Server Application

    This kind of application generates a Service (Windows), or a background process/Daemon (Unix).

Note that other kinds of application can be added be user. Refer to this article that extends Boost.Application to support ‘Apache Http Module’ application mode.

Now we will launch a ‘Common Application’. Add after: ‘// 2’ ;

boost::system::error_code ec;
 
return launch<common>(app, this_application_cxt(), ec);

Note that Boost.Application can handle error setting ‘boost::system::error_code’ or use 'exception version' that throws an exception of type boost::system::system_error.

At this point, we have a complete skeleton of application.

5. Buid an Aspect

Our application will process a timer event as tasks, thus now we will model the behavior on one aspect that we will add on our application context.

// our timer aspect
template< typename Handler >
class timer_job : boost::noncopyable
{
   typedef handler<bool>::global_context_callback job_callback;
 
public:
 
   timer_job(Handler h, unsigned int timeout)
      : jtimer_(io_service_), timeout_(timeout), job_(h)
   {
      trigger();
      boost::thread(
         boost::bind(&boost::asio::io_service::run, 
            boost::ref(io_service_))).detach(); 
   }
 
   void stop()
   {
      io_service_.stop(); 
   }
 
protected:
 
   bool job()
   {
      if(job_()) // retur true from job to stop 
         return true;
 
      trigger(); // trigger next timeout event
      return false; 
   }
 
   void trigger()
   {
      jtimer_.expires_from_now(boost::posix_time::seconds(timeout_));
      jtimer_.async_wait(
         [this](const boost::system::error_code& ec) -> void {
            if(!ec) {
               if(this->job()) {
                  io_service_.stop(); // error 
               }
            } 
            else 
               io_service_.stop(); // 'ec' is true 
         } 
      );
   }
 
private:
   unsigned int timeout_;
 
   job_callback job_;

   boost::asio::io_service io_service_; 
   boost::asio::deadline_timer jtimer_;  
};
 
typedef timer_job< handler<bool>::global_context_callback > timer_callback;

In this class (aspect), we use ‘asio::deadline_timer’ to schedule and call a callback method that will do our required action.

6. Add Our Handmade Aspect to Context

To do this, we need to add our callback handler to our functor class, like this:

// our server class
class time_based_job : boost::noncopyable
{
public:
 
   int operator()()
   {
      return 0;
   }
 
   bool doit()
   { 
      return false;
   }
};

And add the below code after ‘path’ aspect on main.

this_application_cxt()->insert<
   timer_callback >(boost::make_shared<
      timer_callback >(
         handler<bool>::make_global_callback(app, &time_based_job::doit), 5));

Boost Application has a class that we can use to make a callback (handler<bool>::make_global_callback). Here, we use it to bind our timer event to ‘doit’ on our functor class.

Thus, when time event expires, it will call “doit” on functor class.

Note that the time event will expire in 5 seconds.

7. Make our Plug-in

A plugin is a shared library that exports some functions (C function) that gives access to our behavior, see:

The first thing is create our plugin project, refer to sample file for details. Basically it is a “Win32 Console Application (DLL)” Project type.

Now, we will create a file called “job_plugin_api.hpp”, this represents a plugin API interface. In this case, we will build a plugin that will “call a web service” using a sync Boost.Asio interface.

class my_plugin_api
{
public:
   virtual ~my_plugin_api(){};
   virtual float version() = 0;
   virtual bool do_the_job(
      const std::string& query, const std::string& resource, 
      std::string& result, std::string& err) = 0;
};

The implementation goes on “job_plugin_library.cpp”, here the code is based on Boost.Asio sample: http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp03/http/client/sync_client.cpp.

I will comment only the plugin access interface, which is that:

my_plugin_api* create_my_plugin(void)
{
   my_plugin_api *myplugin = new my_job_plugin();
   return myplugin;
}
 
void delete_my_plugin(my_plugin_api* myplugin)
{
   delete myplugin;
}

If we respect this interface, we can have other plug-ins, e.g.: here we use Boost.Asiosync” sample, but we can make another plugin that uses Boost.Asio async” sample:

http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp03/http/client/async_client.cpp

8. Load our Plug-in using Boost.Application "Shared Library Class"

Now, we will modify our “doit” method like this:

bool doit()
 {
   boost::shared_ptr<path> pt
       = this_application_cxt()->find<path>();

    boost::filesystem::path scan_dir(pt->executable_path());

    std::vector<boost::filesystem::path> v; // store paths
    std::copy(boost::filesystem::directory_iterator(scan_dir),
                        boost::filesystem::directory_iterator(), back_inserter(v));

    int count_synced_files = 0;
    for (std::vector<boost::filesystem::path>::const_iterator it
          (v.begin()); it != v.end(); ++it)
    {
       boost::filesystem::path p(*it);

       std::string base = boost::to_lower_copy(p.extension().string());

       if( base == shared_library::suffix() ) // dll or so
       {
          std::cout << "our plugin: " << p.string() << std::endl;

          my_plugin_api* plugin = NULL;

          shared_library sl(library(p.string()));

          if(sl.search_symbol(symbol("create_my_plugin")))
          {
             plugin = ((pluginapi_create)sl(symbol("create_my_plugin")))();
          }

          if(plugin != NULL)
          {
             boost::shared_ptr<void> delete_plugin(nullptr, [&](void*) {
                   // this code will be executed at the scope exit
                   ((pluginapi_delete) sl(symbol("delete_my_plugin")))(plugin);
             });

             std::cout << "Plugin Version: " << plugin->version() << std::endl;

             std::string query = "api.openweathermap.org", resource = "/data/2.5/find?q=Americana,br&mode=json", aswer, err;

             if(plugin->do_the_job(query, resource, aswer, err))
                std::cout << "Error from plugin: " << err << std::endl;
             else
             {
                /*
                {
                    "message": "accurate",
                    "cod": "200",
                    "count": 1,
                    "list": [
                        {
                            "id": 3472343,
                            "name": "Americana",
                            "coord": {
                                "lon": -47.33139,
                                "lat": -22.73917
                            },
                            "main": {
                                "temp": 302.15,
                                "pressure": 1018,
                                "humidity": 48,
                                "temp_min": 302.15,
                                "temp_max": 302.15
                            },
                            "dt": 1396634400,
                            "wind": {
                                "speed": 6.7,
                                "deg": 130
                            },
                            "sys": {
                                "country": "BR"
                            },
                            "clouds": {
                                "all": 40
                            },
                            "weather": [
                                {
                                    "id": 802,
                                    "main": "Clouds",
                                    "description": "scattered clouds",
                                    "icon": "03d"
                                }
                            ]
                        }
                    ]
                }
                */

                pt::ptree json_reponse; std::istringstream is(aswer);
                read_json(is, json_reponse);

                BOOST_FOREACH(boost::property_tree::ptree::value_type& v, json_reponse.get_child("list"))
                {
                   if(v.second.get<int>("main.humidity") < 80)
                   {
                      // record
                      weather_log_ << v.second.get<std::string>("dt") << ":" << v.second.get<std::string>("main.humidity") << std::endl;
                   }
                }
             }
          }
        }
    }

    return false;
 }

Well, here we have a lot of code.

Here we use “boost::filesystem” to scan folder to get all shared modules. To do that, we use the aspect:

boost::shared_ptr<path> pt 
   = this_application_cxt()->find<path>();
 
boost::filesystem::path scan_dir(pt->executable_path());

That gives us the path of our executable module.

The “shared_library::suffix()” return a string dll’ on windows and ‘so’ on unix.

Then we use “application::shared_library” to load a plugin, check if specific simbol is provided, and call it.

shared_library sl(library(p.string()));
 
if(sl.search_symbol(symbol("create_my_plugin")))
{
   plugin = ((pluginapi_create)sl(symbol("create_my_plugin")))();
}
// ...

The plugin response is a json string that we ‘read’ using Boost.Property_Tree:

pt::ptree json_reponse; std::istringstream is(aswer);
read_json(is, json_reponse);
BOOST_FOREACH(boost::property_tree::ptree::value_type& v, json_reponse.get_child("list"))
{
   if(v.second.get<int>("main.humidity") < 80)
   {
      // record
      weather_log_ << v.second.get<std::string>("dt") << ":" << v.second.get<std::string>("main.humidity") << std::endl;
   }
}

9. Instantiate Application as Service or Daemon

To do this, we need change our main function a little. We will provide a way to user to choose if he wants to start the applications as service or as common application.

std::vector<std::string> arg_list
   = this_application_cxt()->find<args>()->arg_vector();

int ret = 0;
if(std::find(arg_list.begin(), arg_list.end(), "--common") != arg_list.end())
   // launch our application as a common app
   ret = launch<common>(app, this_application_cxt(), ec);
else
{
   // launch our application as a server app (service / daemon)
   ret = launch<server>(app, this_application_cxt(), ec);
}

10. Installation

In this version of lib, we don’t provide official support for "service or daemon" installation. This is because we need maintain symmetry between functionality provided on all platforms that the lib support, and on POSIX side, the installation process varies a lot. Thus, we provide installation functionality for major system as sample on examples folder. In future, we have a plan to bring these functionalities to the core lib.

Note: For POSIX, you need to use some shell script to control a daemon, refer to:

\example\setup\posix\ubuntu

That has some samples from Ubuntu.

On Windows, we will use this sample code:

\example\setup\windows\setup\service_setup.hpp

Now, it is time to code our setup method for Windows.

// my setup code for windows service
bool setup()
{
   boost::shared_ptr<args> myargs 
      = this_application_cxt()->find<args>();
 
   boost::shared_ptr<path> mypath 
      = this_application_cxt()->find<path>();
   
// provide setup for windows service   
#if defined(BOOST_WINDOWS_API)      
 
   // get our executable path name
   boost::filesystem::path executable_path_name = mypath->executable_path_name();
 
   std::string exename = mypath->executable_name().stem().string();
 
   // define our simple installation schema options
   po::options_description install("service options");
   install.add_options()
      ("help", "produce a help message")
      (",i", "install service")
      (",u", "unistall service")
      ("user", po::value<std::string>()->default_value(""), 
         "user logon (optional, installation only)")
      ("pass", po::value<std::string>()->default_value(""), 
         "user password (optional, installation only)")
      ("name", po::value<std::string>()->default_value(exename), 
         "service name")
      ("display", po::value<std::string>()->default_value(""), 
         "service display name (optional, installation only)")
      ("description", po::value<std::string>()->default_value(""), 
         "service description (optional, installation only)")
      ;
 
      po::parsed_options parsed = 
      po::command_line_parser(myargs->argc(), myargs->argv()
         ).options(install).allow_unregistered().run();  
 
      po::variables_map vm;
      po::store(parsed, vm);
      boost::system::error_code ec;
 
      if (vm.count("help")) 
      {
         std::cout << install << std::cout;
         return true;
      }
 
      if (vm.count("-i")) 
      {
         example::install_windows_service(
         setup_arg(vm["name"].as<std::string>()), 
         setup_arg(vm["display"].as<std::string>()), 
         setup_arg(vm["description"].as<std::string>()), 
         setup_arg(executable_path_name),
         setup_arg(vm["user"].as<std::string>()), 
         setup_arg(vm["pass"].as<std::string>())).install(ec);
 
         std::cout << ec.message() << std::endl;
 
         return true;
      }
 
      if (vm.count("-u")) 
      {
         example::uninstall_windows_service(
            setup_arg(vm["name"].as<std::string>()), 
            setup_arg(executable_path_name)).uninstall(ec);
                       
         std::cout << ec.message() << std::endl;
 
         return true;
      }
#endif
 
   return false;
}

Here, we use Boost.Program_Options to know user desired options.

Now, we have a command line interface on our server to install and uninstall if the service is installed. When the user requests installation, e.g.: ‘time_based_plugin_job.exe -i', the installation requisition is identified by Boost.Program_options and the code on ‘-i if' is executed, installing the service.

Note that you can provide “user login” to install the service for a specific Windows User.

The command line options are:

C:\Users\Renato Tegon Forti\Desktop\time_based_plugin_job\Debug>time_based_plugin_job.exe –i
  [I] Setup changed the current configuration.

C:\Users\Renato Tegon Forti\Desktop\time_based_plugin_job\Debug>time_based_plugin_job.exe –u
 [I] Setup changed the current configuration.

To install service, you need have permission to do that. An easy way is execute installation as Admin like:

After that, you will see on SCM our application as Service.

11. Other Handlers

Boost.Application allows you to add some handlers, e.g. code some action when user clicks on stop link (Windows) or sends CTRL-C signal on POSIX.

We will add code to stop. First, we need to add ‘stop’ method to our functor class, like this:

bool stop()
{
   if (weather_log_.is_open())
   {
      weather_log_.close();
   }

   this_application_cxt()->find<timer_callback>()->stop();

   return true;
}

Here, we close our log file and stop our timer engine.

Now, we need to add handler to ‘stop’ on main in your application context, like this:

this_application_cxt()->insert<termination_handler>(
   boost::make_shared<termination_handler_default_behaviour>(
      handler<bool>::make_global_callback(app, &time_based_job::stop)));

Now, when user clicks stop or send ctrl-c signal to application, the method ‘stop’ will be called!

Note if you return false from ‘stop’, the application engine will ignore action, and the application will continue.

You have other handles available, like pause, or limit single instance. Check documentation for more details.

Conclusion

Boost.Application can save time when the task is to build a cross platform server; otherwise the developer must work directly with a complex API provided for that. Furthermore Boost.Application provides an extension mechanism for an application (based on plugins) simple and efficient, and many other ready-to-use features.

Feedback

If you are a Boost user, and use Boost Mailing Lists, please provide your feedback about the library, directly on list. (Specify if you think the library should be accepted as part of boost.org.)

If you are a CodeProject user, please provide your feedback about the library directly on this page.

Use

If you intend to use ‘Application’ on your application, please send-me your name and project. I am looking for create a list of users of ‘Application’.

re.tf@acm.or (Renato Tegon Forti)

License

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

Share

About the Author

Renato Tegon Forti
Systems Engineer
Brazil Brazil
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
Questionquestions about service parameters Pin
MINYI YANG13-Aug-14 2:47
memberMINYI YANG13-Aug-14 2:47 
AnswerRe: questions about service parameters Pin
Renato Tegon Forti15-Aug-14 7:00
memberRenato Tegon Forti15-Aug-14 7:00 
QuestionRenato, Excellent article.......but needs updation Pin
Member 1093480626-Jul-14 3:56
memberMember 1093480626-Jul-14 3:56 
AnswerRe: Renato, Excellent article.......but needs updation Pin
Renato Tegon Forti26-Jul-14 8:10
memberRenato Tegon Forti26-Jul-14 8:10 
GeneralRe: Renato, Excellent article.......but needs updation Pin
Member 1093480627-Jul-14 19:30
memberMember 1093480627-Jul-14 19:30 
GeneralRe: Renato, Excellent article.......but needs updation Pin
Renato Tegon Forti28-Jul-14 2:29
memberRenato Tegon Forti28-Jul-14 2:29 
GeneralGood attempt Pin
Member 1093480612-Jul-14 2:00
memberMember 1093480612-Jul-14 2:00 
GeneralRe: Good attempt Pin
Renato Tegon Forti13-Jul-14 9:31
memberRenato Tegon Forti13-Jul-14 9:31 
Generalmy vote of 5 Pin
Southmountain12-Apr-14 9:21
memberSouthmountain12-Apr-14 9:21 
AnswerRe: my vote of 5 Pin
Renato Tegon Forti12-Apr-14 12:03
memberRenato Tegon Forti12-Apr-14 12:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170118.1 | Last Updated 11 Apr 2014
Article Copyright 2014 by Renato Tegon Forti
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid