Qt一步步搭建TcpServer2——线程池
来源:互联网 发布:高校教务网络管理系统 编辑:程序博客网 时间:2024/05/29 19:21
- 1封装TcpThread
- 2修改TcpSession
- 3添加SessionThreads
- 4修改TcpServer
- 5总结
承接上章:Qt一步步搭建TcpServer1——封装QTcpServer,QTcpSocket
本章将在TcpServer的基础上新增线程池,管理Session。
1、封装TcpThread
正所谓工欲善其事,必先利其器。要实现一个线程池,按照Qt的线程用法(如果不懂用法的同学,可以参看我这篇博客:性能特性测试系列4——QT线程与std::thread(下)之QThread)。要想在这里好好用上去,还是得自己重写一个线程类:
头文件:
#ifndef TCPTHREAD_H#define TCPTHREAD_H#include <QThread>#include <atomic>class TcpThread : public QThread{public: TcpThread(); ~TcpThread(); virtual void run()override; //会话数 std::atomic_uint32_t SessionCount = 0;};#endif // TCPTHREAD_H
cpp:
#include "TcpThread.h"TcpThread::TcpThread(){}TcpThread::~TcpThread(){}void TcpThread::run(){ exec();}
又是个看起来很简单的类,别着急,以后还要加东西的。
关于run里的exec()解释下:
添加进事件循环,这样就能确保线程不执行完,且加进来的Session能够顺利接收自己的事件。
2、修改TcpSession
既然QThread都出来了,那么TcpSession也要相应的调整调整了:
头文件:
添加TcpThread的指针:
private: TcpThread *Thread_ = nullptr;
构造函数修改下:
TcpSession(TcpThread *thread);
添加个信号函数和槽函数:
void SignalDisConnected(void *);
void SlotDisConnected();
cpp:
TcpSession::TcpSession(TcpThread *thread){ this->Thread_ = thread; connect(this, &TcpSession::readyRead, this, &TcpSession::SlotStartRead); connect(this, &TcpSession::disconnected, this, &TcpSession::SlotDisConnected);}TcpSession::~TcpSession(){ disconnect(this, &TcpSession::readyRead, this, &TcpSession::SlotStartRead); disconnect(this, &TcpSession::disconnected, this, &TcpSession::SlotDisConnected);}void TcpSession::Disconnect(){ qDebug() << "TcpSession::Disconnect threadID:"<< QThread::currentThreadId(); this->disconnectFromHost();}qint64 TcpSession::Write(const char *data, qint64 len){ return this->write(data, len);}qint64 TcpSession::Write(const char *data){ return this->write(data);}void TcpSession::SlotStartRead(){ qDebug() << "TcpSession::SlotStartRead threadID:"<< QThread::currentThreadId(); QByteArray buffer; buffer = this->readAll(); emit this->SignalRead(buffer.toStdString().c_str(), buffer.length());}void TcpSession::SlotDisConnected(){ if(Thread_) --Thread_->SessionCount; //通知会话断开连接 if(OnDisConnected) OnDisConnected(this); emit this->SignalDisConnected(this);}
简单来说,就是添加了TcpThread的指针,并且在断开连接时,减少TcpThread上的会话计数。同时发个信号,通知断开。
3、添加SessionThreads
准备材料已经弄完,现在就可以正式添加线程池了:
头文件:
#ifndef SESSIONTHREADS_H#define SESSIONTHREADS_H#include <vector>#include <unordered_map>#include <stdint.h>#include <memory>#include <mutex>#include "TcpThread.h"#include "TcpSession.h"class SessionThreads : public QObject{ Q_OBJECTpublic: SessionThreads(); ~SessionThreads(); //启动线程池 bool Start(uint32_t threadNum); //关闭 void Stop(); //获取最小会话数线程 TcpThread *PickMinThread(); //获取会话数 std::vector<uint32_t> GetSessionSize()const; //添加会话 std::shared_ptr<TcpSession> CreateSession(qintptr handle);private slots: //会话断开 void SlotSessionDisConnected(void *id);private: std::vector<TcpThread*> ThreadList_; //互斥量 std::mutex Lock_; //会话列表 std::unordered_map<void*, std::shared_ptr<TcpSession>> SessionList_; bool IsRunning_ = false;};#endif // SESSIONTHREADS_H
cpp文件:
#include "SessionThreads.h"SessionThreads::SessionThreads(){}SessionThreads::~SessionThreads(){ this->Stop();}bool SessionThreads::Start(uint32_t threadNum){ if(IsRunning_) return true; for(uint32_t i = 0; i < threadNum; ++i) { TcpThread *thread = new TcpThread(); ThreadList_.push_back(thread); thread->start(); } IsRunning_ = true; return true;}void SessionThreads::Stop(){ if(!IsRunning_) return; //TODO 待完善 for(TcpThread *thread : this->ThreadList_) { thread->exit(); thread->wait(); } for(TcpThread *thread : this->ThreadList_) delete thread; this->ThreadList_.clear(); std::unordered_map<void*, std::shared_ptr<TcpSession>>::iterator itor = SessionList_.begin(); //关闭连接 for(itor = SessionList_.begin(); itor != SessionList_.end(); ++itor) { std::shared_ptr<TcpSession> session = itor->second; if(session.get()) session.get()->Disconnect(); } this->SessionList_.clear(); IsRunning_ = false;}TcpThread *SessionThreads::PickMinThread(){ TcpThread *thread = nullptr; uint32_t mincount = 0; for(TcpThread *tmpthread : this->ThreadList_) { uint32_t tmpcount = tmpthread->SessionCount; if(mincount == 0 || tmpcount < mincount) { mincount = tmpcount; thread = tmpthread; } } return thread;}std::vector<uint32_t> SessionThreads::GetSessionSize() const{ std::vector<uint32_t> vec; for(TcpThread *thread : this->ThreadList_) vec.push_back(thread->SessionCount); return vec;}std::shared_ptr<TcpSession> SessionThreads::CreateSession(qintptr handle){ TcpThread *thread = this->PickMinThread(); std::shared_ptr<TcpSession> session = std::make_shared<TcpSession>(thread); connect(session.get(), &TcpSession::SignalDisConnected, this, &SessionThreads::SlotSessionDisConnected); session->setSocketDescriptor(handle); session->moveToThread(thread); ++thread->SessionCount; //加锁 std::lock_guard<std::mutex> locker(this->Lock_); this->SessionList_[session.get()] = session; return session;}//会话断开void SessionThreads::SlotSessionDisConnected(void *id){ //加锁 std::lock_guard<std::mutex> locker(this->Lock_); std::unordered_map<void*, std::shared_ptr<TcpSession>>::iterator itor = SessionList_.begin(); itor = SessionList_.find(id); if(itor != SessionList_.end()) { SessionList_.erase(itor); qDebug()<< "SessionThreads::SlotSessionDisConnected" << QThread::currentThread(); }}
其实这里不应该叫线程池,准确说,应该是会话线程池,主要职责就是负责管理会话,和线程。
而且我们可以看到,这个结构其实和TcpServer很像,一样启动,关闭,这里启动需要的参数是线程数量。因为头文件的注释比较详细了,我相信多阅读的话,应该都能看懂,我就不费太多的笔墨了。
4、修改TcpServer
线程池都好了,我们也就可以把TcpServer里的杂七杂八的东西删掉了,让他专心负责监听端口,接受连接就行了:
头文件:
class TcpServer : public QTcpServer{ Q_OBJECTpublic: TcpServer(); ~TcpServer(); bool Start(int port, int threadnum); void Stop(); std::vector<uint32_t> GetSessionSize() const ;public: //新连接回调 std::function<void(std::shared_ptr<TcpSession> &)> OnAccepted = nullptr;protected: virtual void incomingConnection(qintptr handle);private: bool IsRunning_ = false; SessionThreads SessionThreads_;};
这一下就很清爽了,TcpServer的事情很简单,至于线程,会话这些东西,统统都丢给线程池去做了。
cpp:
#include "TcpServer.h"TcpServer::TcpServer(){}TcpServer::~TcpServer(){ this->Stop();}bool TcpServer::Start(int port, int threadnum){ if(IsRunning_) return true; //启动线程池 SessionThreads_.Start(threadnum); //监听端口 if(!this->listen(QHostAddress::Any, port)) return false; IsRunning_ = true; qDebug() << "TcpServer::Start threadID:"<< QThread::currentThreadId(); return true;}void TcpServer::Stop(){ if(!IsRunning_) return; //关闭监听 this->close(); //关闭线程池 SessionThreads_.Stop(); IsRunning_ = false;}std::vector<uint32_t> TcpServer::GetSessionSize() const{ return this->SessionThreads_.GetSessionSize();}void TcpServer::incomingConnection(qintptr handle){ qDebug() << "TcpServer::incomingConnection threadID:"<< QThread::currentThreadId(); std::shared_ptr<TcpSession> session = SessionThreads_.CreateSession(handle); if(this->OnAccepted) this->OnAccepted(session);}
当然 ,这里少了port和线程数的合法性验证,下章会讲到。
5、总结
线程池完成了,那么整个Server端也基本完成了,可以直接使用并接收连接了整个流程大致是没什么问题了。
当然,细心的话,你可能会发现,我在关服的时候顺序是:
关闭线程池里所有线程-》删除线程池里所有线程-》断开所有会话连接-》清空会话。
这样做的话,断开所有会话连接的时候,都是在TcpServer所在线程了,甚至此时的读取数据也在这个线程。
有经验的同学应该知道,关服的时候,为防止正在读写数据产生可能存在的问题,亦或者说为了可读性,体现正常思维,正确的关服顺序应该是:
断开所有会话连接-》关闭线程池里所有线程-》删除线程池里所有线程-》清空会话。
那么问题来了,既然我知道正确的顺序,为什么我不按照正常的逻辑顺序来干呢?
原因:
因为直接按照这个顺序的话,QTcpSocket不提供异步断开连接方法(即将断开连接操作丢到所在线程队列里去),我们显然不能在TcpServer所在线程里这么直接disconnect,逻辑上不对,并且这样干qt也不让(直接给你报错)。
所以这里我采取了一个投机的方法(这种行为不可取,我是为了写博客,一步步走,才这样干的)。
那么要实现正确的关服逻辑怎么办呢?
很简单,请看下篇~~~~
- Qt一步步搭建TcpServer2——线程池
- Qt一步步搭建TcpServer0——序
- Qt一步步搭建TcpServer1——封装QTcpServer,QTcpSocket
- Qt一步步搭建TcpServer3——关闭与启动
- Qt一步步搭建TcpServer4——Client的封装与网络库的使用
- 一步步实现WebServer中间件——自己实现一个线程池
- 一步步搭建Ubuntu环境——vim中文乱码解决方法
- 一步步搭建Ubuntu环境——vimrc的位置
- 一步步搭建Ubuntu环境——Ubuntu下SSH设置
- 如何一步步搭建Exadata虚拟机——Cell节点
- 一步步搭建Ubuntu环境——安装常用软件
- 一步步搭建Ubuntu环境——修改启动选项
- 一步步搭建Ubuntu环境——卸载liboffice
- 一步步搭建Ubuntu环境——乱码修改
- 一步步搭建物联网系统——无处不在的CSS
- 一步步搭建物联网系统——无处不在的Javascript
- QT——QThread线程
- GrblController—QT环境搭建
- HDUOJ 产生冠军(stl)
- Java内存区域与对象创建过程
- spring处理ajax请求
- 练习 2-4 squeeze(s1, s2),将字符串s1 中任何与字符串s2 中字符匹配的字符都删除。
- spark数据处理示例一:分类
- Qt一步步搭建TcpServer2——线程池
- 定期清理服务器日志
- Java泛型常见面试题
- maven基础教程
- 狄利克雷卷积与积性函数
- 贪心,递归,动态规划,及分治算法之间的区别和联系(二)
- spark之5:配置文件
- 练习 2-5 编写函数 any(s1, s2),将字符串s2中的任一字符在字符串s1中第一次出现的位置作为结果返回。如果s1中不包含s2中的字符,则返回-1。
- ubuntu16 jdk7 install&config