In the process of developing several Internet services, we noticed that much of their functionality is common, and guided by the
DRY principle (Don't Repeat Yourself - Do Not Repeat), we decided to put the common functionality into a separate module.
The following requirements were made to the module:
- independence from the services using it;
- simplicity of the "client" code;
- multithreading and high speed.
I will make a reservation that our services are written in PHP and work on Apache server running Linux. The main questions were: “on what to write a module?” And “how to access the module from PHP scripts?”. The analysis of the means of implementation of the module was carried out taking into account the software used, as well as our personal preferences, knowledge and experience.
It was proposed 3 main options:
- Implement the module as another web service in PHP and make calls from client services using CUrl or using SOAP. The disadvantages of this approach are the slow work of the interpreted language, the cost of network requests (unnecessary at the moment, since it is assumed that the module and services will run on the same server), the complexity of the implementation of shared objects with parallel requests.
- Implement the module as a FastCGI application using multithreading. The positive aspects of this option include: the possibility of writing a module in C / C ++, which would increase the speed and allow you to implement shared objects in a multithreaded environment. Calls to the module from PHP scripts could be implemented using Unix domain sockets, which would avoid the cost of making unnecessary calls to the network (if the services and the module are located on the same server).
- In addition to these approaches, our attention was drawn to the D-Bus interprocess communication system (IPC), which is widely used in Linux at the present time. The capabilities and characteristics of D-Bus, such as speed, reliability, customizability, the presence of high-level wrapper libraries, seemed to us attractive and satisfying our requirements. In fact, using the second option would lead us to write our own analogue D-Bus. Next, there was a question about the module calls from the client PHP code. On the Internet, we met two libraries implementing D-Bus in PHP: from Pecl and Japanese from GREE Labs . But, since we already had time to write a working test case on Pecl D-Bus, we did not deserve the Japanese implementation due attention. From the C ++ side, we decided to use QtDBus, because of the familiarity of programmers with the Qt library.
Client Code
So, we will pass to implementation. Let's start with the "client" PHP-code. Suppose that there is some application (our module written in Qt) that registered a D-Bus service with the unique name “test.service”. A service is a hierarchical structure of objects registered in it. If you do not need a hierarchy, you can use the path to the object "/" (like the root directory in Linux). In our case, the service has an object "/ test". Objects provide interfaces that contain sets of methods. The "/ test" object has an "test.iface" interface with the "sum" method.
')
client.php:
The code calls the sum method from the test.iface interface of an object located along the "/ test" path on the test.service service via the D-Bus system bus. The method is called with two integer arguments. As a result of the execution of this script on the “test.service” service, the addition of 42 and 13 should be performed, and the result is displayed using the var_dump function.
Implementation of the D-Bus Module
When designing the module architecture, we decided to use ZendFramework terminology for our purposes (which may seem strange for a program written in C ++). This was due to the fact that such terms as "service", "interface", "object" were already used by us in relation to the D-Bus. And, to avoid confusion, we took the concepts of “Action” (Action) and “Controller” (Controller) from ZendFramework.
By the term
“action” we decided to understand the class inherited from QThread, which is a thread in which any necessary functionality will be implemented.
A
"controller" was called a class that encapsulates the calls to actions in its methods. At the same time, the controller must be inherited from QObject and QDBusContext.
Head function
Here is the code for the head function of the module (the main.cpp file). This is where the controller is registered on the D-Bus system bus.
#include <QCoreApplication> #include <QDebug> #include <QDBusConnection> #include #define SERVICE_NAME #define OBJECT_PATH int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // D-Bus QDBusConnection conn = QDBusConnection::systemBus(); // if (! conn.registerService(SERVICE_NAME)) { qDebug() << << conn.lastError().message(); exit(EXIT_FAILURE); } TestController controller; // conn.registerObject(OBJECT_PATH, &controller, QDBusConnection::ExportAllContents); return app.exec(); }
"Controller"
It should be noted that the controller methods open for interprocessor calls via D-Bus work sequentially. That is, if the first client calls the sum method, the second must wait until the execution of the method ends. Therefore, we decided to reduce the code of methods to a minimum in order to avoid long waiting. Thus, at each client call, the working thread (action) is started and the method is exited.
Consider the controller class (file TestController.h). We will write the implementation of the method for brevity in the header file.
#ifndef TEST_CONTROLLER_H #define TEST_CONTROLLER_H #include <QObject> #include <QDBusContext> #include <QDBusConnection> #include <QDebug> #include class TestController: public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO(, ) // public: Q_INVOKABLE int sum(int a, int b) { // D-Bus, . setDelayedReply(true); // (new SumAction(a, b, this))->start(); // . . return 0; }; }; #endif // TEST_CONTROLLER_H
"Actions"
In the
actions we will place the functionality of the module. Each method of the controller will correspond to an action class. Therefore, it is advisable to write an Action class that is basic for all actions.
Action.h #ifndef ACTION_H #define ACTION_H #include <QThread> #include <QDBusMessage> #include <QDBusArgument> #include <QTime> #include <QDBusReply> class QDBusContext; class Action: public QThread { Q_OBJECT public: Action(const QDBusContext* context); virtual ~Action(); // template<typename X> QDBusReply<X> reply() const { return _reply; } protected: // . inline QDBusMessage& reply() { return _reply; } // inline const QDBusMessage& request() { return _request; } private slots: void onFinished(); private: QDBusConnection* _connection; QDBusMessage _request; QDBusMessage _reply; }; #endif // ACTION_H
Action.cpp #include "Action.h" #include <QDBusConnection> #include <QDBusContext> #include <QDebug> #include <QDir> Action::Action(const QDBusContext* context) { if (context != 0) { // _connection = new QDBusConnection(context->connection()); _request = context->message(); } else { _connection = 0; _request = QDBusMessage(); } // _reply = _request.createReply(); // if (! connect(this, SIGNAL(finished()), this, SLOT(onFinished()))) qFatal("SIGNAL/SLOT connection error"); } Action::~Action() { if (_connection != 0) delete _connection; } void Action::onFinished() { if (_connection != 0) { // D-Bus _connection->send(_reply); } /* * * , (event loop). */ deleteLater(); }
Having inherited this class, we can focus on implementing the necessary functionality without worrying about the details of interaction with the D-Bus. All you need to do is to save the parameters in the properties of the class, add a and b, and write the result by the link reply ().
SumAction.h:
#ifndef SUMACTION_H #define SUMACTION_H #include "Action.h" class SumAction: public Action { Q_OBJECT public: SumAction(int a, int b, const QDBusContext* context): Action(context), _a(a), _b(b) {} virtual ~SumAction() {}; protected: void run() { reply() << _a + _b; } private: int _a; int _b; }; #endif
D-Bus configuration
Having compiled the module described above, we will get an application registering the D-Bus service “test.service”. Let's try to run it. Most likely, the result will be as follows:
$ ./dbus-test Error: "Connection ":1.66" is not allowed to own the service "test.service" due to security policies in the configuration file"
To solve this problem, you need to make changes to the D-Bus configuration. D-Bus provides the ability to
customize security and functionality. For our example, it is enough to create the following in the file: /etc/dbus-1/system.d/dbus-test.conf:
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy context="default"> <allow own="test.service"/> <allow send_destination="test.service"/> <allow receive_sender="test.service"/> </policy> </busconfig>
Restarting the D-Bus daemon is not necessary. Changes will take effect after saving the file.
We will repeat the launch of the module and, if it started up safely, we will try to access it from a PHP script.
$ php client.php int(55)
Here is the expected result: 42 + 13 = 55. The source code can be found
here .
Conclusion
The interprocess communication method described above allowed us to adjust the interaction of a module written in C ++ with several web services that need its functionality. Thus, we obtained high performance and flexibility in building a complex information system that C ++ provides us (and Qt in particular), and ease of development and support of web services in PHP.