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也不让(直接给你报错)。

所以这里我采取了一个投机的方法(这种行为不可取,我是为了写博客,一步步走,才这样干的)。

那么要实现正确的关服逻辑怎么办呢?
很简单,请看下篇~~~~

阅读全文
1 0