Sur cette page

Chat Bluetooth

Montre la communication par Bluetooth à l'aide du protocole RFCOMM.

L'exemple Bluetooth Chat montre comment utiliser l'API pour communiquer avec une autre application sur un appareil distant à l'aide du protocole Bluetooth RFCOMM. Qt Bluetooth pour communiquer avec une autre application sur un appareil distant à l'aide du protocole Bluetooth RFCOMM.

Qt Bluetooth chat UI

L'exemple Bluetooth Chat met en œuvre un programme de chat simple entre plusieurs parties. L'application joue toujours le rôle de serveur et de client, ce qui évite de devoir déterminer qui doit se connecter à qui.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

Serveur de chat

Le serveur de chat est implémenté par la classe ChatServer. La classe ChatServer est déclarée comme suit :

class ChatServer : public QObject
{
    Q_OBJECT

public:
    explicit ChatServer(QObject *parent = nullptr);
    ~ChatServer();

    void startServer(const QBluetoothAddress &localAdapter = QBluetoothAddress());
    void stopServer();

public slots:
    void sendMessage(const QString &message);

signals:
    void messageReceived(const QString &sender, const QString &message);
    void clientConnected(const QString &name);
    void clientDisconnected(const QString &name);

private slots:
    void clientConnected();
    void clientDisconnected();
    void readSocket();

private:
    QBluetoothServer *rfcommServer = nullptr;
    QBluetoothServiceInfo serviceInfo;
    QList<QBluetoothSocket *> clientSockets;
    QMap<QBluetoothSocket *, QString> clientNames;
};

La première chose que le serveur de chat doit faire est de créer une instance de QBluetoothServer pour écouter les connexions Bluetooth entrantes. Le slot clientConnected() sera appelé chaque fois qu'une nouvelle connexion sera créée.

rfcommServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this) ; connect(rfcommServer, &QBluetoothServer::newConnection, this, QOverload<>::of(&ChatServer::clientConnected)) ;bool result =  rfcommServer->listen(localAdapter) ;if (!result) {    qWarning() << "Cannot bind chat server to" << localAdapter.toString();
   return; }

Le serveur de chat n'est utile que si les autres savent qu'il existe. Pour permettre à d'autres appareils de le découvrir, un enregistrement décrivant le service doit être publié dans la base de données SDP (Service Discovery Protocol) du système. La classe QBluetoothServiceInfo encapsule un enregistrement de service.

Nous publierons un enregistrement de service qui contient des descriptions textuelles des services, un UUID qui identifie le service de manière unique, l'attribut de découvrabilité et les paramètres de connexion.

La description textuelle du service est stockée dans les attributs ServiceName, ServiceDescription, et ServiceProvider.

serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, tr("Bt Chat Server"));
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceDescription,
                         tr("Example bluetooth chat server"));
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceProvider, tr("qt-project.org"));

Bluetooth utilise les UUID comme identifiants uniques. Le service de chat utilise un UUID généré de manière aléatoire.

static constexpr auto serviceUuid = "e8e10f95-1a70-4b27-9ccf-02010264e9c8"_L1;
serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));

Un service Bluetooth ne peut être découvert que s'il se trouve sur le site PublicBrowseGroup.

const auto groupUuid = QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup);
QBluetoothServiceInfo::Sequence publicBrowse;
publicBrowse << QVariant::fromValue(groupUuid);
serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);

L'attribut ProtocolDescriptorList est utilisé pour publier les paramètres de connexion dont l'appareil distant a besoin pour se connecter à notre service. Ici, nous spécifions que le protocole Rfcomm est utilisé et nous fixons le numéro de port au port que notre instance rfcommServer écoute.

QBluetoothServiceInfo::Sequence protocolDescriptorList;
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
protocolDescriptorList.append(QVariant::fromValue(protocol));
protocol.clear();
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Rfcomm))
         << QVariant::fromValue(quint8(rfcommServer->serverPort()));
protocolDescriptorList.append(QVariant::fromValue(protocol));
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList,
                         protocolDescriptorList);

Enfin, nous enregistrons l'enregistrement de service auprès du système.

serviceInfo.registerService(localAdapter);

Comme indiqué précédemment, les connexions entrantes sont gérées dans le slot clientConnected() où les connexions en attente sont connectées aux signaux readyRead() et disconnected(). Ces signaux signalent aux autres qu'un nouveau client s'est connecté.

void ChatServer::clientConnected()
{
    QBluetoothSocket *socket = rfcommServer->nextPendingConnection();
    if (!socket)
        return;

    connect(socket, &QBluetoothSocket::readyRead, this, &ChatServer::readSocket);
    connect(socket, &QBluetoothSocket::disconnected,
            this, QOverload<>::of(&ChatServer::clientDisconnected));
    clientSockets.append(socket);
    clientNames[socket] = socket->peerName();
    emit clientConnected(socket->peerName());
}

Le slot readSocket() est appelé chaque fois que des données sont prêtes à être lues à partir d'un socket client. Le slot lit des lignes individuelles depuis le socket, les convertit en UTF-8 et émet le signal messageReceived().

void ChatServer::readSocket()
{
    QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
    if (!socket)
        return;

    while (socket->canReadLine()) {
        QByteArray line = socket->readLine().trimmed();
        emit messageReceived(clientNames[socket],
                             QString::fromUtf8(line.constData(), line.length()));
    }
}

Le slot clientDisconnected() est appelé lorsqu'un client se déconnecte du service. Le slot émet un signal pour notifier aux autres qu'un client s'est déconnecté, et supprime la socket.

void ChatServer::clientDisconnected()
{
    QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
    if (!socket)
        return;

    emit clientDisconnected(clientNames[socket]);

    clientSockets.removeOne(socket);
    clientNames.remove(socket);

    socket->deleteLater();
}

Le slot sendMessage() est utilisé pour envoyer un message à tous les clients connectés. Le message est converti en UTF-8 et complété par une nouvelle ligne avant d'être envoyé à tous les clients.

void ChatServer::sendMessage(const QString &message)
{
    QByteArray text = message.toUtf8() + '\n';

    for (QBluetoothSocket *socket : std::as_const(clientSockets))
        socket->write(text);
}

Lorsque le serveur de chat est arrêté, l'enregistrement du service est supprimé de la base de données SDP du système, toutes les sockets des clients connectés sont supprimées et l'instance rfcommServer est supprimée.

void ChatServer::stopServer() { // Désenregistrement du serviceserviceInfo.unregisterService() ; // Fermeture des sockets    qDeleteAll(clientSockets);
    clientNames.clear() ; // Fermer le serveur delete rfcommServer ; rfcommServer = nullptr ; }

Découverte de services

Avant de se connecter au serveur, le client doit analyser les appareils à proximité et rechercher l'appareil qui annonce le service de chat. C'est ce que fait la classe RemoteSelector.

Pour commencer la recherche de services, RemoteSelector crée une instance de QBluetoothServiceDiscoveryAgent et se connecte à ses signaux.

    m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(localAdapter);

    connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered,
            this, &RemoteSelector::serviceDiscovered);
    connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished,
            this, &RemoteSelector::discoveryFinished);
    connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::canceled,
            this, &RemoteSelector::discoveryFinished);

Un filtre UUID est mis en place, de sorte que la recherche de services n'affiche que les dispositifs qui annoncent le service requis. Ensuite, un site FullDiscovery est lancé :

    m_discoveryAgent->setUuidFilter(uuid);
    m_discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);

Lorsqu'un service correspondant est découvert, un signal serviceDiscovered() est émis avec une instance de QBluetoothServiceInfo comme paramètre. Ces informations sur le service sont utilisées pour extraire le nom du dispositif et le nom du service, et ajouter une nouvelle entrée à la liste des dispositifs distants découverts :

    QString remoteName;
    if (serviceInfo.device().name().isEmpty())
        remoteName = address.toString();
    else
        remoteName = serviceInfo.device().name();

    QListWidgetItem *item =
        new QListWidgetItem(QString::fromLatin1("%1 %2").arg(remoteName,
                                                             serviceInfo.serviceName()));

    m_discoveredServices.insert(item, serviceInfo);
    ui->remoteDevices->addItem(item);

Par la suite, l'utilisateur peut sélectionner l'un des appareils de la liste et essayer de s'y connecter.

Client Chat

Le client de chat est implémenté par la classe ChatClient. La classe ChatClient est déclarée comme suit :

class ChatClient : public QObject
{
    Q_OBJECT

public:
    explicit ChatClient(QObject *parent = nullptr);
    ~ChatClient();

    void startClient(const QBluetoothServiceInfo &remoteService);
    void stopClient();

public slots:
    void sendMessage(const QString &message);

signals:
    void messageReceived(const QString &sender, const QString &message);
    void connected(const QString &name);
    void disconnected();
    void socketErrorOccurred(const QString &errorString);

private slots:
    void readSocket();
    void connected();
    void onSocketErrorOccurred(QBluetoothSocket::SocketError);

private:
    QBluetoothSocket *socket = nullptr;
};

Le client crée un nouveau QBluetoothSocket et se connecte au service distant décrit par le paramètre remoteService. Les slots sont connectés aux signaux readyRead(), connected() et disconnected() de la socket.

void ChatClient::startClient(const QBluetoothServiceInfo &remoteService) { if (socket) return; // Connexion au servicesocket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol) ;    qDebug() << "Create socket";
    socket->connectToService(remoteService) ;    qDebug() << "ConnectToService done";

    connect(socket, &QBluetoothSocket::readyRead, this, &ChatClient::readSocket) ; connect(socket, &QBluetoothSocket::connected, this, QOverload<>::of(&ChatClient::connected)) ; connect(socket, &QBluetoothSocket::disconnected, this, &ChatClient::disconnected) ; connect(socket, &QBluetoothSocket::errorOccurred, this, &ChatClient::onSocketErrorOccurred) ; }

Lorsque la connexion au socket est réussie, nous émettons un signal pour avertir les autres utilisateurs.

void ChatClient::connected()
{
    emit connected(socket->peerName());
}

Comme pour le serveur de chat, le slot readSocket() est appelé lorsque des données sont disponibles sur la socket. Les lignes sont lues individuellement et converties en UTF-8. Le signal messageReceived() est émis.

void ChatClient::readSocket()
{
    if (!socket)
        return;

    while (socket->canReadLine()) {
        QByteArray line = socket->readLine().trimmed();
        emit messageReceived(socket->peerName(),
                             QString::fromUtf8(line.constData(), line.length()));
    }
}

Le slot sendMessage() est utilisé pour envoyer un message à l'appareil distant. Le message est converti en UTF-8 et une nouvelle ligne est ajoutée.

void ChatClient::sendMessage(const QString &message)
{
    QByteArray text = message.toUtf8() + '\n';
    socket->write(text);
}

Pour se déconnecter du service de chat distant, l'instance QBluetoothSocket est supprimée.

void ChatClient::stopClient()
{
    delete socket;
    socket = nullptr;
}

Dialogue de chat

La fenêtre principale de cet exemple est la boîte de dialogue de discussion, implémentée dans la classe Chat. Cette classe affiche une session de chat entre un seul ChatServer et zéro ou plusieurs ChatClient. La classe Chat est déclarée comme suit :

class Chat : public QDialog
{
    Q_OBJECT

public:
    explicit Chat(QWidget *parent = nullptr);
    ~Chat();

signals:
    void sendMessage(const QString &message);

private slots:
    void connectClicked();
    void sendClicked();

    void showMessage(const QString &sender, const QString &message);

    void clientConnected(const QString &name);
    void clientDisconnected(const QString &name);
    void clientDisconnected();
    void connected(const QString &name);
    void reactOnSocketError(const QString &error);

    void newAdapterSelected();

    void initBluetooth();

    void updateIcons(Qt::ColorScheme scheme);

private:
    int adapterFromUserSelection() const;
    int currentAdapterIndex = 0;
    Ui::Chat *ui;

    ChatServer *server = nullptr;
    QList<ChatClient *> clients;
    QList<QBluetoothHostInfo> localAdapters;

    QString localName;
};

Nous commençons par construire l'interface utilisateur

ui->setupUi(this);

connect(ui->connectButton, &QPushButton::clicked, this, &Chat::connectClicked);
connect(ui->sendButton, &QPushButton::clicked, this, &Chat::sendClicked);

Nous créons une instance de ChatServer et répondons à ses signaux clientConnected(), clientDiconnected() et messageReceived().

server = new ChatServer(this);
connect(server, QOverload<const QString &>::of(&ChatServer::clientConnected),
        this, &Chat::clientConnected);
connect(server, QOverload<const QString &>::of(&ChatServer::clientDisconnected),
        this,  QOverload<const QString &>::of(&Chat::clientDisconnected));
connect(server, &ChatServer::messageReceived,
        this,  &Chat::showMessage);
connect(this, &Chat::sendMessage, server, &ChatServer::sendMessage);
server->startServer();

En réponse aux signaux clientConnected() et clientDisconnected() de ChatServer, nous affichons les messages typiques "X a rejoint le chat" et "Y l'a quitté" dans la session de chat.

void Chat::clientConnected(const QString &name)
{
    ui->chat->insertPlainText(QString::fromLatin1("%1 has joined chat.\n").arg(name));
}

void Chat::clientDisconnected(const QString &name)
{
    ui->chat->insertPlainText(QString::fromLatin1("%1 has left.\n").arg(name));
}

Les messages entrants provenant de clients connectés à ChatServer sont traités dans l'emplacement showMessage(). Le texte du message étiqueté avec le nom de l'appareil distant est affiché dans la session de chat.

void Chat::showMessage(const QString &sender, const QString &message)
{
    ui->chat->moveCursor(QTextCursor::End);
    ui->chat->insertPlainText(QString::fromLatin1("%1: %2\n").arg(sender, message));
    ui->chat->ensureCursorVisible();
}

Lorsque l'utilisateur clique sur le bouton de connexion, l'application lance la découverte des services et présente une liste des services de dialogue en ligne découverts sur les appareils distants. L'utilisateur sélectionne une adresse ChatClient pour le service.

void Chat::connectClicked() {  ui->connectButton->setEnabled(false) ; // recherche de services const QBluetoothAddress adapter = localAdapters.isEmpty() ?QBluetoothAddress() : localAdapters.at(currentAdapterIndex).address() ; RemoteSelector remoteSelector(adapter) ;#ifdef Q_OS_ANDROID // QTBUG-61392Q_UNUSED(serviceUuid) ; remoteSelector.startDiscovery(QBluetoothUuid(reverseUuid)) ;#elseremoteSelector.startDiscovery(QBluetoothUuid(serviceUuid)) ;#endif if (remoteSelector.exec() == QDialog::Accepted) { QBluetoothServiceInfo service = remoteSelector.service() ;
        qDebug() << "Connecting to service" << service.serviceName()
                << "on"<< service.device().name() ; // Création du clientChatClient *client = new ChatClient(this) ; connect(client, &ChatClient::messageReceived, this, &Chat::showMessage) ; connect(client, &ChatClient::disconnected, this, QOverload<>::of(&Chat::clientDisconnected)) ; connect(client, QOverload<const QString&>::of(&ChatClient::connected), this, &Chat::connected) ; connect(client, &ChatClient::socketErrorOccurred, this, &Chat::reactOnSocketError) ; connect(this, &Chat::sendMessage, client, &ChatClient::sendMessage) ;  client->startClient(service) ; clients.append(client) ; }  ui->connectButton->setEnabled(true) ; }

En réponse aux signaux connected() de ChatClient, nous affichons le message "Joined chat with X." dans la session de chat.

void Chat::connected(const QString &name)
{
    ui->chat->insertPlainText(QString::fromLatin1("Joined chat with %1.\n").arg(name));
}

Les messages sont envoyés à tous les dispositifs distants via les instances ChatServer et ChatClient en émettant le signal sendMessage().

void Chat::sendClicked()
{
    ui->sendButton->setEnabled(false);
    ui->sendText->setEnabled(false);

    showMessage(localName, ui->sendText->text());
    emit sendMessage(ui->sendText->text());

    ui->sendText->clear();

    ui->sendText->setEnabled(true);
    ui->sendButton->setEnabled(true);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
    // avoid keyboard automatically popping up again on mobile devices
    ui->sendButton->setFocus();
#else
    ui->sendText->setFocus();
#endif
}

Nous devons nettoyer les instances ChatClient lorsque l'appareil distant force une déconnexion.

void Chat::clientDisconnected()
{
    ChatClient *client = qobject_cast<ChatClient *>(sender());
    if (client) {
        clients.removeOne(client);
        client->deleteLater();
    }
}

Exemple de projet @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.