Bluetooth-Chat

Zeigt die Kommunikation über Bluetooth mit dem RFCOMM-Protokoll an.

Das Bluetooth-Chat-Beispiel zeigt, wie man die Qt Bluetooth API verwendet wird, um mit einer anderen Anwendung auf einem entfernten Gerät über das Bluetooth RFCOMM-Protokoll zu kommunizieren.

Das Bluetooth-Chat-Beispiel implementiert ein einfaches Chat-Programm zwischen mehreren Parteien. Die Anwendung fungiert immer sowohl als Server als auch als Client, so dass nicht festgelegt werden muss, wer sich mit wem verbinden soll.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Chat-Server

Der Chatserver wird durch die Klasse ChatServer implementiert. Die Klasse ChatServer ist deklariert als:

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;
};

Das erste, was der Chatserver tun muss, ist eine Instanz von QBluetoothServer zu erstellen, um auf eingehende Bluetooth-Verbindungen zu warten. Der clientConnected() Slot wird immer dann aufgerufen, wenn eine neue Verbindung hergestellt wird.

rfcommServer = neu 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; }

Der Chatserver ist nur dann nützlich, wenn andere wissen, dass er vorhanden ist. Damit andere Geräte ihn erkennen können, muss ein Eintrag, der den Dienst beschreibt, in der SDP-Datenbank (Service Discovery Protocol) des Systems veröffentlicht werden. Die Klasse QBluetoothServiceInfo kapselt einen Diensteintrag.

Wir werden einen Diensteintrag veröffentlichen, der einige textuelle Beschreibungen der Dienste, eine UUID, die den Dienst eindeutig identifiziert, das Auffindbarkeitsattribut und Verbindungsparameter enthält.

Die textuelle Beschreibung des Dienstes wird in den Attributen ServiceName, ServiceDescription und ServiceProvider gespeichert.

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 verwendet UUIDs als eindeutige Bezeichner. Der Chat-Dienst verwendet eine zufällig erzeugte UUID.

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

Ein Bluetooth-Dienst ist nur dann auffindbar, wenn er sich im PublicBrowseGroup befindet.

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

Das Attribut ProtocolDescriptorList wird verwendet, um die Verbindungsparameter zu veröffentlichen, die das entfernte Gerät benötigt, um sich mit unserem Dienst zu verbinden. Hier geben wir an, dass das Protokoll Rfcomm verwendet wird, und setzen die Portnummer auf den Port, den unsere Instanz rfcommServer abhört.

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);

Schließlich registrieren wir den Diensteintrag im System.

serviceInfo.registerService(localAdapter);

Wie bereits erwähnt, werden eingehende Verbindungen im Slot clientConnected() behandelt, wo anstehende Verbindungen mit den Signalen readyRead() und disconnected() verbunden werden. Die Signale teilen anderen mit, dass ein neuer Client eine Verbindung hergestellt hat.

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());
}

Der Slot readSocket() wird immer dann aufgerufen, wenn Daten von einem Client-Socket gelesen werden können. Der Slot liest einzelne Zeilen aus dem Socket, konvertiert sie aus UTF-8 und gibt das Signal messageReceived() aus.

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()));
    }
}

Der Slot clientDisconnected() wird immer dann aufgerufen, wenn ein Client die Verbindung zum Dienst trennt. Der Slot gibt ein Signal aus, um andere darüber zu informieren, dass ein Client die Verbindung beendet hat, und löscht den 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();
}

Der Slot sendMessage() wird verwendet, um eine Nachricht an alle verbundenen Clients zu senden. Die Nachricht wird in UTF-8 konvertiert und mit einem Zeilenumbruch versehen, bevor sie an alle Clients gesendet wird.

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

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

Wenn der Chatserver gestoppt wird, wird der Diensteintrag aus der SDP-Datenbank des Systems entfernt, alle verbundenen Client-Sockets werden gelöscht und die Instanz rfcommServer wird gelöscht.

void ChatServer::stopServer() { // Deregistrierung des DienstesserviceInfo.unregisterService(); // Schließen der Sockets    qDeleteAll(clientSockets);
    clientNames.clear(); // Server schließen delete rfcommServer; rfcommServer = nullptr; }

Dienstentdeckung

Bevor eine Verbindung zum Server hergestellt werden kann, muss der Client die Geräte in der Nähe scannen und nach dem Gerät suchen, das den Chat-Dienst anbietet. Dies wird von der Klasse RemoteSelector durchgeführt.

Um die Dienstsuche zu starten, erstellt RemoteSelector eine Instanz von QBluetoothServiceDiscoveryAgent und stellt eine Verbindung zu dessen Signalen her.

    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);

Ein UUID-Filter wird gesetzt, so dass die Dienstsuche nur die Geräte anzeigt, die den benötigten Dienst anbieten. Danach wird ein FullDiscovery gestartet:

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

Wenn ein passender Dienst gefunden wird, wird ein serviceDiscovered()-Signal mit einer Instanz von QBluetoothServiceInfo als Parameter ausgegeben. Diese Dienstinformation wird verwendet, um den Gerätenamen und den Dienstnamen zu extrahieren und einen neuen Eintrag in die Liste der entdeckten entfernten Geräte aufzunehmen:

    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);

Später kann der Benutzer eines der Geräte aus der Liste auswählen und versuchen, eine Verbindung zu ihm herzustellen.

Chat-Client

Der Chat-Client wird durch die Klasse ChatClient implementiert. Die Klasse ChatClient ist deklariert als:

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;
};

Der Client erstellt eine neue QBluetoothSocket und verbindet sich mit dem entfernten Dienst, der durch den Parameter remoteService beschrieben wird. Die Steckplätze sind mit den Signalen readyRead(), connected() und disconnected() des Sockets verbunden.

void ChatClient::startClient(const QBluetoothServiceInfo &remoteService) { if (socket) return; // Verbindung zum Dienst herstellensocket = 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); }

Bei erfolgreicher Socket-Verbindung geben wir ein Signal aus, um andere Benutzer zu benachrichtigen.

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

Ähnlich wie beim Chatserver wird der Slot readSocket() aufgerufen, wenn Daten vom Socket verfügbar sind. Die Zeilen werden einzeln gelesen und von UTF-8 konvertiert. Das Signal messageReceived() wird ausgegeben.

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

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

Der sendMessage() Slot wird verwendet, um eine Nachricht an das entfernte Gerät zu senden. Die Nachricht wird in UTF-8 konvertiert und ein Zeilenumbruch wird angehängt.

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

Um die Verbindung mit dem entfernten Chat-Dienst zu trennen, wird die Instanz QBluetoothSocket gelöscht.

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

Chat-Dialog

Das Hauptfenster dieses Beispiels ist der Chat-Dialog, der in der Klasse Chat implementiert ist. Diese Klasse zeigt eine Chatsitzung zwischen einem einzelnen ChatServer und null oder mehr ChatClients. Die Klasse Chat ist deklariert als:

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;
};

Zuerst konstruieren wir die Benutzeroberfläche

ui->setupUi(this);

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

Wir erstellen eine Instanz von ChatServer und reagieren auf die Signale clientConnected(), clientDiconnected() und 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();

Als Reaktion auf die Signale clientConnected() und clientDisconnected() des ChatServer zeigen wir die typischen Nachrichten "X ist dem Chat beigetreten" und "Y hat ihn verlassen" in der Chatsitzung an.

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));
}

Eingehende Nachrichten von Clients, die mit dem ChatServer verbunden sind, werden im Slot showMessage() behandelt. Der mit dem Namen des entfernten Geräts versehene Nachrichtentext wird in der Chatsitzung angezeigt.

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();
}

Wenn die Schaltfläche "Verbinden" angeklickt wird, beginnt die Anwendung mit der Dienstsuche und zeigt eine Liste der entdeckten Chat-Dienste auf entfernten Geräten an. Der Benutzer wählt eine ChatClient für den Dienst aus.

void Chat::connectClicked() {  ui->connectButton->setEnabled(false); // scan for 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(); // Client erstellenChatClient *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->connectButtonon->setEnabled(true); }

Als Antwort auf die connected() Signale von ChatClient zeigen wir die Nachricht "Joined chat with X." in der Chatsitzung an.

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

Nachrichten werden an alle entfernten Geräte über die Instanzen ChatServer und ChatClient gesendet, indem das Signal sendMessage() ausgegeben wird.

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
}

Wir müssen die ChatClient Instanzen bereinigen, wenn das entfernte Gerät eine Trennung der Verbindung erzwingt.

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

Beispielprojekt @ code.qt.io

© 2025 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.