Qt一步步搭建TcpServer1——封装QTcpServer,QTcpSocket

来源:互联网 发布:thinkphp5开发大型cms 编辑:程序博客网 时间:2024/05/01 13:47

  • 1序
    • 1NetAPI的设计思路
    • 2了解QTcpServer
    • 3设计QtcpServer
    • 4开始
  • 2TcpServer的封装
  • 3TcpSession的封装
  • 4总结

承接上章:Qt一步步搭建TcpServer0——序

 要搭建网络库,那么肯定要从Server和Socket开始入手,本章就从封装QTcpServer和QTcpSocket开始入手,做好第一步工作。

1、序

1.1、NetAPI的设计思路

画了一张图来描述思路,一般来说,网络库的思路大都如下:
这里写图片描述

其实图中注释描述的也比较清楚,能够较为直观体现出Server端的网络库多线程工作流程,我觉得也就没必要再过多解释了。

1.2、了解QTcpServer

官网文档地址:QTcpServer

关于QTcpServer的介绍,官方已经说的比清楚了:
这里写图片描述

基本上是属于傻瓜式编程,大部分的工作都由底层给我们做好了。通常情况下,我们只需要监听一下端口,绑定个newConnection()就可以在新连接连入的时候获取到套接字:

int port = this->ui->spinBox->value();QTcpServer *tcpserver = new QTcpServer();tcpserver->listen(QHostAddress::Any, port);connect(tcpserver, &QTcpServer::newConnection, this, &MainWindow::SlotNewConnection);

然后通过nextPendingConnection获取套接字就行了。

1.3、设计QtcpServer

关于QTcpServer的设计其实主要有两种方式:

1、组合:如上面官方介绍所言,以组合的形式封装QTcpServer,然后连接newConnection信号槽,在槽函数里nextPendingConnection获取新socket,而这个socket将由QTcpServer来管理,自动删除:
这里写图片描述

2、继承:继承自QTcpServer,然后重载函数incomingConnection,在函数里自己去创建Socket对象并管理。

方式一和方式二其实都可以,但对我而言,我需要自定义socket,并且这个socket将会抛给上层,上层在整个过程中可能会存在需要使用到socket对象的情况,生命周期需要我的网络库来掌管,那么我将使用方式二。

1.4、开始

新建项目,在pro文件中添加network库:

QT       += core gui network

2、TcpServer的封装

TcpServer封装自QTcpServer,基本功能底层已经做了很多,那么我们要做的就是做成能够直接用于项目需求上的接口:
头文件:

class TcpServer : public QTcpServer{    Q_OBJECTpublic:    TcpServer();    ~TcpServer();    bool Start(int port);    void Stop();    size_t GetSessionSize() const ;public:    //新连接回调    std::function<void(TcpSession*)> OnAccepted = nullptr;protected:    virtual void incomingConnection(qintptr handle);private:    bool IsRunning_ = false;    QThread *Thread_;    std::vector<TcpSession*> SessionList_;};

一个Server首先需要提供的肯定是Start和Stop,这点不用多说,OnAccepted 是连接回调,通知上层,并把新连接丢给上层,上层拿到之后,是持有还是简单绑定回调就是上层的事情了。incomingConnection是需要重载的函数,qt的net库Accept后通知我们这层,我们再去创建socket(也就是TcpSession)。

cpp:

TcpServer::TcpServer(){}TcpServer::~TcpServer(){    this->Stop();}bool TcpServer::Start(int port){    if(IsRunning_)        return true;    Thread_ = new QThread();    Thread_->start();    //监听端口    if(!this->listen(QHostAddress::Any, (quint16)port))        return false;    IsRunning_ = true;    return true;}void TcpServer::Stop(){     if(!IsRunning_)        return;    this->close();    Thread_->exit();    Thread_->wait();    delete Thread_;    for(TcpSession *session : this->SessionList_)    {        session->Disconnect();    }    for(TcpSession *session : this->SessionList_)    {        delete session;    }    this->SessionList_.clear();    Thread_ = nullptr;    IsRunning_ = false;}size_t TcpServer::GetSessionSize() const{    return this->SessionList_.size();}void TcpServer::incomingConnection(qintptr handle){    TcpSession *session = new TcpSession();    session->setSocketDescriptor(handle);    session->moveToThread(this->Thread_);    this->SessionList_.push_back(session);    //通知上层    if(this->OnAccepted)        this->OnAccepted(session);}

仔细一看,好嘛 ,我封装的也啥都没干,只是桥接了下启动和把新连接都丢到一个线程里去了。不着急,这只是第一步。当然,主要原因是该提供的接口函数QTcpServer已经提供了大部分功能(包括error回调也提供了)。

Stop函数,Stop函数主要是负责关闭线程,清理内存等,我在后面会讲,这也有一些需要补充的内容。在目前我们假设已经顺利关服,并且清理了Session。

此时存在一个问题,Session在OnAccept的时候需要往上抛,上层拿到之后可能会持有,写数据和disconnect可能不是在同一个线程。所以断开连接的时候,或者说关服的时候,正好业务层写数据,而session已经被析构了,这就可能存在隐患,所以这里引入shared_ptr

头文件:

class TcpServer : public QTcpServer{    Q_OBJECTpublic:    TcpServer();    ~TcpServer();    bool Start(int port);    void Stop();    size_t GetSessionSize() const ;public:    //新连接回调    std::function<void(std::shared_ptr<TcpSession> &)> OnAccepted = nullptr;protected:    virtual void incomingConnection(qintptr handle);private:    bool IsRunning_ = false;    QThread*Thread_;    std::vector<std::shared_ptr<TcpSession>> SessionList_;};

cpp:

TcpServer::TcpServer(){}TcpServer::~TcpServer(){    this->Stop();}bool TcpServer::Start(int port){    if(IsRunning_)        return true;    Thread_ = new QThread();    Thread_->start();    //监听端口    if(!this->listen(QHostAddress::Any, (quint16)port))        return false;    IsRunning_ = true;    return true;}void TcpServer::Stop(){     if(!IsRunning_)        return;    this->close();    Thread_->exit();    Thread_->wait();    delete Thread_;    for(std::shared_ptr<TcpSession> &session : this->SessionList_)    {        if(session.get())            session.get()->Disconnect();    }    this->SessionList_.clear();    Thread_ = nullptr;    IsRunning_ = false;}size_t TcpServer::GetSessionSize() const{    return this->SessionList_.size();}void TcpServer::incomingConnection(qintptr handle){    std::shared_ptr<TcpSession> session = std::make_shared<TcpSession>();    session->setSocketDescriptor(handle);    session->moveToThread(this->Thread_);    this->SessionList_.push_back(session);    if(this->OnAccepted)        this->OnAccepted(session);}

到这里,TcpServer的封装大致完成,上层可以直接用了,拿到Session之后也不用担心内存问题。

3、TcpSession的封装

相对来说,TcpSession的封装更简单了,这里做封装目前没新增什么,之后可能会加入线程的指针或者别的数据等,不管怎样,还是自定义比较方便。

头文件:

#ifndef TCPSESSION_H#define TCPSESSION_H#include <QTcpSocket>#include <functional>class TcpSession : public QTcpSocket{    Q_OBJECTsignals:    void SignalRead(const char *data, int len);public:    TcpSession();    ~TcpSession();    void Disconnect();    qint64 Write(const char *data, qint64 len);    qint64 Write(const char *data);protected:private slots:    void SlotStartRead();};#endif // TCPSESSION_H

cpp:

TcpSession::TcpSession(){    connect(this, &TcpSession::readyRead, this, &TcpSession::SlotStartRead);}TcpSession::~TcpSession(){    disconnect(this, &TcpSession::readyRead, this, &TcpSession::SlotStartRead);}void TcpSession::Disconnect(){    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(){    QByteArray buffer;    buffer = this->readAll();    emit this->SignalRead(buffer.toStdString().c_str(), buffer.length());}

代码比较简单,没什么解释的必要。这里的工作,主要是统一了下发送数据和接收数据的接口参数,之后可能会自定义一个参数类型(看我有没有时间更),方便包的传输。

4、总结

到这里,Socket和Server都封装好了,原本的信号槽也可以直接用。主界面写写就可以直接点击启动了,但我们能发现,目前我这里只是把所有的Session都丢到同一个线程去处理了。这就有很大的问题,假如十个,百个连接还好说,但是有千个万个连接呢?显然不能这么处理。

那么怎么办?其实在写博客之前,我也简单浏览了下别人的设计,大抵分为两派,一派是不处理,不用线程;另一派是一个连接一个线程。。。。。。也不是说不行,但是觉得不是特别好。

这个时候,就需要引入线程池,每个连接连入的时候,动态的根据线程池中线程负载情况来分配,尽量均衡的让一个线程处理多个连接,而我们只需要维护这几个线程就行了。那么下篇再说。

原创粉丝点击