En esta página

Chat Bluetooth

Muestra la comunicación a través de Bluetooth utilizando el protocolo RFCOMM.

El ejemplo de Chat Bluetooth muestra cómo utilizar la Qt Bluetooth API para comunicarse con otra aplicación en un dispositivo remoto utilizando el protocolo Bluetooth RFCOMM.

Interfaz de chat Qt Bluetooth

El ejemplo de Chat Bluetooth implementa un simple programa de chat entre múltiples partes. La aplicación siempre actúa como servidor y cliente, eliminando la necesidad de determinar quién debe conectarse a quién.

Ejecutar el ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.

Servidor de Chat

El servidor de chat es implementado por la clase ChatServer. La clase ChatServer se declara como:

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

Lo primero que necesita hacer el servidor de chat es crear una instancia de QBluetoothServer para escuchar las conexiones Bluetooth entrantes. La instancia clientConnected() será llamada cada vez que se cree una nueva conexión.

rfcommServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocolo, 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; }

El servidor de chat sólo es útil si los demás saben que está ahí. Para que otros dispositivos puedan descubrirlo, es necesario publicar un registro que describa el servicio en la base de datos SDP (Service Discovery Protocol) del sistema. La clase QBluetoothServiceInfo encapsula un registro de servicio.

Publicaremos un registro de servicio que contenga algunas descripciones textuales de los servicios, un UUID que identifique de forma única el servicio, el atributo de descubribilidad y parámetros de conexión.

La descripción textual del servicio se almacena en los atributos ServiceName, ServiceDescription, y 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 utiliza UUID como identificadores únicos. El servicio de chat utiliza un UUID generado aleatoriamente.

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

Un servicio Bluetooth sólo es detectable si se encuentra en PublicBrowseGroup.

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

El atributo ProtocolDescriptorList se utiliza para publicar los parámetros de conexión que el dispositivo remoto necesita para conectarse a nuestro servicio. Aquí especificamos que se utiliza el protocolo Rfcomm y establecemos el número de puerto en el que escucha nuestra instancia rfcommServer.

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

Por último, registramos el servicio en el sistema.

serviceInfo.registerService(localAdapter);

Como ya se ha mencionado, las conexiones entrantes se gestionan en la ranura clientConnected(), donde las conexiones pendientes se conectan a las señales readyRead() y disconnected(). Las señales notifican a otros que un nuevo cliente se ha conectado.

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

La ranura readSocket() es llamada cada vez que los datos están listos para ser leídos desde el socket de un cliente. La ranura lee líneas individuales del socket, las convierte de UTF-8 y emite la señal 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()));
    }
}

La ranura clientDisconnected() es llamada cada vez que un cliente se desconecta del servicio. La ranura emite una señal para notificar a los demás que un cliente se ha desconectado, y borra el 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();
}

La ranura sendMessage() se utiliza para enviar un mensaje a todos los clientes conectados. El mensaje se convierte a UTF-8 y se le añade una nueva línea antes de enviarlo a todos los clientes.

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

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

Cuando se detiene el servidor de chat, se elimina el registro de servicio de la base de datos SDP del sistema, se borran todos los sockets de cliente conectados y se elimina la instancia rfcommServer.

void ChatServer::stopServer() { // Desregistrar servicioserviceInfo.unregisterService(); // Cerrar sockets    qDeleteAll(clientSockets);
    clientNames.clear(); // Cerrar servidor delete rfcommServer; rfcommServer = nullptr; }

Descubrimiento de servicios

Antes de conectarse al servidor, el cliente necesita escanear los dispositivos cercanos y buscar el dispositivo que está anunciando el servicio de chat. Esto lo hace la clase RemoteSelector.

Para iniciar la búsqueda del servicio, RemoteSelector crea una instancia de QBluetoothServiceDiscoveryAgent y se conecta a sus señales.

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

Se establece un filtro UUID para que la búsqueda de servicios sólo muestre los dispositivos que anuncian el servicio necesario. A continuación, se inicia FullDiscovery:

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

Cuando se descubre un servicio que coincide, se emite una señal serviceDiscovered() con una instancia de QBluetoothServiceInfo como parámetro. Esta información de servicio se utiliza para extraer el nombre del dispositivo y el nombre del servicio, y añadir una nueva entrada a la lista de dispositivos remotos descubiertos:

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

Más tarde, el usuario puede seleccionar uno de los dispositivos de la lista e intentar conectarse a él.

Cliente de chat

El cliente de chat es implementado por la clase ChatClient. La clase ChatClient se declara como:

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

El cliente crea un nuevo QBluetoothSocket y se conecta al servicio remoto descrito por el parámetro remoteService. Las ranuras se conectan a las señales readyRead(), connected() y disconnected() del socket.

void ChatClient::startClient(const QBluetoothServiceInfo &remoteService) { if (socket) return; // Conectarse al serviciosocket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocolo);    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); }

Cuando la conexión al socket tiene éxito, emitimos una señal para notificar a otros usuarios.

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

De forma similar al servidor de chat, la ranura readSocket() es llamada cuando los datos están disponibles desde el socket. Las líneas se leen individualmente y se convierten de UTF-8. Se emite la señal messageReceived().

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

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

La ranura sendMessage() se utiliza para enviar un mensaje al dispositivo remoto. El mensaje se convierte a UTF-8 y se añade una nueva línea.

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

Para desconectarse del servicio de chat remoto, se elimina la instancia QBluetoothSocket.

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

Diálogo de chat

La ventana principal de este ejemplo es el diálogo de chat, implementado en la clase Chat. Esta clase muestra una sesión de chat entre un único ChatServer y cero o más ChatClients. La clase Chat se declara como:

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

Primero construimos la interfaz de usuario

ui->setupUi(this);

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

Creamos una instancia de ChatServer y respondemos a sus señales clientConnected(), clientDiconnected(), y 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 respuesta a las señales clientConnected() y clientDisconnected() de ChatServer, mostramos los típicos mensajes "X se ha unido al chat" e "Y se ha ido" en la sesión 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));
}

Los mensajes entrantes de clientes conectados a ChatServer se gestionan en la ranura showMessage(). El texto del mensaje etiquetado con el nombre del dispositivo remoto se muestra en la sesión 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();
}

Cuando se pulsa el botón de conexión, la aplicación inicia el descubrimiento de servicios y presenta una lista de los servicios de chat descubiertos en los dispositivos remotos. El usuario selecciona un ChatClient para el servicio.

void Chat::connectClicked() {  ui->connectButton->setEnabled(false); // buscar servicios const QBluetoothAddress adaptador = 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::Aceptado) { QBluetoothServiceInfo service = remoteSelector.service();
        qDebug() << "Connecting to service" << service.serviceName()
                << "on"<< service.device().name(); // Crear clienteChatClient *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 respuesta a las señales connected() de ChatClient, mostramos el mensaje "Se unió al chat con X." en la sesión de chat.

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

Los mensajes se envían a todos los dispositivos remotos a través de las instancias ChatServer y ChatClient emitiendo la señal 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
}

Necesitamos limpiar las instancias ChatClient cuando el dispositivo remoto fuerza una desconexión.

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

Proyecto de ejemplo @ 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.