Le tutoriel qxClientServer a pour objectif d'expliquer le fonctionnement du module QxService de la
biblioth�que QxOrm.
Le module QxService permet de cr�er rapidement un serveur d'applications C++
performant (notion de services avec demande du client et r�ponse du
serveur).
Les sources du projet qxClientServer sont disponibles dans le dossier
./test/qxClientServer/ de la distribution de QxOrm.
Il est conseill� d'avoir lu le tutoriel qxBlog avant de
lire cet article, notamment tout ce qui concerne la fonction de mapping de QxOrm :
void qx::register_class<T>(...).
Remarque : pour activer le module QxService (n�cessaire pour compiler les
sources de ce tutoriel), il faut d�finir l'option de compilation _QX_ENABLE_QT_NETWORK
dans le fichier de configuration QxOrm.pri.
Pour plus de d�tails sur ces options de compilation, rendez-vous sur le manuel utilisateur de la biblioth�que
QxOrm.
Le r�sultat final de ce tutoriel comporte deux ex�cutables et une couche service :
- qxServer : serveur d'applications C++ avec une interface utilisateur pour
param�trer le serveur et un champ pour afficher la derni�re transaction effectu�e entre
le client et le serveur ;
- qxClient : interface utilisateur contenant plusieurs boutons pour ex�cuter
diff�rentes requ�tes au serveur ;
- qxService : couche service, le serveur et le client partagent cette m�me couche
pour transf�rer les donn�es et appeler les services.
Le tutoriel qxClientServer est constitu� des �tapes suivantes :
Remarque : ce tutoriel est �galement disponible sur le site www.developpez.com.
|

QxOrm library has been accepted into the Qt
Ambassador Program
|
Remarque : pour plus de d�tails sur la notion de socket, de thread et de r�seau, le site de
Qt propose des tutoriels sur l'utilisation du module QtNetwork :
1- Cr�ation de l'interface serveur : qxServer
Le projet qxServer contient une seule fen�tre : l'interface utilisateur a �t� r�alis�e avec
l'outil Qt Designer propos� par la biblioth�que Qt.
Cette interface a pour seul objectif d'afficher � l'utilisateur la derni�re transaction client-serveur,
et de pouvoir configurer certains param�tres du serveur.
Pour une utilisation r�elle (logiciel de production), il est conseill� de proposer un syt�me de
log plut�t qu'un affichage � l'utilisateur.
Une interface la plus minimaliste possible (voire aucune interface) est de mani�re g�n�rale la solution
la plus optimale pour un serveur d'applications.
Les fichiers main_dlg.h et main_dlg.cpp correspondent au code C++ de l'interface du projet
qxServer.
1.1- Description du fichier main_dlg.h
#ifndef _QX_SERVER_MAIN_DLG_H_
#define _QX_SERVER_MAIN_DLG_H_
#include "../qt/ui/include/ui_qxServer.h"
class main_dlg : public QWidget, private Ui::dlgServer
{ Q_OBJECT
private:
qx::service::QxThreadPool_ptr m_pThreadPool;
public:
main_dlg(QWidget * parent = NULL) : QWidget(parent), Ui::dlgServer() { main_dlg::init(); }
virtual ~main_dlg() { ; }
private:
void init();
void loadServices();
private Q_SLOTS:
void onClickStartStop();
void onCboIndexChanged(int index);
void onError(const QString & err, qx::service::QxTransaction_ptr transaction);
void onServerIsRunning(bool bIsRunning, qx::service::QxServer * pServer);
void onTransactionFinished(qx::service::QxTransaction_ptr transaction);
};
#endif // _QX_SERVER_MAIN_DLG_H_
|
La variable m_pThreadPool de type qx::service::QxThreadPool_ptr contient toute la logique
du serveur d'applications.
Cette logique est g�r�e de mani�re automatique par la biblioth�que QxOrm.
La m�thode init() permet d'initialiser les param�tres par d�faut du serveur, de connecter les
�v�nements SIGNAL-SLOT et de lancer automatiquement le serveur.
Nous allons voir tout ceci plus en d�tails avec l'impl�mentation des m�thodes dans le fichier
main_dlg.cpp...
1.2- Description du fichier main_dlg.cpp, m�thode init()
void main_dlg::init()
{
setupUi(this);
QObject::connect(btnStartStop, SIGNAL(clicked()), this, SLOT(onClickStartStop()));
QObject::connect(cboSerializationType, SIGNAL(currentIndexChanged(int)), this, SLOT(onCboIndexChanged(int)));
cboSerializationType->addItem("0- serialization_binary", QVariant((int)qx::service::QxConnect::serialization_binary));
cboSerializationType->addItem("1- serialization_xml", QVariant((int)qx::service::QxConnect::serialization_xml));
cboSerializationType->addItem("2- serialization_text", QVariant((int)qx::service::QxConnect::serialization_text));
cboSerializationType->addItem("3- serialization_portable_binary", QVariant((int)qx::service::QxConnect::serialization_portable_binary));
cboSerializationType->addItem("4- serialization_wide_binary", QVariant((int)qx::service::QxConnect::serialization_wide_binary));
cboSerializationType->addItem("5- serialization_wide_xml", QVariant((int)qx::service::QxConnect::serialization_wide_xml));
cboSerializationType->addItem("6- serialization_wide_text", QVariant((int)qx::service::QxConnect::serialization_wide_text));
cboSerializationType->addItem("7- serialization_polymorphic_binary", QVariant((int)qx::service::QxConnect::serialization_polymorphic_binary));
cboSerializationType->addItem("8- serialization_polymorphic_xml", QVariant((int)qx::service::QxConnect::serialization_polymorphic_xml));
cboSerializationType->addItem("9- serialization_polymorphic_text", QVariant((int)qx::service::QxConnect::serialization_polymorphic_text));
cboSerializationType->setCurrentIndex(cboSerializationType->findData(QVariant((int)qx::service::QxConnect::getSingleton()->getSerializationType())));
spinPortNumber->setValue(7694);
spinThreadCount->setValue(qx::service::QxConnect::getSingleton()->getThreadCount());
onServerIsRunning(false, NULL);
onClickStartStop();
}
|
L'�v�nement onClickStartStop() permet de d�marrer/arr�ter le serveur.
Le serveur d'applications peut s�rialiser les r�ponses � envoyer aux clients de plusieurs fa�ons : ce
param�tre est disponible avec la combobox cboSerializationType.
Pour plus d'informations sur les diff�rents types de s�rialisation support�s par la biblioth�que QxOrm,
suivre ce lien du manuel utilisateur.
D'une mani�re g�n�rale, la s�rialisation binaire est fortement conseill�e pour une transaction r�seau car
elle est plus rapide � ex�cuter et permet de limiter le traffic sur le r�seau.
On d�finit �galement le port d'�coute du serveur d'applications avec le champ spinPortNumber.
Un param�tre important est le nombre de threads disponibles sur le serveur d'applications : cel�
correspond aux nombres de clients pouvant se connecter au serveur simultan�ment.
La valeur par d�faut de ce param�tre est 30, vous pouvez modifier cette valeur suivant la charge
estim�e de votre serveur d'applications.
Si le nombre de clients d�passe le nombre de threads disponibles, la requ�te est mise en attente : d�s
qu'un thread se lib�re, alors la requ�te s'ex�cute normalement.
Tout ceci est g�r� automatiquement par la biblioth�que QxOrm : il est juste important de faire une
estimation de la charge que pourra avoir votre serveur d'applications.
Enfin, l'appel � onClickStartStop() permet de d�marrer automatiquement le serveur d�s l'ex�cution
du programme qxServer.
1.3- Description du fichier main_dlg.cpp, m�thode
loadServices()
void main_dlg::loadServices()
{ server_infos dummy_01; Q_UNUSED(dummy_01);
}
|
La m�thode loadServices() est l'unique d�pendance avec les services propos�s par le serveur
d'applications.
Elle sert uniquement � cr�er une instance fant�me pour �tre certain que la DLL contenant la liste des
services soient correctement charg�e au d�marrage de l'application.
Pour un logiciel en production, il peut �tre int�ressant � ce niveau de proposer un syst�me de
plugins pour charger les diff�rents services.
1.4- Description du fichier main_dlg.cpp, m�thode
onClickStartStop()
void main_dlg::onClickStartStop()
{
if (m_pThreadPool)
{
m_pThreadPool->disconnect();
m_pThreadPool.reset();
txtError->setPlainText("");
txtTransaction->setPlainText("");
onServerIsRunning(false, NULL);
}
else
{
qx::service::QxConnect::getSingleton()->setPort(spinPortNumber->value());
qx::service::QxConnect::getSingleton()->setThreadCount(spinThreadCount->value());
qx::service::QxConnect::getSingleton()->setSerializationType((qx::service::QxConnect::serialization_type)
(cboSerializationType->itemData(cboSerializationType->currentIndex()).toInt()));
qx::service::QxConnect::getSingleton()->setCompressData(chkCompressData->isChecked());
qx::service::QxConnect::getSingleton()->setEncryptData(chkEncryptData->isChecked());
m_pThreadPool.reset(new qx::service::QxThreadPool());
QObject::connect(m_pThreadPool.get(), SIGNAL(error(const QString &, qx::service::QxTransaction_ptr)), this,
SLOT(onError(const QString &, qx::service::QxTransaction_ptr)));
QObject::connect(m_pThreadPool.get(), SIGNAL(serverIsRunning(bool, qx::service::QxServer *)), this,
SLOT(onServerIsRunning(bool, qx::service::QxServer *)));
QObject::connect(m_pThreadPool.get(), SIGNAL(transactionFinished(qx::service::QxTransaction_ptr)), this,
SLOT(onTransactionFinished(qx::service::QxTransaction_ptr)));
m_pThreadPool->start();
}
}
|
La m�thode onClickStartStop() permet de d�marrer/arr�ter le serveur d'applications : elle s'occupe
de cr�er une instance de type qx::service::QxThreadPool_ptr ou bien de la d�truire.
Si la variable m_pThreadPool est valoris�e, alors cel� signifie que l'on souhaite arr�ter le
serveur : m_pThreadPool.reset();.
Sinon, le serveur est arr�t� donc on souhaite le d�marrer :
m_pThreadPool.reset(new qx::service::QxThreadPool());.
m_pThreadPool->start();.
Le param�trage du serveur est effectu� gr�ce au singleton
qx::service::QxConnect::getSingleton().
Enfin, l'interface utilisateur s'abonne aux �v�nements envoy�s par le serveur d'applications (m�canisme
SIGNAL-SLOT de Qt) pour r�cup�rer une erreur ou bien afficher la derni�re transaction
client-serveur.
1.5- Description du fichier main_dlg.cpp, m�thodes onError() et
onTransactionFinished()
void main_dlg::onError(const QString & err, qx::service::QxTransaction_ptr transaction)
{
if (err.isEmpty()) { txtError->setPlainText(""); return; }
QString errText = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm") + " : " + err;
if (transaction) { errText += QString("\r\n\r\n") + qx::serialization::xml::to_string(* transaction); }
txtError->setPlainText(errText.replace("\t", " "));
}
void main_dlg::onTransactionFinished(qx::service::QxTransaction_ptr transaction)
{
if (! transaction) { txtTransaction->setPlainText(""); return; }
QString text = qx::serialization::xml::to_string(* transaction);
txtTransaction->setPlainText(text.replace("\t", " "));
}
|
Toutes les transactions entre client et serveur sont repr�sent�es par la classe
qx::service::QxTransaction_ptr.
Cette classe contient toutes les informations n�cessaires � l'ex�cution d'un service (identifiant unique,
date-heure, requ�te du client, service � ex�cuter, r�ponse du serveur, code et message d'erreur,
etc.).
La transaction est s�rialis�e au format XML (ou JSON) avant d'�tre affich�e � l'utilisateur dans le champ
txtTransaction.
Cette s�rialisation est ind�pendante de la r�ponse envoy�e au client qui, par d�faut, est au format
binaire.
1.6- R�sultat obtenu pour le projet qxServer
Et... c'est tout : vous pouvez constater que l'�criture d'un serveur d'applications est
extr�mement simple avec la biblioth�que QxOrm.
Votre serveur d'applications est pr�t pour proposer de multiples services aux diff�rents clients.
Voici le r�sultat obtenu :
2- Cr�ation de la couche service : qxService
La couche service doit �tre partag�e entre le client et le serveur.
La compilation du projet qxService cr�e deux DLL (ou fichiers *.so sous Linux) :
qxServiceClient et qxServiceServer.
Une option de compilation _QX_SERVICE_MODE_CLIENT permet de faire la distinction entre le client
et le serveur.
L'outil qmake de Qt et le syst�me de fichiers *.pro et *.pri permettent de cr�er
facilement ce type d'architecture :
- Le fichier qxService.pri correspond au tronc commun
des deux DLL, c'est-�-dire l'ensemble des d�pendances et des fichiers � compiler ;
- Le fichier qxServiceClient.pro est sp�cifique
au mode client : d�finition de l'option de compilation _QX_SERVICE_MODE_CLIENT et du nom de
la DLL ;
- Le fichier qxServiceServer.pro est sp�cifique
au mode serveur : d�finition du nom de la DLL.
Il est important de signaler que ce m�canisme permet au programme client de partager les m�mes fichiers
que le programme serveur.
La partie cliente n'a aucun code � �crire pour pouvoir appeler un service : le serveur peut livrer
la liste des fichiers de type headers, les .dll et .lib (ou *.so sous
Linux).
2.1- Ecriture du premier service : r�cup�rer la date-heure courante du
serveur
Le premier service propos� par notre serveur d'applications de test est relativement simple : il
consiste � renvoyer aux clients la date-heure courante du serveur.
Ce service est disponible avec la classe server_infos : fichiers server_infos.h et
server_infos.cpp.
Une m�me classe peut proposer plusieurs services : la classe server_infos pourrait par exemple
renvoyer en plus de la date-heure courante, un nom de machine, une fr�quence processeur du serveur,
etc.
Chaque classe service poss�de des param�tres d'entr�e (demande du client) et des param�tres de sortie
(r�ponse du serveur).
Une classe param�tre (entr�e ou sortie) doit h�riter de la classe qx::service::IxParameter et doit
�tre s�rialisable.
Une classe service doit h�riter du template qx::service::QxService<INPUT, OUTPUT> et doit
d�finir une liste de m�thodes (services disponibles).
Il est conseill� d'�crire les classes param�tres d'entr�e, param�tres de sortie et services dans le m�me
fichier.
2.2- Description du fichier server_infos.h
#ifndef _QX_SERVICE_SERVER_INFOS_H_
#define _QX_SERVICE_SERVER_INFOS_H_
class QX_SERVICE_DLL_EXPORT server_infos_input : public qx::service::IxParameter
{ ; };
QX_REGISTER_HPP_QX_SERVICE(server_infos_input, qx::service::IxParameter, 0)
typedef std::shared_ptr<server_infos_input> server_infos_input_ptr;
class QX_SERVICE_DLL_EXPORT server_infos_output : public qx::service::IxParameter
{ public: QDateTime current_date_time; };
QX_REGISTER_HPP_QX_SERVICE(server_infos_output, qx::service::IxParameter, 0)
typedef std::shared_ptr<server_infos_output> server_infos_output_ptr;
typedef qx::service::QxService<server_infos_input, server_infos_output> server_infos_base_class;
class QX_SERVICE_DLL_EXPORT server_infos : public server_infos_base_class
{
public:
server_infos() : server_infos_base_class("server_infos") { ; }
virtual ~server_infos() { ; }
void get_current_date_time();
};
QX_REGISTER_HPP_QX_SERVICE(server_infos, qx::service::IxService, 0)
typedef std::shared_ptr<server_infos> server_infos_ptr;
#endif // _QX_SERVICE_SERVER_INFOS_H_
|
Le fichier server_infos.h poss�de trois classes :
- server_infos_input : h�rite de qx::service::IxParameter et correspond aux
param�tres d'entr�e du service (demande du client). Notre service de test n'a pas besoin de
param�tres en entr�e, donc cette classe ne contient aucune propri�t� ;
- server_infos_output : h�rite de qx::service::IxParameter et correspond aux
param�tres de sortie du service (r�ponse du serveur). Cette classe contient une seule propri�t�, la
date-heure courante du serveur (QDateTime current_date_time) ;
- server_infos : h�rite de qx::service::QxService<INPUT, OUTPUT> et
contient la liste des services disponibles : une seule m�thode pour r�cup�rer la date-heure
courante du serveur.
Ces trois classes doivent �tre enregistr�es dans le contexte QxOrm, de la m�me fa�on qu'une classe
persistante (voir le tutoriel qxBlog).
C'est pourquoi nous utilisons la macro QX_REGISTER_HPP_QX_SERVICE pour ces trois classes.
De plus, pour simplifier l'�criture des pointeurs, la gestion de la m�moire et �viter les probl�mes de
fuites m�moires, nous utilisons les pointeurs intelligents de la biblioth�que standard :
std::shared_ptr.
Le module QxService travaille essentiellement avec des pointeurs intelligents, c'est pourquoi il
est fortement conseill� de cr�er les typedef correspondants, par exemple :
typedef std::shared_ptr<server_infos_input> server_infos_input_ptr;.
typedef std::shared_ptr<server_infos_output> server_infos_output_ptr;.
typedef std::shared_ptr<server_infos> server_infos_ptr;.
Enfin, le constructeur du service doit indiquer en param�tre le nom de la classe sous forme de cha�ne de
caract�res : ceci est indispensable pour le moteur d'introspection de QxOrm pour pouvoir
instancier dynamiquement les services correspondant aux requ�tes des clients.
2.3- Description du fichier server_infos.cpp
#include "../../include/precompiled.h"
#include "../../include/service/server_infos.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_SERVICE(server_infos_input)
QX_REGISTER_CPP_QX_SERVICE(server_infos_output)
QX_REGISTER_CPP_QX_SERVICE(server_infos)
namespace qx {
template <> void register_class(QxClass<server_infos_input> & t)
{ Q_UNUSED(t); }
template <> void register_class(QxClass<server_infos_output> & t)
{ t.data(& server_infos_output::current_date_time, "current_date_time"); }
template <> void register_class(QxClass<server_infos> & t)
{ t.fct_0<void>(& server_infos::get_current_date_time, "get_current_date_time"); }
}
#ifdef _QX_SERVICE_MODE_CLIENT
void server_infos::get_current_date_time()
{ qx::service::execute_client(this, "get_current_date_time"); }
#else // _QX_SERVICE_MODE_CLIENT
void server_infos::get_current_date_time()
{
server_infos_output_ptr output = server_infos_output_ptr(new server_infos_output());
output->current_date_time = QDateTime::currentDateTime();
setOutputParameter(output);
setMessageReturn(true);
}
#endif // _QX_SERVICE_MODE_CLIENT
|
Le fichier server_infos.cpp contient l'impl�mentation du service pour le mode client et le mode
serveur : c'est la macro _QX_SERVICE_MODE_CLIENT qui fait la distinction entre client et serveur
au moment de la compilation du projet.
La macro QX_REGISTER_CPP_QX_SERVICE permet d'enregistrer les trois classes dans le contexte QxOrm,
de la m�me fa�on qu'une classe persistante (voir le tutoriel qxBlog).
Ensuite, nous �crivons la m�thode de mapping void qx::register_class(...) pour les trois classes
du service :
- Les deux classes de param�tres enregistrent les propri�t�s utilis�es pour effectuer une demande du
client (aucune pour notre service de test), et les propri�t�s qui seront renvoy�es pour la r�ponse
du serveur (date-heure courante : t.data(& server_infos_output::current_date_time,
"current_date_time");) ;
- La classe service doit enregistrer la liste des m�thodes disponibles, dans notre cas :
t.fct_0<void>(& server_infos::get_current_date_time, "get_current_date_time");.
Remarque : toutes les m�thodes de type service doivent avoir la m�me signature : pas de valeur
de retour, et pas d'argument (par exemple : void my_service()).
En effet, dans un service, les param�tres d'entr�e sont disponibles par la m�thode
getInputParameter() (de type server_infos_input_ptr dans notre exemple).
Les param�tres de sortie peuvent �tre valoris�s par la m�thode setOutputParameter() (de type
server_infos_output_ptr dans notre exemple).
Une valeur de retour de type qx_bool permet d'indiquer que la transaction s'est d�roul�e
normalement, ou bien qu'une erreur quelconque est survenue (avec libell� et code de l'erreur).
Il est tr�s important d'�crire setMessageReturn(true); � la fin de chaque m�thode service pour
indiquer que tout s'est bien d�roul�.
La derni�re partie de notre fichier contient l'impl�mentation de la m�thode
server_infos::get_current_date_time() pour le mode client et serveur :
- Pour le mode client, le code est tr�s simple et sera le m�me pour tous les services :
qx::service::execute_client(this, "get_current_date_time"); ;
- Pour le mode serveur, notre service de test est tr�s simple : on valorise la date-heure courante,
on la transf�re dans les param�tres de sortie, puis on indique que la transaction s'est d�roul�e
sans aucune erreur.
2.4- Ecriture du second service : op�rations avec une classe persistante
Le projet qxService contient un second exemple de service plus complet avec une classe persistante
(classe user), et des actions sur une base de donn�es (SELECT, INSERT, UPDATE, DELETE,
etc.).
Ce deuxi�me exemple fait transiter sur le r�seau des structures complexes : pointeurs,
pointeurs intelligents, collections, crit�res de recherche, etc.
Nous ne d�taillerons pas ce second service dans le tutoriel, le principe �tant identique au premier
service :
#include "../../include/precompiled.h"
#include "../../include/service/user_service.h"
#include "../../include/dao/user_manager.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_SERVICE(user_service_input)
QX_REGISTER_CPP_QX_SERVICE(user_service_output)
QX_REGISTER_CPP_QX_SERVICE(user_service)
namespace qx {
template <> void register_class(QxClass<user_service_input> & t)
{
t.data(& user_service_input::id, "id");
t.data(& user_service_input::user, "user");
t.data(& user_service_input::criteria, "criteria");
}
template <> void register_class(QxClass<user_service_output> & t)
{
t.data(& user_service_output::user, "user");
t.data(& user_service_output::list_of_users, "list_of_users");
}
template <> void register_class(QxClass<user_service> & t)
{
t.fct_0<void>(& user_service::insert, "insert");
t.fct_0<void>(& user_service::update, "update");
t.fct_0<void>(& user_service::remove, "remove");
t.fct_0<void>(& user_service::remove_all, "remove_all");
t.fct_0<void>(& user_service::fetch_by_id, "fetch_by_id");
t.fct_0<void>(& user_service::fetch_all, "fetch_all");
t.fct_0<void>(& user_service::get_by_criteria, "get_by_criteria");
}
}
#ifdef _QX_SERVICE_MODE_CLIENT
void user_service::insert() { qx::service::execute_client(this, "insert"); }
void user_service::update() { qx::service::execute_client(this, "update"); }
void user_service::remove() { qx::service::execute_client(this, "remove"); }
void user_service::remove_all() { qx::service::execute_client(this, "remove_all"); }
void user_service::fetch_by_id() { qx::service::execute_client(this, "fetch_by_id"); }
void user_service::fetch_all() { qx::service::execute_client(this, "fetch_all"); }
void user_service::get_by_criteria() { qx::service::execute_client(this, "get_by_criteria"); }
#else // _QX_SERVICE_MODE_CLIENT
void user_service::insert()
{
user_service_input_ptr input = getInputParameter();
if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::insert()'"); return; }
QSqlError err = user_manager().insert(input->user);
if (err.isValid()) { setMessageReturn(0, err.text()); return; }
user_service_output_ptr output = user_service_output_ptr(new user_service_output());
output->user = input->user;
setOutputParameter(output);
setMessageReturn(true);
}
void user_service::update()
{
user_service_input_ptr input = getInputParameter();
if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::update()'"); return; }
QSqlError err = user_manager().update(input->user);
if (err.isValid()) { setMessageReturn(0, err.text()); }
else { setMessageReturn(true); }
}
void user_service::remove()
{
user_service_input_ptr input = getInputParameter();
if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::remove()'"); return; }
user_ptr user_tmp = user_ptr(new user());
user_tmp->id = input->id;
QSqlError err = user_manager().remove(user_tmp);
if (err.isValid()) { setMessageReturn(0, err.text()); }
else { setMessageReturn(true); }
}
void user_service::remove_all()
{
QSqlError err = user_manager().remove_all();
if (err.isValid()) { setMessageReturn(0, err.text()); }
else { setMessageReturn(true); }
}
void user_service::fetch_by_id()
{
user_service_input_ptr input = getInputParameter();
if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::fetch_by_id()'"); return; }
user_ptr user_output = user_ptr(new user());
user_output->id = input->id;
QSqlError err = user_manager().fetch_by_id(user_output);
if (err.isValid()) { setMessageReturn(0, err.text()); return; }
user_service_output_ptr output = user_service_output_ptr(new user_service_output());
output->user = user_output;
setOutputParameter(output);
setMessageReturn(true);
}
void user_service::fetch_all()
{
list_of_users_ptr list_of_users_output = list_of_users_ptr(new list_of_users());
QSqlError err = user_manager().fetch_all(list_of_users_output);
if (err.isValid()) { setMessageReturn(0, err.text()); return; }
user_service_output_ptr output = user_service_output_ptr(new user_service_output());
output->list_of_users = list_of_users_output;
setOutputParameter(output);
setMessageReturn(true);
}
void user_service::get_by_criteria()
{
user_service_input_ptr input = getInputParameter();
if (! input) { setMessageReturn(0, "invalid input parameter to call service 'user_service::get_by_criteria()'"); return; }
list_of_users_ptr list_of_users_output = list_of_users_ptr(new list_of_users());
QSqlError err = user_manager().get_by_criteria(input->criteria, list_of_users_output);
if (err.isValid()) { setMessageReturn(0, err.text()); return; }
user_service_output_ptr output = user_service_output_ptr(new user_service_output());
output->list_of_users = list_of_users_output;
setOutputParameter(output);
setMessageReturn(true);
}
#endif // _QX_SERVICE_MODE_CLIENT
|
A ce niveau du tutoriel, notre serveur d'applications C++ est termin� et propose plusieurs services.
Il reste � pr�sent � �crire le code client qui va appeler tous les services que nous avons mis en
place...
3- Cr�ation de l'interface cliente : qxClient
De la m�me fa�on que le projet qxServer, le projet qxClient poss�de une interface
utilisateur construite avec l'outil Qt Designer de la biblioth�que Qt.
Cette interface poss�de plusieurs boutons pour appeler l'ensemble des services propos�s par notre serveur
d'applications.
L'interface permet �galement d'indiquer une adresse ip et un num�ro de port pour se connecter au serveur
d'applications.
3.1- Description de la m�thode onClickBtnDateTime()
Comment r�cup�rer la date-heure courante du serveur d'applications ?
Voici le code qui s'ex�cute lorsque l'utilisateur clique sur le bouton Get Server DateTime :
void main_dlg::onClickBtnDateTime()
{ server_infos service;
service.get_current_date_time(); updateLastTransactionLog(service.getTransaction());
}
|
Comme vous pouvez le constater, la partie cliente n'a aucun code sp�cifique � �crire pour pouvoir
appeler un service.
Il suffit d'instancier un service, puis d'appeler la m�thode qui nous int�resse :
get_current_date_time().
La m�thode updateLastTransactionLog() permet d'afficher la derni�re transaction client-serveur (au
format XML ou JSON) ex�cut�e.
Si une erreur s'est produite, alors un message appara�t � l'�cran pour le signaler � l'utilisateur.
Pour savoir si le service s'est ex�cut� correctement, il faut utiliser la m�thode :
service.getMessageReturn(); (de type qx_bool qui peut contenir un code et un libell� d'une
�ventuelle erreur).
Enfin, pour r�cup�rer la r�ponse du serveur (donc sa date-heure courante), il faut utiliser la m�thode :
service.getOutputParameter(); (de type user_service_output_ptr).
3.2- Description de la m�thode onClickBtnDateTimeAsync()
void main_dlg::onClickBtnDateTimeAsync()
{
if (m_pDateTimeAsync) { qDebug("[QxOrm] '%s' transaction is already running", "server_infos::get_current_date_time"); return; } server_infos_ptr service = server_infos_ptr(new server_infos());
m_pDateTimeAsync.reset(new qx::service::QxClientAsync());
QObject::connect(m_pDateTimeAsync.get(), SIGNAL(finished()), this, SLOT(onDateTimeAsyncFinished()));
m_pDateTimeAsync->setService(service, "get_current_date_time");
m_pDateTimeAsync->start();
}
void main_dlg::onDateTimeAsyncFinished()
{
if (! m_pDateTimeAsync || ! m_pDateTimeAsync->getService()) { return; }
updateLastTransactionLog(m_pDateTimeAsync->getService()->getTransaction());
m_pDateTimeAsync.reset();
}
|
Ce second exemple correspond au bouton Get Server DateTime Async de l'interface utilisateur.
Il montre comment appeler un service de mani�re asynchrone, c'est-�-dire sans bloquer l'IHM en
attendant la r�ponse du serveur.
La biblioth�que QxOrm propose la classe qx::service::QxClientAsync pour simplifier les
appels asynchrones.
Le m�canisme des appels asynchrones avec le module QxService est tr�s simple :
- cr�ation d'une instance d'un service ;
- cr�ation d'une instance de type qx::service::QxClientAsync ;
- connexion � l'�v�nement finished (pour indiquer qu'une r�ponse du serveur vient d'arriver)
;
- passage de l'instance du service et de la m�thode � appeler (sous forme de chaine de caract�res) �
l'objet qx::service::QxClientAsync ;
- d�marrage de la transaction avec l'appel de la m�thode start().
3.3- Description de la m�thode onClickBtnAddUser()
void main_dlg::onClickBtnAddUser()
{ user_service_input_ptr input = user_service_input_ptr(new user_service_input());
input->user = fileUser(); user_service service;
service.setInputParameter(input);
service.insert(); user_ptr output = (service.isValidWithOutput() ? service.getOutputParameter()->user : user_ptr());
if (output) { fillUser(output); } updateLastTransactionLog(service.getTransaction());
}
|
Ce 3�me exemple correspond au bouton Add dans la section User transaction.
Il permet � l'utilisateur d'ajouter une nouvelle personne dans la base de donn�es.
Cet exemple nous montre comment passer une structure (classe user) en param�tre d'entr�e d'un
service.
La m�thode fileUser() permet de cr�er une instance de type user et de valoriser ses
propri�t�s en fonction des champs de l'IHM.
Cette instance est ensuite utilis�e comme param�tre d'entr�e de notre service.
Si la transaction s'est d�roul�e correctement, le param�tre de retour (r�ponse du serveur) contient lui
aussi une instance de type user avec le nouvel identifiant qui vient d'�tre ajout� en base de
donn�es.
On utilise alors la m�thode fillUser() pour mettre � jour l'interface utilisateur en fonction de
la r�ponse du serveur et afficher ainsi le nouvel identifiant.
3.4- Description de la m�thode onClickBtnGetAllUsers()
void main_dlg::onClickBtnGetAllUsers()
{ user_service service;
service.fetch_all(); list_of_users_ptr output = (service.isValidWithOutput() ? service.getOutputParameter()->list_of_users : list_of_users_ptr());
if (output) { QMessageBox::information(this, "qxClient - get all users", "database contains '" + QString::number(output->size()) + "' user(s)."); } updateLastTransactionLog(service.getTransaction());
}
|
Ce 4�me exemple correspond au bouton Get All de la section User transaction.
Il permet de r�cup�rer la liste de tous les user pr�sents dans la base de donn�es.
Le param�tre de retour est une liste fortement typ�e : il est possible d'utiliser les collections des
biblioth�ques stl, boost, Qt ou qx::QxCollection.
Le module QxService permet donc d'�changer des structures complexes entre client et
serveur.
A pr�sent, bon courage avec le module QxService... ;o)
|