3. TCP多客户端并发处理

TCP多客户端并发处理 学习卡片

任务目标

实现一个基于 QTcpServer 的并发TCP服务器,能够同时接受多个客户端连接,并为每个连接独立收发数据。服务器采用“每连接一线程”模型,确保一个客户端的阻塞操作不影响其他客户端的通信。

实现方案

使用 QTcpServer::incomingConnection() 的默认机制:每当有新连接时,Qt 会自动创建对应的 QTcpSocket 并发出 newConnection() 信号。在槽函数中获取已连接的 socket,将其移入一个 QThread 工作线程,并在该线程中执行独立的收发逻辑。主线程仅负责监听和分发连接,不参与具体的数据通信。

完整代码

// server.h
#ifndef SERVER_H
#define SERVER_H

#include <QTcpServer>
#include <QThread>
#include <QTcpSocket>

class Server : public QTcpServer
{
    Q_OBJECT
public:
    explicit Server(QObject *parent = nullptr);

protected:
    void incomingConnection(qintptr socketDescriptor) override;
};

// 每个连接的工作线程
class ClientWorker : public QThread
{
    Q_OBJECT
public:
    explicit ClientWorker(qintptr socketDescriptor, QObject *parent = nullptr);
    void run() override;

private:
    qintptr m_socketDescriptor;
};

#endif // SERVER_H
// server.cpp
#include "server.h"
#include <QDataStream>
#include <QDebug>

Server::Server(QObject *parent) : QTcpServer(parent) {}

void Server::incomingConnection(qintptr socketDescriptor)
{
    // 为每个连接创建独立线程
    ClientWorker *worker = new ClientWorker(socketDescriptor);
    connect(worker, &QThread::finished, worker, &QObject::deleteLater);
    worker->start();
}

ClientWorker::ClientWorker(qintptr socketDescriptor, QObject *parent)
    : QThread(parent), m_socketDescriptor(socketDescriptor) {}

void ClientWorker::run()
{
    QTcpSocket socket;
    if (!socket.setSocketDescriptor(m_socketDescriptor)) {
        qWarning() << "Failed to set socket descriptor";
        return;
    }

    // 向客户端发送欢迎消息
    socket.write("Welcome! Send 'quit' to disconnect.\n");
    socket.flush();

    // 独立收发循环
    while (socket.state() == QAbstractSocket::ConnectedState) {
        if (!socket.waitForReadyRead(100)) {
            continue; // 超时继续检查连接状态
        }

        while (socket.bytesAvailable() > 0) {
            QByteArray data = socket.readAll();
            QString message = QString::fromUtf8(data).trimmed();

            if (message == "quit") {
                socket.write("Bye!\n");
                socket.flush();
                socket.disconnectFromHost();
                return;
            }

            // 回显收到的消息
            QString response = "Echo: " + message + "\n";
            socket.write(response.toUtf8());
            socket.flush();
        }
    }
}
// main.cpp
#include <QCoreApplication>
#include "server.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Server server;
    if (!server.listen(QHostAddress::Any, 8080)) {
        qCritical() << "Server failed to listen:" << server.errorString();
        return 1;
    }
    qInfo() << "Server listening on port 8080...";

    return a.exec();
}

项目文件(.pro)

QT += core network
QT -= gui

TARGET = tcp_server
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp server.cpp
HEADERS += server.h

运行与验证

编译运行:

qmake
make
./tcp_server

验证步骤:

  1. 启动服务器,观察终端输出 Server listening on port 8080...。
  2. 打开多个终端(至少2个),分别使用 nc localhost 8080 或 telnet localhost 8080 连接服务器。
  3. 每个客户端连接后应立即收到欢迎消息 Welcome! Send 'quit' to disconnect.
  4. 在任意客户端输入文本并回车,服务器应返回 Echo: 输入的文本。
  5. 在其中一个客户端输入 quit,该客户端收到 Bye! 后断开;此时其他客户端仍可正常收发消息,不受影响。

通过条件:上述步骤全部成功执行,且多个客户端同时在线时消息互不干扰,即证明服务器实现了并发处理。

关键说明

  • 本实现使用 waitForReadyRead() 配合超时实现非阻塞式轮询,避免线程永久阻塞无法退出。
  • 每个连接独立线程,线程数随客户端数量增长。对于高并发场景,可改用Qt线程池(QtConcurrent)或事件循环+状态机模型,但本案例聚焦于“每连接一线程”的直观实现。
  • 生产环境中建议限制最大连接数,防止资源耗尽。