QT网盘项目-DAY1-客户端与服务进行连接

本文未经授权,禁止转载

一、项目概述

1.1 项目介绍

        该项目是基于QT的一个网盘项目,会持续更新。

1.2 项目设计到的知识

        学习该项目需要提前掌握

        1、C++的面向对象编程(封装继承多态肯定是要熟悉的)

        2、网络编程(TCP协议、Socket编程等)

                Tcp协议: 后面会发笔记

                Socket编程:简单实现的socket_你一般代码中是怎么实现socket的?-CSDN博客

                                      多路复用IO_io多路复用服务器-CSDN博客

        3、QT的一些基础知识(信号槽机制,常用的控件等)

        4、数据库的相关知识(MySql的基本命令)

1.3 项目编程环境

        Qt Creator 4.11.1 (Community)

       1、 安装包:

        链接:https://pan.baidu.com/s/1eD-DobJAF1497sY--XaJ2w?pwd=1223 
        提取码:1223 
        --来自百度网盘超级会员V4的分享

        2、安装教程(自己搜一个,安装不上,那还学什么项目)

        【安装篇】Qt 安装详细教程(Windows版)_qt安装-CSDN博客

1.4 创建项目

1、文件 --> 新建项目(或者是欢迎 -- > new)

2、选择类型

3、项目名称(客户端:NetdiskClient ;服务器:NetdiskServer)

 4、类名与基类(客户端:Client ;服务器:Server),基类都是 QWidget

         剩下没说到的都是下一步。

二、客户端申请连接(NetdiskClient项目文件)

客户端的核心目标:

        1、与服务器建立连接

        2、建立连接后,与服务器进行通信

2.1 与服务器申请建立连接

        首先我们需要与客户端建立连接,只有建立连接之后,才能进行后续的一系列操作(进行数据交互、通信等)

        在QT中使用网络模块,需要在项目文件 .pro 中添加 network模块。

        在该文件下,添加一个 network。

        有网络编程基础的话,就能明白,建立网络通信,需要使用Socket建立连接,而QT内部已经对Socket进行了封装,使用QTcpSocket类,调用类中的connectToHost函数就能申请建立连接

        我们来看connectToHost函数的定义。

void connectToHost(const QHostAddress &address, // ip地址
                   quint16 port,                // 端口号
                   OpenMode mode = ReadWrite);

        这里我们只看前两个参数:

        const QHostAddress &address:QHostAddress类型的IP地址

        quint16 port:quint16,16位的端口号,2个字节(0~65535)

        也就是说,我们只要传入服务器的IP地址和端口号即可申请与服务器进行连接,因此我们需要创建一个QTcpSocket类的对象,然后调用connectToHost函数将IP地址和端口号传入就能申请连接这里我们将主机IP(127.0.0.1)和端口号(5000)传入即可。

           QHostAddress("127.0.0.1")是将字符串类型转为QHostAddress类型。

    // 使用tcpSocket 进行连接
    // 创建一个 QTcpSocket的对象 s
    QTcpSocket s;   
    // 对象s调用connectToHost函数,申请建立连接
    s.connectToHost(QHostAddress("127.0.0.1"),5000);

         上面的两行代码,就完成了与服务器申请建立连接的内容,但是,如果项目这样写的话,太low了,是不符合要求的。因此我们要对上面的代码进行改进(提升逼格)。

        优化的内容:

                1、将IP地址和端口号放到配置文件中,我们只需要实现一个加载配置文件的函数,就能知道要申请谁建立连接,后续还可以在配置文件中添加其他内容,对配置文件修改等。

                2、将加载配置文件的函数进行封装,将IP和端口号设为成员变量,方便后续调用。

                3、将网络连接中的QTcpSocket类对象设为成员变量,方便后续调用。

                4、定义一个槽函数,在连接成功后进行提示。

                5、实现单例模式。

2.2 添加配置文件

2.2.1 新建资源文件

1、右键项目 --> Add New

2、选择 QT资源文件 Qt Resource File,然后点击Choose。

3、 设置资源文件名称为 config(配置文件)

2.2.2 添加前缀

到这里之后,就会发向项目中多了一个文件夹,而且自动弹出了该界面

1、添加前缀:点击 Add Prefix之后,灰色窗口就会变成下面的样子

        我们将前缀改为 / 即可

2.2.3 添加配置文件

1、添加文件:点击Add Files

2、新建配置文件:点击Add Files,就会弹出新建文件窗体,新建一个client.config即可

3、给配置文件中添加内容,新建之后,按 ctrl s 保存,会发现client.config已经添加到资源文件中,打开该配置文件,输入IP地址和端口号即可。

这样我们就完成了配置文件的添加。

2.3 加载配置文件

2.3.1 将IP地址和端口号设置为成员变量

        我们要在加载配置文件的函数中,读取IP地址和端口号,在其他函数中,利用读取到的IP地址和端口号进行连接,因此,需要将这两个变量设置为成员变量,以便于后续的调用。

        client.h

# Client 类
class Client : public QWidget
{
    Q_OBJECT
public:
private:
    // IP地址
    QString m_strIP;
    // 端口号
    quint16 m_uintPort;
};

2.3.2 将加载配置文件的函数进行封装,以读取配置文件

         在 client.h中声明一个 loadConfig() 函数,在client.cpp中进行实现。

        读取配置的步骤如下:
        1、使用 QFile 类操作文件(路径:“:+ 前缀+文件名”),  需要导入头文件。

#include <QFile>    
    // 获取文件路径
    QFile file(":/client.config");


        2、创建一个 QFile 对象file,使用open()函数只读打开文件。(打开失败提示报错)

    // 只读打开
    if(!file.open(QIODevice::ReadOnly))
    {
        // 打开失败提示错误
        QMessageBox::critical(this,"打开配置文件","打开配置文件失败");
        return;
    }

        3、使用readAll()函数 读取配置文件的内容,会返回一个 QByteArray 对象。

     // 读取配置文件信息
    QByteArray baData = file.readAll();
    // 测试--打印读取到的信息
    qDebug()<<"baData" <<baData;

        读到的内容

        4、将QByteArray类型的数据 转换为 QString类型,并 使用split()函数,按照 \rn 进行拆分出每行的字符串。会返回一个QStringList 的列表。

    // 将QByteArray 强转为字符串
    QString strData = QString(baData);
    // 将字符串按照 “\r\n” 进行切分
    QStringList strList = strData.split("\r\n");

        5、QStringList按下标取出每行数据,使用.toUShort()端口号转成数字 quint16

    // 读取IP,使用成员变量存储
    m_strIP = strList.at(0);
    // 读取端口号--端口号是2个字节16位的short类型,范围是0~65535
    m_uintPort = strList.at(1).toUShort();
    // 测试--分别打印IP和端口号
    // qDebug()<<"strIP: "<<m_strIP;
    // qDebug()<<"uintPort: "<<m_uintPort;

        6、关闭文件 close() 函数。

2.3.3 完整代码

client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACE

class Client : public QWidget
{
    Q_OBJECT
public:
    // 构造函数
    Client(QWidget *parent = nullptr);
    ~Client();
    // 加载配置文件的声明
    void loadConfig();
   
private:
    Ui::Client *ui;
    // IP地址
    QString m_strIP;
    // 端口号
    quint16 m_uintPort;
};
#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"

#include <QFile>
#include <QMessageBox>
#include <QDebug>
#include <QHostAddress>

// 构造函数
Client::Client(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Client)
{
    ui->setupUi(this);
    // 加载配置文件
    loadConfig();
}

// 析构函数
Client::~Client()
{
    delete ui;
}

// 加载配置文件
void Client::loadConfig()
{
    // 获取文件路径
    QFile file(":/client.config");
    // 只读打开
    if(!file.open(QIODevice::ReadOnly))
    {
        // 打开失败提示错误
        QMessageBox::critical(this,"打开配置文件","打开配置文件失败");
        return;
    }
    // 读取配置文件信息
    QByteArray baData = file.readAll();
    // 测试--打印读取到的信息
    // qDebug()<<"baData" <<baData;

    // 将QByteArray 强转为字符串
    QString strData = QString(baData);
    // 将字符串按照 “\r\n” 进行切分
    QStringList strList = strData.split("\r\n");

    // 读取IP
    m_strIP = strList.at(0);
    // 读取端口号--端口号是2个字节16位的short类型,范围是0~65535
    m_uintPort = strList.at(1).toUShort();
    // 测试--分别打印IP和端口号
    // qDebug()<<"strIP: "<<m_strIP;
    // qDebug()<<"uintPort: "<<m_uintPort;
    
    // 关闭文件
    file.close();
}

2.4 与服务器申请连接

2.4.1 将网络连接中的QTcpSocket类对象设为成员变量

        定义一个 QTcpsocket类型的成员变量 m_tcpSocket,并设置一个公有方法getTcpSocket()获取该属性。

        client.h 声明

    // 获取网络连接的声明
    QTcpSocket& getTcpSocket();
private:
    // 设置网络连接的成员变量
    QTcpSocket m_tcpSocket;

        client.cpp 实现

// 获取网络连接
QTcpSocket& Client::getTcpSocket()
{
    return m_tcpSocket;
}

2.4.2 使用tcpSocket 与服务器申请连接

        使用m_tcpSocket调用 connectToHost函数,参数为ip 和端口号(ip 需要转为QHostAddress)。

        在client.cpp的构造函数中调用

    // 使用tcpSocket 与服务器申请连接
    m_tcpSocket.connectToHost(QHostAddress(m_strIP),m_uintPort);

2.4.3 定义槽函数并关联,提示连接成功

        在连接成功后,QTcpSocket会发出一个connected的信号,表示已经连接成功,因此我们需要定义一个槽函数,与该信号关联,一旦连接成功,关联的槽函数会提示连接成功了。

        client.h 声明

    // 槽函数-连接成功展示
    void showConnected();

         client.cpp 实现:QMessageBox对话框进行提示。

// 连接成功展示
void Client::showConnected()
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(this,"建立连接","连接成功");
}

         在client.cpp的构造函数使用 connect()函数将信号与槽函数进行关联

    // 连接成功后,会返回一个连接成功的信号,使用信号槽接收,并显示连接成功
    connect(&m_tcpSocket,&QTcpSocket::connected,this,&Client::showConnected);

2.4.4 完整代码

client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QWidget>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACE

class Client : public QWidget
{
    Q_OBJECT
public:
    // 构造函数
    Client(QWidget *parent = nullptr);
    ~Client();
    // 加载配置文件
    void loadConfig();
    // 槽函数-连接成功展示
    void showConnected();
    // 获取网络连接
    QTcpSocket& getTcpSocket();
private:
    Ui::Client *ui;
    // IP地址
    QString m_strIP;
    // 端口号
    quint16 m_uintPort;
    // 设置网络连接
    QTcpSocket m_tcpSocket;
};
#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"

#include <QFile>
#include <QMessageBox>
#include <QDebug>
#include <QHostAddress>

// 构造函数
Client::Client(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Client)
{
    ui->setupUi(this);
    // 加载配置文件
    loadConfig();

    // 使用tcpSocket 进行连接
    m_tcpSocket.connectToHost(QHostAddress(m_strIP),m_uintPort);

    // 连接成功后,会返回一个连接成功的信号,使用信号槽接收,并显示连接成功
    connect(&m_tcpSocket,&QTcpSocket::connected,this,&Client::showConnected);

}
// 析构函数
Client::~Client()
{
    delete ui;
}

// 加载配置文件
void Client::loadConfig()
{
    // 获取文件路径
    QFile file(":/client.config");
    // 只读打开
    if(!file.open(QIODevice::ReadOnly))
    {
        // 打开失败提示错误
        QMessageBox::critical(this,"打开配置文件","打开配置文件失败");
        return;
    }
    // 读取配置文件信息
    QByteArray baData = file.readAll();
    // 测试--打印读取到的信息
    // qDebug()<<"baData" <<baData;

    // 将QByteArray 强转为字符串
    QString strData = QString(baData);
    // 将字符串按照 “\r\n” 进行切分
    QStringList strList = strData.split("\r\n");

    // 读取IP
    m_strIP = strList.at(0);
    // 读取端口号--端口号是2个字节16位的short类型,范围是0~65535
    m_uintPort = strList.at(1).toUShort();
    // 测试--分别打印IP和端口号
    // qDebug()<<"strIP: "<<m_strIP;
    // qDebug()<<"uintPort: "<<m_uintPort;
    
    // 关闭文件
    file.close();
}

// 连接成功展示
void Client::showConnected()
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(this,"建立连接","连接成功");
}

// 获取网络连接
QTcpSocket& Client::getTcpSocket()
{
    return m_tcpSocket;
}

2.5 实现单例模式

2.5.1 单例模式介绍

        保证一个类只有一个实例,控制某些共享资源的访问权限。创建了一个对象后,之后再创建一个新对象,此时会获得之前已创建的对象,而不是一个新对象。
        为该实例提供一个全局访问节点。单例模式允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。

        这里暂时不对设计模式进行过多赘述,请自行学习单例模式的作用。

        单例设计模式 (refactoringguru.cn)

2.5.2 实现单例模式

1、定义获取单例对象的静态成员函数 getlnstance()

   该静态成员函数就是一个全局的访问节点,静态成员函数,不需要使用对象访问,可以通过 作用域::getlnstance() 调用,来创建对象。(其他途径已经被我们删除了,所以只能通过该节点进行访问)返回引用类型,可以防止拷贝构造。

        client.h中声明函数

    // 定义静态成员函数 获取单例对象
    static Client& getInstance();


2、getlnstance()函数返回静态局部对象的引用

       返回一个静态的局部对象的引用,有俩种好处,一是静态变量只能赋值一次,能够有效防止,多次调用创建多个对象,二是返回引用可以防止进行拷贝构造。

        一开始是通过if(instance==null) 开判断是否创建了该实例,但是这种情况存在线程安全的问题,比如在实际情况中,存在多个线程同时调用getInstance(),此时多个线程判断都是成立的,就会创建多个实例,这种情况下就得添加锁或者互斥量。来保证线程安全

        而返回一个静态的局部对象的引用,在C++11之后,默认支持线程安全。

        在client.cpp中实现

// 实现静态成员函数 获取单例对象
Client &Client::getInstance()
{
    // 返回静态局部引用对象,防止调用拷贝构造,静态变量只能赋值一次
    static Client instance;
    return instance;
}


3、私有化构造函数、删除拷贝构造函数和拷贝赋值运算符,防止通过这些途径创建实例

         将可能创建实例的方法全部私有化或删除,确保只能通过getInstance()这一个方法创建实例

private:
    // 防止通过以下途径创建实例。
    // 私有化构造函数
    Client(QWidget *parent = nullptr);
    // 删除拷贝构造
    Client(const Client& instance) = delete;
    // 删除拷贝赋值 = 运算符
    Client operator= (const Client&) =delete;


4、创建实例时通过 getlnstance() 创建

        构造函数已经私有化了,只能通过getInstance()来创建实例对象。

        在main.c 中调用

#include "client.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // Client w; // 构造函数私有化了,无法使用
    // w.show();

    // 使用静态成员函数 获取单例对象
    Client::getInstance().show();

    return a.exec();
}

 2.6 客户端完整代码

        到这里,我们就完成了客户端申请连接的代码。

client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QWidget>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACE

class Client : public QWidget
{
    Q_OBJECT

public:

    ~Client();
    // 加载配置文件
    void loadConfig();
    // 槽函数-连接成功展示
    void showConnected();
    // 获取网络连接
    QTcpSocket& getTcpSocket();
    // 定义静态成员函数 获取单例对象
    static Client& getInstance();

private:
    Ui::Client *ui;
    // IP地址
    QString m_strIP;
    // 端口号
    quint16 m_uintPort;
    // 设置网络连接
    QTcpSocket m_tcpSocket;

    // 防止通过以下途径创建实例。
    // 私有化构造函数
    Client(QWidget *parent = nullptr);
    // 删除拷贝构造
    Client(const Client& instance) = delete;
    // 删除拷贝赋值运算符
    Client operator= (const Client&) =delete;
};
#endif // CLIENT_H

client.cpp 

#include "client.h"
#include "ui_client.h"

#include <QFile>
#include <QMessageBox>
#include <QDebug>
#include <QHostAddress>

Client::Client(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Client)
{
    ui->setupUi(this);
    // 加载配置文件
    loadConfig();

    // 使用tcpSocket 进行连接
    m_tcpSocket.connectToHost(QHostAddress(m_strIP),m_uintPort);

    // 连接成功后,会返回一个连接成功的信号,使用信号槽接收,并显示连接成功
    connect(&m_tcpSocket,&QTcpSocket::connected,this,&Client::showConnected);

}


Client::~Client()
{
    delete ui;
}

// 加载配置文件
void Client::loadConfig()
{
    // 获取文件路径
    QFile file(":/client.config");
    // 只读打开
    if(!file.open(QIODevice::ReadOnly))
    {
        // 打开失败提示错误
        QMessageBox::critical(this,"打开配置文件","打开配置文件失败");
        return;
    }
    // 读取配置文件信息
    QByteArray baData = file.readAll();
    // 测试--打印读取到的信息
    // qDebug()<<"baData" <<baData;

    // 将QByteArray 强转为字符串
    QString strData = QString(baData);
    // 将字符串按照 “\r\n” 进行切分
    QStringList strList = strData.split("\r\n");

    // 读取IP
    m_strIP = strList.at(0);
    // 读取端口号--端口号是2个字节16位的short类型,范围是0~65535
    m_uintPort = strList.at(1).toUShort();
    // 测试--分别打印IP和端口号
    // qDebug()<<"strIP: "<<m_strIP;
    // qDebug()<<"uintPort: "<<m_uintPort;
    
    // 关闭文件
    file.close();
}

// 连接成功展示
void Client::showConnected()
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(this,"建立连接","连接成功");
}

// 获取网络连接
QTcpSocket& Client::getTcpSocket()
{
    return m_tcpSocket;
}

// 实现静态成员函数 获取单例对象
Client &Client::getInstance()
{
    // 返回静态局部引用对象,防止调用拷贝构造,静态变量只能赋值一次
    // 在C++11之后,默认支持线程安全
    static Client instance;
    return instance;
}

main.h

#include "client.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // Client w; // 构造函数私有化了,无法使用
    // w.show();

    // 使用静态成员函数 获取单例对象
    Client::getInstance().show();

    return a.exec();
}

三、服务器监听连接请求(NetdiskServer项目文件)

服务器的核心目标:

        1、监听客户端发来的连接请求

        2、建立连接后,与处理客户端的各种请求

3.1 服务器监听连接请求

        首先服务器需要监听客户端发来的建立连接请求,只有建立连接之后,才能进行后续的一系列操作(进行数据交互、通信等)

        在QT中使用网络模块,需要在项目文件 .pro 中添加 network模块。

        在该文件下,添加一个 network。

        有网络编程基础的话,就能明白,建立网络通信,服务器需要监听客户端发来的申请连接的请求,而QT内部已经进行了封装,使用QTcpServer类,调用类中的listen()函数就能申请建立连接

        我们来看listen()函数的定义。

bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);

        参数为 IP地址和端口号

         也就是说,我们只要在服务器中使用监听的listen()函数,并传入监听的IP地址和端口号即可实现监听,因此我们需要创建一个QTcpServer类的对象,然后调用listen函数将IP地址和端口号传入就能监听连接这里我们将主机IP(127.0.0.1)和端口号(5000)传入即可。

    // 进行监听
    QTcpServer s;
    s.listen(QHostAddress("127.0.0.1"),5000);

        上面的两行代码,就完成了与服务器监听客户端申请建立连接的内容,但是,如果项目这样写的话,太low了,是不符合要求的。因此我们要对上面的代码进行改进(提升逼格)。

        优化的内容:

                1、将IP地址和端口号放到配置文件中,我们只需要实现一个加载配置文件的函数,就能知道要申请谁建立连接,后续还可以在配置文件中添加其他内容,对配置文件修改等。

                2、将加载配置文件的函数进行封装,将IP和端口号设为成员变量,方便后续调用。

                3、定义一个MyTcpServer类,并继承QTcpServer类,实现MyTcpServer实例对象的单例模式。

                4、将incomingConnection()函数进行重写,实现新的客户端连接成功后,进行提示。

                5、定义一个MyTcpSocket类,并继承QTcpSocket类,方便后续对Socket进行操作。

                6、添加一个成员变量QList<MyTcpSocket*> m_tcpSocketList,一个存储Socket描述符的列表,用来存储多个客户端Socket,以解决多个客户端进行连接的情况。

3.2 添加配置文件

         同 2.2 添加配置文件

3.3 加载配置文件

        同 2.3 加载配置文件

3.4 定义MyTcpServer类,实现单例模式。

        新建一个MyTcpServer类,并继承QTcpServer类,实现MyTcpServer实例对象的单例模式。

        Server类使用 MyTcpServer调用listen 函数

        MyTcpServer.h

#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QWidget>
#include <QTcpServer>
#include <mytcpsocket.h>

class MyTcpServer : public QTcpServer
{
    Q_OBJECT // 由于接收信号
public:
    // 获取单例对象的声明
    static MyTcpServer& getInstance();
private:
    // 私有化构造函数
    MyTcpServer();
    // 删除拷贝构造
    MyTcpServer(const MyTcpServer& instance) = delete;
    // 删除 拷贝赋值运算符
    MyTcpServer operator =(const MyTcpServer&) =delete;

};

#endif // MYTCPSERVER_H

        MyTcpServer.cpp

#include "mytcpserver.h"

// 构造函数
MyTcpServer::MyTcpServer()
{

}

// 获取单例对象
MyTcpServer &MyTcpServer::getInstance()
{
    // 设置静态局部引用
    static MyTcpServer instance;
    // 返回单例对象
    return instance;
}

          Server类的构造函数中使用 MyTcpServer调用listen 函数

    // 进行监听
    MyTcpServer::getInstance().listen(QHostAddress(m_IP),m_Port);

3.5 重写incomingConnection()函数

        在服务器与客户端成功建立连接后,会触发incomingConnection()函数,我们想要连接成功后,进行提示,因此需要incomingConnection()函数进行重写,以实现新的客户端连接成功后,进行提示。

         在  MyTcpServer.h中声明,参数暂时可以不看

    // 重写连接成功的槽函数
    void incomingConnection(qintptr handle);

        在  MyTcpServer.cpp中实现,利用QMessageBox对话框进行提示。

// 重写连接成功的信号槽函数,并将连接成功的socket 放入 m_tcpSocketList 列表中。
void MyTcpServer::incomingConnection(qintptr handle)
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(msgBox,"客户端建立连接","新客户端连接成功");
}

3.6 存储客户端的Socket信息

        在实际情况中,不可能只有一个客户端与服务器进行连接,应该是有很多客户端与服务器进行连接的。因此我们要定义一个数据结构来存储多个客户端的连接信息。

        1、定义一个MyTcpSocket类,并继承QTcpSocket类,方便后续对Socket进行操作。

        2、添加一个成员变量QList<MyTcpSocket*> m_tcpSocketList,一个存储Socket描述符的列表,用来存储多个客户端Socket,以解决多个客户端进行连接的情况。

        3、在incomingConnection()函数中,将当前连接的客户端socket指针放入该列表

3.6.1 定义MyTcpSocket类

        定义一个MyTcpSocket类,并继承QTcpSocket类即可

        MyTcpSocket.h

#ifndef MYTCPSOCKET_H
#define MYTCPSOCKET_H

#include <QWidget>
#include <QTcpSocket>

class MyTcpSocket:public QTcpSocket
{
    Q_OBJECT
public:
    MyTcpSocket();
};

#endif // MYTCPSOCKET_H

        MyTcpSocket.cpp

#include "mytcpsocket.h"

MyTcpSocket::MyTcpSocket()
{

}

3.6.2 添加存储客户端连接的列表

        在  MyTcpServer.h中声明成员变量,QList<MyTcpSocket*> m_tcpSocketList

    // 定义连接成功的客户端 socket 的列表
    QList<MyTcpSocket*> m_tcpSocketList;

3.6.3 将连接成功的客户端存入列表中。

        在incomingConnection()函数中,将当前连接的客户端socket指针放入该列表

        在  MyTcpServer.cpp的incomingConnection()函数中实现,handle中存放了客户端的socket信息。

// 重写连接成功的信号槽函数,并将连接成功的socket 放入 m_tcpSocketList 列表中。
void MyTcpServer::incomingConnection(qintptr handle)
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(msgBox,"客户端建立连接","新客户端连接成功");

    // 将新连接的客户端socket 存到 m_tcpSocketList 列表中。
    MyTcpSocket* tcpSocket = new MyTcpSocket;
    tcpSocket->setSocketDescriptor(handle);
    m_tcpSocketList.append(tcpSocket);
}

3.7 服务器完整代码

mytcpsocket.h

#ifndef MYTCPSOCKET_H
#define MYTCPSOCKET_H

#include <QWidget>
#include <QTcpSocket>

class MyTcpSocket:public QTcpSocket
{
    Q_OBJECT
public:
    MyTcpSocket();
};

#endif // MYTCPSOCKET_H

mytcpsocket.cpp

#include "mytcpsocket.h"

MyTcpSocket::MyTcpSocket()
{

}

mytcpserver.h

#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QWidget>
#include <QTcpServer>
#include <mytcpsocket.h>

class MyTcpServer : public QTcpServer
{
    Q_OBJECT 
public:
    // 获取单例对象
    static MyTcpServer& getInstance();
    // 重写连接成功的槽函数
    void incomingConnection(qintptr handle);

private:
    // 构造私有化
    MyTcpServer();
    // 删除拷贝构造
    MyTcpServer(const MyTcpServer& instance) = delete;
    // 删除 拷贝赋值运算符
    MyTcpServer operator =(const MyTcpServer&) =delete;
    // 定义连接成功的客户端 socket 的列表
    QList<MyTcpSocket*> m_tcpSocketList;
};

#endif // MYTCPSERVER_H

mytcpserver.cpp

#include "mytcpserver.h"

#include <QMessageBox>

// 构造函数
MyTcpServer::MyTcpServer()
{

}

// 获取单例对象
MyTcpServer &MyTcpServer::getInstance()
{
    // 设置静态局部引用
    static MyTcpServer instance;
    // 返回单例对象
    return instance;
}

// 重写连接成功的信号槽函数,并将连接成功的socket 放入 m_tcpSocketList 列表中。
void MyTcpServer::incomingConnection(qintptr handle)
{
    // 消息对话框,展示连接成功。
    QMessageBox* msgBox = new QMessageBox;
    msgBox->information(msgBox,"客户端建立连接","新客户端连接成功");

    // 将新连接的客户端socket 存到 m_tcpSocketList 列表中。
    MyTcpSocket* tcpSocket = new MyTcpSocket;
    tcpSocket->setSocketDescriptor(handle);
    m_tcpSocketList.append(tcpSocket);
}

server.h

#ifndef SERVER_H
#define SERVER_H

#include <QWidget>
#include "mytcpserver.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Server; }
QT_END_NAMESPACE

class Server : public QWidget
{
    Q_OBJECT

public:
    Server(QWidget *parent = nullptr);
    ~Server();

    // 加载本地文件
    void loadConfig();

private:
    Ui::Server *ui;
    QString m_IP;
    quint16 m_Port;
};
#endif // SERVER_H

server.cpp

#include "server.h"
#include "ui_server.h"

#include <QFile>
#include <QMessageBox>
#include <QDebug>

Server::Server(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Server)
{
    ui->setupUi(this);
    // 加载配置文件
    loadConfig();
    
    // 进行监听
    MyTcpServer::getInstance().listen(QHostAddress(m_IP),m_Port);

}

Server::~Server()
{
    delete ui;
}

// 加载配置文件
void Server::loadConfig()
{
    // 获取文件路径
    QFile file(":/server.config");
    // 打开文件
    if(!file.open(QIODevice::ReadOnly))
    {   // 打开失败--错误提示
        QMessageBox::critical(this,"打开配置文件","打开配置文件失败");
        return;
    }
    // 读取文件内容
    QByteArray baData = file.readAll();
    // 转换格式为 QString
    QString strData = QString(baData);
    // 将字符串按照 "\r\n" 进行分割
    QStringList strList = strData.split("\r\n");
    // 获取IP地址
    this->m_IP = strList.at(0);
    // 获取端口号
    this->m_Port = strList.at(1).toShort();
    // 测试打印
    // qDebug()<<"IP:"<<m_IP;
    // qDebug()<<"Port:"<<m_Port;

}

main.cpp

#include "server.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Server w;
    w.show();
    return a.exec();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值