基于C++的网盘系统项目开发教程

项目资源下载

  1. 基于C++的网盘系统项目源码CSDN下载地址
  2. 基于C++的网盘系统项目源码GitHub下载地址

项目简介

  本项目基于C++开发,整个项目采用C/S架构,使用Sqlite3数据库存储用户信息,本地磁盘存储用户文件,使用Socket进行客户端和服务器之间的数据传输。完成了网盘中关于用户的几乎所有功能,包括:用户注册、用户登录、用户退出、查看在线好友、搜索好友、添加好友、刷新在线好友、删除好友、私聊、群聊等等;关于文件夹和文件的所有功能也几乎完成了,包括:创建文件夹、查看所有文件、删除文件夹、重命名文件夹、进入文件夹、返回上一级、上传文件、删除文件、下载文件、分享文件、移动文件等等。


项目开发软件环境

  • Windows 11
  • Qt Creator 4.11.1 (Community)
  • C++ 98
  • Gcc 3.4.5
  • SQLite 3

项目开发硬件环境

  • CPU:Intel® Core™ i7-8750H CPU @ 2.20GHz 2.20 GHz
  • RAM:24GB
  • GPU:NVIDIA GeForce GTX 1060

文章目录


前言

  本项目的完成度非常高,包括了大大小小21个功能功能,自然代码数也非常多,代码多不是问题,因为我写的非常详细,详细到每一步我都有解释,各位读者一定可以看懂。另外,为了方便读者学习,且本着开源精神,我已经将我写好的源码分享在上面了。此项目作为本科毕设或者写在简历上也不失为一个好的选择。此项目花费了我大量精力和时间,前前后后写了两个多月,导致最后这篇博文的内容实在是太多了,一共4万多字,上百张图片,我现在编辑都异常的卡,所以就不写太多前言了,有什么不懂的私信我或者评论我都可以,希望读者可以收获满满,下面就开始学习吧!


零、项目演示

0.1 用户注册

  1. 当我们启动两个项目后,输入用户名和密码最后点击“注册”,发现提示我们注册成功了:
    请添加图片描述

  2. 然后我们来到数据库查看数据,发现新的注册信息已经保存到数据库中了:
    请添加图片描述

  3. 如果此时我们还是以同样的用户名去注册,就会提示我们注册失败,因为数据库中已经有同名的用户名了,而我们设置用户名字段唯一,所以注册会失败:
    请添加图片描述

0.2 用户登录

  1. 启动两个项目,当我们使用已经在数据库中存在的用户,并且此用户的“online”字段为0的时候,是可以成功登陆的:
    请添加图片描述

  2. 当我们尝试使用数据库中不存在的用户登陆时,是不可以成功登录的:
    请添加图片描述

0.3 用户退出

  1. 启动两个项目,使用某一个用户登录,然后查询数据库,发现此用户的“online”字段为1,说明已经成功登陆了:
    请添加图片描述

  2. 然后点击关闭按钮,模仿用户退出行为:
    请添加图片描述

  3. 此时再来数据库查询,就发现对应用户的online字段已经变为了0,说明此用户已经成功退出了:
    请添加图片描述

0.4 查看在线好友

  1. 同时启动多个客户端,然后分别登陆进去,点击“显示在线用户”按钮,可以发现在线的用户的用户名已经成功显示了:
    请添加图片描述

0.5 搜索好友

  1. 启动两个项目后,当我们搜索用户“abc”的时候,发现可以成功展示其用户名和状态:
    请添加图片描述

0.6 添加好友

  1. 打开两个客户端和一个服务器后,发现被加用户可以正常弹出窗口:
    请添加图片描述

  2. 当我们点击“Yes”后,发现可以已经成功添加好友了,并且数据库中也已经保存了两个用户的好友关系:
    请添加图片描述

0.7 刷新在线好友

  1. 分别启动服务器和客户端并登陆后,我们点击“刷新好友”按钮,发现此时可以将最新在线的好友列表显示出来了:
    请添加图片描述

0.8 删除好友

  1. 分别启动服务器和有好友关系的两个客户端,点击“删除好友”按钮后就可以成功删除两个用户之间的好友关系了,并且可以显示提示:
    请添加图片描述

0.9 私聊

  1. 启动服务器和两个客户端进行测试,发现可以正常进行聊天消息的传递:
    请添加图片描述

0.10 群聊

  1. 启动项目后,发现可以正常发送群聊信息:
    请添加图片描述

0.11 创建文件夹

  1. 可以成功在用户名同名的目录下新建文件夹
    请添加图片描述

0.12 查看所有文件

  1. 当我们点击“图书”界面的“刷新文件”,就可以显示此文件夹内所有的文件了:
    请添加图片描述

0.13 删除文件夹

  1. 当我们选中文件夹后点击“删除文件夹”后,就会弹出删除成功的提示,然后当我们点击“刷新文件”后,就会发现原来的被删除文件夹已经不存在了:
    请添加图片描述

0.14 重命名文件夹

  1. 选中某个待重命名的文件,然后输入重命名后的文件名称:
    请添加图片描述

  2. 当我们点击“刷新文件”后,可以发现已经成功重命名文件了:
    请添加图片描述

0.15 进入文件夹

  1. 当我们双击某个文件夹的时候就可以进入到此文件夹:
    请添加图片描述

0.16 返回上一级

  1. 当我们点击“返回”的时候,可以从子目录返回到主目录:
    请添加图片描述

0.17 上传文件

  1. 选中本地某个文件进行上传:
    请添加图片描述

  2. 可以发现,文件已经成功上传了:
    请添加图片描述

0.18 删除文件

  1. 我们可以选中某个文件,然后点击“删除文件”:
    请添加图片描述

  2. 当我们再次“刷新文件”后,就会发现选中的文件已经被我们删除了:
    请添加图片描述

0.19 下载文件

  1. 选中服务器的某个文件进行下载:
    请添加图片描述

  2. 可以发现,已经下载成功了:
    请添加图片描述

0.20 分享文件

  1. 首先启动一个服务端和两个客户端,在两个客户端上点击“刷新好友”,要先刷新好友,才能对好友进行分享文件的操作:
    请添加图片描述

  2. 然后将“rose”用户下的“hello”文件夹进行分享:
    请添加图片描述

  3. 选择“lucy”后,点击“确定”:
    请添加图片描述

  4. 此时两个客户端都有相应的提示了,我们只需要点击接收端的“Yes”:
    请添加图片描述

  5. 然后点击“lucy”用户客户端的“刷新文件”,可以看到“rose”用户的“hello”文件夹及其文件夹中的内容已经成功拷贝到“lucy”用户的文件目录中了:
    请添加图片描述

0.21 移动文件

  1. 首先选择要移动的文件,然后点击“移动文件”:
    请添加图片描述

  2. 然后选择要移动文件的目标目录后,点击“目标目录”:
    请添加图片描述

  3. 当我们进入移动文件的目标目录查看后,发现文件已经移动到目标目录了:
    请添加图片描述

一、项目架构

  整个项目采用C/S架构,使用Sqlite3数据库存储用户信息,本地磁盘存储用户文件,使用Socket进行客户端和服务器之间的数据传输,项目的整体结构并不复杂,跟着我一步一步做下去,基本没有任何问题。项目的整体架构如下所示:
请添加图片描述

二、QT开发框架下载安装与使用

  本项目采用跨平台C++图形用户界面应用程序QT开发框架开发, 它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。QT开发框架是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,QT开发框架很容易扩展,并且允许真正地组件编程。在正式开发项目之前,我们首先下载安装QT开发框架,并简单使用一下,读者只需要跟着我一步一步做下去即可,务必一定下载安装完毕,并简单上手使用,因为整个项目都使用QT开发框架开发。本节的主要内容包括:QT开发框架下载、QT开发框架安装、QT开发框架使用。只有将QT开发框架安装完毕,才能写我们的网盘系统项目。

2.1 QT开发框架下载

  1. 进入QT开发框架的下载地址
    请添加图片描述

  2. 点击“archive/”:
    请添加图片描述

  3. 点击“qt/”:
    请添加图片描述

  4. 点击“5.14/”,当然,读者也可以选择自己适合的版本,不过建议各位读者选择和我一样的版本:
    请添加图片描述

  5. 点击“5.14.2/”:
    请添加图片描述

  6. 点击下图红框所示内容:
    请添加图片描述

  7. 在弹出的窗口中选择“另存为”:
    请添加图片描述

  8. 随便选择一个位置后(建议下载到桌面),点击“保存(S)”:
    请添加图片描述

2.2 QT开发框架安装

  1. 在正式安装之前先去QT开发框架官网进行QT开发框架账号的注册,这里一定要先注册QT开发框架账号,否则会出错:
    请添加图片描述

  2. 输入和选择相关信息后,点击“Create Qt Account”:
    请添加图片描述

  3. 然后来到您注册时填写的邮箱,点击收到的QT开发框架注册邮件链接:
    请添加图片描述

  4. 进入邮件详情界面后,点击下图红框的链接:
    请添加图片描述

  5. 点击“继续访问”:
    请添加图片描述

  6. 输入和选择相关信息后,点击“Confirm”:
    请添加图片描述

  7. 然后来到之前注册QT开发框架账号的界面点击“Continue”:
    请添加图片描述

  8. 注册完后,双击打开下载好的QT开发框架安装包:
    请添加图片描述

  9. 点击“Next”:
    请添加图片描述

  10. 输入刚才注册好的账号密码后点击“Next”:
    请添加图片描述

  11. 在红框处打勾后点击“下一步(N)”:
    请添加图片描述

  12. 点击“下一步(N)”:
    请添加图片描述

  13. 选择您的安装文件夹并在红框处打勾后点击“下一步(N)”:
    请添加图片描述

  14. 默认选择红框中的几个组件即可,其余组件若后续有需求可自行补充安装:
    请添加图片描述

  15. 选择红框处内容并点击“下一步(N)”:
    请添加图片描述

  16. 点击“下一步(N)”:
    请添加图片描述

  17. 点击“安装(I)”:
    请添加图片描述

  18. 等待安装完成:
    请添加图片描述

  19. 点击“完成(F)”:
    请添加图片描述

  20. 此时我们已经来到了QT开发框架的主界面了:
    请添加图片描述

2.3 QT开发框架使用

  1. 点击“Projects”后点击“New”:
    请添加图片描述

  2. 选择红框处内容后点击“Choose…”:
    请添加图片描述

  3. 输入项目名称和项目路径后点击“下一步(N)”,另外需要注意项目路径中不能包括中文和空格:
    请添加图片描述

  4. 点击“下一步(N)”:
    请添加图片描述

  5. 点击“下一步(N)”:
    请添加图片描述

  6. 点击“下一步(N)”:
    请添加图片描述

  7. 按红框内样子选择后点击“下一步(N)”:
    请添加图片描述

  8. 点击“完成(F)”:
    请添加图片描述

  9. 点击红框处:
    请添加图片描述

  10. 可以看到,您的第一个基于QT开发框架创建的窗口已经成功显示了,到此为止,意味着QT开发框架安装成功:
    请添加图片描述

三、SQLite3数据库搭建

  我们的项目采用SQLite3作为数据库,因为我们的项目并不需要很复杂的表结构,而且SQLite3配置和使用也都十分方便,所以我们使用SQLite3作为项目数据库使用。请各位读者务必跟着我将SQLite3数据库安装完毕,否则后面的项目没法推进,只要各位读者按照我下面的步骤一步一步操作,就一定可以将SQLite3数据库安装成功。本节的主要内容包括:SQLite3数据库安装、SQLite3数据库设计、SQLite3数据库测试。这些内容也是我们项目准备工作的最后一步,将SQLite3安装完成之后,就可以正式开始写我们的网盘系统项目了。

3.1 SQLite3数据库安装

  1. 进入SQLite3官网后,点击“Download”:
    请添加图片描述

  2. 进入下一个界面后,往下滑找到“Precompiled Binaries for Windows”:
    请添加图片描述

  3. 在这里注意,如果您的计算机是32位就下载蓝框中的两个文件,如果您的计算机是64位就下载紫框中的两个文件。如果您的计算机是64位也可以直接使用此博客开始介绍的资源中的压缩包:
    请添加图片描述

  4. 下载好后的压缩包如下图所示:
    请添加图片描述

  5. 然后在您喜欢的目录下新建名为“SQLite3”的文件夹:
    请添加图片描述

  6. 然后将刚刚下载的压缩包中的文件全部解压到刚刚创建的名为“SQLite3”的文件夹中:
    请添加图片描述

  7. 然后在“此电脑”上右键点击“属性”:
    请添加图片描述

  8. 选择“高级系统设置”:
    请添加图片描述

  9. 选择“环境变量”:
    请添加图片描述

  10. 双击“系统变量”中的“Path”:
    请添加图片描述

  11. 选择“新建”:
    请添加图片描述

  12. 将之前创建的路径添加进去:
    请添加图片描述

  13. 点击“确定”:
    请添加图片描述

  14. 点击“确定”:
    请添加图片描述

  15. 点击“确定”:
    请添加图片描述

  16. 然后重启系统,重启系统之后才能使刚才的设置生效

  17. 重启系统之后使用键盘上的“Win+R”组合键打开“运行”窗口,并输入cmd,然后点击确定:
    请添加图片描述

  18. 出现cmd窗口后输入“sqlite3”,然后回车,出现如下内容表示SQLite3数据库安装成功:
    请添加图片描述

  19. 然后输入“.quit”,然后回车,即可退出SQLite3数据库
    请添加图片描述

3.2 SQLite3数据库设计

  1. 目前我们需要两个表,分别是用户信息表和用户好友表,用户信息表存放使用网盘系统的用户的id和姓名以及登录密码,而用户好友表保存用户的id和用户的好友id,其中id作为外键使用,这样就可以找到当前用户的其他好友了

    • 用户信息表:

      字段类型约束条件其他
      id整型主键自动增长
      namevarchar(32)not null
      pwdvarchar(32)not null
    • 用户好友表

      字段类型约束条件其他
      id整型主键外键
      friendId整型主键外键
  2. 设计完数据表后,我们就要按照设计的要求创建数据库了。首先在SQLite3的安装目录下新建名为“Database”的文件夹:
    请添加图片描述

  3. 进入“Database”文件夹后,右键单击“在终端中打开”:
    请添加图片描述

  4. 然后输入sqlite3 ./cloud.db并回车:
    请添加图片描述

  5. 首先创建用户信息表,在终端窗口中输入如下内容并回车:

    create table usrInfo(
        id integer primary key autoincrement,
        name varchar(32),
        pwd varchar(32)
    );
    
  6. 然后创建用户好友表,在终端窗口中输入如下内容并回车:

    create table friendInfo(
    	id integer not null,
        friendId integer not null,
        primary key(id,friendId)
    );
    
  7. 然后输入.tables发现已经成功创建了两个表:
    请添加图片描述

3.3 SQLite3数据库测试

  1. 我们插入几条数据测试一下,我们继续在刚才的终端窗口中输入如下内容并回车:

    insert into usrInfo(name,pwd) values('jack','jack'),('rose','rose'),('luck','luck');
    
  2. 然后继续在刚才的终端窗口中输入如下内容来查看我们的数据是否成功插入:

    select * from usrInfo;
    
  3. 可以看到我们的数据已经成功插入了:
    请添加图片描述

四、客户端服务器搭建

  整个项目的准备工作我们已经完成了,下面就要正式开始写我们的网盘系统项目了,写整个项目的第一步就是搭建项目的基本框架,这一步虽然比较简单,但是细节比较多,所以还请各位读者跟着我一步一步做。本节的主要内容包括:设置配置文件、客户端实现、服务器实现。将这些步骤完成之后,我们的项目骨架就基本完成了。

4.1 设置配置文件

  1. 点击“Projects”后点击“New”:
    请添加图片描述

  2. 依次选择红框处内容,最后点击“Choose…”:
    请添加图片描述

  3. 输入项目名称“TcpClient”并选择您的项目路径,然后点击“下一步(N)”:
    请添加图片描述

  4. 点击“下一步(N)”:
    请添加图片描述

  5. 按照我红框内配置,然后点击“下一步(N)”:
    请添加图片描述

  6. 点击“下一步(N)”:
    请添加图片描述

  7. 选择红框内容后点击“下一步(N)”:
    请添加图片描述

  8. 点击“完成(F)”:
    请添加图片描述

  9. 点击红框内图标:
    请添加图片描述

  10. 可以发现我们项目已经成功创建了:
    请添加图片描述

  11. 右键项目后点击“Find in This Directory…”:
    请添加图片描述

  12. 复制此路径:
    请添加图片描述

  13. 进入到刚才复制的路径对应的文件夹内:
    请添加图片描述

  14. 在此文件夹内“新建”一个“文本文档”:
    请添加图片描述

  15. 将其重命名为“client.config”:
    请添加图片描述

  16. 点击“是(Y)”:
    请添加图片描述

  17. 使用记事本或者Sublime Text打开此配置文件:
    请添加图片描述

  18. 在此配置文件内输入如下内容:
    请添加图片描述

  19. 在项目上右键后点击“Add New…”:
    请添加图片描述

  20. 点击“Qt”后双击“Qt Resource File”:
    请添加图片描述

  21. 输入名称“config”后点击“下一步(N)”:
    请添加图片描述

  22. 点击“完成(F)”:
    请添加图片描述

  23. 点击红框后在“前缀”栏输入“/”,然后按“Ctrl+S”保存,此时我们就将配置文件添加到项目中了,所有的配置文件都是按照此方法加入项目的,所以后面不再赘述:
    请添加图片描述

  24. 首先点击“Add Files”,然后选择刚才创建的配置文件,然后点击“打开(O)”,最后按“Ctrl+S”保存:
    请添加图片描述

  25. 将tcpclient.h中的内容全部替换为如下内容:

    #ifndef TCPCLIENT_H
    #define TCPCLIENT_H
    
    #include <QWidget>
    #include <QFile>
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class TcpClient; }
    QT_END_NAMESPACE
    
    class TcpClient : public QWidget
    {
        Q_OBJECT
    
    public:
        TcpClient(QWidget *parent = nullptr);
        ~TcpClient();
        void loadConfig();
    
    private:
        Ui::TcpClient *ui;
        QString m_strIP;
        quint16 m_usPort;
    };
    #endif // TCPCLIENT_H
    
  26. 将tcpclient.cpp中的内容全部替换为如下内容:

    #include "tcpclient.h"
    #include "ui_tcpclient.h"
    #include <QByteArray>
    #include <QDebug>
    #include <QMessageBox>
    
    TcpClient::TcpClient(QWidget *parent) : QWidget(parent), ui(new Ui::TcpClient)
    {
        ui->setupUi(this);
        loadConfig();
    }
    
    TcpClient::~TcpClient()
    {
        delete ui;
    }
    
    void TcpClient::loadConfig()
    {
        QFile file(":/client.config");
        if(file.open(QIODevice::ReadOnly))
        {
            QByteArray baData = file.readAll();
            QString strData = baData.toStdString().c_str();
            strData.replace("\r\n"," ");
            QStringList strList = strData.split(" ");
            m_strIP = strList.at(0);
            m_usPort = strList.at(1).toUShort();
            qDebug() << "IP地址为:" << m_strIP << "端口为:" << m_usPort;
            file.close();
        }
        else
        {
            QMessageBox::critical(this,"open config","open config failed");
        }
    }
    
  27. 然后点击红框中的绿色三角标运行程序,此后若无特殊说明,但凡运行程序,都需要点击红框中的绿色三角标,后文不再赘述:
    请添加图片描述

  28. 可以发现,我们已经成功获取到了配置文件中的IP地址和端口号,这时我们就完成了配置文件的设置:
    请添加图片描述

4.2 客户端实现

  1. Tcp客户端连接服务器的流程如下图所示:
    请添加图片描述

  2. 既然需要和服务器连接,所以我们需要使用QT开发框架的网络变成模块,那么我们需要在TcpClient.pro文件中红框处加入“network”(注意和前面“gui”之间存在一个空格),最后按“Ctrl+S”保存:
    请添加图片描述

  3. 为了与服务器建立连接,我们首先在tcpclient.h中加入如下三处代码:
    请添加图片描述

  4. 然后在tcpclient.cpp中加入如下三处代码:
    请添加图片描述

  5. 最后我们点击下图红框内的小锤子进行编译:
    请添加图片描述

  6. 可以发现没有任何错误,说明我们的代码没有任何问题,但是此时我们还不能进行任何输出,也不能进行客户端与服务器的连接,因为我们只是实现了客户端的基本框架,服务器还没有实现,所以下面我们要实现服务器的基本框架:
    请添加图片描述

4.3 服务器实现

  1. 在空白部分点击“新建项目…”:
    请添加图片描述

  2. 依次点击下图所示位置,最后点击“Choose…”:
    请添加图片描述

  3. 输入项目名称和项目保存目录,然后点击“下一步(N)”:
    请添加图片描述

  4. 点击“下一步(N)”:
    请添加图片描述

  5. 按照红框处配置,然后点击“下一步(N)”:
    请添加图片描述

  6. 点击“下一步(N)”:
    请添加图片描述

  7. 选择红框处,然后点击“下一步(N)”:
    请添加图片描述

  8. 点击“完成(F)”:
    请添加图片描述

  9. 将之前创建的配置文件复制一份到新的项目目录中:
    请添加图片描述

  10. 将复制过来的配置文件重命名为“server.config”:
    请添加图片描述

  11. 按照TcpClient项目中同样的方法,将“server.config”配置文件加载到TcpServer项目中:
    请添加图片描述

  12. 服务器监听客户端IP地址和端口示意图如下所示:
    请添加图片描述

  13. 在TcpServer项目上右键,然后点击“Add New…”:
    请添加图片描述

  14. 按照如下顺序选择,最后点击“Choose…”:
    请添加图片描述

  15. 按照红框内容输入,最后点击“下一步(N)”:
    请添加图片描述

  16. 点击“完成(F)”:
    请添加图片描述

  17. 在“TcpServer.pro”中红框位置加入“network”(注意与前面的gui之间有一个空格),最后按“Ctrl + S”保存:
    请添加图片描述

  18. 将“mytcpserver.h”的内容全部替换为如下内容:

    #ifndef MYTCPSERVER_H
    #define MYTCPSERVER_H
    #include <QTcpServer>
    
    class MyTcpServer : public QTcpServer
    {
        Q_OBJECT
    public:
        MyTcpServer();
        static MyTcpServer &getInstance();
        void incomingConnection(qintptr handle);
    };
    
    #endif // MYTCPSERVER_H
    
  19. 将“mytcpserver.cpp”的内容全部替换为如下内容:

    #include "mytcpserver.h"
    #include <QDebug>
    
    MyTcpServer::MyTcpServer()
    {
    
    }
    
    MyTcpServer &MyTcpServer::getInstance()
    {
        static MyTcpServer instance;
        return instance;
    }
    
    void MyTcpServer::incomingConnection(qintptr handle)
    {
        qDebug() << "new client connected";
    }
    
  20. 将“tcpserver.h”的内容全部替换为如下内容:

    #ifndef TCPSERVER_H
    #define TCPSERVER_H
    
    #include <QWidget>
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class TcpServer; }
    QT_END_NAMESPACE
    
    class TcpServer : public QWidget
    {
        Q_OBJECT
    
    public:
        TcpServer(QWidget *parent = nullptr);
        ~TcpServer();
        void loadConfig();
    
    private:
        Ui::TcpServer *ui;
        QString m_strIP;
        quint16 m_usPort;
    };
    #endif // TCPSERVER_H
    
  21. 将“tcpserver.cpp”的内容全部替换为如下内容:

    #include "tcpserver.h"
    #include "ui_tcpserver.h"
    #include "mytcpserver.h"
    #include <QByteArray>
    #include <QDebug>
    #include <QMessageBox>
    #include <QHostAddress>
    #include <QFile>
    
    TcpServer::TcpServer(QWidget *parent): QWidget(parent), ui(new Ui::TcpServer)
    {
        ui->setupUi(this);
        loadConfig();
        MyTcpServer::getInstance().listen(QHostAddress(m_strIP),m_usPort);
    }
    
    TcpServer::~TcpServer()
    {
        delete ui;
    }
    
    // 加载配置文件
    void TcpServer::loadConfig()
    {
        QFile file(":/server.config");
        if(file.open(QIODevice::ReadOnly))
        {
            QByteArray baData = file.readAll();
            QString strData = baData.toStdString().c_str();
            strData.replace("\r\n"," ");
            QStringList strList = strData.split(" ");
            m_strIP = strList.at(0);
            m_usPort = strList.at(1).toUShort();
            qDebug() << "IP地址为:" << m_strIP << "端口为:" << m_usPort;
            file.close();
        }
        else
        {
            QMessageBox::critical(this,"open config","open config failed");
        }
    }
    
  22. 然后我们来测试一下客户端和服务器的连接,首先我们启动TcpServer服务器项目,直接在项目上右键点击“运行”即可:
    请添加图片描述

  23. 然后我们启动TcpClient客户端项目,同样直接在项目上右键点击“运行”,所以说如果需要同时启动多个项目,我们只需要按顺序依次在各个项目上右键,然后点击“运行”即可,后面不再赘述:
    请添加图片描述

  24. 可以看到,客户端显示了连接成功的字样,服务器也显示了连接成功的字样,这就意味着我们现在已经建立起了客户端和服务器的连接了:
    请添加图片描述

五、通讯协议设计

  当我们建立起客户端和服务器的基本消息传输通路后,就要在这条数据通路上传输消息,但是不能随意传输,我们应该让我们传输的数据遵循某种规则,所以我们这节就要来设计通讯协议,让客户端和服务器按照我们设计的通讯协议进行消息传输。本节的主要内容包括:弹性结构体设计、通讯协议设计、数据收发测试。这些步骤完成之后,我们就可以在客户端和服务器之间进行消息传输了。

5.1 弹性结构体设计

  1. 先简单介绍一下弹性结构体,弹性结构体相较于正常的结构体的区别就是在弹性结构体中的数组我们并没声明数组空间的大小,需要的时候我们再对数组空间大小赋值,这样做的好处是节省存储空间,而且效率较高,因为我们在进行客户端和服务器的信息传输的时候,不确定传输的数据量大小,所以我们应该使用弹性结构体进行数据传输。我们需要了解,客户端与服务器之间的通信协议包括:

    • 总的消息大小
    • 消息类型
    • 实际消息大小
    • 实际消息

    整个通信协议我们应该使用弹性结构体进行设计,其中在设计弹性结构体的时候,实际消息应该为不赋数组空间大小的数组,下面就是关于通信协议的弹性结构体设计的具体步骤

5.2 通讯协议设计

  1. 在TcpClient项目上右键点击“Add New…”:
    请添加图片描述

  2. 按照如下顺序选择,最后点击“Choose…”:
    请添加图片描述

  3. 输入名称“protocol.h”后点击“下一步(N)”:
    请添加图片描述

  4. 点击“完成(F)”,这种新建文件的方式后续不再赘述,如果在需要新建文件,按照此步骤进行即可:
    请添加图片描述

  5. 在“protocol.h”中加入如下代码:

    #ifndef PROTOCOL_H
    #define PROTOCOL_H
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    typedef unsigned int uint;
    
    // 消息回复类型
    #define REGIST_OK "regist ok"
    #define REGIST_FAILED "regist failed : name existed"
    #define LOGIN_OK "login ok"
    #define LOGIN_FAILED "login failed : name error or pwd error or relogin"
    #define SEARCH_USR_NO "no such people"
    #define SEARCH_USR_ONLINE "online"
    #define SEARCH_USR_OFFLINE "offline"
    #define UNKNOW_ERROR "unknow error"
    #define EXISTED_FRIEND "friend exist"
    #define ADD_FRIEND_OFFLINE "usr offline"
    #define ADD_FRIEND_NO_EXIST "usr not exist"
    #define DEL_FRIEND_OK "delete friend ok"
    #define DIR_NO_EXIST "cur dir not exist"
    #define FILE_NAME_EXIST "file name exist"
    #define CREAT_DIR_OK "create dir ok"
    #define DEL_DIR_OK "delete dir ok"
    #define DEL_DIR_FAILURED "delete dir failured: is reguler file"
    #define RENAME_FILE_OK "rename file ok"
    #define RENAME_FILE_FAILURED "rename file failured"
    #define ENTER_DIR_FAILURED "enter dir failured: is reguler file"
    #define DEL_FILE_OK "delete file ok"
    #define DEL_FILE_FAILURED "delete file failured: is diretory"
    #define UPLOAD_FILE_OK "upload file ok"
    #define UPLOAD_FILE_FAILURED "upload file failured"
    #define MOVE_FILE_OK "move file ok"
    #define MOVE_FILE_FAILURED "move file failured:is reguler file"
    #define COMMON_ERR "operate failed: system is busy"
    
    struct PDU
    {
        uint uiPDULen;      // 总的协议数据单元大小
        uint uiMsgType;     // 消息类型
        char caData[64];    // 文件名
        uint uiMsgLen;      // 实际消息长度
        int caMsg[];        // 实际消息
    };
    
    PDU *mkPDU(uint uiMsgLen);
    
    #endif // PROTOCOL_H
    
  6. 将弹性结构体设计完成后,我们就要对此通信协议弹性结构体进行初始化,我们需要新建文件“protocol.cpp”,并在其中加入如下代码:

    #include "protocol.h"
    
    // 动态申请PDU协议空间
    PDU *mkPDU(uint uiMsgLen)
    {
        uint uiPDULen = sizeof(PDU) + uiMsgLen;
        PDU *pdu = (PDU*)malloc(uiPDULen);
        if(NULL == pdu)
        {
            exit(EXIT_FAILURE);
        }
        memset(pdu,0,uiPDULen);
        pdu->uiPDULen = uiPDULen;
        pdu->uiMsgLen = uiMsgLen;
        return pdu;
    }
    

5.2 数据收发测试

  1. 我们现在已经将通信协议设计完毕,下面就应该在客户端和服务器之间进行数据收发测试了,为了进行测试,我们应该简单设计一个界面,所以我们首先双击下图红框处:
    请添加图片描述

  2. 将两个红框内容拖到设计面板,大小和位置尽量和我保持一致,当然,各位读者也可以自行设计,然后双击“Push Button”可以将名称修改为“发送”,之后将紫框部分的名字修改为“send_pd”,蓝框部分可自行修改字体大小:
    请添加图片描述

  3. 再将红框内容拖到设计面板,同样可以自行设计字体大小:
    请添加图片描述

  4. 然后将红框部分设计为水平布局,蓝框部分设计为垂直布局,此时基本界面就设计完毕了,后面如果需要再设计界面,除了需要特殊注意的地方,其他做法都一样,就不再赘述:
    请添加图片描述

  5. 在“发送”按钮上右键,然后点击“转到槽…”:
    请添加图片描述

  6. 选择红框内容,然后点击“OK”:
    请添加图片描述

  7. 然后在“tcpclient.h”和“tcpclient.cpp”中会自动生成如下代码,这种自动生成的代码后面不再赘述,另外,要养成编码的好习惯,代码只要有一点修改,就要及时保存:
    请添加图片描述

  8. 生成了这两处代码后,我们先编译一下项目,点击下图中红框处即可,后面如果需要编译同样不再赘述:
    请添加图片描述

  9. 然后在“tcpclient.h”中引入头文件:

    #include "protocol.h"
    
  10. 然后将“tcpclient.cpp”中刚刚生成的函数“TcpClient::on_send_pd_clicked”替换为如下代码,此时我们就可以从客户端发送数据了:

    // 发送点击事件
    void TcpClient::on_send_pd_clicked()
    {
        QString strMsg = ui->lineEdit->text();
        if(!strMsg.isEmpty())
        {
            PDU *pdu = mkPDU(strMsg.size());
            pdu->uiMsgType = 8888;
            memcpy(pdu->caMsg,strMsg.toStdString().c_str(),strMsg.size());
            m_tcpSockey.write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::warning(this,"信息发送","发送的信息不能为空");
        }
    }
    
  11. 然后我们需要在TcpServer项目中新建“MyTcpSocket”类,目的是接收来自客户端的消息:
    请添加图片描述

  12. 然后将“mytcpsocket.h”中的全部内容替换为如下内容:

    #ifndef MYTCPSOCKET_H
    #define MYTCPSOCKET_H
    #include <QTcpSocket>
    #include "protocol.h"
    
    class MyTcpSocket : public QTcpSocket
    {
        Q_OBJECT;
    public:
        MyTcpSocket();
    public slots:
        void recvMsg();
    };
    
    #endif // MYTCPSOCKET_H
    
  13. 将TcpClient项目中下图红框的两个文件复制到TcpServer项目中:
    请添加图片描述

  14. 然后在TcpServer项目上右键点击“添加现有文件…”:
    请添加图片描述

  15. 将刚刚复制过来的两个文件加入到项目中:
    请添加图片描述

  16. 将“mytcpsocket.cpp”的全部内容替换为如下内容:

    #include "mytcpsocket.h"
    #include <QDebug>
    
    MyTcpSocket::MyTcpSocket()
    {
        connect(this,SIGNAL(readyRead()),this,SLOT(recvMsg()));
    }
    
    // 接收来自客户端的消息
    void MyTcpSocket::recvMsg()
    {
        qDebug() << this->bytesAvailable();
        uint uiPDULen = 0;
        this->read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        qDebug() << pdu->uiMsgType << pdu->caMsg;
    }
    
  17. 在“mytcpserver.h”中加入如下代码:
    请添加图片描述

  18. 然后将“mytcpserver.cpp”中的“MyTcpServer::incomingConnection”函数全部替换为如下内容:

    // 接收客户端的请求
    void MyTcpServer::incomingConnection(qintptr handle)
    {
        qDebug() << "new client connected";
        MyTcpSocket *pTcpSocket = new MyTcpSocket;
        pTcpSocket->setSocketDescriptor(handle);
        m_tcpSocketList.append(pTcpSocket);
    }
    
  19. 这时我们首先启动“TcpServer”项目,然后启动“TcpClient”项目,在“TcpClient”项目生成的图形界面的输入框中输入“HelloWorld”,最后点击发送:
    请添加图片描述

  20. 可以发现,服务器接收了客户端发送的“HelloWorld”,那么此时我们就已经建立起了客户端和服务器之间的数据传输通道:
    请添加图片描述

六、用户基本功能实现

  这节我们主要来完成用户的一些基本功能的实现,这也是一个软件最基本的功能,这些用户基本功能包括:数据库连接初始化、用户注册功能实现、用户登录功能实现、用户退出功能实现。当我们实现这些功能后,也标志着我们的软件雏形已经构建好了。

6.1 数据库连接初始化

  1. 首先在CMD中进入TcpServer项目目录,我们要在此目录下新建数据库,因为要使用服务器端保存用户的登陆注册退出的相关信息:
    请添加图片描述

  2. 创建名为“cloud.db”的数据库,在CMD中每条语句输入完后需要按“回车(Enter)”执行,后面不再赘述:
    请添加图片描述

  3. 然后继续输入如下内容来创建“用户信息表”:

    create table usrInfo(id integer primary key autoincrement,
                         name varchar(32) unique,
                         pwd varchar(32),
                         online integer default 0);
    
  4. 然后向“用户信息表”中加入测试数据:

    insert into usrInfo(name,pwd) values('jack','jacl'),
    								 ('rose','rose'),
    								 ('lucy','lucy');
    
  5. 查询一下数据已经成功添加到数据库了:
    请添加图片描述

  6. 同理输入如下内容来创建“好友表”:

    create table friend(id integer,
                        friendId integer,
                        primary key(id,friendId));
    
  7. 创建好这两个表后,我们可以使用.tables语句来查看我们创建好的两个表:
    请添加图片描述

  8. 然后在TcpServer项目中新建名为“OpeDB”的类来操作数据库:
    请添加图片描述

  9. 在“TcpServer.pro”文件的红框处加入“sql”(注意和前面“netwrok”之间的一个空格):
    请添加图片描述

  10. 将“opedb.h”中的全部内容替换为如下内容:

    #ifndef OPEDB_H
    #define OPEDB_H
    
    #include <QObject>
    #include <QSqlDatabase>
    #include <QSqlQuery>
    
    class OpeDB : public QObject
    {
        Q_OBJECT
    public:
        explicit OpeDB(QObject *parent = nullptr);
        static OpeDB& getInstance();
        void init();
        ~OpeDB();
    
    signals:
    
    public slots:
    
    private:
        QSqlDatabase m_db; // 连接数据库
    
    };
    
    #endif // OPEDB_H
    
  11. 将“opedb.cpp”中的全部内容替换为如下内容:

    #include "opedb.h"
    #include <QMessageBox>
    #include <QDebug>
    
    OpeDB::OpeDB(QObject *parent) : QObject(parent)
    {
        m_db = QSqlDatabase::addDatabase("QSQLITE");
    
    }
    
    // 生成实例
    OpeDB &OpeDB::getInstance()
    {
        static OpeDB instance;
        return instance;
    }
    
    // 数据库连接初始化
    void OpeDB::init()
    {
        m_db.setHostName("localhost");
        // 不同的数据库存放的位置,要更改为对应的位置
        m_db.setDatabaseName("D:\\Software\\C++_Code\\NetworkDiskSystem\\TcpServer\\cloud.db");
        if(m_db.open())
        {
            QSqlQuery query;
            query.exec("select * from usrInfo");
            while(query.next())
            {
                QString data = QString("%1,%2,%3").arg(query.value(0).toString()).arg(query.value(1).toString()).arg(query.value(2).toString());
                qDebug() << data;
            }
        }
        else
        {
            QMessageBox::critical(NULL,"打开数据库","打开数据库失败");
        }
    }
    
    // 析构函数用来关闭数据库连接
    OpeDB::~OpeDB()
    {
        m_db.close();
    }
    
  12. 将“main.cpp”中的全部内容替换为如下内容:

    #include "tcpserver.h"
    
    #include <QApplication>
    #include "opedb.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        OpeDB::getInstance().init();
        TcpServer w;
        w.show();
        return a.exec();
    }
    

6.2 用户注册功能实现

  1. 在TcpClient项目的“protocol.h”中加入如下代码,用来表示我们传输的消息类型:

    // 消息类型枚举
    enum ENUM_MSG_TYPE
    {
        ENUM_MSG_TYPE_MIN = 0,
        ENUM_MSG_TYPE_REGIST_REQUEST,           // 注册请求
        ENUM_MSG_TYPE_REGIST_RESPOND,           // 注册回复
        ENUM_MSG_TYPE_LOGIN_REQUEST,            // 登录请求
        ENUM_MSG_TYPE_LOGIN_RESPOND,            // 登录回复
        ENUM_MSG_TYPE_ALL_ONLINE_REQUEST,       // 在线用户请求
        ENUM_MSG_TYPE_ALL_ONLINE_RESPOND,       // 在线用户回复
        ENUM_MSG_TYPE_SEARCH_USR_REQUEST,       // 搜索用户请求
        ENUM_MSG_TYPE_SEARCH_USR_RESPOND,       // 搜索用户回复
        ENUM_MSG_TYPE_ADD_FRIEND_REQUEST,       // 添加好友请求
        ENUM_MSG_TYPE_ADD_FRIEND_RESPOND,       // 添加好友回复
        ENUM_MSG_TYPE_ADD_FRIEND_AGGREE,        // 同意添加好友
        ENUM_MSG_TYPE_ADD_FRIEND_REFUSE,        // 拒绝添加好友
        ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST,     // 刷新好友请求
        ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND,     // 刷新好友回复
        ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST,    // 删除好友请求
        ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND,    // 删除好友回复
        ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST,     // 私聊请求
        ENUM_MSG_TYPE_PRIVATE_CHAT_RESPOND,     // 私聊回复
        ENUM_MSG_TYPE_GROUP_CHAT_REQUEST,       // 群聊请求
        ENUM_MSG_TYPE_GROUP_CHAT_RESPOND,       // 群聊回复
        ENUM_MSG_TYPE_CREATE_DIR_REQUEST,       // 创建文件夹请求
        ENUM_MSG_TYPE_CREATE_DIR_RESPOND,       // 创建文件夹回复
        ENUM_MSG_TYPE_FLUSH_FILE_REQUEST,       // 刷新文件请求
        ENUM_MSG_TYPE_FLUSH_FILE_RESPOND,       // 刷新文件回复
        ENUM_MSG_TYPE_DEL_DIR_REQUEST,          // 删除目录请求
        ENUM_MSG_TYPE_DEL_DIR_RESPOND,          // 删除目录回复
        ENUM_MSG_TYPE_RENAME_FILE_REQUEST,      // 重命名文件请求
        ENUM_MSG_TYPE_RENAME_FILE_RESPOND,      // 重命名文件回复
        ENUM_MSG_TYPE_ENTER_DIR_REQUEST,        // 进入文件夹请求
        ENUM_MSG_TYPE_ENTER_DIR_RESPOND,        // 进入文件夹回复
        ENUM_MSG_TYPE_DEL_FILE_REQUEST,         // 删除常规文件请求
        ENUM_MSG_TYPE_DEL_FILE_RESPOND,         // 删除常规文件回复
        ENUM_MSG_TYPE_UPLOAD_FILE_REQUEST,      // 上传文件请求
        ENUM_MSG_TYPE_UPLOAD_FILE_RESPOND,      // 上传文件回复
        ENUM_MSG_TYPE_DOWNLOAD_FILE_REQUEST,    // 下载文件请求
        ENUM_MSG_TYPE_DOWNLOAD_FILE_RESPOND,    // 下载文件回复
        ENUM_MSG_TYPE_SHARE_FILE_REQUEST,       // 共享文件请求
        ENUM_MSG_TYPE_SHARE_FILE_RESPOND,       // 共享文件回复
        ENUM_MSG_TYPE_SHARE_FILE_NOTE_REQUEST,  // 共享文件记录请求
        ENUM_MSG_TYPE_SHARE_FILE_NOTE_RESPOND,  // 共享文件记录回复
        ENUM_MSG_TYPE_MOVE_FILE_REQUEST,        // 移动文件请求
        ENUM_MSG_TYPE_MOVE_FILE_RESPOND,        // 移动文件回复
        ENUM_MSG_TYPE_MAX = 0x00ffffff,
    };
    
  2. 然后打开TcpClient项目的“tcpclient.ui”,按照如下布局配置:
    请添加图片描述

  3. 将“用户名标签”的名称改为“name_lab”:
    请添加图片描述

  4. 将“密码标签”的名称改为“pwd_lab”:
    请添加图片描述

  5. 然后按照同样的方法,将:

    • “用户名输入框”的名称改为:name_le
    • “密码输入框”的名称改为:pwd_le
    • “登录按钮”的名称改为:login_pb
    • “注册按钮”的名称改为:regist_pb
    • “注销按钮”的名称改为:cancel_pb
  6. 将“密码输入框”的“echoMode”改为“Password”:
    请添加图片描述

  7. 添加“登录按钮”的信号槽,系统就会自动帮我们生成对应的函数:
    请添加图片描述

  8. 同理为“注册按钮”和“注销按钮”添加点击事件的信号槽,系统同样会自动帮我们生成对应的函数:
    请添加图片描述

  9. 然后将之前TcpClient项目中的tcpclient.cpp测试的函数注释掉,加上蓝框两句代码即可:
    请添加图片描述

  10. 然后将TcpClient项目中的tcpclient.h中的测试函数声明(蓝框部分)注释掉:
    请添加图片描述

  11. 然后在TcpServer项目中的“protocol.h”中加上与TcpClient项目中的“protocol.h”同样的消息类型枚举:

    // 消息类型枚举
    enum ENUM_MSG_TYPE
    {
        ENUM_MSG_TYPE_MIN = 0,
        ENUM_MSG_TYPE_REGIST_REQUEST,           // 注册请求
        ENUM_MSG_TYPE_REGIST_RESPOND,           // 注册回复
        ENUM_MSG_TYPE_LOGIN_REQUEST,            // 登录请求
        ENUM_MSG_TYPE_LOGIN_RESPOND,            // 登录回复
        ENUM_MSG_TYPE_ALL_ONLINE_REQUEST,       // 在线用户请求
        ENUM_MSG_TYPE_ALL_ONLINE_RESPOND,       // 在线用户回复
        ENUM_MSG_TYPE_SEARCH_USR_REQUEST,       // 搜索用户请求
        ENUM_MSG_TYPE_SEARCH_USR_RESPOND,       // 搜索用户回复
        ENUM_MSG_TYPE_ADD_FRIEND_REQUEST,       // 添加好友请求
        ENUM_MSG_TYPE_ADD_FRIEND_RESPOND,       // 添加好友回复
        ENUM_MSG_TYPE_ADD_FRIEND_AGGREE,        // 同意添加好友
        ENUM_MSG_TYPE_ADD_FRIEND_REFUSE,        // 拒绝添加好友
        ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST,     // 刷新好友请求
        ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND,     // 刷新好友回复
        ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST,    // 删除好友请求
        ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND,    // 删除好友回复
        ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST,     // 私聊请求
        ENUM_MSG_TYPE_PRIVATE_CHAT_RESPOND,     // 私聊回复
        ENUM_MSG_TYPE_GROUP_CHAT_REQUEST,       // 群聊请求
        ENUM_MSG_TYPE_GROUP_CHAT_RESPOND,       // 群聊回复
        ENUM_MSG_TYPE_CREATE_DIR_REQUEST,       // 创建文件夹请求
        ENUM_MSG_TYPE_CREATE_DIR_RESPOND,       // 创建文件夹回复
        ENUM_MSG_TYPE_FLUSH_FILE_REQUEST,       // 刷新文件请求
        ENUM_MSG_TYPE_FLUSH_FILE_RESPOND,       // 刷新文件回复
        ENUM_MSG_TYPE_DEL_DIR_REQUEST,          // 删除目录请求
        ENUM_MSG_TYPE_DEL_DIR_RESPOND,          // 删除目录回复
        ENUM_MSG_TYPE_RENAME_FILE_REQUEST,      // 重命名文件请求
        ENUM_MSG_TYPE_RENAME_FILE_RESPOND,      // 重命名文件回复
        ENUM_MSG_TYPE_ENTER_DIR_REQUEST,        // 进入文件夹请求
        ENUM_MSG_TYPE_ENTER_DIR_RESPOND,        // 进入文件夹回复
        ENUM_MSG_TYPE_DEL_FILE_REQUEST,         // 删除常规文件请求
        ENUM_MSG_TYPE_DEL_FILE_RESPOND,         // 删除常规文件回复
        ENUM_MSG_TYPE_UPLOAD_FILE_REQUEST,      // 上传文件请求
        ENUM_MSG_TYPE_UPLOAD_FILE_RESPOND,      // 上传文件回复
        ENUM_MSG_TYPE_DOWNLOAD_FILE_REQUEST,    // 下载文件请求
        ENUM_MSG_TYPE_DOWNLOAD_FILE_RESPOND,    // 下载文件回复
        ENUM_MSG_TYPE_SHARE_FILE_REQUEST,       // 共享文件请求
        ENUM_MSG_TYPE_SHARE_FILE_RESPOND,       // 共享文件回复
        ENUM_MSG_TYPE_SHARE_FILE_NOTE_REQUEST,  // 共享文件记录请求
        ENUM_MSG_TYPE_SHARE_FILE_NOTE_RESPOND,  // 共享文件记录回复
        ENUM_MSG_TYPE_MOVE_FILE_REQUEST,        // 移动文件请求
        ENUM_MSG_TYPE_MOVE_FILE_RESPOND,        // 移动文件回复
        ENUM_MSG_TYPE_MAX = 0x00ffffff,
    };
    
  12. 然后将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::on_regist_pb_clicked”函数的所有内容全部替换为如下内容,此时就完成了用户注册消息向服务器的传递:

    // 注册事件
    void TcpClient::on_regist_pb_clicked()
    {
        QString strName = ui->name_le->text();
        QString strPwd = ui->pwd_le->text();
        if(!strName.isNull() && !strPwd.isNull())
        {
            PDU *pdu = mkPDU(0);
            pdu->uiMsgType = ENUM_MSG_TYPE_REGIST_REQUEST;
            strncpy(pdu->caData,strName.toStdString().c_str(),32);
            strncpy(pdu->caData + 32,strName.toStdString().c_str(),32);
            m_tcpSockey.write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::critical(this,"注册","注册失败:用户名或密码为空");
        }
    }
    
  13. 下面我们来测试一下,看看服务器能否成功获取到客户端的用户名和密码,首先将TcpServer项目中的mytcpsocket.cpp中的“MyTcpSocket::recvMsg”函数全部替换为如下内容:

    // 接收来自客户端的消息
    void MyTcpSocket::recvMsg()
    {
        qDebug() << this->bytesAvailable();
        uint uiPDULen = 0;
        this->read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        char caName[32] = {'\0'};
        char caPwd[32] = {'\0'};
        strncpy(caName,pdu->caData,32);
        strncpy(caPwd,pdu->caData+32,32);
        qDebug() << caName << caPwd << pdu->uiMsgType;
    }
    
  14. 然后分别启动两个项目,并输入用户名和密码,然后点击“注册”,发现服务器已经成功获取到客户端的用户名和密码了:
    请添加图片描述

  15. 既然已经实现了客户端和服务器的通信,那么下面就要将用户的注册信息保存到数据库中,所以首先在TcpServer项目中的“opedb.h”中声明如下函数:

    bool handleRegist(const char *name,const char *pwd);
    
  16. 然后在TcpServer项目中的“opedb.cpp”中实现刚刚声明的函数:

    // 处理客户端的用户注册信息
    bool OpeDB::handleRegist(const char *name, const char *pwd)
    {
        if(NULL == name || NULL == pwd)
        {
            return false;
        }
        QString data = QString("insert into usrInfo(name,pwd) values(\'%1\',\'%2\')").arg(name).arg(pwd);
        QSqlQuery query;
        return query.exec(data);
    }
    
  17. 为了使用数据库的功能,我们在TcpServer项目中的“mytcpsocket.h”中添加如下头文件:

    #include "opedb.h"
    
  18. 然后将TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数全部替换为如下内容,此时我们就能将用户的注册信息保存到数据库中,并给客户端返回用户注册结果信息:

    // 接收来自客户端的消息
    void MyTcpSocket::recvMsg()
    {
        qDebug() << this->bytesAvailable();
        uint uiPDULen = 0;
        this->read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        switch (pdu->uiMsgType)
        {
            // 注册请求
            case ENUM_MSG_TYPE_REGIST_REQUEST:
            {
                char caName[32] = {'\0'};
                char caPwd[32] = {'\0'};
                strncpy(caName,pdu->caData,32);
                strncpy(caPwd,pdu->caData+32,32);
                bool res = OpeDB::getInstance().handleRegist(caName,caPwd);
                PDU *respdu = mkPDU(0);
                respdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
                if(res)
                {
                    strcpy(respdu->caData,REGIST_OK);
                }
                else
                {
                    strcpy(respdu->caData,REGIST_FAILED);
                }
                write((char*)respdu,respdu->uiPDULen);
                free(respdu);
                respdu = NULL;
                break;
            }
            default:
            {
                break;
            }
        }
        free(pdu);
        pdu = NULL;
    }
    
  19. 刚刚我们已经实现了服务器处理客户端的用户注册信息,同时,服务器还将用户注册结果信息回复给了客户端,所以客户端就要接收来自服务器的信息,以方便用户查看注册结果,那么首先在TcpClient项目中的“tcpclient.h”中声明接收服务器信息的函数:

    void recvMsg();
    
  20. 然后将刚刚声明的函数加入到TcpClient项目中的“tcpclient.cpp”中的“TcpClient::TcpClient”:
    请添加图片描述

  21. 然后TcpClient项目中的“tcpclient.cpp”中加入代码,用来接收服务器对于用户注册的回复信息:

    // 接收服务器信息
    void TcpClient::recvMsg()
    {
        qDebug() << m_tcpSockey.bytesAvailable();
        uint uiPDULen = 0;
        m_tcpSockey.read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        m_tcpSockey.read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        switch (pdu->uiMsgType)
        {
            // 注册回复
            case ENUM_MSG_TYPE_REGIST_RESPOND:
            {
                if(0 == strcmp(pdu->caData,REGIST_OK))
                {
                    QMessageBox::information(this,"注册",REGIST_OK);
                }
                else if(0 == strcmp(pdu->caData,REGIST_FAILED))
                {
                    QMessageBox::warning(this,"注册",REGIST_FAILED);
                }
                break;
            }
            default:
            {
                break;
            }
        }
        free(pdu);
        pdu = NULL;
    }
    
  22. 然后我们启动两个项目来测试一下,启动两个项目后,我们输入用户名和密码最后点击“注册”,发现提示我们注册成功了:
    请添加图片描述

  23. 然后我们来到数据库查看数据,发现新的注册信息已经保存到数据库中了:
    请添加图片描述

  24. 如果此时我们还是以同样的用户名去注册,就会提示我们注册失败,因为数据库中已经有同名的用户名了,而我们设置用户名字段唯一,所以注册会失败:
    请添加图片描述

  25. 此时我们就完成了客户端和服务器对于用户注册信息的处理

6.3 用户登录功能实现

  1. 首先我们要设计用户登录按钮的点击事件,因为之前我们已经在TcpClient项目中的“tcpclient.cpp”中生成了处理用户登录点击事件的函数“TcpClient::on_login_pb_clicked”,而且用户登录和用户注册的逻辑基本一致,所以我们将“TcpClient::on_login_pb_clicked”函数中的内容全部替换为如下内容:

    // 登录事件
    void TcpClient::on_login_pb_clicked()
    {
        QString strName = ui->name_le->text();
        QString strPwd = ui->pwd_le->text();
        if(!strName.isNull() && !strPwd.isNull())
        {
            PDU *pdu = mkPDU(0);
            pdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_REQUEST;
            strncpy(pdu->caData,strName.toStdString().c_str(),32);
            strncpy(pdu->caData + 32,strName.toStdString().c_str(),32);
            m_tcpSockey.write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::critical(this,"登录","登录失败:用户名或密码为空");
        }
    }
    
  2. 既然我们要处理用户的登录请求,就要来到TcpServer项目中进行处理,首先要在TcpServer项目中的“opedb.h”中加入处理用户登录信息数据库的函数声明:

    bool handleLogin(const char *name,const char *pwd);
    
  3. 然后我们在TcpServer项目中的“opedb.cpp”中实现上面的函数声明,用来处理客户端的用户登录信息,这里有个点需要注意一下,如果当前用户已经登陆,也就是此数据的“online”字段为1的时候是不能登录的,所以只有此数据的“online”字段为0的时候允许登录:

    // 处理客户端的用户登录信息
    bool OpeDB::handleLogin(const char *name, const char *pwd)
    {
        if(NULL == name || NULL == pwd)
        {
            return false;
        }
        QString data = QString("select * from usrInfo where name = \'%1\' and pwd = \'%2\' and online = 0").arg(name).arg(pwd);
        QSqlQuery query;
        query.exec(data);
        if(query.next())
        {
            data = QString("update usrInfo set online = 1 where name = \'%1\' and pwd = \'%2\'").arg(name).arg(pwd);
            QSqlQuery query;
            query.exec(data);
            return true;
        }
        else
        {
            return false;
        }
    }
    
  4. 因为服务器使用TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数来处理客户端的信息,之前我们已经写好了用户注册的信息处理,现在我们要处理用户的登录信息,基本逻辑不变,只需要将此“MyTcpSocket::recvMsg”函数全部替换为如下内容就可以处理来自客户端的登录请求了,后面还有很多这样类似的请求处理,每次只需要将新的请求处理代码加入到新的“case”中即可,不再赘述:

    // 接收来自客户端的消息
    void MyTcpSocket::recvMsg()
    {
        qDebug() << this->bytesAvailable();
        uint uiPDULen = 0;
        this->read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        switch (pdu->uiMsgType)
        {
            // 注册请求
            case ENUM_MSG_TYPE_REGIST_REQUEST:
            {
                char caName[32] = {'\0'};
                char caPwd[32] = {'\0'};
                strncpy(caName,pdu->caData,32);
                strncpy(caPwd,pdu->caData+32,32);
                bool res = OpeDB::getInstance().handleRegist(caName,caPwd);
                PDU *respdu = mkPDU(0);
                respdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
                if(res)
                {
                    strcpy(respdu->caData,REGIST_OK);
                }
                else
                {
                    strcpy(respdu->caData,REGIST_FAILED);
                }
                write((char*)respdu,respdu->uiPDULen);
                free(respdu);
                respdu = NULL;
                break;
            }
            // 登录请求
            case ENUM_MSG_TYPE_LOGIN_REQUEST:
            {
                char caName[32] = {'\0'};
                char caPwd[32] = {'\0'};
                strncpy(caName,pdu->caData,32);
                strncpy(caPwd,pdu->caData+32,32);
                bool res = OpeDB::getInstance().handleLogin(caName,caPwd);
                PDU *respdu = mkPDU(0);
                respdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
                if(res)
                {
                    strcpy(respdu->caData,LOGIN_OK);
                }
                else
                {
                    strcpy(respdu->caData,LOGIN_FAILED);
                }
                write((char*)respdu,respdu->uiPDULen);
                free(respdu);
                respdu = NULL;
                break;
            }
            default:
            {
                break;
            }
        }
        free(pdu);
        pdu = NULL;
    }
    
  5. 同理,在客户端接收来自服务器的用户登录请求回复的时候,逻辑也是基本一致的,所以我们只需要将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数的全部内容替换为如下内容,后面还有很多这样类似的回复处理,每次只需要将新的回复处理代码加入到新的“case”中即可,不再赘述:

    // 接收服务器信息
    void TcpClient::recvMsg()
    {
        qDebug() << m_tcpSockey.bytesAvailable();
        uint uiPDULen = 0;
        m_tcpSockey.read((char*)&uiPDULen,sizeof(uint));
        uint uiMsgLen = uiPDULen - sizeof(PDU);
        PDU *pdu = mkPDU(uiMsgLen);
        m_tcpSockey.read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
        switch (pdu->uiMsgType)
        {
            // 注册回复
            case ENUM_MSG_TYPE_REGIST_RESPOND:
            {
                if(0 == strcmp(pdu->caData,REGIST_OK))
                {
                    QMessageBox::information(this,"注册",REGIST_OK);
                }
                else if(0 == strcmp(pdu->caData,REGIST_FAILED))
                {
                    QMessageBox::warning(this,"注册",REGIST_FAILED);
                }
                break;
            }
            // 登录回复
            case ENUM_MSG_TYPE_LOGIN_RESPOND:
            {
                if(0 == strcmp(pdu->caData,LOGIN_OK))
                {
                    QMessageBox::information(this,"登录",LOGIN_OK);
                }
                else if(0 == strcmp(pdu->caData,LOGIN_FAILED))
                {
                    QMessageBox::warning(this,"登录",LOGIN_FAILED);
                }
                break;
            }
            default:
            {
                break;
            }
        }
        free(pdu);
        pdu = NULL;
    }
    
  6. 然后我们可以启动两个项目测试一下,当我们使用已经在数据库中存在的用户,并且此用户的“online”字段为0的时候,是可以成功登陆的:
    请添加图片描述

  7. 当我们尝试使用数据库中不存在的用户登陆时,是不可以成功登录的:
    请添加图片描述

  8. 此时我们就完成了客户端和服务器对于用户登录信息的处理

6.4 用户退出功能实现

  1. 首先在TcpServer项目中的“mytcpsocket.h”中加入如下代码,用来保存登录用户的名字,方便在用户退出的时候,根据保存的用户名称来设置对应用户在数据库中的状态:

    private:
        QString m_strName;
    
  2. 然后在用户登录成功时,记录用户名:
    请添加图片描述

  3. 然后在TcpServer项目中的“mytcpsocket.h”中的“public slots”处加入如下代码,用来处理客户端用户下线的信号:

    void clientOffline();
    
  4. 我们在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::MyTcpSocket”函数中加入如下内容,只要客户端断开连接,就发送disconnect信号,服务器就做对应的处理:

    connect(this,SIGNAL(disconnect()),this,SLOT(clientOffline()));
    
  5. 在服务器正式处理之前,我们要先完成几个小工作,我们首先在TcpServer项目中的“opedb.h”中的“public”处声明如下函数,用来处理用户退出的数据库操作:

    void handleOffline(const char *name);
    
  6. 然后TcpServer项目中的“opedb.cpp”中加入刚刚声明的函数的具体实现,用来更新退出用户的登陆状态:

    // 处理客户端的用户退出信息
    void OpeDB::handleOffline(const char *name)
    {
        if(NULL == name)
        {
            return;
        }
        QString data = QString("update usrInfo set online = 0 where name = \'%1\'").arg(name);
        QSqlQuery query;
        query.exec(data);
    }
    
  7. 然后我们在TcpServer项目中的“mytcpsocket.h”中加入如下代码,此代码的作用是作为信号槽来发送信号,借助此信号可以删除当前登录用户的socket:

    signals:
        void offline(MyTcpSocket *mysocket);
    
  8. 然后我们在TcpServer项目中的“mytcpserver.h”中加入如下代码,根据信号槽传来的信号,删除当前登录用户的socket:

    public slots:
        void deleteSocket(MyTcpSocket *mysocket);
    
  9. 然后在TcpServer项目中的“mytcpserver.cpp”中的“MyTcpServer::incomingConnection”函数中加入如下信号槽函数,用来连接信号:

    connect(pTcpSocket,SIGNAL(offline(MyTcpSocket*)),this,SLOT(deleteSocket(MyTcpSocket*)));
    
  10. 然后在TcpServer项目中的“mytcpserver.cpp”中加入如下代码,用来删除当前登录用户的socket:

    // 删除当前登录用户的socket
    void MyTcpServer::deleteSocket(MyTcpSocket *mysocket)
    {
        QList<MyTcpSocket*>::iterator iter = m_tcpSocketList.begin();
        for(;iter!=m_tcpSocketList.end();iter++)
        {
            if(mysocket == *iter)
            {
                delete *iter;
                *iter = NULL;
                m_tcpSocketList.erase(iter);
                break;
            }
        }
    }
    
  11. 然后在TcpServer项目中的“mytcpsocket.cpp”中加入如下代码,将上面所写的功能整合,最终实现用户退出的功能:

    // 处理客户端用户下线的信号
    void MyTcpSocket::clientOffline()
    {
        OpeDB::getInstance().handleOffline(m_strName.toStdString().c_str());
        emit offline(this);
    }
    
  12. 然后我们启动两个项目,使用某一个用户登录,然后查询数据库,发现此用户的“online”字段为1,说明已经成功登陆了:
    请添加图片描述

  13. 然后点击关闭按钮,模仿用户退出行为:
    请添加图片描述

  14. 此时再来数据库查询,就发现对应用户的online字段已经变为了0,说明此用户已经成功退出了:
    请添加图片描述

  15. 此时我们就完成了客户端和服务器对于用户退出信息的处理

七、操作主界面UI设计

  我们已经完成了和用户相关的基本功能的实现,那么当用户登陆进来后,我们应该给用户呈现软件的主操作界面,所以我们这节的主要内容就是进行主操作界面的设计。本节的具体内容包括:好友子界面UI设计、图书子界面UI设计、合并子界面UI到操作主界面UI。

7.1 好友子界面UI设计

  1. 首先在TcpClient项目中按如下配置新建名为“OpeWidget”的类,用来处理主界面UI的相关功能:
    请添加图片描述

  2. 然后在TcpClient项目中的“opewidget.h”中加入如下代码,用来保存操作主界面UI左栏的信息:

    #include <QListWidget>
    
    private:
        QListWidget *m_pListW;
    
  3. 我们使用QListWidget来保存操作主界面UI的左栏信息,只需要将TcpClient项目中的“opewidget.cpp”中的全部内容替换为如下代码:

    #include "opewidget.h"
    
    OpeWidget::OpeWidget(QWidget *parent) : QWidget(parent)
    {
        m_pListW = new QListWidget(this);
        m_pListW->addItem("好友");
        m_pListW->addItem("图书");
    }
    
  4. 此时我们已经设计好了操作主界面UI的左栏,下面需要设计显示所有在线用户的窗口UI,我们首先添加新文件,按照如下配置选择:请添加图片描述

  5. 继续按如下配置选择:
    请添加图片描述

  6. 此类名字改为“Online”,目的是显示所有在线的用户:
    请添加图片描述

  7. 最后创建完的界面UI如下,我们要设计这个界面UI,以显示所有的在线用户:
    请添加图片描述

  8. 然后选择蓝框组件,使用红框布局,其中List Widget组件命名为“online_lw”,Push Button组件命名为“addFriend_pb”:
    请添加图片描述

  9. 此时我们就完成了显示所有在线用户窗口的UI设计,下面就要设计好友相关的界面UI了,我们首先按如下配置在TcpClient项目中创建名为“Friend”的类:
    请添加图片描述

  10. 然后在TcpClient项目中的“friend.h”中加入如下代码,用来存储好友界面UI的布局组件信息:

    #include <QTextEdit>
    #include <QListWidget>
    #include <QLineEdit>
    #include <QPushButton>
    #include <QVBoxLayout>
    #include <QHBoxLayout>
    #include "online.h"
    
    public slots:
        void showOnline();
    
    private:
        QTextEdit *m_pShowMsgTE;
        QListWidget *m_pFriendListWidget;
        QLineEdit *m_pInputMsgLE;
        QPushButton *m_pDelFriendPB;
        QPushButton *m_pFlushFriendPB;
        QPushButton *m_pShowOnlineUsrPB;
        QPushButton *m_pSearchUsrPB;
        QPushButton *m_pMsgSendPB;
        QPushButton *m_pPrivateChatPB;
        Online *m_pOnline;
    
  11. 然后将TcpClient项目中的“friend.cpp”中的全部代码替换为如下内容,用来显示好友功能的主体窗口UI:

    #include "friend.h"
    
    // 好友功能主体窗口
    Friend::Friend(QWidget *parent) : QWidget(parent)
    {
        m_pShowMsgTE = new QTextEdit;
        m_pFriendListWidget = new QListWidget;
        m_pInputMsgLE = new QLineEdit;
        m_pDelFriendPB = new QPushButton("删除好友");
        m_pFlushFriendPB = new QPushButton("刷新好友");
        m_pShowOnlineUsrPB = new QPushButton("显示在线用户");
        m_pSearchUsrPB = new QPushButton("查找用户");
        m_pMsgSendPB = new QPushButton("信息发送");
        m_pPrivateChatPB = new QPushButton("私聊");
        QVBoxLayout *pRightPBVBL = new QVBoxLayout;
        pRightPBVBL->addWidget(m_pDelFriendPB);
        pRightPBVBL->addWidget(m_pFlushFriendPB);
        pRightPBVBL->addWidget(m_pShowOnlineUsrPB);
        pRightPBVBL->addWidget(m_pSearchUsrPB);
        pRightPBVBL->addWidget(m_pPrivateChatPB);
        QHBoxLayout *pTopHBL = new QHBoxLayout;
        pTopHBL->addWidget(m_pShowMsgTE);
        pTopHBL->addWidget(m_pFriendListWidget);
        pTopHBL->addLayout(pRightPBVBL);
        QHBoxLayout *pMsgHBL = new QHBoxLayout;
        pMsgHBL->addWidget(m_pInputMsgLE);
        pMsgHBL->addWidget(m_pMsgSendPB);
        m_pOnline = new Online;
        QVBoxLayout *pMain = new QVBoxLayout;
        pMain->addLayout(pTopHBL);
        pMain->addLayout(pMsgHBL);
        pMain->addWidget(m_pOnline);
        m_pOnline->hide();
        setLayout(pMain);
        connect(m_pShowOnlineUsrPB,SIGNAL(clicked(bool)),this,SLOT(showOnline()));
    }
    
    // 显示在线用户窗口
    void Friend::showOnline()
    {
        if(m_pOnline->isHidden())
        {
            m_pOnline->show();
        }
        else
        {
            m_pOnline->hide();
        }
    }
    
  12. 到此我们就完成了好友相关的界面UI设计了,也代表好友界面UI设计的框架我们已经搭建完毕了,我们下面可以测试一下,只需要将TcpClient项目中的“main.cpp”中的全部内容替换为如下内容:

    #include "tcpclient.h"
    #include <QApplication>
    #include "opewidget.h"
    #include "online.h"
    #include "friend.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    //    TcpClient w;
    //    w.show();
        Friend w;
        w.show();
        return a.exec();
    }
    
  13. 然后我们启动TcpClient项目,发现可以正常显示好友界面UI,而且点击“显示在线用户按钮”,也可以显示我们设计的显示在线用户的界面UI,这就意味着我们的好友界面UI框架已经搭建完毕:
    请添加图片描述

7.2 图书子界面UI设计

  1. 好友界面UI我们已经设计完毕,下面就需要设计图书界面UI了,经过刚才的学习,读者也可能发现了,图书界面UI的设计过程应该是和好友界面UI的设计过程类型,而且我们主要目的是完成好友界面UI的设计与逻辑,所以我们对于图书界面UI暂时先简单设计一下,后面若有需求或有时间,我们可以重新好好设计一下。我们只需要在TcpClient项目中按如下配置新建名为“Book”的类,表示有这么一个界面组件UI即可:
    请添加图片描述

7.3 合并子界面UI到操作主界面UI

  1. 上面我们已经将各个子界面UI设计完毕了,下面就要将各个子界面UI合并到操作主界面UI了,首先我们在TcpClient项目中的“opewidget.h”中加入如下代码:

    #include "friend.h"
    #include "book.h"
    #include <QStackedWidget>
    
    private:
        Friend *m_pFriend;
        Book *m_pBook;
        QStackedWidget *m_pSW;
    
  2. 然后将TcpClient项目中的“opewidget.cpp”中的全部内容替换为如下内容:

    #include "opewidget.h"
    
    // 操作主界面UI
    OpeWidget::OpeWidget(QWidget *parent) : QWidget(parent)
    {
        m_pListW = new QListWidget(this);
        m_pListW->addItem("好友");
        m_pListW->addItem("图书");
        m_pFriend = new Friend;
        m_pBook = new Book;
        m_pSW = new QStackedWidget;
        m_pSW->addWidget(m_pFriend);
        m_pSW->addWidget(m_pBook);
        QHBoxLayout *pMain = new QHBoxLayout;
        pMain->addWidget(m_pListW);
        pMain->addWidget(m_pSW);
        setLayout(pMain);
        connect(m_pListW,SIGNAL(currentRowChanged(int)),m_pSW,SLOT(setCurrentIndex(int)));
    }
    
  3. 此时我们就可以来测试一下,我们需要将TcpClient项目中的“main.cpp”中的全部内容替换为如下内容:

    #include "tcpclient.h"
    #include <QApplication>
    #include "opewidget.h"
    #include "online.h"
    #include "friend.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    //    TcpClient w;
    //    w.show();
        OpeWidget w;
        w.show();
        return a.exec();
    }
    
  4. 然后我们启动TcpClient项目,发现已经实现了我们的功能,也就是当我们点击“好友”按钮的时候,会跳转到好友子界面UI,当我们点击“图书”按钮的时候,会跳转到图书子界面UI:
    请添加图片描述

八、用户登陆跳转功能实现

  我们上面已经完成了操作主界面的UI设计,我们设计操作主界面UI的目的就是当合法用户登陆后可以进入此主界面,所以我们本节的目的就是完成用户登录跳转到操作主界面的功能,跟着我一步一步做即可。

  1. 首先我们将TcpClient项目中的“main.cpp”中的全部内容替换为如下内容,因为我们下面要进行客户端和服务器的信息交互了,不需要在这里测试了:

    #include "tcpclient.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        TcpClient w;
        w.show();
        return a.exec();
    }
    
  2. 然后在TcpClient项目中的“opewidget.h”中的public处中添加如下代码:

    static OpeWidget &getInstance();
    
  3. 然后在TcpClient项目中的“opewidget.cpp”中加入如下代码:

    // 生成OpeWidget实例对象
    OpeWidget &OpeWidget::getInstance()
    {
        static OpeWidget instance;
        return instance;
    }
    
  4. 然后在TcpClient项目中的“tcpclient.cpp”中引入刚刚我们写好的操作主界面UI类的头文件:

    #include "opewidget.h"
    
  5. 然后在TcpClient项目中的“tcpclient.cpp”中如下位置加上红框所示代码,使用操作主界面UI类的实例化对象,在合法用户登录成功之后,跳转到操作主界面:
    请添加图片描述

  6. 然后我们分别启动TcpServer项目和TcpClient项目来测试一下,可以发现当我们登录一个合法用户之后,就可以自动跳转到操作主界面,并且各项功能都没有任何问题,这样我们就实现了用户登陆跳转的功能:
    请添加图片描述

九、好友操作功能实现

  到目前为止,我们的项目已经基本成型了,下面的工作就是丰富其中的功能,因为我们刚刚完成了好友子界面的UI设计,所以这节我们的工作就是丰富关于好友操作的相关功能,这些功能具体包括:查看在线好友、搜索好友、添加好友、刷新在线好友、删除好友、私聊、群聊、文件共享。还是那句话,只要跟着我一步一步操作,这些功能都可以完美实现,加油!

9.1 查看在线好友

  1. 查看在线好友的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“tcpclient.h”中的“public”处加入如下代码:

    static TcpClient &getInstance();
    QTcpSocket &getTcpSocket();
    
  3. 然后在TcpClient项目中的“tcpclient.cpp”中加入如下代码:

    // 返回TcpClient实例对象
    TcpClient &TcpClient::getInstance()
    {
        static TcpClient instance;
        return instance;
    }
    
    // 获取TcpSocket
    QTcpSocket &TcpClient::getTcpSocket()
    {
        return m_tcpSockey;
    }
    
  4. 然后将TcpClient项目中的“main.cpp”中的全部内容替换为如下内容:

    #include "tcpclient.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        TcpClient::getInstance().show();
        return a.exec();
    }
    
  5. 然后在TcpClient项目中的“friend.cpp”中加入如下头文件:

    #include "protocol.h"
    #include "tcpclient.h"
    
  6. 然后将TcpClient项目中的“friend.cpp”中的“Friend::showOnline”函数中的全部内容替换为如下内容:

    // 显示在线用户窗口
    void Friend::showOnline()
    {
        if(m_pOnline->isHidden())
        {
            m_pOnline->show();
            PDU *pdu = mkPDU(0);
            pdu->uiMsgType = ENUM_MSG_TYPE_ALL_ONLINE_REQUEST;
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            m_pOnline->hide();
        }
    }
    
  7. 然后在TcpServer项目中的“opedb.h”中加入如下代码:

    #include <QStringList>
    
    public:
        QStringList handleAllOnline();
    
  8. 然后在TcpServer项目中的“opedb.cpp”中加入如下代码:

    // 查询所有在线用户的用户名列表
    QStringList OpeDB::handleAllOnline()
    {
        QString data = QString("select name from usrInfo where online = 1");
        QSqlQuery query;
        query.exec(data);
        QStringList result;
        result.clear();
        while(query.next())
        {
            result.append(query.value(0).toString());
        }
        return result;
    }
    
  9. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码:

    // 查看在线用户请求
    case ENUM_MSG_TYPE_ALL_ONLINE_REQUEST:
    {
        QStringList res = OpeDB::getInstance().handleAllOnline();
        uint uiMsgLen = res.size() * 32;
        PDU *resPdu = mkPDU(uiMsgLen);
        resPdu->uiMsgType = ENUM_MSG_TYPE_ALL_ONLINE_RESPOND;
        for(int i = 0;i<res.size();i++)
        {
            memcpy((char*)(resPdu->caMsg) + i * 32,res.at(i).toStdString().c_str(),res.at(i).size());
        }
        write((char*)resPdu,resPdu->uiPDULen);
        free(resPdu);
        resPdu = NULL;
        break;
    }
    
  10. 然后在TcpClient项目中的“online.h”中加入如下代码,准备将服务器传来的在线用户的用户名显示在UI界面上:

    #include "protocol.h"
    
    public:
        void showUsr(PDU *pdu);
    
  11. 然后在TcpClient项目中的“online.cpp”中加入如下代码:

    // 将所有的在线用户的用户名显示在UI界面上
    void Online::showUsr(PDU *pdu)
    {
        if(NULL == pdu)
        {
            return;
        }
        uint uiSize = pdu->uiMsgLen / 32;
        char caTmp[32];
        for(uint i = 0;i < uiSize;i++)
        {
            memcpy(caTmp,(char*)pdu->caMsg + i * 32,32);
            ui->online_lw->addItem(caTmp);
        }
    }
    
  12. 然后在TcpClient项目中的“friend.h”中加入如下函数用来显示所有的在线好友的用户名:

    void showAllOnlineUsr(PDU *pdu);
    
  13. 然后在TcpClient项目中的“friend.cpp”中加入如下代码:

    // 显示所有的在线用户的用户名
    void Friend::showAllOnlineUsr(PDU *pdu)
    {
        if(NULL == pdu)
        {
            return;
        }
        m_pOnline->showUsr(pdu);
    }
    
  14. 然后在TcpClient项目中的“opewidget.h”中的“public”处加上如下代码:

    Friend *getFriend();
    
  15. 然后在TcpClient项目中的“opewidget.cpp”中加上如下代码,来返回当前好友的操作对象:

    // 返回当前好友的操作对象
    Friend *OpeWidget::getFriend()
    {
        return m_pFriend;
    }
    
  16. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中加上查看在线好友回复的相关代码:

    // 查看在线用户回复
    case ENUM_MSG_TYPE_ALL_ONLINE_RESPOND:
    {
    	OpeWidget::getInstance().getFriend()->showAllOnlineUsr(pdu);
    	break;
    }
    
  17. 然后我们可以将之前TcpClient项目中的“online.ui”中的测试内容删掉:
    请添加图片描述

  18. 最后我们可以来测试一下,我们可以同时多启动几个客户端,然后分别登陆进去,点击“显示在线用户”按钮,可以发现在线的用户的用户名已经成功显示了:
    请添加图片描述

  19. 如果有的读者无法同时启动多个客户端可以按照如下操作进行:

    • 点击“工具(T)”:
      请添加图片描述

    • 点击“选项(O)…”:
      请添加图片描述

    • 然后按照下图指示的操作步骤进行修改,将最后一个红框中的内容选择为“None”:
      请添加图片描述

  20. 按照上面的操作就可以同时开启多个客户端了,同时也意味着查看在线好友的功能我们已经完成了

9.2 搜索好友

  1. 搜索好友的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目的“friend.h”中的“public”处和“public slots”处添加如下代码,此变量的作用是保存带查找的用户名,此函数的作用是作为搜索按钮的槽函数使用:

    public:
    	QString m_strSearchName;
    
    public slots:
    	void searchUsr();
    
  3. 然后在TcpClient项目的“friend.h”中的“Friend::Friend”函数中添加如下代码,用来关联槽函数和按钮之间的点击事件:

    connect(m_pSearchUsrPB,SIGNAL(clicked(bool)),this,SLOT(searchUsr()));
    
  4. 然后来完善此槽函数,向服务器发送携带用户名的请求,我们只需要在TcpClient项目的“friend.cpp”中添加如下代码:

    #include <QInputDialog>
    #include <QDebug>
    
    // 搜索用户
    void Friend::searchUsr()
    {
        m_strSearchName = QInputDialog::getText(this,"搜索","用户名");
        if(!m_strSearchName.isEmpty())
        {
            qDebug() << "待查找的用户名为:" << m_strSearchName;
            PDU *pdu = mkPDU(0);
            memcpy(pdu->caData, m_strSearchName.toStdString().c_str(), m_strSearchName.size());
            pdu->uiMsgType = ENUM_MSG_TYPE_SEARCH_USR_REQUEST;
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
    }
    
  5. 当服务器接收到带有用户名的请求后,就需要去数据库中进行搜索,所以我们在TcpServer项目中的“opedb.h”中的“public”处加入如下代码用来处理搜索用户的请求:

    int handleSearchUsr(const char *name);
    
  6. 然后在TcpServer项目中的“opedb.cpp”中加入如下代码来完成对于搜索用户的需求的处理:

    // 搜索用户
    int OpeDB::handleSearchUsr(const char *name)
    {
        if(NULL == name)
        {
            return -1;
        }
        QString data = QString("select online from usrInfo where name = \'%1\'").arg(name);
        QSqlQuery query;
        query.exec(data);
        if(query.next())
        {
            int res = query.value(0).toInt();
            if(1 == res)
            {
                return 1;
            }
            else if(0 == res)
            {
                return 0;
            }
        }
        else
        {
            return -1;
        }
    }
    
  7. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中加入如下代码来处理客户端查找用户的请求:

    // 查看查找用户请求
    case ENUM_MSG_TYPE_SEARCH_USR_REQUEST:
    {
    	int res = OpeDB::getInstance().handleSearchUsr(pdu->caData);
    	PDU *resPdu = mkPDU(0);
    	resPdu->uiMsgType = ENUM_MSG_TYPE_SEARCH_USR_RESPOND;
    	if(-1 == res)
    	{
    		strcpy(resPdu->caData,SEARCH_USR_NO);
    	}
    	else if(1 == res)
    	{
    		strcpy(resPdu->caData,SEARCH_USR_ONLINE);
    	}
    	else if(0 == res)
    	{
    		strcpy(resPdu->caData,SEARCH_USR_OFFLINE);
    	}
    	write((char*)resPdu,resPdu->uiPDULen);
    	free(resPdu);
    	resPdu = NULL;
    	break;
    }
    
  8. 然后在TcpClient项目的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下接收服务器回复的代码:

    // 查看查找用户回复
    case ENUM_MSG_TYPE_SEARCH_USR_RESPOND:
    {
    	if(0 == strcmp(SEARCH_USR_NO,pdu->caData))
    	{
    	QMessageBox::information(this,"搜索",QString("%1: not exist").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
    	}
    	else if(0 == strcmp(SEARCH_USR_ONLINE,pdu->caData))
    	{
    	QMessageBox::information(this,"搜索",QString("%1: online").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
    	}
    	else if(0 == strcmp(SEARCH_USR_OFFLINE,pdu->caData))
    	{
    	QMessageBox::information(this,"搜索",QString("%1: offline").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
    	}
    	break;
    }
    
  9. 当我们完成以上代码后,可以分别启动两个项目测试一下,当我们搜索用户“abc”的时候,发现可以成功展示其用户名和状态,这也就意味着搜索好友功能已经完成了:
    请添加图片描述

9.3 添加好友

  1. 添加好友的流程示意图如下所示:
    请添加图片描述

  2. 既然我们要加好友,所以首先给“加好友”按钮添加一个点击事件,点击完成后,系统就自动帮我们生成了对应的初始槽函数声明和基础代码:
    请添加图片描述

  3. 然后在TcpClient项目中的“tcpclient.h”中添加如下代码,用来保存当前登录用户的用户名:

    public:
        QString loginName();
    
    private:
        QString m_strLoginName;
    
  4. 然后将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::on_login_pb_clicked”函数全部替换为如下代码,用来保存当前登录用户的用户名:

    // 登录事件
    void TcpClient::on_login_pb_clicked()
    {
        QString strName = ui->name_le->text();
        QString strPwd = ui->pwd_le->text();
        if(!strName.isNull() && !strPwd.isNull())
        {
            m_strLoginName = strName;
            PDU *pdu = mkPDU(0);
            pdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_REQUEST;
            strncpy(pdu->caData,strName.toStdString().c_str(),32);
            strncpy(pdu->caData + 32,strName.toStdString().c_str(),32);
            m_tcpSockey.write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::critical(this,"登录","登录失败:用户名或密码为空");
        }
    }
    
  5. 然后在TcpClient项目中的“tcpclient.cpp”中添加如下代码,用来返回获取到的私有的当前登录用户的用户名:

    // 获取当前登录用户的用户名
    QString TcpClient::loginName()
    {
        return m_strLoginName;
    }
    
  6. 然后在TcpClient项目中的“online.cpp”中添加如下头文件:

    #include <QDebug>
    #include "tcpclient.h"
    
  7. 然后将TcpClient项目中的“online.cpp”中的“Online::on_addFriend_pb_clicked”函数全部替换为如下内容,用来向客户端发送加好友的请求,并且将数据也发送过去:

    // 加好友功能实现
    void Online::on_addFriend_pb_clicked()
    {
        QListWidgetItem *pItem = ui->online_lw->currentItem();
        QString strPerUsrName = pItem->text();
        QString strLoginName = TcpClient::getInstance().loginName();
        PDU *pdu = mkPDU(0);
        pdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_REQUEST;
        memcpy(pdu->caData,strPerUsrName.toStdString().c_str(),strPerUsrName.size());
        memcpy(pdu->caData + 32,strLoginName.toStdString().c_str(),strLoginName.size());
        TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        free(pdu);
        pdu = NULL;
    }
    
  8. 然后向数据库中的“friend”表中插入一些测试数据:

    insert into friend (id,friendId) values (1,2),(1,3),(2,3);
    
  9. 可以看到测试数据已经成功插入了:
    请添加图片描述

  10. 然后我们在TcpServer项目中的“opedb.h”中的“public”处声明如下函数,用来查找当前用户和待加用户之间的关系:

    int handleAddFriend(const char *pername,const char *name);
    
  11. 然后在TcpServer项目中的“opedb.cpp”中加入如下代码,来完成刚才声明的函数的具体功能:

    // 查看当前用户和待添加好友之间的关系
    int OpeDB::handleAddFriend(const char *pername, const char *name)
    {
        if(NULL == pername || NULL == name)
        {
            return -1;
        }
        QString data = QString("select * from friend where (id = (select id from usrInfo where name=\'%1\') and friendId = (select id from usrInfo where name=\'%2\')) or (id = (select id from usrInfo where name=\'%3\') and friendId = (select id from usrInfo where name=\'%4\'))").arg(pername).arg(name).arg(name).arg(pername);
        qDebug() << data;
        QSqlQuery query;
        query.exec(data);
        if(query.next())
        {
            return 0;
        }
        else
        {
            QString data = QString("select online from usrInfo where name = \'%1\'").arg(pername);
            QSqlQuery query;
            query.exec(data);
            if(query.next())
            {
                int res = query.value(0).toInt();
                if(1 == res)
                {
                    return 1;
                }
                else if(0 == res)
                {
                    return 2;
                }
            }
            else
            {
                return 3;
            }
        }
    }
    
  12. 然后在TcpServer项目中的“mytcpsocket.h”中的“public”处加入如下代码:

    QString getName();
    
  13. 然后在TcpServer项目中的“mytcpsocket.cpp”加入如下代码,用来返回当前请求的用户用户名:

    #include "mytcpserver.h"
    
    // 返回当前请求的用户用户名
    QString MyTcpSocket::getName()
    {
        return m_strName;
    }
    
  14. 然后在TcpServer项目中的“mytcpserver.h”中的“public”处加入如下代码:

    void resend(const char *pername,PDU *pdu);
    
  15. 然后在TcpServer项目中的“mytcpserver.cpp”加入如下代码,用将添加好友的请求转发给客户端:

    // 将添加好友的请求转发给客户端
    void MyTcpServer::resend(const char *pername, PDU *pdu)
    {
        if(NULL == pername || NULL == pdu)
        {
            return;
        }
        QString strName = pername;
        for(int i = 0;i<m_tcpSocketList.size();i++)
        {
            if(strName == m_tcpSocketList.at(i)->getName())
            {
                m_tcpSocketList.at(i)->write((char*)pdu,pdu->uiPDULen);
                break;
            }
        }
    }
    
  16. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中加入如下代码,用来响应客户端添加好友的请求:

    // 查看添加好友请求
    case ENUM_MSG_TYPE_ADD_FRIEND_REQUEST:
    {
    	char caPerName[32] = {'\0'};
    	char caName[32] = {'\0'};
    	strncpy(caPerName,pdu->caData,32);
    	strncpy(caName,pdu->caData+32,32);
    	int res = OpeDB::getInstance().handleAddFriend(caPerName,caName);
    	PDU *respdu = NULL;
    	if(-1== res)
    	{
    		respdu = mkPDU(0);
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
    		strcpy(respdu->caData,UNKNOW_ERROR);
    		write((char*)respdu,respdu->uiPDULen);
    		free(respdu);
    		respdu = NULL;
    	}
    	else if(0 == res)
    	{
    		respdu = mkPDU(0);
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
    		strcpy(respdu->caData,EXISTED_FRIEND);
    		write((char*)respdu,respdu->uiPDULen);
    		free(respdu);
    		respdu = NULL;
    	}
    	else if(1 == res)
    	{
    		MyTcpServer::getInstance().resend(caPerName,pdu);
    	}
    	else if(2 == res)
    	{
    		respdu = mkPDU(0);
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
    		strcpy(respdu->caData,ADD_FRIEND_OFFLINE);
    		write((char*)respdu,respdu->uiPDULen);
    		free(respdu);
    		respdu = NULL;
    	}
    else if(3 == res)
    	{
    		respdu = mkPDU(0);
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
    		strcpy(respdu->caData,ADD_FRIEND_NO_EXIST);
    		write((char*)respdu,respdu->uiPDULen);
    		free(respdu);
    		respdu = NULL;
    	}
    	break;
    }
    
  17. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中加入如下代码,用来处理来自于服务器有关添加好友的回复:

    // 查看添加好友回复
    case ENUM_MSG_TYPE_ADD_FRIEND_REQUEST:
    {
    	char caName[32] = {'\0'};
    	strncpy(caName,pdu->caData+32,32);
    	int res = QMessageBox::information(this,"添加好友",QString("%1 wang to add you as friend").arg(caName),QMessageBox::Yes,QMessageBox::No);
    	PDU *respdu = mkPDU(0);
    	memcpy(respdu->caData,pdu->caData,64);
    	if(res == QMessageBox::Yes)
    	{
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_AGGREE;
    	}
    	else
    	{
    		respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_REFUSE;
    	}
    	m_tcpSockey.write((char*)respdu,respdu->uiPDULen);
    	free(respdu);
    	respdu = NULL;
    	break;
    }
    case ENUM_MSG_TYPE_ADD_FRIEND_RESPOND:
    {
    	QMessageBox::information(this,"添加好友",pdu->caData);
    	break;
    }
    case ENUM_MSG_TYPE_ADD_FRIEND_AGGREE:
    {
    	char caPerName[32] = {'\0'};
    	memcpy(caPerName, pdu->caData, 32);
    	QMessageBox::information(this, "添加好友", QString("添加%1好友成功").arg(caPerName));
    	break;
    }
    case ENUM_MSG_TYPE_ADD_FRIEND_REFUSE:
    {
    	char caPerName[32] = {'\0'};
    	memcpy(caPerName, pdu->caData, 32);
    	QMessageBox::information(this, "添加好友", QString("添加%1好友失败").arg(caPerName));
    	break;
    }
    
  18. 然后在TcpServer项目中的“opedb.h”中的“public”处声明如下函数,用来处理好友添加事件:

    void handleAgreeAddFriend(const char *pername, const char *name);
    
  19. 然后在TcpServer项目中的“opedb.cpp”中加入如下代码,作用是在同意添加好友之后向数据库中写入相关数据:

    // 在同意添加好友之后向数据库中写入相关数据
    void OpeDB::handleAgreeAddFriend(const char *pername, const char *name)
    {
        if (NULL == pername || NULL == name)
        {
            return;
        }
        QString data = QString("insert into friend(id, friendId) values((select id from usrInfo where name=\'%1\'), (select id from usrInfo where name=\'%2\'))").arg(pername).arg(name);
        QSqlQuery query;
        query.exec(data);
    }
    
  20. 然后在TcpServer项目中的“mytcpsocket.cpp”中加入如下代码,用来回复给客户端添加好友的结果:

    case ENUM_MSG_TYPE_ADD_FRIEND_AGGREE:
    {
    	char caPerName[32] = {'\0'};
    	char caName[32] = {'\0'};
    	strncpy(caPerName, pdu->caData, 32);
    	strncpy(caName, pdu->caData+32, 32);
    	OpeDB::getInstance().handleAgreeAddFriend(caPerName, caName);
    	MyTcpServer::getInstance().resend(caName, pdu);
    	break;
    }
    case ENUM_MSG_TYPE_ADD_FRIEND_REFUSE:
    {
    	char caName[32] = {'\0'};
    	strncpy(caName, pdu->caData+32, 32);
    	MyTcpServer::getInstance().resend(caName, pdu);
        break;
    }
    
  21. 然后我们可以打开服务器,并且打开两个客户端测试一下,发现被加用户可以正常弹出窗口:
    请添加图片描述

  22. 当我们点击“Yes”后,发现可以已经成功添加好友了,并且数据库中也已经保存了两个用户的好友关系,此时我们就完成了添加好友的功能:
    请添加图片描述

9.4 刷新在线好友

  1. 刷新好友列表功能具体实现示意图如下所示:
    请添加图片描述

  2. 首先我们在TcpClient项目的“friend.h”中的“public slots”处添加刷新好友列表的槽函数:

    void flushFriend();
    
  3. 然后在TcpClient项目的“friend.h”中的“Friend::Friend”函数中,添加如下代码,将刷新好友按钮的信号槽关联起来:

    connect(m_pFlushFriendPB,SIGNAL(clicked(bool)),this,SLOT(flushFriend()));
    
  4. 然后在TcpClient项目的“friend.cpp”中添加如下代码,用来发送刷新好友列表的请求:

    // 刷新好友列表
    void Friend::flushFriend()
    {
        QString strName = TcpClient::getInstance().loginName();
        PDU *pdu = mkPDU(0);
        pdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST;
        memcpy(pdu->caData,strName.toStdString().c_str(),strName.size());
        TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        free(pdu);
        pdu = NULL;
    }
    
  5. 然后在TcpServer项目的“opedb.h”中的“public”处声明如下函数,用来处理刷新好友列表功能的数据库操作:

    QStringList handleFlushFriend(const char *name);
    
  6. 然后在TcpServer项目的“opedb.cpp”中来完成刚才的函数声明的具体内容,此函数的目的是获取当前用户的好友列表:

    // 获取当前用户的好友列表
    QStringList OpeDB::handleFlushFriend(const char *name)
    {
        QStringList strFriendList;
        strFriendList.clear();
        if(NULL == name)
        {
            return strFriendList;
        }
        QString data = QString("select name from usrInfo where online=1 and id in (select id from friend where friendId=(select id from usrInfo where name=\'%1\'))").arg(name);
        QSqlQuery query;
        query.exec(data);
        while(query.next())
        {
            strFriendList.append(query.value(0).toString());
            qDebug() << query.value(0).toString();
        }
        data = QString("select name from usrInfo where online=1 and id in (select friendId from friend where id=(select id from usrInfo where name=\'%1\'))").arg(name);
        query.exec(data);
        while(query.next())
        {
            strFriendList.append(query.value(0).toString());
            qDebug() << query.value(0).toString();
        }
        return strFriendList;
    }
    
  7. 然后在TcpServer项目的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理刷新在线好友请求:

    // 查看刷新在线好友列表请求
    case ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST:
    {
    	char caName[32] = {'\0'};
    	strncpy(caName, pdu->caData, 32);
    	QStringList res = OpeDB::getInstance().handleFlushFriend(caName);
    	uint uiMsglen = res.size() * 32;
    	PDU *respdu = mkPDU(uiMsglen);
    	respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND;
    	for(int i = 0;i<res.size();i++)
    	{
    		memcpy((char*)(respdu->caMsg) + i * 32,res.at(i).toStdString().c_str(),res.at(i).size());
    	}
    	write((char*)respdu,respdu->uiPDULen);
    	free(respdu);
    	respdu = NULL;
    	break;
    }
    
  8. 然后在TcpClient项目中的“friend.h”的“public”处添加如下代码,此函数用来处理服务器回复的最新在线好友列表:

    void updateFriendList(PDU *pdu);
    
  9. 然后在TcpClient项目中的“friend.cpp”中添加如下代码,用来更新最新在线好友列表:

    // 更新最新在线好友列表
    void Friend::updateFriendList(PDU *pdu)
    {
        if(NULL == pdu)
        {
            return;
        }
        uint uiSize = pdu->uiMsgLen / 32;
        char caName[32] = {'\0'};
        for(uint i = 0;i<uiSize;i++)
        {
            memcpy(caName,(char*)(pdu->caMsg) + i * 32,32);
            m_pFriendListWidget->addItem(caName);
        }
    }
    
  10. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理刷新在线好友列表回复:

    // 查看刷新在线好友列表回复
    case ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND:
    {
    	OpeWidget::getInstance().getFriend()->updateFriendList(pdu);
    	break;
    }
    
  11. 然后我们可以来测试一下,分别启动服务器和客户端并登陆后,我们点击“刷新好友”按钮,发现此时可以将最新在线的好友列表显示出来了,这也意味着我们的刷新在线好友功能已经完成了:
    请添加图片描述

9.5 删除好友

  1. 删除好友过程示意图如下所示:
    请添加图片描述

  2. 首先在“TcpClient”项目中的“friend.h”中的“public slots”处添加“删除好友”按钮的槽函数:

    void deleteFriend();
    
  3. 然后在“TcpClient”项目中的“friend.h”中的“Friend::Friend”中添加如下函数,将槽函数和“删除好友”按钮关联:

    connect(m_pDelFriendPB,SIGNAL(clicked(bool)),this,SLOT(deleteFriend()));
    
  4. 然后在“TcpClient”项目中的“friend.cpp”中添加如下函数,用来向服务器发送删除好友的请求:

    // 删除好友
    void Friend::deleteFriend()
    {
        if(NULL != m_pFriendListWidget->currentItem())
        {
            QString strFriendName = m_pFriendListWidget->currentItem()->text();
            PDU *pdu = mkPDU(0);
            pdu->uiMsgType = ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST;
            QString strSelName = TcpClient::getInstance().loginName();
            memcpy(pdu->caData,strSelName.toStdString().c_str(),strSelName.size());
            memcpy(pdu->caData + 32,strFriendName.toStdString().c_str(),strFriendName.size());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
    }
    
  5. 然后在TcpServer项目中的“opedb.h”中的“public”处声明如下处理删除好友数据库操作的函数:

    bool handleDelFriend(const char *name,const char *friendName);
    
  6. 然后在TcpServer项目中的“opedb.cpp”中完成刚刚声明的函数的具体功能:

    // 数据库删除好友操作
    bool OpeDB::handleDelFriend(const char *name, const char *friendName)
    {
        if(NULL == name || NULL == friendName)
        {
            return false;
        }
        QString data = QString("delete from friend where id = (select id from usrInfo where name = \'%1\') and friendId = (select id from usrInfo where name = \'%2\')").arg(name).arg(friendName);
        QSqlQuery query;
        query.exec(data);
        data = QString("delete from friend where id = (select id from usrInfo where name = \'%1\') and friendId = (select id from usrInfo where name = \'%2\')").arg(friendName).arg(name);
        query.exec(data);
        return true;
    }
    
  7. 然后TcpServer项目中的“mytcpsocket.cpp”中添加如下内容,用来处理客户端删除好友的请求:

    // 查看删除好友请求
    case ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST:
    {
    	char caSelName[32] = {'\0'};
    	char caFriendName[32] = {'\0'};
    	strncpy(caSelName,pdu->caData,32);
    	strncpy(caFriendName,pdu->caData+32,32);
    	OpeDB::getInstance().handleDelFriend(caSelName,caFriendName);
    	PDU *respdu = mkPDU(0);
    	respdu->uiMsgType = ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND;
    	strcpy(respdu->caData,DEL_FRIEND_OK);
    	write((char*)respdu,respdu->uiPDULen);
    	free(respdu);
    	respdu = NULL;
    	MyTcpServer::getInstance().resend(caFriendName,pdu);
    	break;
    }
    
  8. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下内容,用来处理服务器删除好友的回复:

    // 查看删除好友回复
    case ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST:
    {
    	char caName[32] = {'\0'};
    	memcpy(caName,pdu->caData,32);
    	QMessageBox::information(this,"删除好友",QString("%1删除你作为他的好友").arg(caName));
    	break;
    }
    case ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND:
    {
    	QMessageBox::information(this,"删除好友","删除好友成功");
    	break;
    }
    
  9. 然后我们分别启动服务器和有好友关系的两个客户端,点击“删除好友”按钮后就可以成功删除两个用户之间的好友关系了,并且可以显示提示,这也就意味着我们的删除好友功能已经完成了:
    请添加图片描述

9.6 私聊

  1. 私聊功能的流程示意图如下所示:
    请添加图片描述

  2. 在TcpClient项目中添加名为“PrivateChat”的UI界面类,系统会帮我们自动创建对应的cpp类和h头文件:
    请添加图片描述

  3. 然后按照如下配置创建私聊窗口的UI界面:
    请添加图片描述

  4. 然后给“发送”按钮添加信号槽,系统会自动帮我们创建需要的内容:
    请添加图片描述

  5. 然后在TcpClient项目中的“privatechat.h”中添加如下内容:

    #include "protocol.h"
    
    public:
    	static PrivateChat &getInstance();
    	void setChatName(QString strName);
    	void updateMsg(const PDU *pdu);
    
    private:
    	QString m_strChatName;
    	QString m_strLoginName;
    
  6. 然后在TcpClient项目中的“privatechat.cpp”中添加如下内容,用来发送聊天信息:

    #include "protocol.h"
    #include "tcpclient.h"
    #include <QMessageBox>
    
    // 用单例返回此对象
    PrivateChat &PrivateChat::getInstance()
    {
        static PrivateChat instance;
        return instance;
    }
    
    // 保存聊天用户的用户名
    void PrivateChat::setChatName(QString strName)
    {
        m_strChatName = strName;
        m_strLoginName = TcpClient::getInstance().loginName();
    }
    
    // 更新聊天框口信息
    void PrivateChat::updateMsg(const PDU *pdu)
    {
        if(NULL == pdu)
        {
            return;
        }
        char caSendName[32] = {'\0'};
        memcpy(caSendName,pdu->caData,32);
        QString strMsg = QString("%1 says: %2").arg(caSendName).arg((char*)(pdu->caMsg));
        ui->showMsg_te->append(strMsg);
    }
    
    // 点击“发送”按钮后发送聊天信息
    void PrivateChat::on_sendMsg_pb_clicked()
    {
        QString strMsg = ui->inputMsg_le->text();
        ui->inputMsg_le->clear();
        if(!strMsg.isEmpty())
        {
            PDU *pdu = mkPDU(strMsg.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST;
            memcpy(pdu->caData,m_strLoginName.toStdString().c_str(),m_strLoginName.size());
            memcpy(pdu->caData + 32,m_strChatName.toStdString().c_str(),m_strChatName.size());
            strcpy((char*)(pdu->caMsg),strMsg.toStdString().c_str());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::warning(this,"私聊","发送的聊天信息不能为空");
        }
    }
    
  7. 然后在TcpClient项目中的“friend.h”中的public slots处添加私聊功能的槽函数:

    void privateChat();
    
  8. 然后在TcpClient项目中的“friend.cpp”中的“Friend::Friend”函数中添加如下关联,将私聊功能的槽函数与处理函数关联起来:

    connect(m_pPrivateChatPB,SIGNAL(clicked(bool)),this,SLOT(privateChat()));
    
  9. 然后在TcpClient项目中的“friend.cpp”中添加如下内容来完成私聊的请求工作:

    #include "privatechat.h"
    #include <QMessageBox>
    
    // 私聊
    void Friend::privateChat()
    {
        if(NULL != m_pFriendListWidget->currentItem())
        {
            QString strChatName = m_pFriendListWidget->currentItem()->text();
            PrivateChat::getInstance().setChatName(strChatName);
            if(PrivateChat::getInstance().isHidden())
            {
                PrivateChat::getInstance().show();
            }
        }
        else
        {
            QMessageBox::warning(this,"私聊","请选择私聊对象");
        }
    }
    
  10. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理来自客户端的私聊请求:

    // 查看私聊请求
    case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST:
    {
    	char caPerName[32] = {'\0'};
    	memcpy(caPerName,pdu->caData + 32,32);
    	qDebug() << caPerName;
    	MyTcpServer::getInstance().resend(caPerName,pdu);
    	break;
    }
    
  11. 然后在TcpClient项目中的“tcpclient.cpp”中添加如下头文件:

    #include "privatechat.h"
    
  12. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理服务器的私聊回复:

     // 查看私聊回复
    case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST:
    {
    	if(PrivateChat::getInstance().isHidden())
    	{
    		PrivateChat::getInstance().show();
    	}
    	char caSendName[32] = {'\0'};
    	memcpy(caSendName,pdu->caData,32);
    	QString strSendName = caSendName;
    	PrivateChat::getInstance().setChatName(caSendName);
    	PrivateChat::getInstance().updateMsg(pdu);
    	break;
    }
    
  13. 然后我们可以启动服务器和两个客户端进行测试,发现可以正常进行聊天消息的传递,这也就意味着我们的私聊功能也完成了:
    请添加图片描述

9.7 群聊

  1. 群聊功能具体实现流程示意图如下所示:
    请添加图片描述

  2. 然后在TcpClient项目中的“friend.h”中添加群聊功能的槽函数和更新群聊信息的函数声明:

    public:
        void updateGroupMsg(PDU *pdu);
    
    public slots:
        void groupChat();
    
  3. 然后在TcpClient项目中的“friend.cpp”中的“Friend::Friend”函数中添加如下代码,用来关联槽函数:

    connect(m_pMsgSendPB,SIGNAL(clicked(bool)),this,SLOT(groupChat()));
    
  4. 然后在TcpClient项目中的“friend.cpp”中添加如下代码,用来向服务器发送群聊的请求:

    // 群聊
    void Friend::groupChat()
    {
        QString strMsg = m_pInputMsgLE->text();
        if(!strMsg.isEmpty())
        {
            PDU *pdu = mkPDU(strMsg.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_GROUP_CHAT_REQUEST;
            QString strName = TcpClient::getInstance().loginName();
            strncpy(pdu->caData,strName.toStdString().c_str(),strName.size());
            strncpy((char*)(pdu->caMsg),strMsg.toStdString().c_str(),strMsg.size());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        }
        else
        {
            QMessageBox::warning(this,"群聊","信息不能为空");
        }
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理来自客户端的群聊请求:

    // 查看群聊请求
    case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST:
    {
    	char caName[32] = {'\0'};
    	strncpy(caName, pdu->caData, 32);
    	QStringList onlineFriend = OpeDB::getInstance().handleFlushFriend(caName);
    	QString temp;
    	for(int i = 0;i<onlineFriend.size();i++)
    	{
    		temp = onlineFriend.at(i);
    		MyTcpServer::getInstance().resend(temp.toStdString().c_str(),pdu);
    	}
    	break;
    }
    
  6. 然后在TcpClient项目中的“friend.cpp”中添加如下代码,用来更新群聊信息:

    // 更新群聊信息
    void Friend::updateGroupMsg(PDU *pdu)
    {
        QString strMsg = QString("%1 says: %2").arg(pdu->caData).arg((char*)pdu->caMsg);
        m_pShowMsgTE->append(strMsg);
    }
    
  7. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理来自服务器的群聊回复:

    // 查看群聊回复
    case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST:
    {
    	OpeWidget::getInstance().getFriend()->updateGroupMsg(pdu);
    	break;
    }
    
  8. 然后我们来测试一下,发现可以正常发送群聊信息,这也就意味着我们的群聊功能已经完成了:
    请添加图片描述

十、文件操作功能实现

  本部分的内容是关于项目的文件操作功能的具体实现,这也是我们整个项目的最后一个大模块,我们现阶段默认使用之前设计的图书UI界面和初始代码来完成文件操作功能的具体实现。本章的具体内容包括:图书界面设计、文件夹操作、常规文件操作。文件夹操作中包括六大功能的实现,常规文件操作中包括五大功能的实现。这些功能基本囊括了我们日常生活中使用网盘的基本内容。跟着我一步一步做下去,一定会有很大的收获,加油!

10.1 图书界面设计

  1. 我们需要设计的文件操作功能界面的最终效果简图如下所示:
    请添加图片描述

  2. 然后在TcpClient项目中的“book.h”中添加如下代码,声明界面组件的定义:

    #include <QListWidget>
    #include <QPushButton>
    #include <QHBoxLayout>
    #include <QVBoxLayout>
    
    private:
    	QListWidget *m_pBookListW;
    	QPushButton *m_pReturnPB;
    	QPushButton *m_pCreateDirPB;
    	QPushButton *m_pDelDirPB;
    	QPushButton *m_pRenamePB;
    	QPushButton *m_pFlushFilePB;
    	QPushButton *m_pUploadPB;
    	QPushButton *m_DownLoadPB;
    	QPushButton *m_pDelFilePB;
    	QPushButton *m_pShareFilePB;
    
  3. 然后将TcpClient项目中的全部代码替换为如下内容,用来显示界面布局:

    #include "book.h"
    
    Book::Book(QWidget *parent) : QWidget(parent)
    {
        m_pBookListW = new QListWidget;
        m_pReturnPB = new QPushButton("返回");
        m_pCreateDirPB = new QPushButton("创建文件夹");
        m_pDelDirPB = new QPushButton("删除文件夹");
        m_pRenamePB = new QPushButton("重命名文件");
        m_pFlushFilePB = new QPushButton("刷新文件");
        QVBoxLayout *pDirVBL = new QVBoxLayout;
        pDirVBL->addWidget(m_pReturnPB);
        pDirVBL->addWidget(m_pCreateDirPB);
        pDirVBL->addWidget(m_pDelDirPB);
        pDirVBL->addWidget(m_pRenamePB);
        pDirVBL->addWidget(m_pFlushFilePB);
        m_pUploadPB = new QPushButton("上传文件");
        m_DownLoadPB = new QPushButton("下载文件");
        m_pDelFilePB = new QPushButton("删除文件");
        m_pShareFilePB = new QPushButton("共享文件");
        QVBoxLayout *pFileVBL = new QVBoxLayout;
        pFileVBL->addWidget(m_pUploadPB);
        pFileVBL->addWidget(m_DownLoadPB);
        pFileVBL->addWidget(m_pDelFilePB);
        pFileVBL->addWidget(m_pShareFilePB);
        QHBoxLayout *pMain = new QHBoxLayout;
        pMain->addWidget(m_pBookListW);
        pMain->addLayout(pDirVBL);
        pMain->addLayout(pFileVBL);
        setLayout(pMain);
    }
    
  4. 然后我们可以来测试一下,当我们点击“图书”时,就可以显示我们刚刚设计的界面了,这也就意味着图书界面设计已经完成了:
    请添加图片描述

10.2 文件夹操作

10.2.1 创建文件夹

  1. 创建文件夹的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpServer项目中的“mytcpsocket.h”中添加如下头文件:

    #include <QDir>
    
  3. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中的“case ENUM_MSG_TYPE_REGIST_REQUEST”中添加如下代码用来创建文件夹:

    qDebug() << "create dir:" << dir.mkdir(QString("./%1").arg(caName));
    
  4. 此时我们测试一下,看看新注册的用户能否成功创建与用户名同名的文件夹:
    请添加图片描述

  5. 可以发现已经成功创建文件夹了,刚才完成的是新用户创建文件夹的功能,下面我们要完成新用户注册之后在此目录下新建文件夹的功能。首先我们在TcpClient项目中的“tcpclient.h”中添加如下代码:

    public:
        QString curPath();
    
    private:
        QString m_strCurPath;
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中的“case ENUM_MSG_TYPE_LOGIN_RESPOND”中添加如下代码,目的是使用登陆的用户名保存当前文件夹的目录:

    m_strCurPath = QString("./%1").arg(m_strLoginName);
    
  7. 然后在TcpClient项目中的“tcpclient.cpp”中添加如下代码,用来返回当前文件夹目录:

    // 返回当前文件夹目录
    QString TcpClient::curPath()
    {
        return m_strCurPath;
    }
    
  8. 然后在TcpClient项目中的“book.h”中添加如下代码:

    #include "protocol.h"
    
    public slots:
        void createDir();
    
  9. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来创建文件夹:

    #include "tcpclient.h"
    #include <QInputDialog>
    #include <QMessageBox>
    
    // 创建文件夹
    void Book::createDir()
    {
        QString strNewDir = QInputDialog::getText(this,"新建文件夹","新文件夹名字");
        if(!strNewDir.isEmpty())
        {
            if(strNewDir.size() > 32)
            {
                QMessageBox::warning(this,"新建文件夹","新文件夹名字不能超过32个字符");
            }
            else
            {
                QString strName = TcpClient::getInstance().loginName();
                QString strCurPath = TcpClient::getInstance().curPath();
                PDU *pdu = mkPDU(strCurPath.size() + 1);
                pdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_REQUEST;
                strncpy(pdu->caData,strName.toStdString().c_str(),strName.size());
                strncpy(pdu->caData + 32,strNewDir.toStdString().c_str(),strNewDir.size());
                memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
                TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
                free(pdu);
                pdu = NULL;
            }
        }
        else
        {
            QMessageBox::warning(this,"新建文件夹","新文件夹名字不能为空");
        }
    }
    
  10. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码,用来关联槽函数:

    connect(m_pCreateDirPB,SIGNAL(clicked(bool)),this,SLOT(createDir()));
    
  11. 然后在TcpServer项目中的“mytcpsocket.cpp”中添加如下头文件:

    #include <QDir>
    
  12. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数添加如下代码,用来查看创建文件夹的请求:

    // 查看创建文件夹请求
    case ENUM_MSG_TYPE_CREATE_DIR_REQUEST:
    {
        QDir dir;
        QString strCurPath = QString("%1").arg((char*)(pdu->caMsg));
        bool res = dir.exists(strCurPath);
        PDU *respdu = NULL;
        // 当前目录存在
        if(res)
        {
            char caNewDir[32] = {'\0'};
            memcpy(caNewDir,pdu->caData + 32,32);
            QString strNewPath = strCurPath + "/" + caNewDir;
            qDebug() << strNewPath;
            res = dir.exists(strNewPath);
            // 创建的文件名已存在
            if(res)
            {
                respdu = mkPDU(0);
                respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
                strcpy(respdu->caData,FILE_NAME_EXIST);
            }
            // 创建的文件名不存在
            else
            {
                dir.mkdir(strNewPath);
                respdu = mkPDU(0);
                respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
                strcpy(respdu->caData,CREAT_DIR_OK);
            }
        }
        // 当前目录不存在
        else
        {
            respdu = mkPDU(0);
            respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
            strcpy(respdu->caData,DIR_NO_EXIST);
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  13. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来查看来自服务器的创建文件夹回复:

    // 查看创建文件夹回复
    case ENUM_MSG_TYPE_CREATE_DIR_RESPOND:
    {
    	QMessageBox::information(this,"创建文件",pdu->caData);
    	break;
    }
    
  14. 然后我们来测试一下,看看能否成功在用户名同名的目录下新建文件夹:
    请添加图片描述

  15. 可以发现,已经成功在用户名同名的目录下新建文件夹,这也代表着创建文件夹功能我们已经成功实现了

10.2.2 查看所有文件

  1. 查看所有文件流程示意图如下所示:
    请添加图片描述

  2. 首先在之前创建好的文件夹下添加一些测试文件,为了方便后续测试使用:
    请添加图片描述

  3. 然后在TcpClient项目中的“book.h”中添加如下代码:

    public slots:
        void flushFile();
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,目的是向服务器发送查看所有文件的请求:

    // 查看所有文件
    void Book::flushFile()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        PDU *pdu = mkPDU(strCurPath.size() + 1);
        pdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FILE_REQUEST;
        strncpy((char*)(pdu->caMsg),strCurPath.toStdString().c_str(),strCurPath.size());
        TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        free(pdu);
        pdu = NULL;
    }
    
  5. 然后在TcpClient项目中的“protocol.h”中添加如下代码,用来保存文件信息:

    // 文件信息结构体
    struct FileInfo
    {
        char caFileName[32];    // 文件名字
        int iFileType;          // 文件类型
    };
    
  6. 然后在TcpServer项目中的“protocol.h”中添加如下代码,用来保存文件信息:

    // 文件信息结构体
    struct FileInfo
    {
        char caFileName[32];    // 文件名字
        int iFileType;          // 文件类型
    };
    
  7. 然后在TcpServer项目中的“mytcpsocket.cpp”中添加如下头文件:

    #include <QFileInfoList>
    
  8. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来查看查看所有文件请求:

    // 查看查看所有文件请求
    case ENUM_MSG_TYPE_FLUSH_FILE_REQUEST:
    {
        char *pCurPath = new char[pdu->uiMsgLen];
        memcpy(pCurPath,pdu->caMsg,pdu->uiMsgLen);
        QDir dir(pCurPath);
        QFileInfoList fileInfoList = dir.entryInfoList();
        int iFileCount = fileInfoList.size();
        PDU *respdu = mkPDU(sizeof(FileInfo) * iFileCount);
        respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FILE_RESPOND;
        FileInfo *pFileInfo = NULL;
        QString strFileName;
        for(int i = 0;i<fileInfoList.size();i++)
        {
            pFileInfo = (FileInfo*)(respdu->caMsg) + i;
            strFileName = fileInfoList[i].fileName();
            memcpy(pFileInfo->caFileName,strFileName.toStdString().c_str(),strFileName.size());
            if(fileInfoList[i].isDir())
            {
                 pFileInfo->iFileType = 0;
            }
            else if(fileInfoList[i].isFile())
            {
                pFileInfo->iFileType = 1;
            }
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  9. 然后在TcpClient项目中的“opewidget.h”中添加如下代码:

    public:
        Book *getBook();
    
  10. 然后在TcpClient项目中的“opewidget.cpp”中添加如下代码:

    // 获取图书对象
    Book *OpeWidget::getBook()
    {
        return m_pBook;
    }
    
  11. 然后使用我的代码中的两个图片作为不同文件的图标使用,当然读者也可以自定义图标,不过要放到下图所示的目录中,而且图标图片的名字需要注意要和代码中的图标图片的名字保持一致:
    请添加图片描述

  12. 然后添加资源文件:
    请添加图片描述

  13. 输入名称后点击“Choose…”:
    请添加图片描述

  14. 添加前缀:
    请添加图片描述

  15. 然后将刚才的图标文件读取到资源文件中,并保存,因为此步骤之前已经演示过了,所以在此不再赘述:
    请添加图片描述

  16. 然后在TcpClient项目中的“book.h”中添加如下代码:

    public:
        void updateFileList(const PDU *pdu);
    
  17. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来更新文件列表:

    // 更新文件列表
    void Book::updateFileList(const PDU *pdu)
    {
        if(NULL == pdu)
        {
            return;
        }
        QListWidgetItem *pItemTemp = NULL;
        int row = m_pBookListW->count();
        while(m_pBookListW->count() > 0)
        {
            pItemTemp = m_pBookListW->item(row - 1);
            m_pBookListW->removeItemWidget(pItemTemp);
            delete pItemTemp;
            row -= 1;
        }
        FileInfo *pFileInfo = NULL;
        int iCount = pdu->uiMsgLen / sizeof(FileInfo);
        for(int i = 0;i<iCount;i++)
        {
            pFileInfo = (FileInfo*)(pdu->caMsg) + i;
            QListWidgetItem *pItem = new QListWidgetItem;
            if(0 == pFileInfo->iFileType)
            {
                pItem->setIcon(QIcon(QPixmap(":/icon/dir.jpg")));
            }
            else if(1 == pFileInfo->iFileType)
            {
                pItem->setIcon(QIcon(QPixmap(":/icon/reg.jpg")));
            }
            pItem->setText(pFileInfo->caFileName);
            m_pBookListW->addItem(pItem);
        }
    }
    
  18. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来查看查看所有文件回复:

    // 查看查看所有文件回复
    case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND:
    {
    	OpeWidget::getInstance().getBook()->updateFileList(pdu);
    	break;
    }
    
  19. 然后我们可以测试一下,当我们点击“图书”界面的“刷新文件”,就可以显示此文件夹内所有的文件了,这也就说明我们的查看所有文件功能已经完成了:
    请添加图片描述

10.2.3 删除文件夹

  1. 删除文件夹流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下声明删除文件夹的槽函数:

    public slots:
        void delDir();
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下函数,用来关联槽函数:

    connect(m_pDelDirPB,SIGNAL(clicked(bool)),this,SLOT(delDir()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来完成删除文件夹的操作:

    // 删除文件夹
    void Book::delDir()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        QListWidgetItem *pItem = m_pBookListW->currentItem();
        if(NULL == pItem)
        {
            QMessageBox::warning(this,"删除文件","请选择要删除的文件");
        }
        else
        {
            QString strDelName = pItem->text();
            PDU *pdu = mkPDU(strCurPath.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_DEL_DIR_REQUEST;
            strncpy(pdu->caData,strDelName.toStdString().c_str(),strDelName.size());
            memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来查看删除文件夹请求:

    // 查看删除文件夹请求
    case ENUM_MSG_TYPE_DEL_DIR_REQUEST:
    {
        char caName[32] = {'\0'};
        strcpy(caName,pdu->caData);
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
        QString strPath = QString("%1/%2").arg(pPath).arg(caName);
        qDebug() << strPath;
        QFileInfo fileInfo(strPath);
        bool res = false;
        // 文件夹
        if(fileInfo.isDir())
        {
            QDir dir;
            dir.setPath(strPath);
            res = dir.removeRecursively();
        }
        // 常规文件
        else if(fileInfo.isFile())
        {
            res = false;
        }
        PDU *respdu = NULL;
        if(res)
        {
            respdu = mkPDU(strlen(DEL_DIR_OK) + 1);
            respdu->uiMsgType = ENUM_MSG_TYPE_DEL_DIR_RESPOND;
            memcpy(respdu->caData,DEL_DIR_OK,strlen(DEL_DIR_OK));
        }
        else
        {
            respdu = mkPDU((strlen(DEL_DIR_FAILURED)) + 1);
            respdu->uiMsgType = ENUM_MSG_TYPE_DEL_DIR_RESPOND;
            memcpy(respdu->caData,DEL_DIR_FAILURED,strlen(DEL_DIR_FAILURED));
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”中添加如下代码,用来查看删除文件夹回复:

    // 查看删除文件夹回复
    case ENUM_MSG_TYPE_DEL_DIR_RESPOND:
    {
    	QMessageBox::information(this,"删除文件夹",pdu->caData);
    	break;
    }
    
  7. 然后我们可以测试一下,当我们选中文件夹后点击“删除文件夹”后,就会弹出删除成功的提示,然后当我们点击“刷新文件”后,就会发现原来的被删除文件夹已经不存在了,这说明删除文件夹功能已经被我们完成了:
    请添加图片描述

10.2.4 重命名文件

  1. 重命名文件流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码:

    public slots:
        void renameFile();
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码:

    connect(m_pRenamePB,SIGNAL(clicked(bool)),this,SLOT(renameFile()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来发送重命名文件的请求:

    // 重命名文件
    void Book::renameFile()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        QListWidgetItem *pItem = m_pBookListW->currentItem();
        if(NULL == pItem)
        {
            QMessageBox::warning(this,"重命名文件","请选择要重命名的文件");
        }
        else
        {
            QString strOldName = pItem->text();
            QString strNewName = QInputDialog::getText(this,"重命名文件","请输入新的文件名");
            if(!strNewName.isEmpty())
            {
                PDU *pdu = mkPDU(strCurPath.size() + 1);
                pdu->uiMsgType = ENUM_MSG_TYPE_RENAME_FILE_REQUEST;
                strncpy(pdu->caData,strOldName.toStdString().c_str(),strOldName.size());
                strncpy(pdu->caData + 32,strNewName.toStdString().c_str(),strNewName.size());
                memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
                TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
                free(pdu);
                pdu = NULL;
            }
            else
            {
                QMessageBox::warning(this,"重命名文件","新文件名不能为空");
            }
        }
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理重命名文件请求:

    // 查看重命名文件请求
    case ENUM_MSG_TYPE_RENAME_FILE_REQUEST:
    {
        char caOldName[32] = {'\0'};
        char caNewName[32] = {'\0'};
        strncpy(caOldName,pdu->caData,32);
        strncpy(caNewName,pdu->caData + 32,32);
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
        QString strOldPath = QString("%1/%2").arg(pPath).arg(caOldName);
        QString strNewPath = QString("%1/%2").arg(pPath).arg(caNewName);
        QDir dir;
        bool res = dir.rename(strOldPath,strNewPath);
        PDU *respdu = mkPDU(0);
        respdu->uiMsgType = ENUM_MSG_TYPE_RENAME_FILE_RESPOND;
        if(res)
        {
            strcpy(pdu->caData,RENAME_FILE_OK);
        }
        else
        {
            strcpy(pdu->caData,RENAME_FILE_FAILURED);
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理来自服务器的重命名文件回复:

    // 查看重命名文件回复
    case ENUM_MSG_TYPE_RENAME_FILE_RESPOND:
    {
    	QMessageBox::information(this,"重命名文件",pdu->caData);
    	break;
    }
    
  7. 然后我们可以启动项目测试一下:
    请添加图片描述

  8. 当我们点击“刷新文件”后,可以发现已经成功重命名文件了,这就说明我们的重命名文件功能已经成功实现了:
    请添加图片描述

10.2.5 进入文件夹

  1. 进入文件夹的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码:

    public slots:
        void enterDir(const QModelIndex &index);
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码:

    connect(m_pBookListW,SIGNAL(doubleClicked(QModelIndex)),this,SLOT(enterDir(QModelIndex)));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来向服务器发送进入文件夹的请求:

    // 进入文件夹
    void Book::enterDir(const QModelIndex &index)
    {
        QString strDirName = index.data().toString();
        QString strCurPath = TcpClient::getInstance().curPath();
        PDU *pdu = mkPDU(strCurPath.size() + 1);
        pdu->uiMsgType = ENUM_MSG_TYPE_ENTER_DIR_REQUEST;
        strncpy(pdu->caData,strDirName.toStdString().c_str(),strDirName.size());
        memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
        TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        free(pdu);
        pdu = NULL;
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理进入文件夹请求:

    // 查看进入文件夹请求
    case ENUM_MSG_TYPE_ENTER_DIR_REQUEST:
    {
        char caEnterName[32] = {'\0'};
        strncpy(caEnterName,pdu->caData,32);
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
        QString strPath = QString("%1/%2").arg(pPath).arg(caEnterName);
        QFileInfo fileInfo(strPath);
        PDU *respdu = NULL;
        if(fileInfo.isDir())
        {
            QDir dir(strPath);
            QFileInfoList fileInfoList = dir.entryInfoList();
            int iFileCount = fileInfoList.size();
            respdu = mkPDU(sizeof(FileInfo) * iFileCount);
            respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FILE_RESPOND;
            FileInfo *pFileInfo = NULL;
            QString strFileName;
            for(int i = 0;i<fileInfoList.size();i++)
            {
                pFileInfo = (FileInfo*)(respdu->caMsg) + i;
                strFileName = fileInfoList[i].fileName();
                memcpy(pFileInfo->caFileName,strFileName.toStdString().c_str(),strFileName.size());
                if(fileInfoList[i].isDir())
                {
                    pFileInfo->iFileType = 0;
                }
                else if(fileInfoList[i].isFile())
                {
                    pFileInfo->iFileType = 1;
                }
            }
            write((char*)respdu,respdu->uiPDULen);
            free(respdu);
            respdu = NULL;
        }
        else if(fileInfo.isFile())
        {
            respdu = mkPDU(0);
            respdu->uiMsgType = ENUM_MSG_TYPE_ENTER_DIR_RESPOND;
            strcpy(respdu->caData,ENTER_DIR_FAILURED);
            write((char*)respdu,respdu->uiPDULen);
            free(respdu);
            respdu = NULL;
        }
        break;
    }
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理来自服务器的进入文件夹回复:

    // 查看进入文件夹回复
    case ENUM_MSG_TYPE_ENTER_DIR_RESPOND:
    {
    	QMessageBox::information(this,"进入文件夹",pdu->caData);
    	break;
    }
    
  7. 因为进入新的文件夹后,我们的当前目录就要变化了,所以需要更新一下当前目录。我们首先在TcpClient项目中的“book.h”中添加如下代码:

    public:
        void clearEnterDir();
        QString enterDir();
    
    private:
        QString m_strEnterDir;
    
  8. 然后在TcpClient项目中的“book.cpp”中添加如下代码:

    // 清空当前保存的目录
    void Book::clearEnterDir()
    {
        m_strEnterDir.clear();
    }
    
    // 获取当前保存的目录
    QString Book::enterDir()
    {
        return m_strEnterDir;
    }
    
  9. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码:

    m_strEnterDir.clear();
    
  10. 然后将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中的“case ENUM_MSG_TYPE_ENTER_DIR_RESPOND”全部替换为如下代码:

    // 查看进入文件夹回复
    case ENUM_MSG_TYPE_ENTER_DIR_RESPOND:
    {
    	OpeWidget::getInstance().getBook()->clearEnterDir();
    	QMessageBox::information(this,"进入文件夹",pdu->caData);
    	break;
    }
    
  11. 然后将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中的“case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND”全部替换为如下代码:

    // 查看查看所有文件回复
    case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND:
    {
        OpeWidget::getInstance().getBook()->updateFileList(pdu);
        QString strEnterDir = OpeWidget::getInstance().getBook()->enterDir();
        if(!strEnterDir.isEmpty())
        {
            m_strCurPath = m_strCurPath + "/" + strEnterDir;
        }
        break;
    }
    
  12. 然后我们启动项目测试一下,当我们双击某个文件夹的时候就可以进入到此文件夹,这也就说明我们的进入文件夹功能已经实现了:
    请添加图片描述

10.2.6 返回上一级

  1. 返回上一级的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码,用来声明槽函数:

    public slots:
        void returnPre();
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码,用来关联槽函数:

    connect(m_pReturnPB,SIGNAL(clicked(bool)),this,SLOT(returnPre()));
    
  4. 然后在TcpClient项目中的“tcpclient.h”中声明如下函数:

    public:
        void setCurPath(QString strCurPath);
    
  5. 然后在TcpClient项目中的“tcpclient.cpp”中添加如下代码,用来设置当前目录位置:

    // 设置当前目录位置
    void TcpClient::setCurPath(QString strCurPath)
    {
        m_strCurPath = strCurPath;
    }
    
  6. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来完成返回上一级的功能:

    // 返回上一级
    void Book::returnPre()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        QString strRootPath = "./" + TcpClient::getInstance().loginName();
        if(strCurPath == strRootPath)
        {
           QMessageBox::warning(this,"返回","返回失败:已经在最开始的文件夹目录中");
        }
        else
        {
            int index = strCurPath.lastIndexOf('/');
            strCurPath.remove(index,strCurPath.size() - index);
            TcpClient::getInstance().setCurPath(strCurPath);
            clearEnterDir();
            flushFile();
        }
    }
    
  7. 然后我们启动项目来测试一下,当我们点击“返回”的时候,可以从子目录返回到主目录,这也就意味着我们的返回上一级功能已经成功实现了:
    请添加图片描述

10.3 常规文件操作

10.3.1 上传文件

  1. 上传文件的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码:

    #include <QTimer>
    
    public slots:
    	void uploadFile();
    	void uploadFileData();
    
    private:
    	QString m_strUploadFilePath;
    	QTimer *m_pTimer;
    
  3. 然后在TcpClient项目中的“book.h”中的“Book::Book”函数中添加如下代码,用来创建定时器和关联槽函数:

    m_pTimer = new QTimer;
    connect(m_pUploadPB,SIGNAL(clicked(bool)),this,SLOT(uploadFile()));
    connect(m_pTimer,SIGNAL(timeout()),this,SLOT(uploadFileData()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来向服务器发送上传文件的请求:

    // 上传文件请求
    void Book::uploadFile()
    {
        m_strUploadFilePath = QFileDialog::getOpenFileName();
        if(!m_strUploadFilePath.isEmpty())
        {
            int index = m_strUploadFilePath.lastIndexOf('/');
            QString strFileName = m_strUploadFilePath.right(m_strUploadFilePath.size() - index - 1);
            QFile file(m_strUploadFilePath);
            qint64 fileSize = file.size();
            QString strCurPath = TcpClient::getInstance().curPath();
            PDU *pdu = mkPDU(strCurPath.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_UPLOAD_FILE_REQUEST;
            memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
            sprintf(pdu->caData,"%s %lld",strFileName.toStdString().c_str(),fileSize);
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
            m_pTimer->start(1000);
        }
        else
        {
            QMessageBox::warning(this,"上传文件","上传文件名字不能为空");
        }
    }
    
    // 上传文件传输数据
    void Book::uploadFileData()
    {
        m_pTimer->stop();
        QFile file(m_strUploadFilePath);
        if(!file.open(QIODevice::ReadOnly))
        {
            QMessageBox::warning(this,"上传文件","打开文件失败");
            return;
        }
        char *pBuffer = new char[4096];
        qint64 res = 0;
        while(true)
        {
            res = file.read(pBuffer,4096);
            if(res > 0 && res <= 4096)
            {
                TcpClient::getInstance().getTcpSocket().write(pBuffer,res);
            }
            else if(0 == res)
            {
                break;
            }
            else
            {
                QMessageBox::warning(this,"上传文件","打开文件失败:读文件失败");
                break;
            }
        }
        file.close();
        delete []pBuffer;
        pBuffer = NULL;
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.h”中添加如下代码:

    #include <QFile>
    
    private:
        QFile m_file;
        qint64 m_iTotal;
        qint64 m_iRecved;
        bool m_bUpload;
    
  6. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::MyTcpSocket”函数中添加如下代码:

    m_bUpload = false;
    
  7. 然后将TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数的全部内容替换为如下内容:

    // 接收来自客户端的消息
    void MyTcpSocket::recvMsg()
    {
        if(!m_bUpload)
        {
            qDebug() << this->bytesAvailable();
            uint uiPDULen  = 0;
            this->read((char*)&uiPDULen,sizeof(uint));
            uint uiMsgLen = uiPDULen - sizeof(PDU);
            PDU *pdu = mkPDU(uiMsgLen);
            this->read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
            switch (pdu->uiMsgType)
            {
                // 注册请求
                case ENUM_MSG_TYPE_REGIST_REQUEST:
                {
                    char caName[32] = {'\0'};
                    char caPwd[32] = {'\0'};
                    strncpy(caName,pdu->caData,32);
                    strncpy(caPwd,pdu->caData+32,32);
                    bool res = OpeDB::getInstance().handleRegist(caName,caPwd);
                    PDU *respdu = mkPDU(0);
                    respdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND;
                    if(res)
                    {
                        strcpy(respdu->caData,REGIST_OK);
                        QDir dir;
                        qDebug() << "create dir:" << dir.mkdir(QString("./%1").arg(caName));
                    }
                    else
                    {
                        strcpy(respdu->caData,REGIST_FAILED);
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 登录请求
                case ENUM_MSG_TYPE_LOGIN_REQUEST:
                {
                    char caName[32] = {'\0'};
                    char caPwd[32] = {'\0'};
                    strncpy(caName,pdu->caData,32);
                    strncpy(caPwd,pdu->caData+32,32);
                    bool res = OpeDB::getInstance().handleLogin(caName,caPwd);
                    PDU *respdu = mkPDU(0);
                    respdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
                    if(res)
                    {
                        strcpy(respdu->caData,LOGIN_OK);
                        m_strName= caName;
                    }
                    else
                    {
                        strcpy(respdu->caData,LOGIN_FAILED);
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看在线用户请求
                case ENUM_MSG_TYPE_ALL_ONLINE_REQUEST:
                {
                    QStringList res = OpeDB::getInstance().handleAllOnline();
                    uint uiMsgLen = res.size() * 32;
                    PDU *resPdu = mkPDU(uiMsgLen);
                    resPdu->uiMsgType = ENUM_MSG_TYPE_ALL_ONLINE_RESPOND;
                    for(int i = 0;i<res.size();i++)
                    {
                        memcpy((char*)(resPdu->caMsg) + i * 32,res.at(i).toStdString().c_str(),res.at(i).size());
                    }
                    write((char*)resPdu,resPdu->uiPDULen);
                    free(resPdu);
                    resPdu = NULL;
                    break;
                }
                // 查看查找用户请求
                case ENUM_MSG_TYPE_SEARCH_USR_REQUEST:
                {
                    int res = OpeDB::getInstance().handleSearchUsr(pdu->caData);
                    PDU *resPdu = mkPDU(0);
                    resPdu->uiMsgType = ENUM_MSG_TYPE_SEARCH_USR_RESPOND;
                    if(-1 == res)
                    {
                        strcpy(resPdu->caData,SEARCH_USR_NO);
                    }
                    else if(1 == res)
                    {
                        strcpy(resPdu->caData,SEARCH_USR_ONLINE);
                    }
                    else if(0 == res)
                    {
                        strcpy(resPdu->caData,SEARCH_USR_OFFLINE);
                    }
                    write((char*)resPdu,resPdu->uiPDULen);
                    free(resPdu);
                    resPdu = NULL;
                    break;
                }
                // 查看添加好友请求
                case ENUM_MSG_TYPE_ADD_FRIEND_REQUEST:
                {
                    char caPerName[32] = {'\0'};
                    char caName[32] = {'\0'};
                    strncpy(caPerName,pdu->caData,32);
                    strncpy(caName,pdu->caData+32,32);
                    int res = OpeDB::getInstance().handleAddFriend(caPerName,caName);
                    qDebug() << "当前这个人在不在?" << res;
                    PDU *respdu = NULL;
                    if(-1== res)
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
                        strcpy(respdu->caData,UNKNOW_ERROR);
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    else if(0 == res)
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
                        strcpy(respdu->caData,EXISTED_FRIEND);
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    else if(1 == res)
                    {
                        MyTcpServer::getInstance().resend(caPerName,pdu);
                    }
                    else if(2 == res)
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
                        strcpy(respdu->caData,ADD_FRIEND_OFFLINE);
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    else if(3 == res)
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_RESPOND;
                        strcpy(respdu->caData,ADD_FRIEND_NO_EXIST);
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    break;
                }
                case ENUM_MSG_TYPE_ADD_FRIEND_AGGREE:
                {
                    char caPerName[32] = {'\0'};
                    char caName[32] = {'\0'};
                    strncpy(caPerName, pdu->caData, 32);
                    strncpy(caName, pdu->caData+32, 32);
                    OpeDB::getInstance().handleAgreeAddFriend(caPerName, caName);
                    MyTcpServer::getInstance().resend(caName, pdu);
                    break;
                }
                case ENUM_MSG_TYPE_ADD_FRIEND_REFUSE:
                {
                    char caName[32] = {'\0'};
                    strncpy(caName, pdu->caData+32, 32);
                    MyTcpServer::getInstance().resend(caName, pdu);
                    break;
                }
                // 查看刷新在线好友列表请求
                case ENUM_MSG_TYPE_FLUSH_FRIEND_REQUEST:
                {
                    char caName[32] = {'\0'};
                    strncpy(caName, pdu->caData, 32);
                    QStringList res = OpeDB::getInstance().handleFlushFriend(caName);
                    uint uiMsglen = res.size() * 32;
                    PDU *respdu = mkPDU(uiMsglen);
                    respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND;
                    for(int i = 0;i<res.size();i++)
                    {
                        memcpy((char*)(respdu->caMsg) + i * 32,res.at(i).toStdString().c_str(),res.at(i).size());
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看删除好友请求
                case ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST:
                {
                    char caSelName[32] = {'\0'};
                    char caFriendName[32] = {'\0'};
                    strncpy(caSelName,pdu->caData,32);
                    strncpy(caFriendName,pdu->caData+32,32);
                    OpeDB::getInstance().handleDelFriend(caSelName,caFriendName);
                    PDU *respdu = mkPDU(0);
                    respdu->uiMsgType = ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND;
                    strcpy(respdu->caData,DEL_FRIEND_OK);
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    MyTcpServer::getInstance().resend(caFriendName,pdu);
                    break;
                }
                // 查看私聊请求
                case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST:
                {
                    char caPerName[32] = {'\0'};
                    memcpy(caPerName,pdu->caData + 32,32);
                    qDebug() << caPerName;
                    MyTcpServer::getInstance().resend(caPerName,pdu);
                    break;
                }
                // 查看群聊请求
                case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST:
                {
                    char caName[32] = {'\0'};
                    strncpy(caName, pdu->caData, 32);
                    QStringList onlineFriend = OpeDB::getInstance().handleFlushFriend(caName);
                    QString temp;
                    for(int i = 0;i<onlineFriend.size();i++)
                    {
                        temp = onlineFriend.at(i);
                        MyTcpServer::getInstance().resend(temp.toStdString().c_str(),pdu);
                    }
                    break;
                }
                // 查看创建文件夹请求
                case ENUM_MSG_TYPE_CREATE_DIR_REQUEST:
                {
                    QDir dir;
                    QString strCurPath = QString("%1").arg((char*)(pdu->caMsg));
                    bool res = dir.exists(strCurPath);
                    PDU *respdu = NULL;
                    // 当前目录存在
                    if(res)
                    {
                        char caNewDir[32] = {'\0'};
                        memcpy(caNewDir,pdu->caData + 32,32);
                        QString strNewPath = strCurPath + "/" + caNewDir;
                        qDebug() << strNewPath;
                        res = dir.exists(strNewPath);
                        // 创建的文件名已存在
                        if(res)
                        {
                            respdu = mkPDU(0);
                            respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
                            strcpy(respdu->caData,FILE_NAME_EXIST);
                        }
                        // 创建的文件名不存在
                        else
                        {
                            dir.mkdir(strNewPath);
                            respdu = mkPDU(0);
                            respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
                            strcpy(respdu->caData,CREAT_DIR_OK);
                        }
                    }
                    // 当前目录不存在
                    else
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_CREATE_DIR_RESPOND;
                        strcpy(respdu->caData,DIR_NO_EXIST);
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看查看所有文件请求
                case ENUM_MSG_TYPE_FLUSH_FILE_REQUEST:
                {
                    char *pCurPath = new char[pdu->uiMsgLen];
                    memcpy(pCurPath,pdu->caMsg,pdu->uiMsgLen);
                    QDir dir(pCurPath);
                    QFileInfoList fileInfoList = dir.entryInfoList();
                    int iFileCount = fileInfoList.size();
                    PDU *respdu = mkPDU(sizeof(FileInfo) * iFileCount);
                    respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FILE_RESPOND;
                    FileInfo *pFileInfo = NULL;
                    QString strFileName;
                    for(int i = 0;i<fileInfoList.size();i++)
                    {
                        pFileInfo = (FileInfo*)(respdu->caMsg) + i;
                        strFileName = fileInfoList[i].fileName();
                        memcpy(pFileInfo->caFileName,strFileName.toStdString().c_str(),strFileName.size());
                        if(fileInfoList[i].isDir())
                        {
                            pFileInfo->iFileType = 0;
                        }
                        else if(fileInfoList[i].isFile())
                        {
                            pFileInfo->iFileType = 1;
                        }
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看删除文件夹请求
                case ENUM_MSG_TYPE_DEL_DIR_REQUEST:
                {
                    char caName[32] = {'\0'};
                    strcpy(caName,pdu->caData);
                    char *pPath = new char[pdu->uiMsgLen];
                    memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
                    QString strPath = QString("%1/%2").arg(pPath).arg(caName);
                    qDebug() << strPath;
                    QFileInfo fileInfo(strPath);
                    bool res = false;
                    // 文件夹
                    if(fileInfo.isDir())
                    {
                        QDir dir;
                        dir.setPath(strPath);
                        res = dir.removeRecursively();
                    }
                    // 常规文件
                    else if(fileInfo.isFile())
                    {
                        res = false;
                    }
                    PDU *respdu = NULL;
                    if(res)
                    {
                        respdu = mkPDU(strlen(DEL_DIR_OK) + 1);
                        respdu->uiMsgType = ENUM_MSG_TYPE_DEL_DIR_RESPOND;
                        memcpy(respdu->caData,DEL_DIR_OK,strlen(DEL_DIR_OK));
                    }
                    else
                    {
                        respdu = mkPDU((strlen(DEL_DIR_FAILURED)) + 1);
                        respdu->uiMsgType = ENUM_MSG_TYPE_DEL_DIR_RESPOND;
                        memcpy(respdu->caData,DEL_DIR_FAILURED,strlen(DEL_DIR_FAILURED));
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看重命名文件请求
                case ENUM_MSG_TYPE_RENAME_FILE_REQUEST:
                {
                    char caOldName[32] = {'\0'};
                    char caNewName[32] = {'\0'};
                    strncpy(caOldName,pdu->caData,32);
                    strncpy(caNewName,pdu->caData + 32,32);
                    char *pPath = new char[pdu->uiMsgLen];
                    memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
                    QString strOldPath = QString("%1/%2").arg(pPath).arg(caOldName);
                    QString strNewPath = QString("%1/%2").arg(pPath).arg(caNewName);
                    QDir dir;
                    bool res = dir.rename(strOldPath,strNewPath);
                    PDU *respdu = mkPDU(0);
                    respdu->uiMsgType = ENUM_MSG_TYPE_RENAME_FILE_RESPOND;
                    if(res)
                    {
                        strcpy(pdu->caData,RENAME_FILE_OK);
                    }
                    else
                    {
                        strcpy(pdu->caData,RENAME_FILE_FAILURED);
                    }
                    write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                // 查看进入文件夹请求
                case ENUM_MSG_TYPE_ENTER_DIR_REQUEST:
                {
                    char caEnterName[32] = {'\0'};
                    strncpy(caEnterName,pdu->caData,32);
                    char *pPath = new char[pdu->uiMsgLen];
                    memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
                    QString strPath = QString("%1/%2").arg(pPath).arg(caEnterName);
                    QFileInfo fileInfo(strPath);
                    PDU *respdu = NULL;
                    if(fileInfo.isDir())
                    {
                        QDir dir(strPath);
                        QFileInfoList fileInfoList = dir.entryInfoList();
                        int iFileCount = fileInfoList.size();
                        respdu = mkPDU(sizeof(FileInfo) * iFileCount);
                        respdu->uiMsgType = ENUM_MSG_TYPE_FLUSH_FILE_RESPOND;
                        FileInfo *pFileInfo = NULL;
                        QString strFileName;
                        for(int i = 0;i<fileInfoList.size();i++)
                        {
                            pFileInfo = (FileInfo*)(respdu->caMsg) + i;
                            strFileName = fileInfoList[i].fileName();
                            memcpy(pFileInfo->caFileName,strFileName.toStdString().c_str(),strFileName.size());
                            if(fileInfoList[i].isDir())
                            {
                                pFileInfo->iFileType = 0;
                            }
                            else if(fileInfoList[i].isFile())
                            {
                                pFileInfo->iFileType = 1;
                            }
                        }
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    else if(fileInfo.isFile())
                    {
                        respdu = mkPDU(0);
                        respdu->uiMsgType = ENUM_MSG_TYPE_ENTER_DIR_RESPOND;
                        strcpy(respdu->caData,ENTER_DIR_FAILURED);
                        write((char*)respdu,respdu->uiPDULen);
                        free(respdu);
                        respdu = NULL;
                    }
                    break;
                }
                // 查看上传文件请求
                case ENUM_MSG_TYPE_UPLOAD_FILE_REQUEST:
                {
                    char caFileName[32] = {'\0'};
                    qint64 fileSize = 0;
                    sscanf(pdu->caData,"%s %lld",caFileName,&fileSize);
                    char *pPath = new char[pdu->uiMsgLen];
                    memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
                    QString strPath = QString("%1/%2").arg(pPath).arg(caFileName);
                    delete []pPath;
                    pPath = NULL;
                    m_file.setFileName(strPath);
                    // 以只写的方式打开文件,若文件不存在,则会自动创建文件
                    if(m_file.open(QIODevice::WriteOnly))
                    {
                        m_bUpload = true;
                        m_iTotal = fileSize;
                        m_iRecved = 0;
                    }
                    break;
                }
                default:
                {
                    break;
                }
            }
            free(pdu);
            pdu = NULL;
        }
        else
        {
            PDU *respdu = NULL;
            respdu = mkPDU(0);
            respdu->uiMsgType = ENUM_MSG_TYPE_UPLOAD_FILE_RESPOND;
            QByteArray buff = readAll();
            m_file.write(buff);
            m_iRecved += buff.size();
            if(m_iTotal == m_iRecved)
            {
                m_file.close();
                m_bUpload = false;
                strcpy(respdu->caData,UPLOAD_FILE_OK);
                write((char*)respdu,respdu->uiPDULen);
                free(respdu);
                respdu = NULL;
            }
            else if(m_iTotal < m_iRecved)
            {
                m_file.close();
                m_bUpload = false;
                strcpy(respdu->caData,UPLOAD_FILE_FAILURED);
                write((char*)respdu,respdu->uiPDULen);
                free(respdu);
                respdu = NULL;
            }
        }
    }
    
  8. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来接收来自服务器的回复信息:

    // 查看上传文件回复
    case ENUM_MSG_TYPE_UPLOAD_FILE_RESPOND:
    {
    	QMessageBox::information(this,"上传文件",pdu->caData);
    	break;
    }
    
  9. 然后我们可以启动项目,上传一个文件测试一下:
    请添加图片描述

  10. 经过测试后发现,文件已经可以成功上传了,这也就意味着我们的上传文件功能已经成功实现了:
    请添加图片描述

10.3.2 删除文件

  1. 删除文件的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中声明如下函数:

    public slots:
        void delRegFile();
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下函数,用来关联槽函数:

    connect(m_pDelFilePB,SIGNAL(clicked(bool)),this,SLOT(delRegFile()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码,用来向服务器发送删除文件的请求:

    // 删除文件
    void Book::delRegFile()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        QListWidgetItem *pItem = m_pBookListW->currentItem();
        if(NULL == pItem)
        {
            QMessageBox::warning(this,"删除文件","请选择要删除的文件");
        }
        else
        {
            QString strDelName = pItem->text();
            PDU *pdu = mkPDU(strCurPath.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_DEL_FILE_REQUEST;
            strncpy(pdu->caData,strDelName.toStdString().c_str(),strDelName.size());
            memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码,用来处理来自客户端的删除文件请求:

    // 查看删除文件请求
    case ENUM_MSG_TYPE_DEL_FILE_REQUEST:
    {
        char caName[32] = {'\0'};
        strcpy(caName,pdu->caData);
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
        QString strPath = QString("%1/%2").arg(pPath).arg(caName);
        qDebug() << strPath;
        QFileInfo fileInfo(strPath);
        bool res = false;
        // 文件夹
        if(fileInfo.isDir())
        {
            res = false;
        }
        // 常规文件
        else if(fileInfo.isFile())
        {
            QDir dir;
            res = dir.remove(strPath);
        }
        PDU *respdu = NULL;
        if(res)
        {
            respdu = mkPDU(strlen(DEL_FILE_OK) + 1);
            respdu->uiMsgType = ENUM_MSG_TYPE_DEL_FILE_RESPOND;
            memcpy(respdu->caData,DEL_FILE_OK,strlen(DEL_FILE_OK));
        }
        else
        {
            respdu = mkPDU((strlen(DEL_FILE_FAILURED)) + 1);
            respdu->uiMsgType = ENUM_MSG_TYPE_DEL_FILE_RESPOND;
            memcpy(respdu->caData,DEL_FILE_FAILURED,strlen(DEL_FILE_FAILURED));
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码,用来处理来自服务器的删除文件回复:

    // 查看删除文件回复
    case ENUM_MSG_TYPE_DEL_FILE_RESPOND:
    {
    	QMessageBox::information(this,"删除文件",pdu->caData);
    	break;
    }
    
  7. 然后启动项目测试一下,我们可以选中某个文件,然后点击“删除文件”:
    请添加图片描述

  8. 当我们再次“刷新文件”后,就会发现选中的文件已经被我们删除了,这也就意味着删除文件功能我们已经成功实现了:
    请添加图片描述

10.3.3 下载文件

  1. 下载文件的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码:

    public:
    	void setDownloadStatus(bool status);
    	qint64 m_iTotal;    // 总的文件大小
    	qint64 m_iRecved;   // 已收到多少
    	bool getDownloadStatus();
    	QString getSaveFilePath();
    
    public slots:
    	void downloadFile();
    
    private:
    	QString m_strSaveFilePath;
    	bool m_bDownload;
    
  3. 然后在TcpClient项目中的“book.h”中的“Book::Book”函数中添加如下代码:

    m_bDownload = false;
    
    connect(m_DownLoadPB,SIGNAL(clicked(bool)),this,SLOT(downloadFile()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码:

    // 下载文件
    void Book::downloadFile()
    {
        QString strCurPath = TcpClient::getInstance().curPath();
        QListWidgetItem *pItem = m_pBookListW->currentItem();
        if(NULL == pItem)
        {
            QMessageBox::warning(this,"下载文件","请选择要下载的文件");
        }
        else
        {
            QString strSaveFilePath = QFileDialog::getSaveFileName();
            if(strSaveFilePath.isEmpty())
            {
                QMessageBox::warning(this,"下载文件","请指定要保存的位置");
                m_strSaveFilePath.clear();
            }
            else
            {
                m_strSaveFilePath = strSaveFilePath;
            }
            QString strCurPath = TcpClient::getInstance().curPath();
            PDU *pdu = mkPDU(strCurPath.size() + 1);
            pdu->uiMsgType = ENUM_MSG_TYPE_DOWNLOAD_FILE_REQUEST;
            QString strFileName = pItem->text();
            strcpy(pdu->caData,strFileName.toStdString().c_str());
            memcpy(pdu->caMsg,strCurPath.toStdString().c_str(),strCurPath.size());
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        }
    }
    
    // 设置下载状态
    void Book::setDownloadStatus(bool status)
    {
        m_bDownload = status;
    }
    
    // 返回下载状态
    bool Book::getDownloadStatus()
    {
        return m_bDownload;
    }
    
    // 返回保存文件的路径
    QString Book::getSaveFilePath()
    {
        return m_strSaveFilePath;
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.h”中添加如下代码:

    #include <QTimer>
    
    public slots:
        void sendFileToClient();
        
    private:
        QTimer *m_pTimer;
    
  6. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::MyTcpSocket”函数中添加如下代码:

    m_pTimer = new QTimer;
    connect(m_pTimer,SIGNAL(timeout()),this,SLOT(sendFileToClient()));
    
  7. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码:

    // 查看下载文件请求
    case ENUM_MSG_TYPE_DOWNLOAD_FILE_REQUEST:
    {
        char caFileName[32] = {'\0'};
        strcpy(caFileName, pdu->caData);
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath, pdu->caMsg, pdu->uiMsgLen);
        QString strPath = QString("%1/%2").arg(pPath).arg(caFileName);
        qDebug() << strPath;
        delete []pPath;
        pPath = NULL;
        QFileInfo fileInfo(strPath);
        qint64 fileSize = fileInfo.size();
        PDU *respdu = mkPDU(0);
        respdu->uiMsgType = ENUM_MSG_TYPE_DOWNLOAD_FILE_RESPOND;
        sprintf(respdu->caData, "%s %lld", caFileName, fileSize);
        write((char*)respdu, respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        m_file.setFileName(strPath);
        m_file.open(QIODevice::ReadOnly);
        m_pTimer->start(1000);
        break;
    }
    
  8. 然后在TcpServer项目中的“mytcpsocket.cpp”中添加如下代码:

    // 向客户端发送文件数据
    void MyTcpSocket::sendFileToClient()
    {
        char *pData = new char[4096];
        qint64 res = 0;
        while(true)
        {
            res = m_file.read(pData,4096);
            if(res > 0 && res <= 4096)
            {
                write(pData,res);
            }
            else if(0 == res)
            {
                m_file.close();
                break;
            }
            else if(0 > res)
            {
                qDebug() << "发送文件内容给客户端过程中失败";
                m_file.close();
                break;
            }
        }
        delete []pData;
        pData = NULL;
    }
    
  9. 然后在TcpClient项目中的“tcpclient.h”中添加如下代码:

    private:
        QFile m_file;
    
  10. 然后将TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数全部替换为如下代码:

    // 接收服务器信息
    void TcpClient::recvMsg()
    {
        if(!OpeWidget::getInstance().getBook()->getDownloadStatus())
        {
            qDebug() << m_tcpSockey.bytesAvailable();
            uint uiPDULen = 0;
            m_tcpSockey.read((char*)&uiPDULen,sizeof(uint));
            uint uiMsgLen = uiPDULen - sizeof(PDU);
            PDU *pdu = mkPDU(uiMsgLen);
            m_tcpSockey.read((char*)pdu+sizeof(uint),uiPDULen-sizeof(uint));
            switch (pdu->uiMsgType)
            {
                // 注册回复
                case ENUM_MSG_TYPE_REGIST_RESPOND:
                {
                    if(0 == strcmp(pdu->caData,REGIST_OK))
                    {
                        QMessageBox::information(this,"注册",REGIST_OK);
                    }
                    else if(0 == strcmp(pdu->caData,REGIST_FAILED))
                    {
                        QMessageBox::warning(this,"注册",REGIST_FAILED);
                    }
                    break;
                }
                // 登录回复
                case ENUM_MSG_TYPE_LOGIN_RESPOND:
                {
                    if(0 == strcmp(pdu->caData,LOGIN_OK))
                    {
                        m_strCurPath = QString("./%1").arg(m_strLoginName);
                        QMessageBox::information(this,"登录",LOGIN_OK);
                        OpeWidget::getInstance().show();
                        this->hide();
                    }
                    else if(0 == strcmp(pdu->caData,LOGIN_FAILED))
                    {
                        QMessageBox::warning(this,"登录",LOGIN_FAILED);
                    }
                    break;
                }
                // 查看在线用户回复
                case ENUM_MSG_TYPE_ALL_ONLINE_RESPOND:
                {
                    OpeWidget::getInstance().getFriend()->showAllOnlineUsr(pdu);
                    break;
                }
                // 查看查找用户回复
                case ENUM_MSG_TYPE_SEARCH_USR_RESPOND:
                {
                    if(0 == strcmp(SEARCH_USR_NO,pdu->caData))
                    {
                        QMessageBox::information(this, "搜索", QString("%1: not exist").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
                    }
                    else if(0 == strcmp(SEARCH_USR_ONLINE,pdu->caData))
                    {
                        QMessageBox::information(this,"搜索",QString("%1: online").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
                    }
                    else if(0 == strcmp(SEARCH_USR_OFFLINE,pdu->caData))
                    {
                        QMessageBox::information(this,"搜索",QString("%1: offline").arg(OpeWidget::getInstance().getFriend()->m_strSearchName));
                    }
                    break;
                }
                // 查看添加好友回复
                case ENUM_MSG_TYPE_ADD_FRIEND_REQUEST:
                {
                    char caName[32] = {'\0'};
                    strncpy(caName,pdu->caData+32,32);
                    qDebug() << "添加好友";
                    int res = QMessageBox::information(this,"添加好友",QString("%1 wang to add you as friend").arg(caName),QMessageBox::Yes,QMessageBox::No);
                    qDebug() << "结果为:" << res;
                    PDU *respdu = mkPDU(0);
                    memcpy(respdu->caData,pdu->caData,64);
                    if(res == QMessageBox::Yes)
                    {
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_AGGREE;
                    }
                    else
                    {
                        respdu->uiMsgType = ENUM_MSG_TYPE_ADD_FRIEND_REFUSE;
                    }
                    m_tcpSockey.write((char*)respdu,respdu->uiPDULen);
                    free(respdu);
                    respdu = NULL;
                    break;
                }
                case ENUM_MSG_TYPE_ADD_FRIEND_RESPOND:
                {
                    QMessageBox::information(this,"添加好友",pdu->caData);
                    break;
                }
                case ENUM_MSG_TYPE_ADD_FRIEND_AGGREE:
                {
                    char caPerName[32] = {'\0'};
                    memcpy(caPerName, pdu->caData, 32);
                    QMessageBox::information(this, "添加好友", QString("添加%1好友成功").arg(caPerName));
                    break;
                }
                case ENUM_MSG_TYPE_ADD_FRIEND_REFUSE:
                {
                    char caPerName[32] = {'\0'};
                    memcpy(caPerName, pdu->caData, 32);
                    QMessageBox::information(this, "添加好友", QString("添加%1好友失败").arg(caPerName));
                    break;
                }
                // 查看刷新在线好友列表回复
                case ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND:
                {
                    OpeWidget::getInstance().getFriend()->updateFriendList(pdu);
                    break;
                }
                // 查看删除好友回复
                case ENUM_MSG_TYPE_DELETE_FRIEND_REQUEST:
                {
                    char caName[32] = {'\0'};
                    memcpy(caName,pdu->caData,32);
                    QMessageBox::information(this,"删除好友",QString("%1删除你作为他的好友").arg(caName));
                    break;
                }
                case ENUM_MSG_TYPE_DELETE_FRIEND_RESPOND:
                {
                    QMessageBox::information(this,"删除好友","删除好友成功");
                    break;
                }
                // 查看私聊回复
                case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST:
                {
                    if(PrivateChat::getInstance().isHidden())
                    {
                        PrivateChat::getInstance().show();
                    }
                    char caSendName[32] = {'\0'};
                    memcpy(caSendName,pdu->caData,32);
                    QString strSendName = caSendName;
                    PrivateChat::getInstance().setChatName(caSendName);
                    PrivateChat::getInstance().updateMsg(pdu);
                    break;
                }
                // 查看群聊回复
                case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST:
                {
                    OpeWidget::getInstance().getFriend()->updateGroupMsg(pdu);
                    break;
                }
                // 查看创建文件夹回复
                case ENUM_MSG_TYPE_CREATE_DIR_RESPOND:
                {
                    QMessageBox::information(this,"创建文件",pdu->caData);
                    break;
                }
                // 查看查看所有文件回复
                case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND:
                {
                    OpeWidget::getInstance().getBook()->updateFileList(pdu);
                    QString strEnterDir = OpeWidget::getInstance().getBook()->enterDir();
                    if(!strEnterDir.isEmpty())
                    {
                        m_strCurPath = m_strCurPath + "/" + strEnterDir;
                    }
                    break;
                }
                // 查看删除文件夹回复
                case ENUM_MSG_TYPE_DEL_DIR_RESPOND:
                {
                    QMessageBox::information(this,"删除文件夹",pdu->caData);
                    break;
                }
                // 查看重命名文件回复
                case ENUM_MSG_TYPE_RENAME_FILE_RESPOND:
                {
                    QMessageBox::information(this,"重命名文件",pdu->caData);
                    break;
                }
                // 查看进入文件夹回复
                case ENUM_MSG_TYPE_ENTER_DIR_RESPOND:
                {
                    OpeWidget::getInstance().getBook()->clearEnterDir();
                    QMessageBox::information(this,"进入文件夹",pdu->caData);
                    break;
                }
                // 查看上传文件回复
                case ENUM_MSG_TYPE_UPLOAD_FILE_RESPOND:
                {
                    QMessageBox::information(this,"上传文件",pdu->caData);
                    break;
                }
                // 查看删除文件回复
                case ENUM_MSG_TYPE_DEL_FILE_RESPOND:
                {
                    QMessageBox::information(this,"删除文件",pdu->caData);
                    break;
                }
                // 查看下载文件回复
                case ENUM_MSG_TYPE_DOWNLOAD_FILE_RESPOND:
                {
                    qDebug() << pdu->caData;
                    char caFileName[32] = {'\0'};
                    sscanf(pdu->caData,"%s %lld",caFileName,&(OpeWidget::getInstance().getBook()->m_iTotal));
                    if(strlen(caFileName) > 0 && OpeWidget::getInstance().getBook()->m_iTotal > 0)
                    {
                        OpeWidget::getInstance().getBook()->setDownloadStatus(true);
                        m_file.setFileName(OpeWidget::getInstance().getBook()->getSaveFilePath());
                        if(!m_file.open(QIODevice::WriteOnly))
                        {
                            QMessageBox::warning(this,"下载文件","获得保存文件的路径失败");
                        }
                    }
                    break;
                }
                default:
                {
                    break;
                }
            }
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QByteArray buffer = m_tcpSockey.readAll();
            m_file.write(buffer);
            Book *pBook = OpeWidget::getInstance().getBook();
            pBook->m_iRecved += buffer.size();
            if (pBook->m_iTotal == pBook->m_iRecved)
            {
                m_file.close();
                pBook->m_iTotal = 0;
                pBook->m_iRecved = 0;
                pBook->setDownloadStatus(false);
                QMessageBox::information(this, "下载文件", "下载文件成功");
            }
            else if (pBook->m_iTotal < pBook->m_iRecved)
            {
                m_file.close();
                pBook->m_iTotal = 0;
                pBook->m_iRecved = 0;
                pBook->setDownloadStatus(false);
                QMessageBox::critical(this, "下载文件", "下载文件失败");
            }
        }
    }
    
  11. 然后我们可以启动两个项目测试一下:
    请添加图片描述

  12. 可以发现,已经下载成功了,这也就意味着我们的下载文件功能已经成功实现了:
    请添加图片描述

10.3.4 分享文件

  1. 分享文件的流程示意图如下所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“friend.h”中添加如下代码:

    public:
        QListWidget *getFriendList();
    
  3. 然后在TcpClient项目中的“friend.cpp”中添加如下代码:

    // 返回当前用户的好友列表
    QListWidget *Friend::getFriendList()
    {
        return m_pFriendListWidget;
    }
    
  4. 然后在TcpClient项目中添加新的类和其头文件:
    请添加图片描述

  5. 然后在TcpClient项目中的“sharefile.h”中添加如下代码:

    #include <QPushButton>
    #include <QHBoxLayout>
    #include <QVBoxLayout>
    #include <QButtonGroup>
    #include <QScrollArea>
    #include <QCheckBox>
    #include <QListWidget>
    
    public:
    	static ShareFile &getInstance();
    	void updateFriend(QListWidget *pFriendList);
    
    private:
    	QPushButton *m_pSelectAllPB;
    	QPushButton *m_pCancelSelectPB;
    	QPushButton *m_pOKPB;
    	QPushButton *m_pCancelPB;
    	QScrollArea *m_pSA;
    	QWidget *m_pFriendW;
    	QVBoxLayout *m_pFriendWVBL;
    	QButtonGroup *m_pButtonGroup;
    
  6. 然后将TcpClient项目中的“sharefile.cpp”中的全部内容替换为如下代码:

    #include "sharefile.h"
    
    ShareFile::ShareFile(QWidget *parent) : QWidget(parent)
    {
        m_pSelectAllPB = new QPushButton("全选");
        m_pCancelSelectPB = new QPushButton("取消选择");
        m_pOKPB = new QPushButton("确定");
        m_pCancelPB = new QPushButton("取消");
        m_pSA = new QScrollArea;
        m_pFriendW = new QWidget;
        m_pFriendWVBL = new QVBoxLayout(m_pFriendW);
        m_pButtonGroup = new QButtonGroup(m_pFriendW);
        m_pButtonGroup->setExclusive(false);
        QHBoxLayout *pTopHBL = new QHBoxLayout;
        pTopHBL->addWidget(m_pSelectAllPB);
        pTopHBL->addWidget(m_pCancelSelectPB);
        pTopHBL->addStretch();
        QHBoxLayout *pDownHBL = new QHBoxLayout;
        pDownHBL->addWidget(m_pOKPB);
        pDownHBL->addWidget(m_pCancelPB);
        QVBoxLayout *pMainVBL = new QVBoxLayout;
        pMainVBL->addLayout(pTopHBL);
        pMainVBL->addWidget(m_pSA);
        pMainVBL->addLayout(pDownHBL);
        setLayout(pMainVBL);
    }
    
    // 返回对象的单例
    ShareFile &ShareFile::getInstance()
    {
        static ShareFile instance;
        return instance;
    }
    
    // 更新当前用户的好友列表
    void ShareFile::updateFriend(QListWidget *pFriendList)
    {
        if(NULL == pFriendList)
        {
            return;
        }
        QAbstractButton* temp = NULL;
        QList<QAbstractButton*> preFriendList = m_pButtonGroup->buttons();
        for(int i = 0;i<preFriendList.size();i++)
        {
            temp = preFriendList[i];
            m_pFriendWVBL->removeWidget(temp);
            m_pButtonGroup->removeButton(temp);
            preFriendList.removeOne(temp);
            delete temp;
            temp = NULL;
        }
        QCheckBox *pCB = NULL;
        for(int i = 0;i<pFriendList->count();i++)
        {
            pCB = new QCheckBox(pFriendList->item(i)->text());
            m_pFriendWVBL->addWidget(pCB);
            m_pButtonGroup->addButton(pCB);
        }
        m_pSA->setWidget(m_pFriendW);
    }
    
  7. 然后在TcpClient项目中的“book.h”中添加如下代码:

    public:
    	QString getShareFileName();
    
    public slots:
    	void shareFile();
    
    private:
    	QString m_strShareFileName;
    
  8. 然后在TcpClient项目中的“book.cpp”中添加如下头文件:

    #include "opewidget.h"
    #include "sharefile.h"
    
  9. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码:

    connect(m_pShareFilePB,SIGNAL(clicked(bool)),this,SLOT(shareFile()));
    
  10. 然后在TcpClient项目中的“book.cpp”中添加如下代码:

    // 共享文件
    void Book::shareFile()
    {
        QListWidgetItem *pItem = m_pBookListW->currentItem();
        if(NULL == pItem)
        {
            QMessageBox::warning(this,"共享文件","请选择要共享的文件");
            return;
        }
        else
        {
            m_strShareFileName = pItem->text();
        }
        Friend *pFriend = OpeWidget::getInstance().getFriend();
        QListWidget *pFriendList = pFriend->getFriendList();
        ShareFile::getInstance().updateFriend(pFriendList);
        if(ShareFile::getInstance().isHidden())
        {
            ShareFile::getInstance().show();
        }
    }
    
    // 返回分享的文件名
    QString Book::getShareFileName()
    {
        return m_strShareFileName;
    }
    
  11. 然后在TcpClient项目中的“sharefile.h”中添加如下代码:

    public slots:
        void cancelSelect();
        void selectAll();
        void okShare();
        void cancelShare();
    
  12. 然后在TcpClient项目中的“sharefile.cpp”中添加如下头文件:

    #include "tcpclient.h"
    #include "opewidget.h"
    
  13. 然后在TcpClient项目中的“sharefile.cpp”中的“ShareFile::ShareFile”函数中添加如下代码:

    connect(m_pCancelSelectPB,SIGNAL(clicked(bool)),this,SLOT(cancelSelect()));
    connect(m_pSelectAllPB,SIGNAL(clicked(bool)),this,SLOT(selectAll()));
    connect(m_pOKPB,SIGNAL(clicked(bool)),this,SLOT(okShare()));
    connect(m_pCancelPB,SIGNAL(clicked(bool)),this,SLOT(cancelShare()));
    
  14. 然后在TcpClient项目中的“sharefile.cpp”中添加如下代码:

    // 取消选择
    void ShareFile::cancelSelect()
    {
        QList<QAbstractButton*> cbList =  m_pButtonGroup->buttons();
        for(int i = 0;i<cbList.size();i++)
        {
            if(cbList[i]->isChecked())
            {
                cbList[i]->setChecked(false);
            }
        }
    }
    
    // 全选
    void ShareFile::selectAll()
    {
        QList<QAbstractButton*> cbList =  m_pButtonGroup->buttons();
        for(int i = 0;i<cbList.size();i++)
        {
            if(!cbList[i]->isChecked())
            {
                cbList[i]->setChecked(true);
            }
        }
    }
    
    // 取消
    void ShareFile::cancelShare()
    {
        hide();
    }
    
    // 确定
    void ShareFile::okShare()
    {
        QString strName = TcpClient::getInstance().loginName();
        QString strCurPath = TcpClient::getInstance().curPath();
        QString strShareFileName = OpeWidget::getInstance().getBook()->getShareFileName();
        QString strPath = strCurPath + "/" + strShareFileName;
        QList<QAbstractButton*> cbList =  m_pButtonGroup->buttons();
        int num = 0;
        for(int i = 0;i<cbList.size();i++)
        {
            if(cbList[i]->isChecked())
            {
                num++;
            }
        }
        PDU *pdu = mkPDU(32 * num + strPath.size() + 1);
        pdu->uiMsgType = ENUM_MSG_TYPE_SHARE_FILE_REQUEST;
        sprintf(pdu->caData,"%s %d",strName.toStdString().c_str(),num);
        int j = 0;
        for(int i = 0;i<cbList.size();i++)
        {
            if(cbList[i]->isChecked())
            {
                memcpy((char*)(pdu->caMsg) + j * 32,cbList[i]->text().toStdString().c_str(),cbList[i]->text().size());
                j++;
            }
        }
        memcpy((char*)(pdu->caMsg) + num * 32,strPath.toStdString().c_str(),strPath.size());
        TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
        free(pdu);
        pdu = NULL;
    }
    
  15. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码:

    // 查看分享文件请求
    case ENUM_MSG_TYPE_SHARE_FILE_REQUEST:
    {
        char caSendName[32] = {'\0'};
        int num = 0;
        sscanf(pdu->caData,"%s%d",caSendName,&num);
        int size = num * 32;
        PDU *respdu = mkPDU(pdu->uiMsgLen - size);
        respdu->uiMsgType = ENUM_MSG_TYPE_SHARE_FILE_NOTE_REQUEST;
        strcpy(respdu->caData,caSendName);
        memcpy(respdu->caMsg,(char*)(pdu->caMsg) + size,pdu->uiMsgLen - size);
        char caRecvName[32] = {'\0'};
        for(int i = 0;i<num;i++)
        {
            memcpy(caRecvName,(char*)(pdu->caMsg) + i * 32,32);
            MyTcpServer::getInstance().resend(caRecvName,respdu);
        }
        free(respdu);
        respdu = NULL;
        respdu = mkPDU(0);
        respdu->uiMsgType = ENUM_MSG_TYPE_SHARE_FILE_RESPOND;
        strcpy(respdu->caData,"share file ok");
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  16. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码:

    // 查看分享文件回复
    case ENUM_MSG_TYPE_SHARE_FILE_RESPOND:
    {
        QMessageBox::information(this,"分享文件",pdu->caData);
        break;
    }
    
    // 查看分享文件通知请求
    case ENUM_MSG_TYPE_SHARE_FILE_NOTE_REQUEST:
    {
        char *pPath = new char[pdu->uiMsgLen];
        memcpy(pPath,pdu->caMsg,pdu->uiMsgLen);
        char *pos = strrchr(pPath,'/');
        if(NULL != pos)
        {
            pos++;
            QString strNote = QString("%1 share file->%2 \n Do you accept?").arg(pdu->caData).arg(pos);
            int res = QMessageBox::question(this,"分享文件",strNote);
            if(QMessageBox::Yes == res)
            {
                PDU *respdu = mkPDU(pdu->uiMsgLen);
                respdu->uiMsgType = ENUM_MSG_TYPE_SHARE_FILE_NOTE_RESPOND;
                memcpy(respdu->caMsg,pdu->caMsg,pdu->uiMsgLen);
                QString strName = TcpClient::getInstance().loginName();
                strcpy(respdu->caData,strName.toStdString().c_str());
                m_tcpSockey.write((char*)respdu,respdu->uiPDULen);
            }
        }
        break;
    }
    
  17. 然后在TcpServer项目中的“mytcpsocket.h”中添加如下代码:

    public:
        void copyDir(QString strSrcDir,QString strDestDir);
    
  18. 然后在TcpServer项目中的“mytcpsocket.cpp”中添加如下代码:

    // 拷贝文件夹
    void MyTcpSocket::copyDir(QString strSrcDir, QString strDestDir)
    {
        QDir dir;
        dir.mkdir(strDestDir);
        dir.setPath(strSrcDir);
        QFileInfoList fileInfoList = dir.entryInfoList();
        QString srcTemp;
        QString destTemp;
        for(int i = 0;i<fileInfoList.size();i++)
        {
            qDebug() << "filename:" << fileInfoList[i].fileName();
            if(fileInfoList[i].isFile())
            {
                srcTemp = strSrcDir + '/' + fileInfoList[i].fileName();
                destTemp = strDestDir + '/' + fileInfoList[i].fileName();
                QFile::copy(srcTemp,destTemp);
            }
            else if(fileInfoList[i].isDir())
            {
                if(QString(".") == fileInfoList[i].fileName() || QString("..") == fileInfoList[i].fileName())
                {
                    continue;
                }
                srcTemp = strSrcDir + '/' + fileInfoList[i].fileName();
                destTemp = strDestDir + '/' + fileInfoList[i].fileName();
                copyDir(srcTemp,destTemp);
            }
        }
    }
    
  19. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码:

    // 查看分享文件通知回复
    case ENUM_MSG_TYPE_SHARE_FILE_NOTE_RESPOND:
    {
        QString strRecvPath = QString("./%1").arg(pdu->caData);
        QString strShareFilePath = QString("%1").arg((char*)(pdu->caMsg));
        int index = strShareFilePath.lastIndexOf('/');
        QString strFileName = strShareFilePath.right(strShareFilePath.size() - index - 1);
        strRecvPath = strRecvPath + '/' + strFileName;
        QFileInfo fileInfo(strShareFilePath);
        if(fileInfo.isFile())
        {
            QFile::copy(strShareFilePath,strRecvPath);
        }
        else if(fileInfo.isDir())
        {
            copyDir(strShareFilePath,strRecvPath);
        }
        break;
    }
    
  20. 然后我们来测试一下,首先启动一个服务端和两个客户端,在两个客户端上点击“刷新好友”,要先刷新好友,才能对好友进行分享文件的操作:
    请添加图片描述

  21. 然后将“rose”用户下的“hello”文件夹进行分享:
    请添加图片描述

  22. 选择“lucy”后,点击“确定”:
    请添加图片描述

  23. 此时两个客户端都有相应的提示了,我们只需要点击接收端的“Yes”:
    请添加图片描述

  24. 然后点击“lucy”用户客户端的“刷新文件”,可以看到“rose”用户的“hello”文件夹及其文件夹中的内容已经成功拷贝到“lucy”用户的文件目录中了,这就说明我们的分享文件功能已经成功实现了:
    请添加图片描述

10.3.5 移动文件

  1. 移动文件的流程示意图如下图所示:
    请添加图片描述

  2. 首先在TcpClient项目中的“book.h”中添加如下代码:

    public slots:
    	void moveFile();
    	void selectDestDir();
        
    private:
    	QPushButton *m_pMoveFilePB;
    	QPushButton *m_pSelectDirPB;
    	QString m_strMoveFileName;
    	QString m_strMoveFilePath;
    	QString m_strDestDir;
    
  3. 然后在TcpClient项目中的“book.cpp”中的“Book::Book”函数中添加如下代码:

    m_pMoveFilePB = new QPushButton("移动文件");
    m_pSelectDirPB = new QPushButton("目标目录");
    m_pSelectDirPB->setEnabled(false);
    pFileVBL->addWidget(m_pMoveFilePB);
    pFileVBL->addWidget(m_pSelectDirPB);
    connect(m_pMoveFilePB,SIGNAL(clicked(bool)),this,SLOT(moveFile()));
    connect(m_pSelectDirPB,SIGNAL(clicked(bool)),this,SLOT(selectDestDir()));
    
  4. 然后在TcpClient项目中的“book.cpp”中添加如下代码:

    // 移动文件
    void Book::moveFile()
    {
        QListWidgetItem *pCurItem = m_pBookListW->currentItem();
        if(NULL != pCurItem)
        {
            m_strMoveFileName = pCurItem->text();
            QString strCurPath = TcpClient::getInstance().curPath();
            m_strMoveFilePath = strCurPath + '/' + m_strMoveFileName;
            m_pSelectDirPB->setEnabled(true);
        }
        else
        {
            QMessageBox::warning(this,"移动文件","请选择要移动的文件");
        }
    }
    
    // 选择移动文件的目的地
    void Book::selectDestDir()
    {
        QListWidgetItem *pCurItem = m_pBookListW->currentItem();
        if(NULL != pCurItem)
        {
            QString strDestDir = pCurItem->text();
            QString strCurPath = TcpClient::getInstance().curPath();
            m_strDestDir = strCurPath + '/' +strDestDir;
            int srcLen = m_strMoveFilePath.size();
            int destLen = m_strDestDir.size();
            PDU *pdu = mkPDU(srcLen + destLen + 2);
            pdu->uiMsgType = ENUM_MSG_TYPE_MOVE_FILE_REQUEST;
            sprintf(pdu->caData,"%d %d %s",srcLen,destLen,m_strMoveFileName.toStdString().c_str());
            memcpy(pdu->caMsg,m_strMoveFilePath.toStdString().c_str(),srcLen);
            memcpy((char*)(pdu->caMsg) + srcLen + 1,m_strDestDir.toStdString().c_str(),destLen);
            TcpClient::getInstance().getTcpSocket().write((char*)pdu,pdu->uiPDULen);
            free(pdu);
            pdu = NULL;
        }
        else
        {
            QMessageBox::warning(this,"移动文件","请选择要移动的文件");
        }
        m_pSelectDirPB->setEnabled(false);
    }
    
  5. 然后在TcpServer项目中的“mytcpsocket.cpp”中的“MyTcpSocket::recvMsg”函数中添加如下代码:

    // 查看移动文件请求
    case ENUM_MSG_TYPE_MOVE_FILE_REQUEST:
    {
        char caFileName[32] = {'\0'};
        int srcLen = 0;
        int destLen = 0;
        sscanf(pdu->caData,"%d%d%s",&srcLen,&destLen,caFileName);
        char *pSrcPath = new char[srcLen + 1];
        char *pDestPath = new char[destLen + 1 + 32];
        memset(pSrcPath,'\0',srcLen + 1);
        memset(pDestPath,'\0',destLen + 1 + 32);
        memcpy(pSrcPath,pdu->caMsg,srcLen);
        memcpy(pDestPath,(char*)(pdu->caMsg) + (srcLen + 1),destLen);
        PDU *respdu = mkPDU(0);
        respdu->uiMsgType = ENUM_MSG_TYPE_MOVE_FILE_RESPOND;
        QFileInfo fileInfo(pDestPath);
        if(fileInfo.isDir())
        {
            strcat(pDestPath,"/");
            strcat(pDestPath,caFileName);
            bool res = QFile::rename(pSrcPath,pDestPath);
            if(res)
            {
                strcpy(respdu->caData,MOVE_FILE_OK);
            }
            else
            {
                strcpy(respdu->caData,COMMON_ERR);
            }
        }
        else if(fileInfo.isFile())
        {
            strcpy(respdu->caData,MOVE_FILE_FAILURED);
        }
        write((char*)respdu,respdu->uiPDULen);
        free(respdu);
        respdu = NULL;
        break;
    }
    
  6. 然后在TcpClient项目中的“tcpclient.cpp”中的“TcpClient::recvMsg”函数中添加如下代码:

    // 查看移动文件回复
    case ENUM_MSG_TYPE_MOVE_FILE_RESPOND:
    {
        QMessageBox::information(this,"移动文件",pdu->caData);
        break;
    }
    
  7. 然后分别启动服务端和客户端进行测试,首先选择要移动的文件,然后点击“移动文件”:
    请添加图片描述

  8. 然后选择要移动文件的目标目录后,点击“目标目录”:
    请添加图片描述

  9. 当我们进入移动文件的目标目录查看后,发现文件已经移动到目标目录了,这就意味着我们的移动文件功能已经成功实现了:
    请添加图片描述


总结

  看到这段话的读者应该已经将这个项目完成了,本项目花费了我大量的心血,由于篇幅受限(由于字数过多,现在写博客的网页已经卡死了),我就不再赘述了,希望读者都可以学到知识,加油!

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IronmanJay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值