Qt et Zeroconf

| Trenton Schulz | traducteur : charlespf | Qt Quarterly |

Bonjour est l'implémentation d'Apple concernant le réseau sans configuration (Zeroconf), grâce auquel plusieurs applications offrent leurs services à un réseau local. Utiliser Bonjour simplifie grandement la découverte et l'utilisation des services réseau. Dans cet article, on créera des objets Qt pour manipuler les différentes parties de Bonjour et ensuite on les utilisera dans des exemples d'applications réseau avec Qt.

Commentez

- Titre : Qt et Zeroconf

- Auteurs : Trenton Schulz, traducteur : charlespf, Qt Quarterly

- Parution : 13 août 2012

- Mise à jour : 13 août 2012

- Licence : Copyright ® 2007 Nokia. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Qui est qui : Zeroconf, Bonjour, Avahi

- III. Sauvegarder une entrée Bonjour

- IV. Enregistrer un service Bonjour

- V. Naviguer entre les différents services Bonjour disponibles

- VI. Atteindre les services Bonjour

- VII. Exemple : client et serveur « Fortune »

- VIII. Conclusion

- IX. Remerciements

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.