Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Du 22 au 24 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !
  1. Livres et vidéos
  2. Maîtrisez Qt
  3. Qt Network
Extrait - Maîtrisez Qt Guide de développement d'applications professionnelles (3e édition)
Extraits du livre
Maîtrisez Qt Guide de développement d'applications professionnelles (3e édition)
1 avis
Revenir à la page d'achat du livre

Qt Network

Objectifs

Le module Qt Network contient plusieurs API de haut niveau permettant de communiquer avec des sockets TCP et UDP. Les interactions avec les hôtes distants se font de manière asynchrone ou synchrone si le développeur le souhaite.

La plupart des fonctionnalités réseau applicatives sont regroupées au sein de la classe QNetworkAccessManager. Cette classe très riche offre de nombreuses possibilités pour communiquer, via le protocole TCP, avec des services distants, et ce de manière asynchrone. Le développeur peut aussi créer des services réseau à l’aide des classes spécialisées QTcpServer et QudpSocket

Le principal ajout de Qt 6 porte sur les objets distants (remote objects) qui est une technologie de RPC (Remote Procedure Call) propre à Qt, s’appuyant sur les signaux et les slots. Nous étudierons cette technologie dans le détail.

Intégration

Le module Qt Network est intégré à votre projet grâce à l’ajout du mot-clé network dans la déclaration QT de votre fichier .pro.

QT += network 

Accéder au réseau

1. Portée de Qt Network

Qt Network intervient sur les couches 3 à 7 du modèle OSI.

images/07_SC_01.png

Son utilisation est liée, la plupart du temps, au téléchargement de fichiers sur un serveur FTP, à la connexion à un serveur web HTTP ou de courrier. Tous les protocoles situés dans la couche 7 (application) sont exploitables par les programmes Qt. En effet, Qt nous fournit des API pour échanger des données avec d’autres machines en utilisant les protocoles TCP et UDP. Les échanges peuvent aussi être sécurisés grâce à l’utilisation de certificats SSL pour le chiffrement des communications (HTTPs ou FTPs par exemple).

Qt ne propose aucune implémentation des protocoles de la couche 7, en dehors de DNS et FTP. Vous ne trouverez pas d’API qui permette d’échanger des fichiers avec le protocole CIFS par exemple.

Voici les possibilités offertes par Qt Network :

  • Couche 3 : adresse IPv4 et IPv6.

  • Couche 4 : socket client et serveur TCP, socket UDP.

  • Couche 5 : proxy Socks 5, certificats SSL.

  • Couche 7 : résolution DNS, client HTTP, client FTP.

2. Appel bloquant vs non bloquant

La particularité des échanges de données au travers d’un réseau, en particulier lorsque ce réseau est Internet, est qu’ils peuvent être interrompus...

Sockets

Les classes de socket QTcpSocket et QUdpSocket héritent toutes les deux de la classe abstraite QAbstractSocket qui définit la quasi-totalité des fonctions dont elles disposent. Cette dernière hérite elle de QIODevice, tout comme QFile. La gestion d’un socket s’effectue donc de la même manière que celle d’un fichier et les flux de données se gèrent aussi de la même manière. Tout ce qui est possible avec un fichier l’est avec un socket.

Les exemples que nous fournissons sont tous orientés asynchrone. Nous utilisons les signaux, les slots et l’héritage de QObject.

1. Client TCP

Un socket TCP est généralement déclaré comme attribut de la classe qui le contrôle. En effet, sa durée de vie ne peut pas être déterminée, il convient donc de l’instancier dynamiquement en lui passant en paramètre l’instance du contrôleur.

public : 
 
   Controleur::Controleur(QObject* parent = nullptr)  
   : QObject(parent), socket_(new QTcpSocket(this))  { } 
 
private : 
   QTcpSocket* socket_ ;  

La communication TCP s’effectue en mode connecté, cela signifie que l’émission de données est acquittée par le serveur. Si une erreur survient lors de la transmission d’une partie des données, celles-ci sont réémises automatiquement.

Un échange de données s’effectue en plusieurs étapes, ordonnancées :

1)

Ouverture d’une connexion avec un hôte.

2)

Réception d’un signal de connexion.

3)

Envoi d’une requête.

4)

Réception d’un signal d’écriture des données.

5)

Réception d’un signal de mise à disposition de données dans le tampon.

6)

Lecture des données reçues jusqu’au vidage du tampon.

a. Connexion avec un hôte

La connexion à un serveur TCP se fait grâce à la fonction connectToHost(). Celle-ci peut utiliser aussi bien le nom que l’adresse IP de la machine. Le nom doit pouvoir être résolu par un serveur DNS (y compris mDNS), les noms NetBIOS ne sont pas utilisables.

socket_->connectToHost("www.editions-eni.fr"...

Internet

1. Résolution DNS

La résolution DNS se fait automatiquement lorsque vous appelez les fonctions connectToHost() et bindToHost() en fournissant un nom d’hôte dans le paramètre du type QHostAddress. Qt gère la résolution DNS de manière asynchrone, émet un signal lorsque celle-ci échoue et met à jour le statut du socket avec l’état courant : QAbstractSocket::HostLookupState pendant la résolution DNS et QAbstractSocket::ConnectingState lorsque le socket est connecté.

Qt propose aussi d’effectuer des requêtes sur un serveur DNS en utilisant une API simple : QDnsLookup.

Un enregistrement DNS contient non seulement la ou les adresses IP pour les serveurs web d’un domaine, mais aussi la ou les adresses des serveurs d’e-mail, de services distribués, et un certain nombre d’enregistrements de type texte relativement complexes informant parfois sur les différents services proposés par la machine ou le domaine sur lequel porte la requête.

Le code source est disponible dans le projet Resolution_DNS.

Pour en savoir plus sur le protocole DNS, consultez le document de référence RFC 6195.

a. Résoudre un nom de machine

La résolution d’un nom de machine se fait en deux temps :

1.

Envoi de la requête.

2.

Réception asynchrone de la réponse.

Pour effectuer la requête DNS utilisez la fonction setName(QString&) pour définir le nom de la machine dont vous souhaitez connaître l’adresse IP, puis appelez la fonction lookup().

dns_->setName("www.editions-eni.fr") ; 
dns_->lookup() ; 

Vous pouvez spécifier le type de champ DNS sur lequel vous souhaitez effectuer votre requête grâce à la fonction setType(). Si vous n’appelez pas cette fonction, c’est le champ A qui sera utilisé et la résolution se fera sur une IPv4.

dns_->setType(QDnsLookup::AAAA) ; //La résolution se fera sur IPv6 

La résolution se fera sur un des enregistrements de type A, AAAA, NS, MX, SRV, PTR ou TXT. Pour une adresse IPv4 vous utilisez le type A et pour une IPv6 le type AAAA. Par défaut c’est le type A qui est utilisé.

Une fois effectuée, la résolution DNS aboutira à l’émission d’un signal...

Les interfaces réseau

Qt permet aussi de gérer les interfaces réseau et plus généralement la connectivité du point de vue du système. Cela comprend notamment la gestion des interfaces réseau.

La liste des interfaces réseau, qu’elles soient virtuelles, locales, physiques selon le protocole Ethernet, ou distantes en point à point, est gérée par la classe QNetworkConfigurationManager. Pour connaître la liste des interfaces réseau disponibles, il suffit d’appeler la fonction allConfigurations() qui retourne une instance de QList<QNetworkConfiguration>. La connexion par défaut, c’est-à-dire le plus souvent la connexion active, est obtenue grâce à un appel à la fonction defaultConfiguration().

Le code source est disponible dans le projet QNetworkConfigurationManager.

La classe QNetworkConfiguration fournit des informations variées sur l’état (active ou inactive) et le type de connexion (LAN, WLAN, 2G/3G/4G, Bluetooth, etc.).

QNetworkConfigurationManager ncm; 
QList<QNetworkConfiguration> confs = ncm.allConfigurations(); 
QListIterator<QNetworkConfiguration> it(confs); 
 
while(it.hasNext()) 
{ 
    QNetworkConfiguration conf = it.next(); 
    qDebug() << "Configuration : " << conf.name(); ...

Les objets distants

Derrière ce terme se cache tout un monde de possibilités pour les développeurs d’applications. Même s’il n’est pas indispensable d’avoir une connexion réseau pour réaliser des appels distants entre deux applications sur un unique appareil, le principe même de ce type d’exécution est fortement lié aux principes des réseaux, c’est la raison pour laquelle nous avons choisi d’intégrer cet ajout dans le chapitre sur Qt Network.

Le mécanisme des objets distants est proche des appels de fonctions distants (RPC) qui est très ancien et qui a fait l’objet de différentes implémentations. En Java il s’agit de RMI, en Web il s’agit de XML-RPC ou plus généralement des webservices. Ce qu’on souhaite faire, au départ, avec des appels distants est d’alléger le client pour centraliser certaines fonctions sur un serveur. C’est le principe du client léger.

Il est proche mais pas tout à fait équivalent. Il est décrit par les ingénieurs de Qt comme de la communication interprocessus (IPC) car il étend les fonctionnalités de Qt pour les rendre utilisables à distance.

Ce type de mécanismes sont à la base d’un nouveau paradigme d’architecture logicielle : les microservices. L’idée...

Mécanismes communs

Les différences sont mineures entre les deux types de connexion, sur le plan de l’architecture logicielle et du code. C’est un peu comme de brancher un câble Ethernet directement sur un PC ou bien sur un switch réseau.

La première étape est la création du contrat d’interface entre les nœuds. Cela passe par la création d’un fichier portant l’extension .rep.

 Cliquez sur le menu Fichier puis choisissez New File... - General - Empty File.

 Nommez le fichier Slider.rep.

 Saisissez le contrat d’interface dans ce nouveau fichier :

class Slider 
{ 
 PROP(int valeur=0); 
 SLOT(server_slot(int valeurRecue)); 
} 

Nous venons de déclarer une classe en utilisant un certain nombre de macros de Qt. En effet, ce fichier sera traité et compilé par l’utilitaire repc (Qt Remote Objects Compiler) et transformé en une classe avec des propriétés et des fonctions. 

La macro PROP a le même effet que la macro Q_PROPERTY, elle permet de déclarer une propriété nommée valeur avec ses accesseurs et son signal.

La macro SLOT sert à créer un slot qui sera appelé du côté du client lorsque le signal aura été reçu. Dans notre exemple, nous provoquerons un acquittement de la réception du signal afin d’informer le serveur que le signal a bien été reçu.

Dans le fichier .pro du projet, vous devez à présent ajouter plusieurs déclarations :

QT += remoteobjects 

ainsi que :

REPC_SOURCE = Slider.rep 
REPC_REPLICA = Slider.rep 

Ces deux dernières déclarations permettent de déclarer le contrat d’interface de la Source et du Replica. Bien entendu, si vous séparez le client et le serveur en plusieurs applications, vous effectuerez ces déclarations dans des projets séparés. REPC_SOURCE n’est nécessaire que pour le serveur et REPC_REPLICA n’est nécessaire que pour les réplicas.

Ensuite, exécutez qmake et compilez le projet. Deux nouveaux fichiers ont été créés par l’utilitaire...

Connexion via un concentrateur

Dans l’hypothèse où vous auriez plusieurs Replicas à connecter à une Source, vous devrez créer un Registry, sorte de concentrateur permettant de copier et rediriger les signaux entre les différents nœuds.

La représentation graphique de ce type de connexion est :

images/06EP05N1.png

La seule différence avec la connexion point à point que nous avons vue précédemment se situe dans la configuration des nœuds.

Reprenons le code du contrôleur EcranServeur et ajoutons quelques lignes pour instancier le Registry :

EcranServeur.h

private: 
 ...  
 QRemoteObjectRegistryHost *registryNode_ = nullptr; 

EcranServeur.cpp :

EcranServeur::EcranServeur(QWidget *parent) : QWidget(parent)  
 , registryNode_(  
   new QRemoteObjectRegistryHost(  
     QUrl("local:registry")  
     , this)  
  )  
 , srcNode_(new QRemoteObjectHost(  
      QUrl("local:slider")  
      , QUrl("local:registry")  
      , QRemoteObjectHost::BuiltInSchemasOnly  
      , this  
  ))  
 
{ ... } 

Nous avons donc...

Communications TCP

Les communications que nous avons faites jusqu’à présent utilisaient un protocole « local », probablement une mémoire partagée, cela n’est pas précisé dans la documentation de Qt. Il est possible bien entendu de réaliser ces opérations sur un réseau, même routé.

Pour utiliser un protocole réseau routé, comme TCP par exemple, vous devez simplement modifier l’URL fournie aux nœuds.

Par exemple, pour connecter le Replica au serveur sur le port 26636 de l’interface locale de la machine, utilisez le code suivant :

remoteNode_->connectToNode(QUrl("tcp://127.0.0.1:26636")); 

Versions du protocole

La mécanique des objets distants utilise un protocole interne pour fonctionner, celui-ci est versionné et évolue en fonction des version de Qt. Le même protocole doit être utilisé par toutes les parties pour que les communications fonctionnent.

Les versions actuellement connues sont :

  • 1.2 dans la version 5.12.0 de Qt

  • 1.3 dans la version 5.12.4 de Qt

  • 2.0 depuis la version 6.2.0 de Qt.

La maturité de la technologie est suffisante aujourd’hui pour espérer que le protocole évoluera peu à l’avenir.

Objets distants en QML

Pour utiliser les objets distants en QML, c’est-à-dire émettre et recevoir des signaux distants, la mise en œuvre est identique à ce que nous avons vu précédemment. Vous devez créer une interface dans un fichier .rep, compiler ce fichier et l’exposer dans Qt Quick.

En reprenant l’exemple précédent, l’exposition à Qt Quick se fera ainsi :

#include "rep_Slider_replica.h"  
 
... 
 
qmlRegisterType<SliderReplica>("net.alefbet",1,0,"SliderReplica") 

Ensuite, il faut définir le nœud à connecter dans le code QML :

import QtRemoteObjects 
import net.alefbet 1.0 
 
Item { 
 SliderReplica {  
   id: client 
   node: Node { registryUrl: "tcp://127.0.0.1:26636" } 
 } 
}