Qt一步步搭建TcpServer3——关闭与启动

来源:互联网 发布:windows toolkit 2.6 编辑:程序博客网 时间:2024/05/16 01:27

  • 前言
  • 1Server启动
  • 2关闭Server
    • 1修改TcpSession
    • 2 线程池关闭
  • 5总结

承接上章: Qt一步步搭建TcpServer2——线程池

本章将在上一章的基础上,讲述如何安全的关闭与启动Server。

前言

首先要更正上一篇的一个文字错误:
这里写图片描述
在这里这样用信号槽,并且删除Session是安全的。因为其事件循环机制是在按线程来的,在退出session线程之后,才会进入下一个线程的事件循环机制。因此这里不会存在这种隐患。

这里解释下,因为我代码是跟着博客每篇进度来写的,也就是说准备写某篇的时候,再把其功能实现,而设计这种工作本来是不断优化的。每一天的想法都可能不一样,或者说这算是具现每个阶段的过程思路,所以每一篇的代码,可以注意到和前一篇的方式和想法可能会略有出入。但整体不会变化。

1、Server启动

Server端的启动,主要依赖于监听端口和线程数量,可以封成Config类,包含数据的验证和初始化过程,如果需要拓展,也更方便:

TcpServer头文件:

//Server数据格式struct ServerData{    uint16_t Port;    uint32_t ThreadNum;    //验证逻辑,不合法则设定初始值    void Verify()    {        if(Port == 0)            Port = 12345;        if(ThreadNum)            ThreadNum = std::thread::hardware_concurrency();    }};class TcpServer : public QTcpServer{    Q_OBJECTpublic:    TcpServer();    ~TcpServer();    bool Start(ServerData &conf);    ......    //后面代码就不贴了

cpp:

bool TcpServer::Start(ServerData &conf){    if(IsRunning_)        return true;    //验证数据    conf.Verify();     //启动线程池    SessionThreads_.Start(conf.ThreadNum);    //监听端口    if(!this->listen(QHostAddress::Any, (quint16)conf.Port))        return false;    IsRunning_ = true;    qDebug() << "TcpServer::Start threadID:"<< QThread::currentThreadId();    return true;}

2、关闭Server

之前在公司封装asio库的时候,同事间交流感觉,这种网络库的封装,其实安全的关服比启动更加困难。毕竟涉及到多线程读写的问题。
所以在写这个之前我觉得关服可能也需要特别在意,包括在写上一章的的时候,我也说了一些关服的时候的问题,甚至当时还取巧直接写。但后来写本章的代码的时候,询问了下同事Qt的事件循环机制(原谅我不够理解),仔细一想其实压根没什么复杂的事情,用信号槽不久完事儿了,摊手= =。

上一篇里我提到,可能要用DoubleList(以后有时间单独更新一篇吧,其实就是双缓冲队列,逻辑上交换,写锁,读不锁)。现在看来比我想象的要简单许多。

那么就开始上代码吧:

2.1、修改TcpSession

既然要保证关服的顺序(参考上一章结尾),那TcpSession断开连接和写数据的操作,都需要在所属线程排队执行,才能确保没问题。
头文件:

#ifndef TCPSESSION_H#define TCPSESSION_H#include <QTcpSocket>#include <functional>#include "TcpThread.h"class TcpSession : public QTcpSocket{    Q_OBJECTsignals:    void SignalRead(const QByteArray &, int);    void SignalDisConnected(void *);    void SignalDoWrite(const char *, qint64);    void SignalDoDisConnect();public:    TcpSession(TcpThread *thread);    ~TcpSession();    //断开连接    void Disconnect();    void Write(const char *data, qint64 len);public:    //断开连接回调,将来可能要用    std::function<void(void*)> OnDisConnected = nullptr;private slots:    //开始读数据    void SlotStartRead();    //断开连接回调    void SlotDisConnected();    //写数据    void SlotDoWrite(const char *data, qint64 len);    //断开连接    void SlotDoDisconnect();private:    TcpThread *Thread_ = nullptr;    QByteArray Buffer_ = nullptr;};#endif // TCPSESSION_H

cpp文件:

#include "TcpSession.h"TcpSession::TcpSession(TcpThread *thread){    this->Thread_ = thread;    connect(this, &TcpSession::readyRead,            this, &TcpSession::SlotStartRead);    connect(this, &TcpSession::disconnected,            this, &TcpSession::SlotDisConnected);    connect(this, &TcpSession::SignalDoDisConnect,            this, &TcpSession::SlotDoDisconnect);    connect(this, &TcpSession::SignalDoWrite,            this, &TcpSession::SlotDoWrite);}TcpSession::~TcpSession(){    disconnect(this, &TcpSession::readyRead,               this, &TcpSession::SlotStartRead);    disconnect(this, &TcpSession::disconnected,               this, &TcpSession::SlotDisConnected);    disconnect(this, &TcpSession::SignalDoDisConnect,            this, &TcpSession::SlotDoDisconnect);    disconnect(this, &TcpSession::SignalDoWrite,            this, &TcpSession::SlotDoWrite);}void TcpSession::Disconnect(){    qDebug() << "TcpSession::Disconnect threadID:"<< QThread::currentThreadId();    emit this->SignalDoDisConnect();}void TcpSession::Write(const char *data, qint64 len){    emit this->SignalDoWrite(data, len);}void TcpSession::SlotStartRead(){    qDebug() << "TcpSession::SlotStartRead threadID:"<< QThread::currentThreadId();    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);}void TcpSession::SlotDoWrite(const char *data, qint64 len){    qDebug() << "TcpSession::SlotDoWrite threadID:"<< QThread::currentThreadId();    this->write(data, len);}void TcpSession::SlotDoDisconnect(){    qDebug() << "TcpSession::SlotDoDisconnect threadID:"<< QThread::currentThreadId();    this->disconnectFromHost();}

Connection默认是auto,这里会丢到所属线程,不了解的可以补补Connection的第五个参数。

2.2 线程池关闭:

void SessionThreads::Stop(){    if(!IsRunning_)        return;    {        std::lock_guard<std::mutex> locker(this->Lock_);        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())            {                disconnect(session.get(),                           &TcpSession::SignalDisConnected,                           this,                           &SessionThreads::SlotSessionDisConnected);                session.get()->Disconnect();            }        }    }    for(TcpThread *thread : this->ThreadList_)    {        thread->exit();        thread->wait();    }    for(TcpThread *thread : this->ThreadList_)        delete thread;    this->ThreadList_.clear();    this->SessionList_.clear();    IsRunning_ = false;}

代码比较简单,就是按照顺序,先断开连接,然后等线程推出循环。最后清空TcpSession列表。

5、总结

到这里NetApi的Server端的已经完成,TcpSession也可直接用于客户端,当然,你得自建立一个线程,传入进去。对于上层来说,还是有点麻烦,所以打算Client也来一个类似Server一样的ClientManager:这样的好处是,可以客户端也有个Manager了,比较一个客户端连N个Server的情况还是比较常见。

所以下一章把Client也搞下。

这里把到这个阶段的代码上传下,Client只是个测试端,还没怎么搞,比较简单。Server端的界面也很简单,代码都还没整理,包括些debug信息还在,最后再统一整理吧
Server:
这里写图片描述
Client:
这里写图片描述

代码地址:

TcpServerDemo
有什么问题或建议,可以留言或者联系QQ:1281581259 ,谢谢。

2017.11.30更新:
可以直接下载下面的,更完善。
基于QtcpServer的网络库

原创粉丝点击