强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Qt 与 GTK 图形框架教程 / 05 - Qt 网络编程 / Qt Network

Qt 网络编程 / Qt Network Programming

掌握 Qt 网络模块:TCP/UDP Socket、HTTP 客户端、WebSocket、JSON 和 REST API。 Master Qt Network: TCP/UDP sockets, HTTP client, WebSocket, JSON, and REST APIs.


5.1 网络模块总览 / Network Module Overview

Qt 网络类层次 / Qt Network Class Hierarchy

类别 / Category类 / Class用途 / Purpose
TCPQTcpServer, QTcpSocketTCP 服务端/客户端
UDPQUdpSocketUDP 数据报通信
HTTPQNetworkAccessManagerHTTP/HTTPS 请求
WebSocketQWebSocketWebSocket 双向通信
SSLQSslSocket安全套接字 / Secure socket
DNSQDnsLookupDNS 查询
网络信息QNetworkInterface网络接口信息
代理QNetworkProxy代理配置
# CMakeLists.txt
find_package(Qt6 REQUIRED COMPONENTS Network)
target_link_libraries(myapp PRIVATE Qt6::Network)

5.2 TCP 通信 / TCP Communication

TCP 服务端 / TCP Server

// tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMap>

class TcpServer : public QObject {
    Q_OBJECT

public:
    explicit TcpServer(quint16 port, QObject *parent = nullptr);

    bool start();
    void stop();
    int clientCount() const { return m_clients.size(); }

signals:
    void clientConnected(const QString &address);
    void clientDisconnected(const QString &address);
    void messageReceived(const QString &from, const QString &message);
    void errorOccurred(const QString &error);

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

private slots:
    void onNewConnection();
    void onReadyRead();
    void onDisconnected();

private:
    QTcpServer *m_server;
    QMap<QTcpSocket*, QString> m_clients;  // socket -> address
    quint16 m_port;
};
#endif
// tcpserver.cpp
#include "tcpserver.h"
#include <QDebug>

TcpServer::TcpServer(quint16 port, QObject *parent)
    : QObject(parent), m_server(new QTcpServer(this)), m_port(port)
{
    connect(m_server, &QTcpServer::newConnection,
            this, &TcpServer::onNewConnection);
}

bool TcpServer::start()
{
    if (!m_server->listen(QHostAddress::Any, m_port)) {
        emit errorOccurred(m_server->errorString());
        return false;
    }
    qDebug() << "TCP Server listening on port" << m_port;
    return true;
}

void TcpServer::stop()
{
    for (auto *client : m_clients.keys()) {
        client->disconnectFromHost();
    }
    m_server->close();
}

void TcpServer::onNewConnection()
{
    while (QTcpSocket *socket = m_server->nextPendingConnection()) {
        QString addr = QStringLiteral("%1:%2")
            .arg(socket->peerAddress().toString())
            .arg(socket->peerPort());

        m_clients.insert(socket, addr);
        connect(socket, &QTcpSocket::readyRead, this, &TcpServer::onReadyRead);
        connect(socket, &QTcpSocket::disconnected, this, &TcpServer::onDisconnected);

        // 欢迎消息
        socket->write("Welcome! You are connected.\n");
        emit clientConnected(addr);
        qDebug() << "Client connected:" << addr;
    }
}

void TcpServer::onReadyRead()
{
    auto *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;

    while (socket->canReadLine()) {
        QString line = QString::fromUtf8(socket->readLine()).trimmed();
        if (!line.isEmpty()) {
            emit messageReceived(m_clients.value(socket), line);

            // 广播给其他客户端
            QByteArray msg = QStringLiteral("[%1]: %2\n")
                .arg(m_clients.value(socket), line).toUtf8();
            for (auto *client : m_clients.keys()) {
                if (client != socket) {
                    client->write(msg);
                }
            }
        }
    }
}

void TcpServer::onDisconnected()
{
    auto *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;

    QString addr = m_clients.take(socket);
    emit clientDisconnected(addr);
    socket->deleteLater();
    qDebug() << "Client disconnected:" << addr;
}

void TcpServer::broadcast(const QString &message)
{
    QByteArray data = message.toUtf8() + "\n";
    for (auto *client : m_clients.keys()) {
        client->write(data);
    }
}

TCP 客户端 / TCP Client

// tcpclient.cpp - 简单 TCP 客户端
#include <QTcpSocket>
#include <QDebug>

class TcpClient : public QObject {
    Q_OBJECT

public:
    explicit TcpClient(QObject *parent = nullptr)
        : QObject(parent), m_socket(new QTcpSocket(this))
    {
        connect(m_socket, &QTcpSocket::connected, this, [this]() {
            qDebug() << "Connected to server";
            emit connected();
        });
        connect(m_socket, &QTcpSocket::readyRead, this, [this]() {
            while (m_socket->canReadLine()) {
                QString line = QString::fromUtf8(m_socket->readLine()).trimmed();
                emit messageReceived(line);
            }
        });
        connect(m_socket, &QTcpSocket::disconnected, this, &TcpClient::disconnected);
        connect(m_socket, &QAbstractSocket::errorOccurred, this, [this]() {
            emit errorOccurred(m_socket->errorString());
        });
    }

    void connectToHost(const QString &host, quint16 port) {
        m_socket->connectToHost(host, port);
    }

    void send(const QString &message) {
        if (m_socket->state() == QAbstractSocket::ConnectedState) {
            m_socket->write((message + "\n").toUtf8());
        }
    }

signals:
    void connected();
    void disconnected();
    void messageReceived(const QString &msg);
    void errorOccurred(const QString &error);

private:
    QTcpSocket *m_socket;
};

Python 示例 (PySide6)

#!/usr/bin/env python3
"""TCP 服务端示例 - PySide6"""

import sys
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtNetwork import QTcpServer, QTcpSocket, QHostAddress


class ChatServer(QObject):
    client_connected = Signal(str)
    client_disconnected = Signal(str)
    message_received = Signal(str, str)

    def __init__(self, port: int = 9999, parent=None):
        super().__init__(parent)
        self._server = QTcpServer()
        self._clients: dict[QTcpSocket, str] = {}
        self._port = port
        self._server.newConnection.connect(self._on_new_connection)

    def start(self) -> bool:
        if not self._server.listen(QHostAddress.SpecialAddress.Any, self._port):
            print(f"Error: {self._server.errorString()}")
            return False
        print(f"Server listening on port {self._port}")
        return True

    def stop(self):
        for socket in self._clients:
            socket.disconnectFromHost()
        self._server.close()

    def broadcast(self, message: str, exclude: QTcpSocket = None):
        data = (message + "\n").encode()
        for socket in self._clients:
            if socket != exclude:
                socket.write(data)

    @Slot()
    def _on_new_connection(self):
        while (socket := self._server.nextPendingConnection()):
            addr = f"{socket.peerAddress().toString()}:{socket.peerPort()}"
            self._clients[socket] = addr
            socket.readyRead.connect(lambda s=socket: self._on_ready_read(s))
            socket.disconnected.connect(lambda s=socket: self._on_disconnected(s))
            socket.write(b"Welcome!\n")
            self.client_connected.emit(addr)
            print(f"Client connected: {addr}")

    def _on_ready_read(self, socket: QTcpSocket):
        while socket.canReadLine():
            line = socket.readLine().data().decode().strip()
            if line:
                addr = self._clients.get(socket, "unknown")
                self.message_received.emit(addr, line)
                self.broadcast(f"[{addr}]: {line}", exclude=socket)

    def _on_disconnected(self, socket: QTcpSocket):
        addr = self._clients.pop(socket, "unknown")
        self.client_disconnected.emit(addr)
        socket.deleteLater()
        print(f"Client disconnected: {addr}")


if __name__ == "__main__":
    from PySide6.QtCore import QCoreApplication
    app = QCoreApplication(sys.argv)
    server = ChatServer(9999)
    if server.start():
        sys.exit(app.exec())

5.3 UDP 通信 / UDP Communication

// udpsocket 示例
#include <QUdpSocket>

class UdpHandler : public QObject {
    Q_OBJECT

public:
    explicit UdpHandler(QObject *parent = nullptr)
        : QObject(parent), m_socket(new QUdpSocket(this))
    {
        connect(m_socket, &QUdpSocket::readyRead,
                this, &UdpHandler::onReadyRead);
    }

    bool bind(quint16 port) {
        return m_socket->bind(QHostAddress::Any, port);
    }

    void send(const QHostAddress &addr, quint16 port, const QByteArray &data) {
        m_socket->writeDatagram(data, addr, port);
    }

    // 广播
    void broadcast(quint16 port, const QByteArray &data) {
        m_socket->writeDatagram(data, QHostAddress::Broadcast, port);
    }

private slots:
    void onReadyRead() {
        while (m_socket->hasPendingDatagrams()) {
            QByteArray data;
            data.resize(m_socket->pendingDatagramSize());
            QHostAddress sender;
            quint16 port;

            m_socket->readDatagram(data.data(), data.size(), &sender, &port);
            qDebug() << "UDP from" << sender.toString() << ":" << port
                     << "data:" << data;
        }
    }

private:
    QUdpSocket *m_socket;
};

5.4 HTTP 客户端 / HTTP Client

QNetworkAccessManager 示例

// httpclient.h
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

class HttpClient : public QObject {
    Q_OBJECT

public:
    explicit HttpClient(QObject *parent = nullptr)
        : QObject(parent), m_manager(new QNetworkAccessManager(this)) {}

    // GET 请求
    void get(const QUrl &url) {
        QNetworkRequest request(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

        QNetworkReply *reply = m_manager->get(request);
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            reply->deleteLater();
            if (reply->error() == QNetworkReply::NoError) {
                QByteArray data = reply->readAll();
                emit getFinished(QJsonDocument::fromJson(data));
            } else {
                emit errorOccurred(reply->errorString());
            }
        });
    }

    // POST 请求 (JSON)
    void post(const QUrl &url, const QJsonObject &json) {
        QNetworkRequest request(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

        QJsonDocument doc(json);
        QNetworkReply *reply = m_manager->post(request, doc.toJson());
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            reply->deleteLater();
            if (reply->error() == QNetworkReply::NoError) {
                emit postFinished(QJsonDocument::fromJson(reply->readAll()));
            } else {
                emit errorOccurred(reply->errorString());
            }
        });
    }

    // PUT 请求
    void put(const QUrl &url, const QJsonObject &json) {
        QNetworkRequest request(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
        QJsonDocument doc(json);
        QNetworkReply *reply = m_manager->put(request, doc.toJson());
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            reply->deleteLater();
            if (reply->error() == QNetworkReply::NoError) {
                emit putFinished(QJsonDocument::fromJson(reply->readAll()));
            } else {
                emit errorOccurred(reply->errorString());
            }
        });
    }

    // DELETE 请求
    void deleteResource(const QUrl &url) {
        QNetworkRequest request(url);
        QNetworkReply *reply = m_manager->deleteResource(request);
        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            reply->deleteLater();
            if (reply->error() == QNetworkReply::NoError) {
                emit deleteFinished(true);
            } else {
                emit errorOccurred(reply->errorString());
            }
        });
    }

signals:
    void getFinished(const QJsonDocument &response);
    void postFinished(const QJsonDocument &response);
    void putFinished(const QJsonDocument &response);
    void deleteFinished(bool success);
    void errorOccurred(const QString &error);

private:
    QNetworkAccessManager *m_manager;
};

Python HTTP 客户端

#!/usr/bin/env python3
"""HTTP REST 客户端 - PySide6"""

import json
import sys
from PySide6.QtCore import QObject, Signal, Slot, QUrl
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply


class RestClient(QObject):
    finished = Signal(int, dict)  # status_code, body
    error = Signal(str)

    def __init__(self, base_url: str = "", parent=None):
        super().__init__(parent)
        self._manager = QNetworkAccessManager()
        self._base_url = base_url.rstrip("/")

    def get(self, path: str):
        request = QNetworkRequest(QUrl(f"{self._base_url}{path}"))
        request.setHeader(
            QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/json"
        )
        reply = self._manager.get(request)
        reply.finished.connect(lambda: self._handle_reply(reply))

    def post(self, path: str, data: dict):
        request = QNetworkRequest(QUrl(f"{self._base_url}{path}"))
        request.setHeader(
            QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/json"
        )
        body = json.dumps(data).encode()
        reply = self._manager.post(request, body)
        reply.finished.connect(lambda: self._handle_reply(reply))

    def _handle_reply(self, reply: QNetworkReply):
        reply.deleteLater()
        status = reply.attribute(
            QNetworkRequest.Attribute.HttpStatusCodeAttribute
        )
        if reply.error() == QNetworkReply.NetworkError.NoError:
            body = json.loads(reply.readAll().data())
            self.finished.emit(status or 200, body)
        else:
            self.error.emit(reply.errorString())


# 使用示例
def main():
    from PySide6.QtCore import QCoreApplication, QTimer

    app = QCoreApplication(sys.argv)
    client = RestClient("https://jsonplaceholder.typicode.com")

    client.finished.connect(
        lambda code, body: print(f"Status: {code}, Data: {json.dumps(body, indent=2)[:200]}")
    )
    client.error.connect(lambda err: print(f"Error: {err}"))

    QTimer.singleShot(0, lambda: client.get("/posts/1"))
    QTimer.singleShot(2000, app.quit)
    app.exec()

5.5 WebSocket / WebSocket

// websocketclient.h
#include <QWebSocket>

class WebSocketClient : public QObject {
    Q_OBJECT

public:
    explicit WebSocketClient(QObject *parent = nullptr)
        : QObject(parent), m_socket(new QWebSocket)
    {
        connect(m_socket, &QWebSocket::connected, this, [this]() {
            qDebug() << "WebSocket connected";
            emit connected();
        });
        connect(m_socket, &QWebSocket::textMessageReceived,
                this, [this](const QString &msg) {
            emit messageReceived(msg);
        });
        connect(m_socket, &QWebSocket::disconnected, this, &WebSocketClient::disconnected);
        connect(m_socket, &QWebSocket::errorOccurred, this, [this]() {
            emit errorOccurred(m_socket->errorString());
        });
    }

    void connectToServer(const QUrl &url) {
        m_socket->open(url);
    }

    void send(const QString &message) {
        if (m_socket->state() == QAbstractSocket::ConnectedState) {
            m_socket->sendTextMessage(message);
        }
    }

    void close() { m_socket->close(); }

signals:
    void connected();
    void disconnected();
    void messageReceived(const QString &message);
    void errorOccurred(const QString &error);

private:
    QWebSocket *m_socket;
};

5.6 JSON 处理 / JSON Handling

// JSON 操作速查 / JSON Quick Reference

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

void jsonExamples()
{
    // 1. 创建 JSON 对象
    QJsonObject person;
    person["name"] = "张三";
    person["age"] = 30;
    person["active"] = true;

    QJsonArray hobbies;
    hobbies << "读书" << "编程" << "跑步";
    person["hobbies"] = hobbies;

    // 嵌套对象
    QJsonObject address;
    address["city"] = "北京";
    address["zip"] = "100000";
    person["address"] = address;

    // 序列化
    QJsonDocument doc(person);
    QByteArray json = doc.toJson(QJsonDocument::Indented);
    qDebug().noquote() << json;

    // 2. 解析 JSON
    QJsonDocument parsed = QJsonDocument::fromJson(json);
    QJsonObject root = parsed.object();

    QString name = root["name"].toString();
    int age = root["age"].toInt();
    QJsonArray h = root["hobbies"].toArray();

    for (const auto &hobby : h) {
        qDebug() << "Hobby:" << hobby.toString();
    }

    // 3. 深层访问
    QString city = root["address"].toObject()["city"].toString();
    qDebug() << "City:" << city;
}

Python JSON

"""Python JSON 处理"""
import json
from PySide6.QtCore import QJsonDocument, QJsonObject, QJsonArray

# 方式1: 直接用 Python json 模块 (推荐)
data = {
    "name": "张三",
    "age": 30,
    "hobbies": ["读书", "编程", "跑步"],
    "address": {"city": "北京", "zip": "100000"}
}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
parsed = json.loads(json_str)

# 方式2: 使用 Qt JSON API
doc = QJsonDocument.fromJson(json_str.encode())
obj = doc.object()
name = obj["name"].toString()

注意事项 / Important Notes

⚠️ 异步编程 / Asynchronous Programming

Qt 网络操作都是异步的,不要在信号槽回调之外阻塞等待。 所有结果通过信号返回。

Qt network operations are asynchronous. Don’t block-wait outside callbacks. All results come via signals.

⚠️ SSL 配置 / SSL Configuration

HTTPS 请求需要 SSL 库支持。确保 OpenSSL 已安装。 某些平台需要手动部署 SSL 库。

HTTPS requires SSL library support. Ensure OpenSSL is installed.

⚠️ 错误处理 / Error Handling

始终检查 QNetworkReply::error()。网络请求失败是常态。 Always check QNetworkReply::error(). Network failures are normal.


扩展阅读 / Further Reading

资源 / Resource链接 / Link
Qt Network 文档https://doc.qt.io/qt-6/qtnetwork-index.html
QNetworkAccessManagerhttps://doc.qt.io/qt-6/qnetworkaccessmanager.html
JSON 文档https://doc.qt.io/qt-6/json.html
WebSocket 示例https://doc.qt.io/qt-6/qtnetwork-websockets-example.html

04 - QML 与 Qt Quick | 06 - Qt 数据库