当前位置: 首页 > news >正文

Chap25-SeparateUI-NetThread

Separate UI-Net Thread

线程分离

我们的线程独立之前:

image-20251210171859843

独立之后:

image-20251210171921155

之所以考虑到将主线程和网络线程分开,是因为接下来要做文件传输,如果和主线程在一起,那么UI可能会卡顿,比如传输音频,大文件之类的。

因此我们将TcpManager也就是网络线程独立到新的线程,不要阻塞我们的主线程:

class TcpThread:public std::enable_shared_from_this<TcpThread> {
public:TcpThread();~TcpThread();
private:QThread* _tcp_thread;
};TcpThread::TcpThread()
{_tcp_thread = new QThread();TcpMgr::GetInstance()->moveToThread(_tcp_thread);QObject::connect(_tcp_thread, &QThread::finished, _tcp_thread, &QObject::deleteLater);_tcp_thread->start();
}TcpThread::~TcpThread()
{_tcp_thread->quit();
}// main.cpp//启动tcp线程
TcpThread tcpthread;
MainWindow w;
w.show();
return a.exec();

分离的问题

在原来的单线程中,直接信号和槽进行连接没有问题,信号传递参数,槽函数执行对应的逻辑。但是在qt的信号和槽在多线程中跨线程传递参数的时候,对于常用内置类型,比如int,QString等,没有问题正常传递。否则,对于复杂的类型比如std::vector,std::shared_ptr等等,传递过程会有极大概率异常。例如std::vector他的内存管理机制和Qt的队列管理机制不兼容,盲目的传参会导致未定义。所以我们需要使用qt的元对象系统进行声明和注册。

比如TLV的T,是我们的自定义枚举类RequestType,但是Qt并不认知这个参数,我们在定义之后,加一个宏声明:

Q_DECLARE_METATYPE(RequestType)

还有对于struct结构的UserInfo:

Q_DECLARE_METATYPE(UserInfo)
Q_DECLARE_METATYPE(std::shared_ptr<UserInfo>)
Q_DECLARE_METATYPE(std::vector<std::shared_ptr<UserInfo>>)

除此之外,在使用槽函数之前,必须进行注册:

void TcpManager::RegisterMetaType()
{// 注册自定义类型qRegisterMetaType<RequestType>("RequestType");qRegisterMetaType<UserInfo>("UserInfo");qRegisterMetaType<FriendInfo>("FriendInfo");qRegisterMetaType<MessageItem>("MessageItem");qRegisterMetaType<ConversationItem>("ConversationItem");// 注册std::shared_ptr类型qRegisterMetaType<std::shared_ptr<UserInfo>>("std::shared_ptr<UserInfo>");qRegisterMetaType<std::shared_ptr<FriendInfo>>("std::shared_ptr<FriendInfo>");qRegisterMetaType<std::shared_ptr<MessageItem>>("std::shared_ptr<MessageItem>");qRegisterMetaType<std::shared_ptr<ConversationItem>>("std::shared_ptr<ConversationItem>");// 注册容器类型qRegisterMetaType<std::vector<std::shared_ptr<UserInfo>>>("std::vector<std::shared_ptr<UserInfo>>");qRegisterMetaType<std::vector<std::shared_ptr<MessageItem>>>("std::vector<std::shared_ptr<MessageItem>>");qRegisterMetaType<QList<std::shared_ptr<FriendInfo>>>("QList<std::shared_ptr<FriendInfo>>");
}

在 Qt 的信号/槽机制中,信号参数的传递方式取决于连接(connect)的类型,而连接类型又由发信号对象和接收槽对象所在的线程决定:

  1. 同线程(Direct Connection)
  • 如果信号和槽都在同一个线程里,默认使用 Direct Connection
  • Direct Connection 本质上就是一个普通的 C++ 函数调用,参数直接按值或按引用传递,编译时就已经知道了类型,不需要任何额外的元类型信息。
  • 因此,即使你没有把 SearchInfo 注册为 QMetaType,编译器也能直接生成函数调用代码,信号里就可以直接传递 SearchInfo
  1. 跨线程(Queued Connection)
  • 如果信号发送者和接收者不在同一个线程,Qt 会自动把连接转成 Queued Connection
  • Queued Connection 的实现是:当信号发出时,Qt 会把信号参数打包成一个事件(QEvent),然后把事件放到目标线程的事件队列里;目标线程的事件循环(QCoreApplication::processEvents())再把这个事件取出来,调用槽函数。
  • 这里的“打包”与“解包”就需要运行时才能确定参数类型,以及如何拷贝或序列化这个类型——这正是 Qt 元对象系统(QMetaType)要干的事情。
  • 如果没有把 SearchInfo 声明成一个元类型,Qt 就不知道如何在内部把它从一个线程“打包”到事件里,又如何在另一线程里还原。

因此,跨线程传递自定义类型,必须在类型定义后加上:

  • Q_DECLARE_METATYPE(SearchInfo)

并在运行时注册(通常在 main() 里调用一次):

  • qRegisterMetaType("SearchInfo");

小结

  • 同线程:Direct Connection,编译时直接调用,不需要Q_DECLARE_METATYPE
  • 跨线程:Queued Connection,需要运行时打包/解包参数,必须Q_DECLARE_METATYPE(以及 qRegisterMetaType)来注册你的自定义类型。

发送队列

在do_send_data函数中,我们直接进行了_socket.write(data),这样在我们数据量并不大的时候没有问题,很少会出现一次写不完的情况。但如果我们发送很大的大文件,比如几百MB,甚至几个G,虽然会有切片,但是每一次发送仍有可能因为发送缓冲区已经满了而发丝失败,这时候如果不处理的话,就会数据异常丢失。

解决思路是,可以模仿我们的服务器写法,添加一个发送队列,然后将要发送的数据投递到发送队列。

//发送队列
QQueue<QByteArray> _send_queue;
//正在发送的包
QByteArray  _current_block;
//当前已发送的字节数
qint64        _bytes_sent;
//是否正在发送
bool _pending;

修改发送逻辑

void TcpMgr::slot_send_data(ReqId reqId, QByteArray dataBytes)
{uint16_t id = reqId;// 计算长度(使用网络字节序转换)quint16 len = static_cast<quint16>(dataBytes.length());// 创建一个QByteArray用于存储要发送的所有数据QByteArray block;QDataStream out(&block, QIODevice::WriteOnly);// 设置数据流使用网络字节序out.setByteOrder(QDataStream::BigEndian);// 写入ID和长度out << id << len;// 添加字符串数据block.append(dataBytes);//判断是否正在发送if (_pending) {//放入队列直接返回,因为目前有数据正在发送_send_queue.enqueue(block);return;}// 没有正在发送,把这包设为“当前块”,重置计数,并写出去_current_block = block;        // ← 保存当前正在发送的 block_bytes_sent = 0;            // ← 归零_pending = true;         // ← 标记正在发送qint64 written = _socket.write(_current_block);qDebug() << "tcp mgr send byte data is" << _current_block<< ", write() returned" << written;
}

在此之后我们还要新加一个槽函数,关于每次写入之后,返回一个信号,指明写入的字节数,我们连接这个信号:

QObject::connect(&_socket, &QTcpSocket::bytesWritten, this, [this](qint64 bytes) {//更新发送数据_bytes_sent += bytes;//未发送完整if (_bytes_sent < _current_block.size()) {//继续发送auto data_to_send = _current_block.mid(_bytes_sent);_socket.write(data_to_send);return;}//发送完全,则查看队列是否为空if (_send_queue.isEmpty()) {//队列为空,说明已经将所有数据发送完成,将pending设置为false,这样后续要发送数据时可以继续发送_current_block.clear();_pending = false;_bytes_sent = 0;return;}//队列不为空,则取出队首元素_current_block = _send_queue.dequeue();_bytes_sent = 0;_pending = true;qint64 w2 = _socket.write(_current_block);qDebug() << "[TcpMgr] Dequeued and write() returned" << w2;
});
http://www.gsyq.cn/news/149058.html

相关文章:

  • 如果计算引擎是MapReduce,那么Hive能跑Spark SQL作业吗?
  • 告别复杂笔记软件!Memos+cpolar,让你的笔记随时随地可用
  • 佳能LBP2900 linux驱动 captdriver - 童晓伟
  • 为何选择 Go 语言进行开发?
  • Asio15-QuitGracefully
  • 基于Android的XX校园学习娱乐交流APP--论文小程序
  • 领导根本不关心你干了多少活,只在意这3点
  • 【课程设计/毕业设计】基于SpringBoot的植物知识分享系统的设计与实现基于SpringBoot的植物知识管理与分享平台的设计与实现【附源码、数据库、万字文档】
  • 手把手吃透设计模式内功:七大原则代码解析与重构指南
  • Elasticsearch倒排索引详解:如何实现高效搜索
  • excel如何筛选重复项?码住这4种方法!
  • Asio12-HandlePacketStickingProblemSimply
  • 第四章 SQL Server备份和还原
  • 【课程设计/毕业设计】基于springboot美发门店管理系统设计与实现基于springboot的美发商城系统【附源码、数据库、万字文档】
  • 计算机Java毕设实战-基于springboot的美发商城系统服务预约技师选择、到店时间预约【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 计算机Java毕设实战-基于SpringBoot的植物知识管理与分享平台的设计与实现家庭园艺种植分享平台设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 番茄小说下载器 2025.12.21 | 现代化、高效的番茄小说下载器,支持批量下载和多种格式导出
  • STM32平衡车工具-匿名助手+虚拟串口如何使用。
  • 计算机Java毕设实战-基于springboot+vue技术的二手车交易管理系统的设计与实现基于SpringBoot+Vue的二手车交易平台设计【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 2-[(2-叠氮乙酰基)氨基]-2-脱氧-D-吡喃甘露糖—糖生物学与代谢标记的关键化学探针 1971934-97-0
  • Java毕设选题推荐:基于springboot+vue技术的二手车交易管理系统的设计与实现汽车管理汽车品牌管理,公告类型管理,论坛管理【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 新品限免|国产大模型工程化实战:GLM-4.7与MiniMax M2.1 免费选型对比
  • Java毕设项目:基于springboot+vue技术的二手车交易管理系统的设计与实现(源码+文档,讲解、调试运行,定制等)
  • Java毕设选题推荐:基于Java的停车场管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • python自定义注解
  • 12/24第五章
  • python基于Vue的客户关系管理系统的设计与实现_3itcvt88
  • vue基于python的民宿房间预订推荐系统的设计与实现_7r8s9b63(pycharm django flask)
  • Pandoc转换Word文档:使用Lua过滤器统一调整Pandoc文档中的图片和表格格式
  • VFF-Net:一种取代反向传播的AI训练新算法