Vous n'êtes pas identifié(e).
Qt 4: 4.8.4 - Qt 5: 5.0.2 - Qt Creator: 2.7.0 - Qt Installer: 1.3.0 - VS Qt 4: 1.1.11 - VS Qt 5: 1.2.1 - Monkey Studio: 1.9.0.4
beaucoup de gens, quand il doivent réaliser une application client - serveur
attaquent directement le problème en se demandant, par exemple:
"je veux envoyer ceci, comment fais-je ?"
... puis:
"je veux envoyer cela, comment fais-je ?"
...
ce qui mène souvent à des solutions dans lesquelles le code est compliqué, difficilement maintenable / évolutif.
tout d'abord, il est souvent utile de disposer de petits de debug simples
exemple: QMap initialisables et à usage de mise au point
dans la suite, le "protocol" désignera le "format des message" pour échange,
par opposition à leur contenu qu'on pourait appeler le "protocole applicatif"
si la première question qu'on se pose est: "de quoi ai-je besoin ?"
la réponse ne doit pas être:
- envoyer mon nom et mon mot de passe --> récupérer et traiter
- envoyer ... --> récupérer et traiter
mais plutôt:
- d'un protocole de transport / échange de données ( n'importe quoi )
- d'un client
- d'un serveur
- d'un point de connection
- de quelques définitions communes ( et variables )
ceci m'amène à définir une structure globale Tcp dans laquelle tout sera imbriqué:
l'idée d'imbriquer les structures / classes est de complexifier la structure pour en simplifier l'usage
je dis bien complexifier et non compliquer dans le même sens qu'un nombre qu'un nombre complexe n'est pas un nombre compliqué !
il nous faut définir le protocole.
pour faire simple, disons:
COMMAND DATA_SIZE DATA
une réponse à une commande pouvant être considérée elle même comme une commande
les classes qui gèreront cela seront:
- Tcp::Buffer
- Tcp::Buffer::Input
- Tcp::Buffer::Output
un intérêt d'avoir la classe Buffer abstraite est d'avoir un seul point pour les modifications communes,
par exemple: QDatasStream::setByteOrder, QDatasStream::setVersion ... tout ce qui doit être "symétrique"
les seuls points de gestion de la cohérence emission / réception sont:
coté Tcp::Buffer::Output
- void init()
- void send( CmdVal command )
coté Tcp::Buffer::Input
- bool msgRead()
- CmdVal command;
- DataSize dataSize;
pour ce qui est du "support" Socket 3 classes
- Tcp::Socket fournit la gestion de base
- Tcp::Socket::Client gère emission, réception, exécution coté client
- Tcp::Socket::Server gère emission, réception, exécution coté serveur
on peut constater qu'à ce stade, le traitement de chaque commande est simple
de même qu'il est facile d'en ajouter une nouvelle ou de modifier une qui existe
le point d'entrée, qui est vu de l'exterieur comme le server, que j'appèle Listener car "il ne fait qu'écouter" et créer une Socket qui est le serveur effectif
et enfin !!! un petit test minimum
l'idée de ce sujet n'est pas de fournir
"un truc tout cuit"
encore moins "un truc unviersel" qui conviendrait à tout le monde
encore moins "un modèle de programmation"
mais plutôt de faire sentir que si la finalité est de transporter des données qui nous intéressent,
on peut avoir avantage à attaquer du coté du moyen de transport de ces données sans se soucier d'elles a priori
ou (sic) (Rouletabille) regarder par le bon bout de la lorgnette
Hors ligne
si quelqu'un s'es intéressé au sujet (peut-être suis-je présomptueux)
il aura remarqué que le cas des commandes sans données n'est pas envisagé
la correction est facile
Hors ligne
IMPRESSIONNANT !
Excellent travail qui vas aider beaucoup de membres 
Sans la liberté de blâmer, il n'est point d'éloge flatteur; et qu'il n'y a que les petits hommes qui redoutent les petits écrits !!!
- Beaumarchais -
Hors ligne
Rome ne s'est pas faite en un jour !
recopier dans une nouvelle application un "modèle" existant (source + header),
changer les noms de fichier, les macros de protection des headers, les noms de structures
pour rester cohérent devient vite fastidieux et source d'erreur(s)
j'ai donc décidé de faire l'économie de la récuparation transport (struct Tcp::Buffer)
pour cela je l'ai "templatisé" avec pour paramètres:
- CV : type de la valeur de commande
- DS : type de la valeur data size
- S : type de la Socket associée
- NC : valeur indiquant une commande non initialisée
-->
- création de tcptransport.h
- suppression de tcp_buffer.h et tcp_buffer.cpp
les corrections apportées au reste sont minimes:
- tcp.h suppression de la déclaration de Tcp::Buffer (+ une petite réorganisation)
- tcp_socket.h ajout d'un typedef pour acces au nouveau Transport
et correction des directives #include
Hors ligne
j'en ai profité pour changer le nom de la structure Tcp --> TcpProtocol
sans rapport avec la modif précedente sinon "logique"
TcpTransport gère le protocole de transport
TcpProtocol gère le protocole applicatif
changé aussi les noms de fichier, macros de protection ... pour rester cohérent
travail beaucoup plus long et fastidieux que le précedent !!!
je donne les nouveaux listings en vrac
Hors ligne
pour faire encore l'économie de récupération de tcpprotocol_socket.h et tcpprotocol_socket.cpp,
il me faut rendre les classe Socket dérivables proprement
j'ai besoin que leur méthodes retournent un status, un bool par exemple
pour pouvoir écrire dans une classe fille ClientFille:
sans avoir à réimplémenter la base
encore une fois la modif est simple et localisée
pour bien faire les méthodes execute() devraient purement virtuelles pour forcer la réimplementation
purement virtuelles mais implémentée, c'est tout à fait légitime !!!
mais pour mon exemple, ce n'est pas encore possible
Hors ligne
on ne va pas toujours droit au but !
le quatrième paramtre du buffer de transport ne me satisfait pas
je le supprime
j'introduit une gestion par état
- attente de la commande
- attente de la taille des données
- attente des données
je "template" TcpTransport plutôt que TcpTransport::Buffer
une correction à apporter dans tcpprotocol_socket.h ... mais c'est évident !
Hors ligne
certains se sont sans doute demandés:
pourquoi avoir "templaté" la structure TcpTransport, les structures de TcpProtocol n'en utilisent qu'une instance ???
réponse:
pour prévoir la suite
j'avais pensé (quelle présomption !) "templater" de la même façon TcpProtocol, la solution simple
hélas !!!
Q_OBJECT ne supporte pas les classes template, je le savais
mais de plus moc ne supporte pas les classes QObject imbriquées dans une classe template, j'aurais du m'en douter
hiérarchie "imbrication" actuelle des casses de transport:
TcpTransport<CV,DS>
Buffer
Input
Output
hiérarchie "heritage" actuelle des casses de transport:
TcpTransport<CV,DS>
TcpTransport<CV,DS>::Buffer
Input
Output
je décide
- d'utiliser des fonctions de création d'instance des classes buffer utilisées
- de créer des classes interface non "templatées" organisées comme suit
hiérarchie "imbrication" des classes interface:
AbsTcpTransport
Buffer
Input
Output
hiérarchie "heritage" des classes interface:
AbsTcpTransport
AbsTcpTransport::Buffer
Input
Output
>>>> organisation complète:
hiérarchie "imbrication":
AbsTcpTransport
Buffer
Input
Output
TcpTransport<CV,DS>
Buffer
Input
Output
hiérarchie "heritage":
AbsTcpTransport
TcpTransport<CV,DS>
AbsTcpTransport::Buffer
TcpTransport<CV,DS>::Buffer
Input
TcpTransport<CV,DS>::Buffer::Input
Output
TcpTransport<CV,DS>::Buffer:Outnput
les modifications de tcptransport.h sont relativement importantes:
tcpprotocol.h se simplifie:
suppression des type concernant les commandes et la taille des données
tcpprotoclo.cpp
reste inchangé
toutes petites modifications dans tcpprotocol_socket.h et tcpprotocol_socket.cpp pour passer les fonctions de création des buffers
toutes petites modifications dans tcpprotocol_listener.h et tcpprotocol_listener.cpp pour passer les fonctions de création des buffers
et enfin le programme de test
Hors ligne

comment se protéger de l'emploi de deux protocoles de transport incompatibles
(sic)
Pour fabriquer une bombe "A", mes enfants, croyez moi, c'est vraiment de la tarte!
La question du détonateur s'résout en un quart d'heure, c'est de cell' qu'on écarte!
d'accord sur tout, sauf de l'écarter !!!
je vous laisse réfléchir !!!
Hors ligne
[curieux=on][mémoire=off]
Je connais cette citation sur la bombe A... mais plus moyen de situer...

[memoire=on] 
"L'informatique n'est pas plus la science des ordinateurs que l'astronomie n'est celle des télescopes." Michael R. Fellows et Ian Parberry
Si tu ne sais pas : demande, si tu sais : partage !
Hors ligne
Boris Vian : La java des bombes atomiques
Mon oncle, infâme bricoleur, fasait en amateur des bombes atomiques.
...
Hors ligne
[content=on]
Merci à toi et ta grande culture ! \o/
[content=pas tout à fait off][curieux=off]
Dernière modification par myrddin772 (29-04-2012 13:49:50)
"L'informatique n'est pas plus la science des ordinateurs que l'astronomie n'est celle des télescopes." Michael R. Fellows et Ian Parberry
Si tu ne sais pas : demande, si tu sais : partage !
Hors ligne
[content=pas tout à fait off]
pourquoi ???
Merci à toi et ta grande culture ! \o/
n'éxagérons rien, but, as my sister says, "il y a une chanson pour chaque occasion"
inutile de dire qu'elle chante mieux que moi.
Hors ligne
Zanor84 a écrit :
Pour ton serveur, utilise tu les thread ?Sinon un thread règle le problème, voila,
![]()
![]()
vachement péremptoire !!! sans avoir vu une ligne de code intéressante
d'une façon générale, les classe de QtNetwork sont asynchrones et ne nécessitent pas de thread
si certains ont essayé l'exemple précédent, ils ont pu constater que tout fonctionnait très bien sans thread
j'aurais pu ajouter: les threads sont "une vaste source d'emmerdements !!!" ... surtout avec les QTcpSocket
explications:
la création d'une QTcpSocket provoque la création d'un/plusieurs QSocketNotifier
ce/ces QSocketNotifier doit/doivent "être" dans le même thread que la QTcpSocket
impossible donc d'utiliser moveToThread avec une QTcpSocket
impossible, par exemple, d'écrire sur la socket sous-jascente à la QTcpSocket depuis un autre thread
--> communication par signal/slot impérative pour demander à la QTcpSocket d'écrire,
pas moyen d'appeler directement ses méthodes
résultat:
mais restons zen:
tout problème à une solution !!!
j'ai modifié les structures protocole de transport
- pour résoudre ce problème de façon simple
AbsTcpTransport::Buffer::Output dispose d'un mutex et de methode lock/unlock
l'émission des données se fait par signal/slot
- pour prévoir des évolutions éventuelles, eg: ajout d'une checksum ...
les deux buffer travaillent sur un QByteArray interne qui remonte
de AbsTcpTransport::Buffer::Output dans AbsTcpTransport::Buffer
- j'ai déplacé la structure des fonction de creation des buffers dans AbsTcpTransport::Buffer
juste pour la logique (perso)
nouvelle structure globale ci-dessous, sources ici chat.gz
Hors ligne
myrddin772 a écrit :[content=pas tout à fait off]
pourquoi ???
Parce que les quelques chansons de Vian que je connaisse me font (sou)rire alors, tant que je l'avais en tête, j'etais encore un peu content 
"L'informatique n'est pas plus la science des ordinateurs que l'astronomie n'est celle des télescopes." Michael R. Fellows et Ian Parberry
Si tu ne sais pas : demande, si tu sais : partage !
Hors ligne
il faut bien par une application: un petit chat par exemple
les structures s'inspireront de TcpProtocol::Listener précédentes, mais n'en hériteront pas, elles ne servaient qu'à la mise au point du principe
organisation des structures:
Chat // definitions communes
Listener // écoute (listen) et accepte les connection entrantes, crée threads et serveurs
Server // le serveur effectif
List // liste vérouillable de serveurs
Client // ça va de soi
Thread // ça va de soi
Widget // prévu pour un avenir "plus ou moins lointain"
questions:
c'est quoi ce Chat::Widget prévu pour un avenir "plus ou moins lointain" ?
il est vrai que: qui dit chat dit fenêtre à partir de laquelle on peut envoyer et recevoir des messages, mais ...
le principe que jai suivi jusqu'ici est "grosso modo":
pourquoi se soucier d'afficher les données échangées avant d'avoir le moyen de les échanger ?
pourquoi se soucier d'échanger des données avant d'avoir le moyen de controler leur transport ?
pourquoi se soucier d'envoyer des données sur un réseau avant d'avoir un réseau ?
heureusement, TCP existe déjà !
en bref, pourquoi mettre la charrue avant les boeufs ?
je développe donc, sur la base des classes de TcpProtocol, la partie réseau de mon chat, avec implémentation des threads
je valide le bon fonctionnement ( threads, mutex ...) à coup de qDebug() << ...
c'est souvent beaucoup plus simple que l'utilisation d'un debugger
sources chat1.gz
répartition dans les fichiers
PS: j'allais oublier le petit programme de test
Hors ligne
n'oublions pas que les objectifs
- dans l'écriture des classes TcpTranport::* est la simplification des classes TcpProtocol::*
- dans l'écriture des classes TcpProtocol::* est la simplification des classes Xxxx::* (en l'occurence, Xxxx, c'est Chat)
actuellement, il faut
- (1) réimplementer TcpProtocol::Socket::Client::execute() et TcpProtocol::Socket::Server::execute() pour pas grand chose
pour ajouter une commande (client -> serveur par exemple), il faut
- (2) ajouter son nom à l'enum des commandes
- (3) ajouter son nom au mapping de noms pour mise au point
- (4) ajouter la méthode d'envoi au client
- (5) ajouter la méthode d'exécution au serveur
- (6) ajouter l'appel à la méthode d'exécution dans Xxxx::Server::execute()
- (7)
ce que j'ai pu oublier
on peut remarquer que les méthodes d'exécution de commande ont toutes la même signature:
void execCommand();
avec un mapping de fonctions on doit pouvoir faire l'économie du points (1) et subséquemment du point (6)
on commence par valider le principe
modif dans tccprotocol_socket.h
modif dans tccprotocol_socket.cpp
les modifs sont simples
on lance le test et on constate que "tout baigne !"
mais on s'arrête là !
un peu de réflexion avant de tout casser !
je me contente d'appliquer le même traitement à bool TcpProtocol::Socket::Client
Hors ligne
cul de sac !!!
je ne peux pas appliquer la dernière modification pour simplifier comme je le voulais jusqu'au bout
tout au plus je mets en place la même chose dans le client et le serveur du chat
juste pour le plaisir de simplifier le "corps" des méthodes execute()
Hors ligne
un chat !!! il va bien finir par envoyer un message, quand même !!!
OUI !!!
jusque là les commandes échangées étaient simple:
- le client envoie la commande, le serveur l'exécute
ou
- le serveur envoie la commande, le client l'exécute
pour les messages, c'est un peu plus complexe
- le client envoie la commande, du genre MESSAGE TO_LIST MSG_TEXT
- le serveur reçoit, emet un signal
- le listener (qui centralise) envoie une commande du genre MESSAGE FROM MSG_TEXT à tous les clients adressés
on choisit comme convention que TO_LIST vide adressele message à tout les clients connectés
on adapte un peu le petit programme de test
je donne la nouvelle version ci-joint, c'est moins visuel que des extraits de code
mais sans doute plus commode pour ceux que cela intéresse
et de plus ma souris a "la tremblote" et je "pète les plombs" avec les copier/coller
notez que : toujours pas d'interface
notez aussi que même si c'est un peu plus complexe, cela se fait quand même assez facilement
Hors ligne
un dernier test avant le gui
pour l'instant, tout fonctionne dans un même process, ça me gêne !
j'adapte un peu la structure de test
et le main
je lance le test,
tout baigne !
Hors ligne
les attentifs vont dire :
tout baigne, tout baigne, il va un peu vite !
les clients déjà connectés sont informés de l'arrivée d'un nouveau
mais les nouveaux ne savent pas qui est déja connecté
je m'empresse de rectifier le tir :
je relance le test, tout a l'air de fonctionner
mais une fois de temps en temps un client reçoit deux fois l'information "nouveau client xxxx"
une fois de temps en temps >>>> délicat à diagnostiquer, mais on finit par trouver : le serveur est nommé trop tôt et de plus dans un autre thread
délicat à diagnostiquer, mais correction simple:
je relance le test, tout a l'air de fonctionner
Hors ligne
à ce stade, j'introduis dans Chat une structure Chat::Message
implémentation
à la compilation, ça fonctionne
je voulais montrer l'utilisation, mais mes archives ont un petit trou !!!
l'intérêt est que si mes messages évoluent, tout ce qui concernent les prototypes de signaux/slots n'auront pas à changer
Hors ligne
je suis comme beaucoup de gens (sinon tout le monde !) j'ai voulu avoir un résultat tangible trop vite !
l'utilisation de la structure précédente est plus laborieuse que prévue !
pas difficile, mais la circulation d'un message est complexe, elle intervient souvent et il ne faut rien oublier, :
- le client envoie : "un peu de texte" adressé à ... : structure Chat::Message::To
- le serveur reçoit et s'empresse d'ajouter l'origine ; structure Chat::Message::FromTo et "émet" un signal
- le listener demande à tous les serveurs destinataires d'envoyer "un peu de texte" de ... : structure Chat::Message::From
- les clients reçoivent "un peu de texte" de ...
dans mes structures, il manquait le passage de Chat::Message::FromTo à Chat::Message::From
je ne montre pas l'utilisation ce serait trop fastidieux à rédiger !!!
la prochaine étape sera un gui minimum !!!
certains impatients diront:
Il était temps !!!
Hors ligne
comme tout me semble bien fonctionner je passe à l'étape finale: développement du "gui"
il sera "des plus rustiques" : un petit QWidget composé comme suit:
- un QPlainTextEdit (RO) d'affichage du fil du chat : ChatStreamWidget
- un QPlainTextEdit (RO) d'affichage des autres chatte(u)rs connectés : ChatUsersWidget
- un QWidget de saisie/envoi de message : ChatMessageWidget composé comme suit:
-- QPlainTextEdit (RW) de saisie du message
-- un QPushButton d'envoi
mais permettra de visualiser le bon fonctionnement plus commodément qu'avec les qDebug()
certains remarqueront que je n'utilise pas l'imbrication des structures pour les widgets
la raison en est que le système de feuille de style Qt ne le supporte pas !!!
je joint les sources complets : chat3.gz
Hors ligne
Point final coté développement !!!
Ouf !!!
C'avait démarré dans les "trucs et astuces"
en "réponse indirecte" à une question posée
et à des trucs tordus ... (à mon humble avis) ... (pas si humble que ça en fait)
et ça s'est transformé en une espèce de tutoriel, d'où le déplacement
Pour clore, quelques petites remarques
Vous l'aurez constaté, je suis un "maniaque de l'alignement"
je trouve que ça participe beaucoup à la lisibilité
vous allez me dire: mais c'est pas aligné !
J'utilise des tabulations à 4 caractères
je mets des commentaires "bizarres"
c'est que j'utilise vim et le folding par la méthode des markers + une "customization" de vim
@babaOrums
Quelques remarques plus intéressantes
trop de gens ont tendance à réduire C++ à la POO, la structure à l'objet, mais c'est beaucoup plus que cela
- l'imbrication des structures est déjà une forme d'héritage (au sens structurel, pas objet), un petit exemple:
A::Enum est privé à A, mais est accessible directement par A::B
si l'on voulais "sortir B de A, il faudrait
- (1) rendre A::Enum public ou (2) faire de B une classe amie de A
- dans B écrire B() : x( A::V0 ) {}
soit
- une sécurité affaiblie si l'on choisit la solution (1) : A::Enum devient accessible par tout le monde
- une écriture plus compliquée dans le créateur de B
pour ceux qui ont utilisé les classes QDesignerXxxxYyyyZzzzAaaaBbbb,
imaginez le bonheur potentiel !
- un tout petit bémol lié au signaux/slots de Qt qui fonctionnent avec la chaine de caractère qui nomme un type d'argument
alors que pour une méthode classique j'ai pu écrire par exemple
pour un signal/slot il faut employer le nom de classe complètement préfixé
- le C++ c'est encore la structure vide, magnifique, sublime invention
le namespace c'est bien, le namespace c'est génial
le namespace c'est extensible et je ne veux pas que mon Chat le soit
j'en fais une structure vide au lieu d'un namespace
autre exemple
TcpProtocol gère des commandes :
- HANDSHAKE
- BAD_PROTOCOL
...
des status
SUCCESS
BAD_PROTOCOL // pourquoi le nommer autrement
...
j'introduis des structures vides autour des enums correspondant
et là j'ajoute : je hais le using namespace xxxx;
la structure vide permet de déclarer fonctions locales (pour les nostalgiques de Pascal entre autres)
elle est même instanciable !!!
Il est possible que certaines choses me semblent légales parce que mon compilateur les permet
si quelqu'un a un contre exemple, je suis ouvert à toute remarque
-
un petit mot à propos de mes amis
on voit un peu partout:
j'aime pas les 'friend'
les 'friend' son à proscrire
...
je réponds:
conneries !!!
et je justifie quand même un peu avec un exemple
imaginons une structure
dans certaines circonstances on peut avoir d'un d'un point "restreint", eg: coordonnés d'une case sur un échiquier, ...
on adapte
on veut pouvoir sérialiser, eg: pour pouvoir interrompre une partie puis la reprendre
il nous faut des opérateurs de "streaming" et pour des raisons "techniques"
ils ne peuvent pas être membres de notre classe Point (ils sont évalués de gauche à droite)
on a donc besoin de "friend operators"
il faut considérer que ces opérateurs font partie intégrante de l'interface de la structure Point
et non qu'ils constituent un défaut d'encapsulation
-
les "setters / getters" c'est bien ... si on en a vraiment besoin !!!
ceci est une connerie (excusez la vulgarité)
ça empêche un code simple
qui doit être remplacé par un autre plus complexe
sans rien gagner : x et y, quoique déclarés privés, sont de fait publiques du fait de l'existence du "setter"
tout ce qui rend l'utilisation finale plus complexe devrait être justifié par un réel besoin !!!
et Qt, me direz vous, quid de tous ses "setters / getters"
Qt en a besoin pour le système des "object's properties", c'est donc (au moins en grande partie) justifié !!!
Hors ligne
| Liens interne à QtFR | Les partenaires de QtFR | Liens pour les sites officiels Qt | Liens sur les ressources Qt |
|---|---|---|---|
| Communauté QtFR sur Google+ | Monkey Studio | Télécharger Qt | Notre tutoriel |
| QtFR sur Twitter | ZeGrapher | Site officiel Qt | Documentation Qt |
| IRC box | Qt Blog | Qt Centre | |
| Galeries | Qt Wiki | Qt Apps | |
| Contactez-nous | Qt Quarterly |