I. L'article original▲
Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.
Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.
Cet article est une traduction de l'article original écrit par Trenton Schulz paru dans la Qt Quarterly Issue 23.
Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(--ies), inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.
II. Qui est qui : Zeroconf, Bonjour, Avahi▲
Zeroconf doit résoudre le problème de recherche de services et de connexion à ceux-ci. Plutôt que de devoir connaitre l'adresse IP d'une machine et le numéro de port d'un service, une machine offre un service qui annonce simplement qu'elle offre ce service. Les clients qui veulent utiliser un service répondent à toutes les machines qui l'offrent et ensuite l'utilisateur décide sur quelle machine il veut se connecter.
Traditionnellement, il faut pouvoir être sûr que chaque machine est configurée correctement sur le réseau. Zeroconf s'occupe de tout ceci sur le réseau local. Beaucoup de nouveaux équipements, comme les imprimantes disposant du support réseau et les routeurs sans fil, arrivent avec leur propre serveur Zeroconf, pour configuration facile du réseau. Sur Mac OS X, la plupart des applications peuvent utiliser Bonjour pour offrir les services, comme le serveur SSH, les partages iTunes ou la disponibilité d'iChat. Zeroconf est une voie puissante pour simplifier vos applications et des implémentations sont disponibles pour la plupart des systèmes d'exploitation.
Pour cet article, nous allons utiliser l'implémentation d'Apple qui s'appelle Bonjour. Bonjour consiste en un démon mDnsResponder et une bibliothèque de découverte multicast pour s'interfacer avec le démon. Depuis que les nouvelles versions de Bonjour sortent avec une licence Open Source controversée, le démon courant est sorti sous une licence standard Apache 2.0, tandis que la bibliothèque est dans une licence BSD libérale à « trois clauses ».
Si vous avez Mac OS X, vous avez déjà Bonjour installé. Sinon, vous pouvez télécharger le code source depuis le site Web d'Apple (http://developer.apple.com/Bonjour), compiler et installer Bonjour en relativement peu de temps. La plupart des distributions Linux arrivent avec Avahi, une implémentation LGPL de Zeroconf avec une API compatible avec Bonjour. Les exemples présentés ici ont été testés pour fonctionner tant avec l'implémentation d'Apple que son pendant Avahi compatible.
La découverte de services se déroule en trois étapes : l'enregistrement du service, le parcours pour obtenir les services disponibles et la résolution des services pour une adresse IP donnée. Un serveur peut enregistrer ces services auprès du démon Bonjour. Les clients peuvent parcourir les services pour obtenir une liste à fournir aux utilisateurs. Finalement, lorsqu'il est temps de se connecter à un service, le client pourra déterminer le service sélectionné avec une adresse IP et un port et ensuite se connecter au service fourni en utilisant TCP/IP.
III. Sauvegarder une entrée Bonjour▲
Pour commencer à utiliser Bonjour, il faut créer une simple classe qui contiendra toutes les informations contenues dans une entrée Bonjour :
class
BonjourRecord
{
public
:
BonjourRecord() {}
BonjourRecord(const
QString
&
name,
const
QString
&
regType,
const
QString
&
domain)
:
serviceName(name), registeredType(regType),
replyDomain(domain) {}
BonjourRecord(const
char
*
name, const
char
*
regType,
const
char
*
domain) {
serviceName =
QString
::
fromUtf8(name);
registeredType =
QString
::
fromUtf8(regType);
replyDomain =
QString
::
fromUtf8(domain);
}
QString
serviceName;
QString
registeredType;
QString
replyDomain;
bool
operator
==
(const
BonjourRecord &
other) const
{
return
serviceName ==
other.serviceName
&&
registeredType ==
other.registeredType
&&
replyDomain ==
other.replyDomain;
}
}
;
Q_DECLARE_METATYPE
(BonjourRecord)
La classe BonjourRecord est une simple structure de données avec des membres publics pour le nom du service, le type de service ainsi le domaine de repli (que nous n'utiliserons pas ici). Cette classe est déclarée comme un métatype Qt, on pourra donc enregistrer un objet BonjourRecord dans un objet QVariant ou en émettre un dans un signal interthread.
De là, on peut utiliser les classes wrapper de Bonjour, qui fournissent une API haut niveau Qt à Bonjour. Ces classes seront nommées BonjourRegistrar, BonjourBrowser et BonjourResolver et suivent le même modèle. On communique d'abord à Bonjour ce qui nous intéresse (enregistrer, parcourir et résoudre) en enregistrant une fonction de rappel pour récupérer ses informations. Bonjour envoie une structure de données et un socket, indiquant les informations qui sont prêtes. On passe alors la structure de retour vers Bonjour et ce dernier invoque la fonction enregistrée.
IV. Enregistrer un service Bonjour▲
Voici la déclaration de la classe BonjourRegistrar :
class
BonjourRegistrar : public
QObject
{
Q_OBJECT
public
:
BonjourRegistrar(QObject
*
parent =
0
);
~
BonjourRegistrar();
void
registerService(const
BonjourRecord &
record,
quint16
servicePort);
BonjourRecord registeredRecord() const
{
return
finalRecord; }
signals
:
void
error(DNSServiceErrorType error);
void
serviceRegistered(const
BonjourRecord &
record);
private
slots
:
void
bonjourSocketReadyRead();
private
:
static
void
DNSSD_API bonjourRegisterService(
DNSServiceRef, DNSServiceFlags,
DNSServiceErrorType, const
char
*
, const
char
*
,
const
char
*
, void
*
);
DNSServiceRef dnssref;
QSocketNotifier
*
bonjourSocket;
BonjourRecord finalRecord;
}
;
Cette classe consiste en une méthode principale, registerService(), qui enregistre le service. Tout au long de la vie de l'objet, le service reste enregistré. Il y a également plusieurs signaux qui vérifient les résultats de l'enregistrement, mais ce n'est pas nécessaire pour enregistrer le service avec succès. Le reste de la classe contient les différents éléments de l'enregistrement d'un service Bonjour. La méthode statique de rappel est marquée avec la macro DNSSD_API pour être sûr que la méthode de rappel a la bonne convention d'appel sous Windows.
Voici maintenant le constructeur et le destructeur :
BonjourRegistrar::
BonjourRegistrar(QObject
*
parent)
:
QObject
(parent), dnssref(0
), bonjourSocket(0
)
{
}
BonjourRegistrar::
~
BonjourRegistrar()
{
if
(dnssref) {
DNSServiceRefDeallocate(dnssref);
dnssref =
0
;
}
}
Le constructeur positionne les variables membres dnssref et bonjourSocket à zéro. Ces variables seront utilisées plus tard lors de l'enregistrement du service. Si le service a été enregistré, le destructeur désalloue la mémoire et supprime l'enregistrement.
void
BonjourRegistrar::
registerService(
const
BonjourRecord &
record, quint16
servicePort)
{
if
(dnssref) {
qWarning
("Already registered a service"
);
return
;
}
quint16
bigEndianPort =
servicePort;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
bigEndianPort =
((servicePort &
0x00ff
) <<
8
)
|
((servicePort &
0xff00
) >>
8
);
#endif
DNSServiceErrorType err =
DNSServiceRegister(&
dnssref,
0
, 0
, record.serviceName.toUtf8().constData(),
record.registeredType.toUtf8().constData(),
record.replyDomain.isEmpty() ? 0
:
record.replyDomain.toUtf8().constData(),
0
, bigEndianPort, 0
, 0
, bonjourRegisterService,
this
);
if
(err !=
kDNSServiceErr_NoError) {
emit
error(err);
}
else
{
int
sockfd =
DNSServiceRefSockFD(dnssref);
if
(sockfd ==
-
1
) {
emit
error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket =
new
QSocketNotifier
(sockfd,
QSocketNotifier
::
Read, this
);
connect
(bonjourSocket, SIGNAL
(activated(int
)),
this
, SLOT
(bonjourSocketReadyRead()));
}
}
}
La méthode registerService() est la méthode où tout se passe. Premièrement, on vérifie que le service n'est pas déjà enregistré pour cet objet. Si c'est le cas, on affiche un avertissement et on sort de la méthode. Bonjour requiert que le port du service soit enregistré de manière gros-boutiste ; néanmoins, on convertit le numéro de port si on se trouve sur une machine en ordre de type « petit-boutiste » (Qt 4.3 introduit qToBigEndian(), ce qui permet de rendre le code plus propre).
Avec les bits du port dans le bon ordre, on peut appeler DNSServiceRegister(). Cette fonction comporte beaucoup de paramètres qui indiquent sur quelle interface réseau avertir, comment on gère les conflits de noms et le propre nom DNS-SRV du service. On passe 0 pour ceux-ci car les paramètres par défaut conviennent. On passe un DNSServiceRef, le nom de service unique, le type d'enregistrement et le domaine de repli optionnel. On passe aussi le numéro de port et la méthode de rappel pour récupérer certaines informations. On passe également un pointeur sur l'objet this comme dernier argument pour que l'on puisse y accéder dans la fonction.
Si l'appel à DNSServiceRegister() échoue, on émet une erreur par l'intermédiaire du signal error(). En se connectant à ce signal, les slots peuvent savoir quelle erreur a eu lieu. Si la fonction DNSServiceRegister() a été appelée avec succès, on récupère le descripteur de socket réseau associé au service et on peut alors créer un objet QSocketNotifier pour « lire » dans la socket. On connecte ensuite le signal activated() de la socket de notification au slot bonjourSocketReadyRead().
void
BonjourRegistrar::
bonjourSocketReadyRead()
{
DNSServiceErrorType err =
DNSServiceProcessResult(dnssref);
if
(err !=
kDNSServiceErr_NoError)
emit
error(err);
}
Dans la méthode bonjourSocketReadyRead(), on indique à Bonjour de traiter les informations de la socket. Ceci va invoquer la méthode de rappel. Si une erreur se produit, on émet alors le signal avec le code d'erreur en paramètre.
void
BonjourRegistrar::
bonjourRegisterService(
DNSServiceRef, DNSServiceFlags,
DNSServiceErrorType errorCode, const
char
*
name,
const
char
*
regType, const
char
*
domain, void
*
data)
{
BonjourRegistrar *
registrar =
static_cast
<
BonjourRegistrar *>
(data);
if
(errorCode !=
kDNSServiceErr_NoError) {
emit
registrar->
error(errorCode);
}
else
{
registrar->
finalRecord =
BonjourRecord(QString
::
fromUtf8(name),
QString
::
fromUtf8(regType),
QString
::
fromUtf8(domain));
emit
registrar->
serviceRegistered(
registrar->
finalRecord);
}
}
La méthode de rappel contrôle la réussite du traitement. Si oui, alors on affecte les données définitives à notre BonjourRecord. Dans la pratique, la seule chose qui peut potentiellement changer est le nom qui est fourni par registerService(), puisqu'il pourrait y avoir un service portant déjà le même nom.
V. Naviguer entre les différents services Bonjour disponibles▲
Dans le client Bonjour, on a besoin de récupérer la liste des services disponibles. Ceci est géré dans la classe BonjourBrowser :
class
BonjourBrowser : public
QObject
{
Q_OBJECT
public
:
BonjourBrowser(QObject
*
parent =
0
);
~
BonjourBrowser();
void
browseForServiceType(const
QString
&
serviceType);
QList
<
BonjourRecord>
currentRecords() const
{
return
bonjourRecords; }
QString
serviceType() const
{
return
browsingType; }
signals
:
void
currentBonjourRecordsChanged(
const
QList
<
BonjourRecord>
&
list);
void
error(DNSServiceErrorType err);
private
slots
:
void
bonjourSocketReadyRead();
private
:
static
void
DNSSD_API bonjourBrowseReply(DNSServiceRef,
DNSServiceFlags, quint32
, DNSServiceErrorType,
const
char
*
, const
char
*
, const
char
*
, void
*
);
DNSServiceRef dnssref;
QSocketNotifier
*
bonjourSocket;
QList
<
BonjourRecord>
bonjourRecords;
QString
browsingType;
}
;
BonjourBrowser suit un modèle similaire à celui de la classe BonjourRegistrar. Une fois qu'on a créé une instance de BonjourBrowser et indiqué quel service on veut écouter, il suffit de récupérer la liste des machines qui fournissent ce service et d'informer des changements via le signal currentBonjourRecordsChanged().
Voici l'implémentation de la méthode browserForServiceType() :
void
BonjourBrowser::
browseForServiceType(
const
QString
&
serviceType)
{
DNSServiceErrorType err =
DNSServiceBrowse(&
dnssref, 0
,
0
, serviceType.toUtf8().constData(), 0
,
bonjourBrowseReply, this
);
if
(err !=
kDNSServiceErr_NoError) {
emit
error(err);
}
else
{
int
sockfd =
DNSServiceRefSockFD(dnssref);
if
(sockfd ==
-
1
) {
emit
error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket =
new
QSocketNotifier
(sockfd,
QSocketNotifier
::
Read, this
);
connect
(bonjourSocket, SIGNAL
(activated(int
)),
this
, SLOT
(bonjourSocketReadyRead()));
}
}
}
En appelant browseForServiceType(), on indique à BonjourBrowser de lancer la navigation pour un type spécifique de service. En interne, on appelle DNSServiceBrowse(), qui a une signature similaire à DNSServiceRegister(). Si les appels réussissent, on crée un objet QSocketNotifier pour la socket associée et on connecte le signal activated() au slot bonjourSocketReadyRead(). Ce slot est identique au slot du même nom dans BonjourRegistrar, dont voici la méthode de rappel :
void
BonjourBrowser::
bonjourBrowseReply(DNSServiceRef,
DNSServiceFlags flags, quint32
,
DNSServiceErrorType errorCode,
const
char
*
serviceName, const
char
*
regType,
const
char
*
replyDomain, void
*
context)
{
BonjourBrowser *
browser =
static_cast
<
BonjourBrowser *>
(context);
if
(errorCode !=
kDNSServiceErr_NoError) {
emit
browser->
error(errorCode);
}
else
{
BonjourRecord record(serviceName, regType,
replyDomain);
if
(flags &
kDNSServiceFlagsAdd) {
if
(!
browser->
bonjourRecords.contains(record))
browser->
bonjourRecords.append(record);
}
else
{
browser->
bonjourRecords.removeAll(record);
}
if
(!
(flags &
kDNSServiceFlagsMoreComing)) {
emit
browser->
currentBonjourRecordsChanged(
browser->
bonjourRecords);
}
}
}
La méthode de rappel de BonjourBrowser est plus compliquée. Premièrement, on récupère l'objet BonjourBrowser à partir du pointeur context passé en paramètre. Si on est dans une condition d'erreur, on émet le signal, sinon on examine les options passées.
Les options indiquent si un service a été ajouté ou supprimé ; on met à jour la liste QList en tenant compte de ces changements. Si un service est ajouté, on vérifie en premier si l'enregistrement n'existe pas déjà dans la liste avant de l'ajouter (on pourrait enregistrer un élément en double si, par exemple, on offre un service localement et s'il est disponible à travers la boucle locale ou via un port réseau Ethernet). Si la méthode de rappel peut être seulement appelée pour un enregistrement, il faut aussi spécifier l'option kDNSServiceFlagsMoreComing, qui indique que la méthode de rappel peut être appelée de façon rapide. Quand tous les enregistrements ont été envoyés, cette option permet d'effacer et d'émettre le signal currentBonjourRecordsChanged() avec la liste courante des enregistrements.
VI. Atteindre les services Bonjour▲
Les clients peuvent maintenant naviguer dans les services mais il manque un élément qui permettrait de résoudre les services depuis une adresse IP et un numéro de port. Ceci est géré dans la classe BonjourResolver :
class
BonjourResolver : public
QObject
{
Q_OBJECT
public
:
BonjourResolver(QObject
*
parent);
~
BonjourResolver();
void
resolveBonjourRecord(const
BonjourRecord &
record);
signals
:
void
recordResolved(const
QHostInfo
&
hostInfo,
int
port);
void
error(DNSServiceErrorType error);
private
slots
:
void
bonjourSocketReadyRead();
void
cleanupResolve();
void
finishConnect(const
QHostInfo
&
hostInfo);
private
:
static
void
DNSSD_API bonjourResolveReply(DNSServiceRef,
DNSServiceFlags, quint32
, DNSServiceErrorType,
const
char
*
, const
char
*
, quint16
, quint16
,
const
char
*
, void
*
);
DNSServiceRef dnssref;
QSocketNotifier
*
bonjourSocket;
int
bonjourPort;
}
;
La classe BonjourResolver est un peu plus compliquée que les autres classes présentées ci-dessus, mais elle suit le même modèle général qui a été vu dans BonjourRegistrar et BonjourBrowser.
void
BonjourResolver::
resolveBonjourRecord(const
BonjourRecord &
record)
{
if
(dnssref) {
qWarning
("Resolve already in process"
);
return
;
}
DNSServiceErrorType err =
DNSServiceResolve(&
dnssref, 0
,
0
, record.serviceName.toUtf8().constData(),
record.registeredType.toUtf8().constData(),
record.replyDomain.toUtf8().constData(),
bonjourResolveReply, this
);
if
(err !=
kDNSServiceErr_NoError) {
emit
error(err);
}
else
{
int
sockfd =
DNSServiceRefSockFD(dnssref);
if
(sockfd ==
-
1
) {
emit
error(kDNSServiceErr_Invalid);
}
else
{
bonjourSocket =
new
QSocketNotifier
(sockfd,
QSocketNotifier
::
Read, this
);
connect
(bonjourSocket, SIGNAL
(activated(int
)),
this
, SLOT
(bonjourSocketReadyRead()));
}
}
}
La méthode resolveBonjourRecord() est assez simple. On ne peut résoudre qu'un enregistrement à la fois et cela peut prendre un certain temps, on retourne donc si une résolution est en cours. Sinon, on appelle DNSServiceResolve() avec un DNSServiceRef, les valeurs de BonjourRecord, la méthode de rappel et l'objet à utiliser dans le contexte de la méthode de rappel.
Encore, si l'appel est en succès, on crée un notificateur de socket pour la socket associée à l'objet DNSServiceResolverRef. Les erreurs sont gérées par le signal error().
void
BonjourResolver::
bonjourResolveReply(DNSServiceRef,
DNSServiceFlags, quint32
,
DNSServiceErrorType errorCode, const
char
*
,
const
char
*
hostTarget, quint16
port, quint16
,
const
char
*
, void
*
context)
{
BonjourResolver *
resolver =
static_cast
<
BonjourResolver *>
(context);
if
(errorCode !=
kDNSServiceErr_NoError) {
emit
resolver->
error(errorCode);
return
;
}
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
port =
((port &
0x00ff
) <<
8
) |
((port &
0xff00
) >>
8
);
#endif
resolver->
bonjourPort =
port;
QHostInfo
::
lookupHost(QString
::
fromUtf8(hostTarget),
resolver, SLOT
(finishConnect(const
QHostInfo
&
)));
}
Dans la méthode de rappel, on vérifie que la conversion se fait bien avec le bon ordre d'octets pour la machine et on enregistre le numéro de port. On récupère ensuite le nom de la machine et on appelle QHostInfo::lookupHost(), en passant le résultat au slot finishConnect().
void
BonjourResolver::
finishConnect(
const
QHostInfo
&
hostInfo)
{
emit
recordResolved(hostInfo, bonjourPort);
QMetaObject
::
invokeMethod(this
, "cleanupResolve"
,
Qt
::
QueuedConnection);
}
Le slot finishConnect() reçoit en paramètre l'objet QHostInfo. Celui-ci est émis avec le numéro de port. Ces informations peuvent être utilisées par les classes Qt Network comme QTcpSocket, QUdpSocket et QSslSocket.
VII. Exemple : client et serveur « Fortune »▲
Maintenant qu'on dispose de toutes les classes pour dialoguer avec Bonjour, on peut regarder comment les utiliser. On n'a pas besoin d'aller voir très loin pour trouver un exemple. Dans les exemples réseau disponibles dans le répertoire examples/network de Qt, il y a le client et le serveur Fortune. Actuellement, l'utilisateur doit spécifier l'adresse IP du serveur et le numéro de port au client. Ceci est donc un candidat idéal pour utiliser Bonjour.
Voici donc un lancement de port rapide avec le serveur Fortune :
BonjourRegistrar *
registrar =
new
BonjourRegistrar(this
);
registrar->
registerService(
BonjourRecord(tr("Fortune Server on %1"
)
.arg(QHostInfo
::
localHostName()),
"_trollfortune._tcp"
, ""
),
tcpServer->
serverPort());
De façon basique, on a juste besoin d'ajouter un objet BonjourRegistrar et d'enregistrer le service avec un nom unique. On utilise le nom « Fortune Server » avec le nom de la machine et le numéro de port. On crée ensuite un type de service appelé « _trollfortune._tcp » qui sera utilisé pour cet exemple. Si on avait pris cet exemple pour une utilisation sur Internet, il aurait été nécessaire d'enregistrer le type de service (gratuitement) sur http://dns-sd.org pour être sûr qu'il n'y a pas de collision avec d'autres types de service. Il faut aussi gérer les erreurs mais on laisse simple cet exemple donc on passe au point suivant.
On réalise quelque chose de similaire dans le thread du serveur Fortune. Néanmoins, le client Fortune a besoin de plus gros changements. Voici les parties les plus pertinentes du constructeur :
BonjourBrowser *
browser =
new
BonjourBrowser(this
);
treeWidget =
new
QTreeWidget
(this
);
treeWidget->
setHeaderLabels(
QStringList
() <<
tr("Available Fortune Servers"
));
connect
(browser,
SIGNAL
(currentBonjourRecordsChanged(...)),
this
, SLOT
(updateRecords(...)));
...
connect
(getFortuneButton, SIGNAL
(clicked()),
this
, SLOT
(requestNewFortune()));
...
browser->
browseForServiceType("_trollfortune._tcp"
);
On supprime les deux QLineEdit pour l'adresse IP et le numéro de port, et ensuite on crée un objet BonjourBrowser avec un QTreeWidget pour présenter la liste des serveurs disponibles aux utilisateurs. On convertit le signal currentBonjourBrowserChanged() au slot updateRecords() du client. On modifie également le slot qui est appelé lorsque le bouton Get Fortune est cliqué.
void
Client::
updateRecords(
const
QList
<
BonjourRecord>
&
list)
{
treeWidget->
clear();
foreach
(BonjourRecord record, list) {
QVariant
variant;
variant.setValue(record);
QTreeWidgetItem
*
processItem =
new
QTreeWidgetItem
(treeWidget,
QStringList
() <<
record.serviceName);
processItem->
setData(0
, Qt
::
UserRole, variant);
}
if
(treeWidget->
invisibleRootItem()->
childCount() >
0
)
treeWidget->
invisibleRootItem()->
child(0
)
->
setSelected(true
);
enableGetFortuneButton();
}
Dans le slot updateRecords(), on efface tous les éléments du QTreeWidget. On itère ensuite sur tous les enregistrements de la liste et on crée un QTreeWidgetItem pour chaque enregistrement, en affichant le nom du service. On enregistre aussi l'objet BonjourRecord dans un QVariant et on l'ajoute comme une donnée spéciale dans le QTreeWidgetItem. Finalement, on sélectionne le premier élément de l'arbre et on appelle la méthode enableGetFortuneMode().
void
Client::
requestNewFortune()
{
getFortuneButton->
setEnabled(false
);
blockSize =
0
;
tcpSocket->
abort();
QList
<
QTreeWidgetItem
*>
selectedItems =
treeWidget->
selectedItems();
if
(selectedItems.isEmpty())
return
;
if
(!
resolver) {
resolver =
new
BonjourResolver(this
);
connect
(resolver,
SIGNAL
(recordResolved(const
QHostInfo
&
, int
)),
this
,
SLOT
(connectToServer(const
QHostInfo
&
, int
)));
}
QTreeWidgetItem
*
item =
selectedItems.first();
QVariant
variant =
item->
data(0
, Qt
::
UserRole);
bonjourResolver->
resolveBonjourRecord(
variant.value<
BonjourRecord>
());
}
Quand l'utilisateur clique sur le bouton Get Fortune, on tente de récupérer l'élément sélectionné dans le QTreeWidget. S'il y en a un, alors on crée un objet BonjourResolver et on connecte son signal au slot original connectToServer(). Finalement, on récupère l'objet BonjourRecord présent dans l'objet QTreeWidgetItem et ensuite on appelle resolveBonjourRecord() pour obtenir l'adresse IP du serveur et le numéro de port. Le reste du code du client est le même qu'avant et fonctionne exactement de la même façon.
Figure 1 - Client Fortune
Du côté des modifications de code, on aura besoin de lier avec les bibliothèques Bonjour sur les plateformes autres que Mac OS X. Ceci se fait en ajoutant la ligne suivante dans le profil :
!
mac:LIBS +=
-
ldns_sd
VIII. Conclusion▲
Ces exemples ont montré comment il était simple d'ajouter le support de Bonjour dans une application existante une fois qu'on a les bonnes classes pour gérer le protocole Bonjour. Les classes de gestion de Bonjour présentées ici couvrent environ 90 % des cas. Néanmoins, il y a certaines choses qui peuvent être utiles pour une utilisation de manière générale :
- donner à la classe BonjourRegistrar la possibilité d'enregistrer des services multiples. Une bonne chose serait d'avoir seulement les méthodes publiques statiques, registerService() et unregisterService(), et d'utiliser les API Bonjour pour enregistrer les services multiples ;
- réunir les classes BonjourResolver et BonjourRecord et les faire fonctionner comme QHostInfo::lookupHost() ;
- ajouter le support aux réseaux Bonjour de grande envergure ;
- ajouter le support des enregistrements SRV ;
- ajouter une meilleure gestion des erreurs.
Le code source des exemples présentés dans cet article est également disponible.
IX. Remerciements▲
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisés à traduire cet article !
Je tiens à remercier Thibaut Cuvelier pour ses conseils et Claude Leloup pour sa relecture.