基于Qt开发的多线程TCP服务端

news2025/5/13 1:10:45

目录

      • 一、Qt TCP服务端开发环境准备
          • 1. 项目配置
          • 2. 核心类说明
      • 二、服务端搭建步骤详解
          • 步骤1:初始化服务端对象
          • 步骤2:启动端口监听
          • 步骤3:处理客户端连接
      • 三、数据通信与状态管理
          • 1. 数据收发实现
          • 2. 客户端状态监控
      • 四、进阶功能扩展
          • 1. 多客户端并发处理
          • 2. 心跳检测机制
      • 五、调试与优化建议
          • 1. 网络调试工具
          • 2. 错误处理
          • 3. 性能优化
      • 六、完整代码示例:
          • 1.tcpserver.h
          • 2.tcpserver.cpp
          • 3.session.h
          • 4.session.cpp
          • 5.main.cpp

成果展示:
在这里插入图片描述

一、Qt TCP服务端开发环境准备

1. 项目配置

在Qt Creator中新建控制台或GUI项目,需在.pro文件中添加网络模块依赖:

QT += network  # 引用网络库

该配置使项目支持QTcpServer和QTcpSocket类,这是实现TCP通信的核心组件。

2. 核心类说明
  • QTcpServer:负责监听端口并接受客户端连接请求,通过incomingConnection(qintptr socketDescripto)信号触发新连接处理。
  • QTcpSocket:用于与客户端建立通信通道,支持异步数据读写和状态管理。

二、服务端搭建步骤详解

步骤1:初始化服务端对象
 	m_port = 9999;//监听端口号
    m_maxConns = 100;//最大连接数
    this->setMaxPendingConnections(m_maxConns);
步骤2:启动端口监听
//监听端口
    if (!this->listen(QHostAddress::Any, port)){
        qDebug()<<"TCP服务端启动失败,请检查端口是否被占用";
        return false;
    }
    m_isRunning = true;
    qDebug()<<"TCP服务端启动成功,IP:"<<this->serverAddress().toIPv4Address()<<",端口:"<<port;
步骤3:处理客户端连接
void TcpServer::incomingConnection(qintptr socketDescriptor)
{
    Session *session = new Session(socketDescriptor);
    session->moveToThread(m_thread);
    connect(session,&Session::disconn,this,[session](){
        session->deleteLater();
    });
    connect(session,&Session::disconn,this,&TcpServer::OnDisConnectedToHost);
    connect(session,&Session::activeconn,this,&TcpServer::OnActiveConn);
    m_socketMap.insert(socketDescriptor,session);
    qInfo()<<QString("有新的客户端连接,%1:%2").arg(session->peerAddress().toString()).arg(session->peerPort());
    OnActiveConn(socketDescriptor);
    if(!m_thread->isRunning()){
        m_thread->start();
    }
}

此处通过incomingConnection获取与客户端通信的套接字对象,并关联数据接收和断开事件。

三、数据通信与状态管理

1. 数据收发实现
  • 发送数据:通过套接字write()方法写入字节流。
void Session::sendMsg(const QString msg)
{
    this->write(msg.toUtf8());
    this->flush(); // 确保数据立即发送
}
  • 接收数据:监听readyRead()信号,调用readAll()获取内容。
void Session::recvMsg()
{
    emit activeconn(m_socketDescriptor);
    QByteArray msg;
    msg.append(this->readAll());
    while(this->bytesAvailable()>0){
        this->waitForReadyRead(10);
        msg.append(this->readAll());
    }
    QTextCodec *codec = QTextCodec::codecForName("GBK");
    QString data = codec->toUnicode(msg);
    qDebug()<<data;
}
2. 客户端状态监控
  • 断开检测:通过disconnected()信号触发清理操作
void Session::disconnectedToHost()
{
    QString ip = this->peerAddress().toString();
    qDebug()<<QString("客户端%1:%2断开连接").arg(ip).arg(this->peerPort());
    this->close();
    emit disconn(m_socketDescriptor);
}

四、进阶功能扩展

1. 多客户端并发处理

Qt通过为每个连接创建独立QTcpSocket实现多线程通信。可通过容器管理多个套接字:

	session->moveToThread(m_thread);
	m_socketMap.insert(socketDescriptor,session);
2. 心跳检测机制

定时发送心跳包验证连接活性:

void TcpServer::OnDelInvalidConn()
{
    QVector<qintptr> socketDescriptorVec;
    uint stamp = QDateTime::currentDateTime().toTime_t();
    foreach (const auto it,m_socketTimeout.keys()) {
        if(qAbs(stamp-m_socketTimeout.value(it))>600){
            socketDescriptorVec.push_back(it);
        }
    }
    foreach (const auto it,socketDescriptorVec) {
        m_socketMap.value(it)->disconn(it);
    }
    socketDescriptorVec.clear();
}

void TcpServer::OnActiveConn(qintptr socketDescriptor)
{
    m_socketTimeout.insert(socketDescriptor,QDateTime::currentDateTime().toTime_t());
}

五、调试与优化建议

1. 网络调试工具

使用Telnet或TCP/UDP调试助手(如NetAssist)验证服务端功能。
测试命令示例:

telnet 127.0.0.1 9999
2. 错误处理
  • 监听失败时检查端口占用(QAbstractSocket::AddressInUseError)
  • 使用QSocketNotifier监控异常状态(需启用事件循环)
3. 性能优化
  • 设置接收缓冲区大小:socket->setReadBufferSize(1024*1024)
  • 启用Nagle算法优化小数据包:socket->setSocketOption(QAbstractSocket::LowDelayOption, 0)

六、完整代码示例:

1.tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include<QObject>
#include<QtNetwork/QTcpServer>
#include "session.h"
#include<QTimer>
#include<QThreadPool>
#include<QThread>
class TcpServer:public QTcpServer
{
    Q_OBJECT
public:
    TcpServer();
    ~TcpServer();
    bool startTcpServer(quint16 port);
    void stopTcpServer();
    bool isStart();

protected:
    void incomingConnection(qintptr socketDescriptor);
public slots:
    void OnDisConnectedToHost(qintptr socketDescriptor);
    void OnDelInvalidConn();//删除无效连接
    void OnActiveConn(qintptr socketDescriptor);//活跃连接
private:
    unsigned int m_maxConnections;			//最大连接数
    bool m_isRunning;						//判断服务启动状态,false停止,true运行
    QList<QTcpSocket*> m_socketList;
    QMap<qintptr,Session*> m_socketMap;
    QMap<qintptr,uint> m_socketTimeout;
    int m_port;
    int m_maxConns;
    QTimer m_timer;
    QThread *m_thread;

};
#endif // TCPSERVER_H
2.tcpserver.cpp
#include "tcpserver.h"
#include <QDateTime>

TcpServer::TcpServer()
{
    m_isRunning = false;
    m_socketMap.clear();
    m_thread = new QThread;

    m_port = 9999;
    m_maxConns = 100;
    this->setMaxPendingConnections(m_maxConns);
    startTcpServer(m_port);
    connect(&m_timer,&QTimer::timeout,this,&TcpServer::OnDelInvalidConn);
    m_timer.start(5*60*1000);
}

TcpServer::~TcpServer()
{
    m_timer.stop();
    m_socketMap.clear();
    stopTcpServer();
    m_thread->quit();
    m_thread->wait();
    m_thread->deleteLater();
}

bool TcpServer::startTcpServer(quint16 port)
{
    if (isStart())
        return true;
    //监听端口
    if (!this->listen(QHostAddress::Any, port)){
        qDebug()<<"TCP服务端启动失败,请检查端口是否被占用";
        return false;
    }
    m_isRunning = true;
    qDebug()<<"TCP服务端启动成功,IP:"<<this->serverAddress().toIPv4Address()<<",端口:"<<port;
    return true;
}

void TcpServer::stopTcpServer()
{
    if (!isStart())
        return;
    //关闭监听
    this->close();
    m_isRunning = false;
}

bool TcpServer::isStart()
{
    return m_isRunning;
}

void TcpServer::incomingConnection(qintptr socketDescriptor)
{
    Session *session = new Session(socketDescriptor);
    session->moveToThread(m_thread);
    connect(session,&Session::disconn,this,[session](){
        session->deleteLater();
    });
    connect(session,&Session::disconn,this,&TcpServer::OnDisConnectedToHost);
    connect(session,&Session::activeconn,this,&TcpServer::OnActiveConn);
    m_socketMap.insert(socketDescriptor,session);
    qInfo()<<QString("有新的客户端连接,%1:%2").arg(session->peerAddress().toString()).arg(session->peerPort());
    OnActiveConn(socketDescriptor);
    if(!m_thread->isRunning()){
        m_thread->start();
    }
}

void TcpServer::OnDisConnectedToHost(qintptr socketDescriptor)
{
    m_socketMap.remove(socketDescriptor);
    m_socketTimeout.remove(socketDescriptor);
}

void TcpServer::OnDelInvalidConn()
{
    QVector<qintptr> socketDescriptorVec;
    uint stamp = QDateTime::currentDateTime().toTime_t();
    foreach (const auto it,m_socketTimeout.keys()) {
        if(qAbs(stamp-m_socketTimeout.value(it))>600){
            socketDescriptorVec.push_back(it);
        }
    }
    foreach (const auto it,socketDescriptorVec) {
        m_socketMap.value(it)->disconn(it);
    }
    socketDescriptorVec.clear();
}

void TcpServer::OnActiveConn(qintptr socketDescriptor)
{
    m_socketTimeout.insert(socketDescriptor,QDateTime::currentDateTime().toTime_t());
}

3.session.h
#ifndef SESSION_H
#define SESSION_H

#include<QtNetwork/QTcpSocket>
#include<QObject>

class Session:public QTcpSocket
{
    Q_OBJECT
public:
    Session(qintptr socketDescriptor);
    ~Session();
    void sendMsg(const QString msg);

signals:
    void disconn(qintptr socketDescriptor);
    void activeconn(qintptr socketDescriptor);
public slots:
    void disconnectedToHost();
    void recvMsg();
private:
    qintptr m_socketDescriptor;
};

#endif // SESSION_H

4.session.cpp
#include"session.h"
#include<QtNetwork/QHostAddress>
#include <QTextCodec>


Session::Session(qintptr socketDescriptor)
    :m_socketDescriptor(socketDescriptor)
{
    qRegisterMetaType<qintptr>("qintptr");
    this->setSocketDescriptor(m_socketDescriptor);
    connect(this, &QTcpSocket::readyRead, this, &Session::recvMsg);
    connect(this, &QTcpSocket::disconnected, this, &Session::disconnectedToHost);
}

Session::~Session()
{

}

void Session::sendMsg(const QString msg)
{
    this->write(msg.toUtf8());
}

void Session::disconnectedToHost()
{
    QString ip = this->peerAddress().toString();
    qDebug()<<QString("客户端%1:%2断开连接").arg(ip).arg(this->peerPort());
    this->close();
    emit disconn(m_socketDescriptor);
}

void Session::recvMsg()
{
    emit activeconn(m_socketDescriptor);
    QByteArray msg;
    msg.append(this->readAll());
    while(this->bytesAvailable()>0){
        this->waitForReadyRead(10);
        msg.append(this->readAll());
    }
    QTextCodec *codec = QTextCodec::codecForName("GBK");
    QString data = codec->toUnicode(msg);
    qDebug()<<data;
}


5.main.cpp
#include <QCoreApplication>
#include "tcpserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TcpServer tcps;
    return a.exec();
}

通过以上步骤,开发者可快速构建一个稳定高效的Qt TCP服务端,实际项目中需根据业务需求扩展身份验证、数据加密等功能。Qt的异步编程模型与丰富的网络API,使其成为开发跨平台网络应用的理想选择。
更多Qt开发实战教程持续更新中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2374305.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos离线安装mysql、redis、nginx等工具缺乏层层依赖的解决方案

Centos离线安装mysql、redis、nginx等工具缺乏层层依赖的解决方案 引困境yum-utils破局 引 前段时间&#xff0c;有个项目有边缘部署的需求&#xff0c;一台没有的外网的Centos系统服务器&#xff0c;需要先安装jdk&#xff0c;node&#xff0c;mysql&#xff0c;reids&#xf…

从零开始开发纯血鸿蒙应用之XML解析

从零开始开发纯血鸿蒙应用 〇、前言一、鸿蒙SDK中的 XML API1、ohos.xml2、ohos.convertxml 三、XML 解析实践1、源数据结构2、定义映射关系3、定义接收对象4、获取文章信息 四、总结 〇、前言 在前后端的数据传输方面&#xff0c;论格式化形式&#xff0c;JSON格式自然是首选…

10.王道_HTTP

1. 互联网时代的诞生 2. HTTP的基本特点 2.1客户端-服务端模型 2.2 无状态协议 2.3 可靠性 2.4 文本协议 3. HTML,CSS和JS 4. HTTP的各个组件 4.1 客户端 4.2 服务端 4.3 代理 5. URI和URL 6. HTTP报文 HTTP报文分为两种——请求报文和响应报文。 6.1 GET请求示例 注意&#…

解决stm32HAL库使用vscode打开,识别不到头文件及uint8_t等问题

解决stm32HAL库使用vscode打开&#xff0c;识别不到头文件及uint8_t等问题 结论&#xff0c;问题有2问题1问题2解决办法将Keil Assistant自动生成的.vscode目录复制到MDK-ARM上层目录将Keil Assistant自动生成的.vscode目录复制到MDK-ARM上层目录将Keil Assistant自动生成的.vs…

uniapp-商城-50-后台 商家信息(输入进行自定义规则验证)

本文介绍了如何在后台管理系统中添加和展示商家信息&#xff0c;包括商家logo、名称、电话、地址和介绍等内容&#xff0c;并支持后期上传营业许可等文件。通过使用uni-app的uni-forms组件&#xff0c;可以方便地实现表单的创建、校验和管理操作。文章详细说明了组件的引入、页…

网页版部署MySQL + Qwen3-0.5B + Flask + Dify 工作流部署指南

1. 安装MySQL和PyMySQL 安装MySQL # 在Ubuntu/Debian上安装 sudo apt update sudo apt install mysql-server sudo mysql_secure_installation# 启动MySQL服务 sudo systemctl start mysql sudo systemctl enable mysql 安装PyMySQL pip install pymysql 使用 apt 安装 My…

WEBSTORM前端 —— 第2章:CSS —— 第8节:网页制作2(小兔鲜儿)

目录 1.项目目录 2.SEO 三大标签 3.Favicon 图标 4.版心 5.快捷导航(shortcut) 6.头部(header) 7.底部(footer) 8.banner 9.banner – 圆点 10.新鲜好物(goods) 11.热门品牌(brand) 12.生鲜(fresh) 13.最新专题(topic) 1.项目目录 【xtx-pc】 ima…

仓储车间安全革命:AI叉车防撞装置系统如何化解操作风险

在现代物流体系中&#xff0c;仓储承担着货物储存、保管、分拣和配送等重要任务。但现代仓储行业的安全现状却不容乐观&#xff0c;诸多痛点严重制约着其发展&#xff0c;其中叉车作业的安全问题尤为突出。相关数据显示&#xff0c;全球范围内&#xff0c;每年因叉车事故导致的…

修改图像分辨率

在这个教程中&#xff0c;您将学习如何使用Python和深度学习技术来调整图像的分辨率。我们将从基础的图像处理技术开始&#xff0c;逐步深入到使用预训练的深度学习模型进行图像超分辨率处理。 一、常规修改方法 1. 安装Pillow库 首先&#xff0c;你需要确保你的Python环境中…

Redis 主从同步与对象模型(四)

目录 1.淘汰策略 1.1 expire/pexpire&#xff08;设置键的过期时间&#xff09; 1.2 配置 1.maxmemory 2.maxmemory-policy 3.maxmemory-samples 2.持久化 2.1背景 2.2 fork 的写时复制机制 2.3 大 key 3.持久化方式 3.1 aof&#xff08;Apped Only File&#xff09…

Linux系列:如何用perf跟踪.NET程序的mmap泄露

一&#xff1a;背景 1. 讲故事 如何跟踪.NET程序的mmap泄露&#xff0c;这个问题困扰了我差不多一年的时间&#xff0c;即使在官方的github库中也找不到切实可行的方案&#xff0c;更多海外大佬只是推荐valgrind这款工具&#xff0c;但这款工具底层原理是利用模拟器&#xff…

如何租用服务器并通过ssh连接远程服务器终端

这里我使用的是智算云扉 没有打广告 但确实很便宜 还有二十小时免费额度 链接如下 注册之后 租用新实例 选择操作系统 选择显卡型号 点击租用 选择计费方式 选择镜像 如果跑深度学习的话 就选项目对应的torch版本 没有的话 就创建纯净的cuda 自己安装 点击创建实例 创建之后 …

华为设备链路聚合实验:网络工程实战指南

链路聚合就像为网络搭建 “并行高速路”&#xff0c;既能扩容带宽&#xff0c;又能保障链路冗余&#xff0c;超实用&#xff01; 一、实验拓扑速览 图中两台交换机 LSW1 和 LSW2&#xff0c;PC1、PC2 归属 VLAN 10&#xff0c;PC3 归属 VLAN 30。LSW1 与 LSW2 通过 GE0/0/1、…

AUTOSAR图解==>AUTOSAR_TR_AIDesignPatternsCatalogue

AUTOSAR 人工智能设计模式目录 AUTOSAR传感器执行器与仲裁设计模式的深入解析与图解 目录 简介传感器和执行器模式 架构概述组件结构交互流程应用场景 多请求者或提供者之间的仲裁模式 架构概述组件结构仲裁流程应用场景 总结 1. 简介 AUTOSAR&#xff08;AUTomotive Open Sy…

双系统电脑中如何把ubuntu装进外接移动固态硬盘

电脑&#xff1a;win11 ubuntu22.04 实体机 虚拟机&#xff1a;VMware17 镜像文件&#xff1a;ubuntu-22.04.4-desktop-amd64.iso 或者 ubuntu20.4的镜像 外接固态硬盘1个 一、首先win11中安装vmware17 具体安装方法&#xff0c;网上很多教程 二、磁盘分区 1.在笔…

【C语言】程序的预处理,#define详解

一、预定义符号 二、#define 1.#define定义标识符 #define &#xff0b; 自定义名称 &#xff0b; 代替的内容 例&#xff1a; #define MAX 100 #define CASE break;case #define CASE break;caseint main() {int n 0;switch (n){case 1:CASE 2:CASE 3:CASE 4:}return …

NVM完全指南:安装、配置与最佳实践

发布于 2025年5月7日 • 阅读时间&#xff1a;10分钟 &#x1f4a1; TL;DR: 本文详细介绍了如何完整卸载旧版Node.js&#xff0c;安装NVM&#xff0c;配置阿里云镜像源&#xff0c;以及设置node_global与node_cache目录&#xff0c;打造高效Node.js开发环境。 &#x1f4cb; 目…

(二)毛子整洁架构(CQRS/Dapper/领域事件处理器/垂直切片)

文章目录 项目地址一、Application 层1.1 定义CQRS的接口以及其他服务1. Command2. IQuery查询3. 当前时间服务接口4. 邮件发送服务接口 1.2 ReserveBooking Command1. 处理传入的参数2. ReserveBookingCommandHandler3. BookingReservedDomainEvent 1.3 Query使用Sql查询1. 创…

如何修改MySQL数据库密码

文章目录 一、忘记数据库密码该如何修改1. 关闭数据库的服务2.跳过安全检查3. 重置密码4.查询用户是否存在5.退出验证密码是否正确 二、未忘记密码该如何修改密码1.直接修改密码2.登录mysql 时间久了&#xff0c;忘记数据库密码了。。。。。 一、忘记数据库密码该如何修改 1. …

【Python】mat npy npz 文件格式

1、简介 MAT 文件和 NP&#xff08;.npy 或 .npz&#xff09;文件是两种不同的格式&#xff0c;用于存储数组数据。它们分别由 MATLAB 和 NumPy 开发&#xff0c;主要用于各自环境中的数据存储和交换。以下是这两种格式的主要区别&#xff1a; 1.1 格式和用途 MAT 文件&…