C++-based network disk system project development tutorial

Project resource download

  1. C++-based network disk system project source code CSDN download address
  2. GitHub download address of the source code of the C++-based network disk system project

Project Description

  This project is developed based on C++. The whole project adopts C/S architecture, uses Sqlite3 database to store user information, local disk to store user files, and uses Socket for data transmission between client and server. Completed almost all functions about users in the network disk, including: user registration, user login, user exit, view online friends, search for friends, add friends, refresh online friends, delete friends, private chat, group chat, etc.; about files All functions of folders and files are almost completed, including: create folder, view all files, delete folder, rename folder, enter folder, return to previous level, upload file, delete file, download file, share file , move files, and more.


Project development software environment

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

Project development hardware environment

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

Article directory


前言

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


零、项目演示

0.1 用户注册

  1. 当我们启动两个项目后,输入用户名和密码最后点击“注册”,发现提示我们注册成功了:
    Please add a picture description

  2. 然后我们来到数据库查看数据,发现新的注册信息已经保存到数据库中了:
    Please add a picture description

  3. 如果此时我们还是以同样的用户名去注册,就会提示我们注册失败,因为数据库中已经有同名的用户名了,而我们设置用户名字段唯一,所以注册会失败:
    Please add a picture description

0.2 用户登录

  1. 启动两个项目,当我们使用已经在数据库中存在的用户,并且此用户的“online”字段为0的时候,是可以成功登陆的:
    Please add a picture description

  2. When we try to log in with a user that does not exist in the database, we cannot log in successfully:
    Please add a picture description

0.3 User Logout

  1. Start two projects, log in with a certain user, and then query the database, and find that the "online" field of this user is 1, indicating that you have successfully logged in:
    Please add a picture description

  2. Then click the close button to mimic the user exit behavior:
    Please add a picture description

  3. At this time, if you query the database again, you will find that the online field of the corresponding user has changed to 0, indicating that the user has successfully logged out:
    Please add a picture description

0.4 View online friends

  1. Start multiple clients at the same time, and then log in separately, click the "Show Online Users" button, you can find that the usernames of online users have been successfully displayed:
    Please add a picture description

0.5 search friends

  1. After starting the two projects, when we searched for the user "abc", we found that its username and status can be successfully displayed:
    Please add a picture description

0.6 Add friends

  1. After opening two clients and one server, it is found that the added user can pop up a window normally:
    Please add a picture description

  2. When we clicked "Yes", we found that the friend has been successfully added, and the friend relationship of two users has been saved in the database:
    Please add a picture description

0.7 Refresh online friends

  1. After starting the server and client respectively and logging in, we click the "Refresh Friends" button and find that the latest online friend list can be displayed at this time:
    Please add a picture description

0.8 Delete friends

  1. Start the server and the two clients with friendship respectively, and click the "Delete Friends" button to successfully delete the friendship between the two users, and a prompt can be displayed:
    Please add a picture description

0.9 private chat

  1. Start the server and two clients for testing, and find that chat messages can be delivered normally:
    Please add a picture description

0.10 Group Chat

  1. After starting the project, it is found that group chat messages can be sent normally:
    Please add a picture description

0.11 Create a folder

  1. You can successfully create a new folder under the directory with the same name as the user
    Please add a picture description

0.12 View all files

  1. When we click "Refresh Files" on the "Books" interface, all the files in this folder can be displayed:
    Please add a picture description

0.13 Delete folder

  1. When we select the folder and click "Delete Folder", a prompt will pop up that the deletion is successful, and then when we click "Refresh File", we will find that the original deleted folder no longer exists:
    Please add a picture description

0.14 Rename folder

  1. Select a file to be renamed, and enter the renamed file name:
    Please add a picture description

  2. When we click "Refresh File", we can find that the file has been successfully renamed:
    Please add a picture description

0.15 Enter folder

  1. When we double-click a folder, we can enter this folder:
    Please add a picture description

0.16 Return to previous level

  1. When we click "Back", we can return to the main directory from the subdirectory:
    Please add a picture description

0.17 Upload files

  1. Select a local file to upload:
    Please add a picture description

  2. It can be found that the file has been successfully uploaded:
    Please add a picture description

0.18 Delete files

  1. We can select a file and click "Delete File":
    Please add a picture description

  2. When we "refresh the file" again, we will find that the selected file has been deleted by us:
    Please add a picture description

0.19 Download file

  1. Select a file on the server to download:
    Please add a picture description

  2. It can be found that the download has been successful:
    Please add a picture description

0.20 Share files

  1. First start a server and two clients, click "Refresh friends" on the two clients, you must refresh friends before you can share files with friends:
    Please add a picture description

  2. Then share the "hello" folder under the "rose" user:
    Please add a picture description

  3. After selecting "lucy", click "OK":
    Please add a picture description

  4. At this point, both clients have corresponding prompts, we only need to click "Yes" on the receiving end:
    Please add a picture description

  5. Then click "Refresh File" on the "lucy" user client, and you can see that the "hello" folder of the "rose" user and its contents have been successfully copied to the file directory of the "lucy" user:
    Please add a picture description

0.21 Move files

  1. First select the files you want to move, then click "Move Files":
    Please add a picture description

  2. Then select the target directory to move the files to, and click "Target Directory":
    Please add a picture description

  3. When we enter the target directory of the moved file to check, we find that the file has been moved to the target directory:
    Please add a picture description

1. Project structure

  The whole project adopts C/S architecture, uses Sqlite3 database to store user information, local disk to store user files, and uses Socket for data transmission between client and server. The overall structure of the project is not complicated. Follow me step by step, basically There is no problem. The overall structure of the project is as follows:
Please add a picture description

2. Download, install and use the QT development framework

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

2.1 QT开发框架下载

  1. 进入QT开发框架的下载地址
    Please add a picture description

  2. 点击“archive/”:
    Please add a picture description

  3. 点击“qt/”:
    Please add a picture description

  4. 点击“5.14/”,当然,读者也可以选择自己适合的版本,不过建议各位读者选择和我一样的版本:
    Please add a picture description

  5. 点击“5.14.2/”:
    Please add a picture description

  6. 点击下图红框所示内容:
    Please add a picture description

  7. 在弹出的窗口中选择“另存为”:
    Please add a picture description

  8. 随便选择一个位置后(建议下载到桌面),点击“保存(S)”:
    Please add a picture description

2.2 QT开发框架安装

  1. 在正式安装之前先去QT开发框架官网进行QT开发框架账号的注册,这里一定要先注册QT开发框架账号,否则会出错:
    Please add a picture description

  2. 输入和选择相关信息后,点击“Create Qt Account”:
    Please add a picture description

  3. 然后来到您注册时填写的邮箱,点击收到的QT开发框架注册邮件链接:
    Please add a picture description

  4. 进入邮件详情界面后,点击下图红框的链接:
    Please add a picture description

  5. 点击“继续访问”:
    Please add a picture description

  6. 输入和选择相关信息后,点击“Confirm”:
    Please add a picture description

  7. 然后来到之前注册QT开发框架账号的界面点击“Continue”:
    Please add a picture description

  8. 注册完后,双击打开下载好的QT开发框架安装包:
    Please add a picture description

  9. 点击“Next”:
    Please add a picture description

  10. 输入刚才注册好的账号密码后点击“Next”:
    Please add a picture description

  11. 在红框处打勾后点击“下一步(N)”:
    Please add a picture description

  12. 点击“下一步(N)”:
    Please add a picture description

  13. Select your installation folder and click "Next (N)" after ticking the red box:
    Please add a picture description

  14. By default, select a few components in the red box, and the rest of the components can be supplemented and installed if there is a need in the future:
    Please add a picture description

  15. Select the content in the red box and click "Next (N)":
    Please add a picture description

  16. Click "Next (N)":
    Please add a picture description

  17. Click "Install (I)":
    Please add a picture description

  18. Wait for the installation to complete:
    Please add a picture description

  19. Click "Finish (F)":
    Please add a picture description

  20. At this point we have come to the main interface of the QT development framework:
    Please add a picture description

2.3 Use of QT development framework

  1. Click "Projects" and then click "New":
    Please add a picture description

  2. Select the content in the red box and click "Choose...":
    Please add a picture description

  3. After entering the project name and project path, click "Next (N)". In addition, it should be noted that Chinese characters and spaces cannot be included in the project path:
    Please add a picture description

  4. Click "Next (N)":
    Please add a picture description

  5. Click "Next (N)":
    Please add a picture description

  6. Click "Next (N)":
    Please add a picture description

  7. Select according to the appearance in the red box and click "Next (N)":
    Please add a picture description

  8. Click "Finish (F)":
    Please add a picture description

  9. Click on the red box:
    Please add a picture description

  10. It can be seen that your first window created based on the QT development framework has been successfully displayed. So far, it means that the QT development framework has been successfully installed:
    Please add a picture description

3. SQLite3 database construction

  Our project uses SQLite3 as the database, because our project does not require a very complicated table structure, and SQLite3 configuration and use are also very convenient, so we use SQLite3 as the project database. Readers, please be sure to follow me to install the SQLite3 database, otherwise the subsequent projects will not be able to advance. As long as you follow my steps below, you will be able to install the SQLite3 database successfully. The main content of this section includes: SQLite3 database installation, SQLite3 database design, SQLite3 database test. These contents are also the last step of our project preparation. After the installation of SQLite3 is completed, we can officially start writing our network disk system project.

3.1 SQLite3 database installation

  1. After entering the official website of SQLite3 , click "Download":
    Please add a picture description

  2. After entering the next interface, slide down to find "Precompiled Binaries for Windows":
    Please add a picture description

  3. Note here, if your computer is 32-bit, download the two files in the blue box, if your computer is 64-bit, download the two files in the purple box. If your computer is 64-bit, you can also directly use the compressed package in the resources introduced at the beginning of this blog:
    Please add a picture description

  4. The compressed package after downloading is shown in the figure below:
    Please add a picture description

  5. Then create a new folder named "SQLite3" in your favorite directory:
    Please add a picture description

  6. Then unzip all the files in the compressed package you just downloaded to the folder you just created called "SQLite3":
    Please add a picture description

  7. Then right click on "This PC" and select "Properties":
    Please add a picture description

  8. Select "Advanced system settings":
    Please add a picture description

  9. Select "Environment Variables":
    Please add a picture description

  10. Double-click "Path" in "System Variables":
    Please add a picture description

  11. Select New:
    Please add a picture description

  12. Add the previously created path to it:
    Please add a picture description

  13. Click "OK":
    Please add a picture description

  14. Click "OK":
    Please add a picture description

  15. Click "OK":
    Please add a picture description

  16. Then restart the system, the settings just take effect after restarting the system

  17. After restarting the system, use the "Win+R" key combination on the keyboard to open the "Run" window, enter cmd, and click OK:
    Please add a picture description

  18. After the cmd window appears, enter "sqlite3" and press Enter, and the following content appears, indicating that the SQLite3 database is installed successfully:
    Please add a picture description

  19. Then enter ".quit" and press Enter to exit the SQLite3 database
    Please add a picture description

3.2 SQLite3 database design

  1. At present, we need two tables, namely the user information table and the user friend table. The user information table stores the id, name and login password of the user who uses the network disk system, and the user friend table stores the user's id and the user's friend id. id is used as a foreign key, so that other friends of the current user can be found

    • User information form:

      field type Restrictions other
      id integer primary key automatic growth
      name varchar(32) not null
      pwd varchar(32) not null
    • user friend list

      field type Restrictions other
      id integer primary key foreign key
      friendId integer primary key foreign key
  2. After designing the data table, we will create a database according to the design requirements. First, create a new folder named "Database" in the SQLite3 installation directory:
    Please add a picture description

  3. Once in the "Database" folder, right-click "Open in Terminal":
    Please add a picture description

  4. Then type sqlite3 ./cloud.dband press enter:
    Please add a picture description

  5. First create a user information table, enter the following content in the terminal window and press Enter:

    create table usrInfo(
        id integer primary key autoincrement,
        name varchar(32),
        pwd varchar(32)
    );
    
  6. Then create a user friend table, enter the following content in the terminal window and press Enter:

    create table friendInfo(
    	id integer not null,
        friendId integer not null,
        primary key(id,friendId)
    );
    
  7. Then enter .tablesand find that two tables have been successfully created:
    Please add a picture description

3.3 SQLite3 database test

  1. Let's insert a few pieces of data to test, we continue to enter the following content in the terminal window just now and press Enter:

    insert into usrInfo(name,pwd) values('jack','jack'),('rose','rose'),('luck','luck');
    
  2. Then continue to enter the following content in the terminal window just now to check whether our data is successfully inserted:

    select * from usrInfo;
    
  3. You can see that our data has been successfully inserted:
    Please add a picture description

Fourth, client server construction

  We have completed the preparatory work for the entire project, and now we will officially start writing our network disk system project. The first step in writing the entire project is to build the basic framework of the project. Although this step is relatively simple, there are many details, so Readers are also invited to follow me step by step. The main contents of this section include: setting configuration files, client implementation, and server implementation. After completing these steps, our project skeleton is basically completed.

4.1 Setting up configuration files

  1. Click "Projects" and then click "New":
    Please add a picture description

  2. Select the content in the red box in turn, and finally click "Choose...":
    Please add a picture description

  3. Enter the project name "TcpClient" and select your project path, then click "Next (N)":
    Please add a picture description

  4. Click "Next (N)":
    Please add a picture description

  5. Follow the configuration in the red box, and then click "Next (N)":
    Please add a picture description

  6. Click "Next (N)":
    Please add a picture description

  7. After selecting the content in the red box, click "Next (N)":
    Please add a picture description

  8. Click "Finish (F)":
    Please add a picture description

  9. Click the icon inside the red box:
    Please add a picture description

  10. It can be found that our project has been successfully created:
    Please add a picture description

  11. Right-click the project and click "Find in This Directory...":
    Please add a picture description

  12. Copy this path:
    Please add a picture description

  13. Go to the folder corresponding to the path just copied:
    Please add a picture description

  14. "Create" a "text document" in this folder:
    Please add a picture description

  15. Rename it to "client.config":
    Please add a picture description

  16. Click "Yes (Y)":
    Please add a picture description

  17. Open this configuration file with Notepad or Sublime Text:
    Please add a picture description

  18. Enter the following in this configuration file:
    Please add a picture description

  19. Right-click on the project and click "Add New...":
    Please add a picture description

  20. Click "Qt" and double-click "Qt Resource File":
    Please add a picture description

  21. Enter the name "config" and click "Next (N)":
    Please add a picture description

  22. Click "Finish (F)":
    Please add a picture description

  23. After clicking the red box, enter "/" in the "prefix" column, and then press "Ctrl+S" to save. At this time, we will add the configuration file to the project. All configuration files are added to the project according to this method, so I won't repeat them later:
    Please add a picture description

  24. First click "Add Files", then select the configuration file you just created, then click "Open (O)", and finally press "Ctrl+S" to save:
    Please add a picture description

  25. Replace all the content in tcpclient.h with the following content:

    #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. Replace all the content in tcpclient.cpp with the following content:

    #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. Then click the green triangle in the red box to run the program. If there is no special instruction thereafter, you need to click the green triangle in the red box to run the program. I won’t repeat it in the following article:
    Please add a picture description

  28. It can be found that we have successfully obtained the IP address and port number in the configuration file, and we have completed the configuration file settings:
    Please add a picture description

4.2 Client implementation

  1. The flow of the Tcp client connecting to the server is shown in the figure below:
    Please add a picture description

  2. Since we need to connect to the server, we need to use the network of the QT development framework to become a module, then we need to add "network" in the red box in the TcpClient.pro file (note that there is a space between the previous "gui"), and finally Press "Ctrl+S" to save:
    Please add a picture description

  3. In order to establish a connection with the server, we first add the following three codes in tcpclient.h:
    Please add a picture description

  4. Then add the following three codes to tcpclient.cpp:
    Please add a picture description

  5. Finally, we click the small hammer in the red box below to compile:
    Please add a picture description

  6. It can be found that there are no errors, indicating that there is no problem with our code, but at this time we cannot perform any output, nor can we connect the client to the server, because we have only implemented the basic framework of the client, and the server has not yet been implemented, so Next we will implement the basic framework of the server:
    Please add a picture description

4.3 Server implementation

  1. Click "New Project..." in the blank section:
    Please add a picture description

  2. Click the positions shown in the figure below in turn, and finally click "Choose...":
    Please add a picture description

  3. Enter the project name and project save directory, and then click "Next (N)":
    Please add a picture description

  4. Click "Next (N)":
    Please add a picture description

  5. Configure according to the red box, and then click "Next (N)":
    Please add a picture description

  6. Click "Next (N)":
    Please add a picture description

  7. Select the red box and click "Next (N)":
    Please add a picture description

  8. Click "Finish (F)":
    Please add a picture description

  9. Copy the previously created configuration file to the new project directory:
    Please add a picture description

  10. Rename the copied configuration file to "server.config":
    Please add a picture description

  11. According to the same method in the TcpClient project, load the "server.config" configuration file into the TcpServer project:
    Please add a picture description

  12. The schematic diagram of the server listening to the client IP address and port is as follows:
    Please add a picture description

  13. Right click on the TcpServer project and click "Add New...":
    Please add a picture description

  14. Select in the following order, and finally click "Choose...":
    Please add a picture description

  15. Input according to the content in the red box, and finally click "Next (N)":
    Please add a picture description

  16. Click "Finish (F)":
    Please add a picture description

  17. Add "network" in the red box in "TcpServer.pro" (note that there is a space between the previous gui), and finally press "Ctrl + S" to save:
    Please add a picture description

  18. Replace all the contents of "mytcpserver.h" with the following:

    #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. Replace all the contents of "mytcpserver.cpp" with the following:

    #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. Replace all the contents of "tcpserver.h" with the following:

    #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. Replace all the contents of "tcpserver.cpp" with the following:

    #include "mytcpserver.h"
    #include <QDebug>
    
    MyTcpServer::MyTcpServer()
    {
          
          
    
    }
    
    MyTcpServer &MyTcpServer::getInstance()
    {
          
          
        static MyTcpServer instance;
        return instance;
    }
    
    void MyTcpServer::incomingConnection(qintptr handle)
    {
          
          
        qDebug() << "new client connected";
    }
    
  22. Then let's test the connection between the client and the server. First, we start the TcpServer server project, right-click on the project and "Run":
    Please add a picture description

  23. Then we start the TcpClient client project, and right-click on the project and "Run" directly, so if we need to start multiple projects at the same time, we only need to right-click on each project in order, and then click "Run". No longer:
    Please add a picture description

  24. It can be seen that the client displays the words that the connection is successful, and the server also displays the words that the connection is successful, which means that we have now established a connection between the client and the server:
    Please add a picture description

5. Communication protocol design

  After we have established the basic message transmission path between the client and the server, we need to transmit messages on this data path, but we cannot transmit them at will. We should make the data we transmit follow certain rules, so we will come here in this section Design a communication protocol so that the client and server can transmit messages according to the communication protocol we designed. The main content of this section includes: elastic structure design, communication protocol design, data sending and receiving test. After these steps are completed, we can transfer messages between the client and the server.

5.1 Design of elastic structure

  1. First briefly introduce the elastic structure. The difference between the elastic structure and the normal structure is that the array in the elastic structure does not declare the size of the array space. When necessary, we assign the size of the array space. Do this The advantage is to save storage space, and the efficiency is high, because when we transmit information between the client and the server, we are not sure about the amount of data to be transmitted, so we should use elastic structures for data transmission. We need to understand that the communication protocol between the client and the server includes:

    • total message size
    • message type
    • actual message size
    • actual news

    The entire communication protocol should be designed using an elastic structure. When designing the elastic structure, the actual message should be an array that does not have an array space size. The following are the specific steps for designing the elastic structure of the communication protocol.

5.2 Communication protocol design

  1. Right-click on the TcpClient project and "Add New...":
    Please add a picture description

  2. Select in the following order, and finally click "Choose...":
    Please add a picture description

  3. Enter the name "protocol.h" and click "Next (N)":
    Please add a picture description

  4. Click "Finish (F)", this method of creating a new file will not be described in detail later, if you need to create a new file, just follow this step:
    Please add a picture description

  5. Add the following code to "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. After the design of the flexible structure is completed, we need to initialize the flexible structure of the communication protocol. We need to create a new file "protocol.cpp" and add the following code to it:

    #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 Data sending and receiving test

  1. Now that we have designed the communication protocol, we should test the data sending and receiving between the client and the server. In order to test, we should simply design an interface, so we first double-click the red box in the figure below:
    Please add a picture description

  2. Drag the content of the two red boxes to the design panel, try to keep the size and position consistent with mine, of course, readers can also design their own, and then double-click "Push Button" to change the name to "Send", and then change the purple box part Change the name to "send_pd", and the font size of the blue box can be modified by yourself:
    Please add a picture description

  3. Then drag the contents of the red frame to the design panel, and you can also design the font size yourself:
    Please add a picture description

  4. Then design the red frame part as a horizontal layout, and the blue frame part as a vertical layout. At this time, the basic interface is designed. If you need to design the interface later, except for the places that need special attention, the other methods are the same, so I won’t repeat them here. :
    Please add a picture description

  5. Right click on the "Send" button and click "Go to slot...":
    Please add a picture description

  6. Select the content in the red box and click "OK":
    Please add a picture description

  7. Then the following code will be automatically generated in "tcpclient.h" and "tcpclient.cpp". This automatically generated code will not be described in detail later. In addition, to develop a good habit of coding, as long as there is a little modification to the code, it must be timely save:
    Please add a picture description

  8. After generating these two codes, let's compile the project first, and click on the red box in the figure below. If you need to compile it later, you will not repeat it:
    Please add a picture description

  9. Then import the header file in "tcpclient.h":

    #include "protocol.h"
    
  10. Then replace the newly generated function "TcpClient::on_send_pd_clicked" in "tcpclient.cpp" with the following code, then we can send data from the client:

    // 发送点击事件
    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. Then we need to create a new "MyTcpSocket" class in the TcpServer project, the purpose is to receive messages from the client:
    Please add a picture description

  12. Then replace the entire contents of "mytcpsocket.h" with the following:

    #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. Copy the two files in the red box below in the TcpClient project to the TcpServer project:
    Please add a picture description

  14. Then right-click on the TcpServer project and "Add Existing File...":
    Please add a picture description

  15. Add the two files just copied to the project:
    Please add a picture description

  16. Replace the entire content of "mytcpsocket.cpp" with the following:

    #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. Add the following code in "mytcpserver.h":
    Please add a picture description

  18. Then replace all the "MyTcpServer::incomingConnection" functions in "mytcpserver.cpp" with the following:

    // 接收客户端的请求
    void MyTcpServer::incomingConnection(qintptr handle)
    {
          
          
        qDebug() << "new client connected";
        MyTcpSocket *pTcpSocket = new MyTcpSocket;
        pTcpSocket->setSocketDescriptor(handle);
        m_tcpSocketList.append(pTcpSocket);
    }
    
  19. At this time, we first start the "TcpServer" project, then start the "TcpClient" project, enter "HelloWorld" in the input box of the graphical interface generated by the "TcpClient" project, and finally click Send:
    Please add a picture description

  20. It can be found that the server has received the "Hello World" sent by the client, and at this point we have established a data transmission channel between the client and the server:
    Please add a picture description

6. Realization of basic user functions

  In this section, we mainly implement some basic user functions, which are also the most basic functions of a software. These basic user functions include: database connection initialization, user registration function realization, user login function realization, and user exit function realization. When we realize these functions, it also marks that our software prototype has been built.

6.1 Database connection initialization

  1. First enter the TcpServer project directory in CMD, we need to create a new database in this directory, because we need to use the server to save the relevant information of the user's login, registration and exit:
    Please add a picture description

  2. Create a database named "cloud.db". After entering each statement in CMD, you need to press "Enter" to execute it. I won't repeat it later:
    Please add a picture description

  3. Then continue to enter the following content to create a "user information table":

    create table usrInfo(id integer primary key autoincrement,
                         name varchar(32) unique,
                         pwd varchar(32),
                         online integer default 0);
    
  4. Then add test data to the "User Information Table":

    insert into usrInfo(name,pwd) values('jack','jacl'),
    								 ('rose','rose'),
    								 ('lucy','lucy');
    
  5. Check that the data has been successfully added to the database:
    Please add a picture description

  6. In the same way, enter the following content to create a "friend table":

    create table friend(id integer,
                        friendId integer,
                        primary key(id,friendId));
    
  7. After creating these two tables, we can use .tablesthe statement to view the two tables we created:
    Please add a picture description

  8. Then create a new class named "OpeDB" in the TcpServer project to operate the database:
    Please add a picture description

  9. Add "sql" in the red box of the "TcpServer.pro" file (note a space between the previous "netwrok"):
    Please add a picture description

  10. Replace the entire contents of "opedb.h" with the following:

    #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. Replace the entire contents of "opedb.cpp" with the following:

    #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. Replace the entire contents of "main.cpp" with the following:

    #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 Realization of user registration function

  1. Add the following code to "protocol.h" of the TcpClient project to indicate the type of message we transmit:

    // 消息类型枚举
    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. Then open the "tcpclient.ui" of the TcpClient project, and configure it according to the following layout:
    Please add a picture description

  3. Change the name of the "Username Label" to "name_lab":
    Please add a picture description

  4. Change the name of the "Password Label" to "pwd_lab":
    Please add a picture description

  5. Then follow the same method to:

    • The name of the "username input box" is changed to: name_le
    • The name of the "password input box" is changed to: pwd_le
    • Change the name of the "login button" to: login_pb
    • The name of the "Registration Button" was changed to: regist_pb
    • Change the name of the "logout button" to: cancel_pb
  6. Change the "echoMode" of the "Password Input Box" to "Password":
    Please add a picture description

  7. Add the signal slot of the "login button", and the system will automatically generate the corresponding function for us:
    Please add a picture description

  8. Similarly, add click event signal slots for the "Registration Button" and "Logout Button", and the system will automatically generate corresponding functions for us:
    Please add a picture description

  9. Then comment out the function of the tcpclient.cpp test in the previous TcpClient project, and add two lines of code in the blue box:
    Please add a picture description

  10. Then comment out the test function declaration (blue box part) in tcpclient.h in the TcpClient project:
    Please add a picture description

  11. Then add the same message type enumeration as "protocol.h" in the TcpClient project to "protocol.h" in the TcpServer project:

    // 消息类型枚举
    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. Then replace all the content of the "TcpClient::on_regist_pb_clicked" function in "tcpclient.cpp" in the TcpClient project with the following content, and then the transmission of the user registration message to the server is completed:

    // 注册事件
    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. Next, let's test to see if the server can successfully obtain the client's username and password. First, replace all the "MyTcpSocket::recvMsg" functions in mytcpsocket.cpp in the TcpServer project with the following:

    // 接收来自客户端的消息
    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. Then start the two projects separately, enter the user name and password, and click "Register", and find that the server has successfully obtained the user name and password of the client:
    Please add a picture description

  15. Now that the communication between the client and the server has been realized, the user's registration information must be saved in the database, so first declare the following function in "opedb.h" in the TcpServer project:

    bool handleRegist(const char *name,const char *pwd);
    
  16. Then implement the function just declared in "opedb.cpp" in the TcpServer project:

    // 处理客户端的用户注册信息
    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. In order to use the function of the database, we add the following header file to "mytcpsocket.h" in the TcpServer project:

    #include "opedb.h"
    
  18. Then replace the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project with the following content. At this time, we can save the user's registration information in the database and return the user registration result information to the client. :

    // 接收来自客户端的消息
    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. Just now we have realized that the server processes the user registration information of the client. At the same time, the server also replies the user registration result information to the client, so the client needs to receive the information from the server to facilitate the user to view the registration result. Then firstly, in TcpClient The function to receive server information is declared in "tcpclient.h" in the project:

    void recvMsg();
    
  20. Then add the function just declared to "TcpClient::TcpClient" in "tcpclient.cpp" in the TcpClient project:
    Please add a picture description

  21. Then add code to "tcpclient.cpp" in the TcpClient project to receive the server's response to user registration:

    // 接收服务器信息
    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. Then we start two projects to test it. After starting the two projects, we enter the user name and password and finally click "Register", and we find that the registration is successful:
    Please add a picture description

  23. Then we went to the database to view the data and found that the new registration information had been saved in the database:
    Please add a picture description

  24. If we still register with the same username at this time, we will be prompted that the registration failed, because there is already a username with the same name in the database, and we set the username field to be unique, so the registration will fail:
    Please add a picture description

  25. At this point, we have completed the processing of user registration information by the client and the server.

6.3 Realization of user login function

  1. First of all, we need to design the click event of the user login button, because we have previously generated the function "TcpClient::on_login_pb_clicked" to handle the user login click event in "tcpclient.cpp" in the TcpClient project, and the logic of user login and user registration Basically the same, so we replace all the content in the "TcpClient::on_login_pb_clicked" function with the following content:

    // 登录事件
    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. Since we want to process the user's login request, we have to come to the TcpServer project for processing. First, we need to add a function declaration for processing the user login information database in "opedb.h" in the TcpServer project:

    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的时候,是可以成功登陆的:
    Please add a picture description

  7. 当我们尝试使用数据库中不存在的用户登陆时,是不可以成功登录的:
    Please add a picture description

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

6.4 用户退出功能实现

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

    private:
        QString m_strName;
    
  2. 然后在用户登录成功时,记录用户名:
    Please add a picture description

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

    void clientOffline();
    
  4. We add the following content to the "MyTcpSocket::MyTcpSocket" function in "mytcpSocket.cpp" in the TcpServer project. As long as the client disconnects, it will send a disconnect signal, and the server will do the corresponding processing:

    connect(this,SIGNAL(disconnect()),this,SLOT(clientOffline()));
    
  5. Before the server is formally processed, we need to complete a few small tasks. We first declare the following function in the "public" of "opedb.h" in the TcpServer project to handle the database operations that the user exits:

    void handleOffline(const char *name);
    
  6. Then add the specific implementation of the function just declared in "opedb.cpp" in the TcpServer project to update the login status of the exiting user:

    // 处理客户端的用户退出信息
    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. Then we add the following code to "mytcpsocket.h" in the TcpServer project. The function of this code is to send a signal as a signal slot. With this signal, the socket of the currently logged-in user can be deleted:

    signals:
        void offline(MyTcpSocket *mysocket);
    
  8. Then we add the following code to "mytcpserver.h" in the TcpServer project, and delete the socket of the currently logged-in user according to the signal from the signal slot:

    public slots:
        void deleteSocket(MyTcpSocket *mysocket);
    
  9. Then add the following signal slot function to the "MyTcpServer::incomingConnection" function in "mytcpserver.cpp" in the TcpServer project to connect the signal:

    connect(pTcpSocket,SIGNAL(offline(MyTcpSocket*)),this,SLOT(deleteSocket(MyTcpSocket*)));
    
  10. Then add the following code to "mytcpserver.cpp" in the TcpServer project to delete the socket of the currently logged-in user:

    // 删除当前登录用户的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. Then add the following code to "mytcpsocket.cpp" in the TcpServer project, integrate the functions written above, and finally realize the function of user exit:

    // 处理客户端用户下线的信号
    void MyTcpSocket::clientOffline()
    {
          
          
        OpeDB::getInstance().handleOffline(m_strName.toStdString().c_str());
        emit offline(this);
    }
    
  12. Then we start two projects, use a certain user to log in, and then query the database, and find that the "online" field of this user is 1, indicating that it has successfully logged in:
    Please add a picture description

  13. Then click the close button to mimic the user exit behavior:
    Please add a picture description

  14. At this time, if you query the database again, you will find that the online field of the corresponding user has changed to 0, indicating that the user has successfully logged out:
    Please add a picture description

  15. At this point, we have completed the processing of the client and server for the user's exit information

7. UI design of the main interface of the operation

  We have completed the realization of the basic functions related to the user, then when the user logs in, we should present the main operation interface of the software to the user, so the main content of our section is to design the main operation interface. The specific content of this section includes: friend sub-interface UI design, book sub-interface UI design, merging sub-interface UI to main operation interface UI.

7.1 UI Design of Friend Sub-interface

  1. First, create a new class named "OpeWidget" in the TcpClient project as follows to handle the related functions of the main interface UI:
    Please add a picture description

  2. Then add the following code to "opewidget.h" in the TcpClient project to save the information in the left column of the main interface UI:

    #include <QListWidget>
    
    private:
        QListWidget *m_pListW;
    
  3. We use QListWidget to save the left column information of the UI of the main interface of the operation. We only need to replace all the contents of "opewidget.cpp" in the TcpClient project with the following code:

    #include "opewidget.h"
    
    OpeWidget::OpeWidget(QWidget *parent) : QWidget(parent)
    {
          
          
        m_pListW = new QListWidget(this);
        m_pListW->addItem("好友");
        m_pListW->addItem("图书");
    }
    
  4. At this point, we have designed the left column of the UI of the main operation interface. Next, we need to design a window UI that displays all online users. We first add a new file and select according to the following configuration:Please add a picture description

  5. Continue to configure the selection as follows:
    Please add a picture description

  6. The name of this class was changed to "Online" in order to show all users who are online:
    Please add a picture description

  7. The finally created interface UI is as follows, we need to design this interface UI to display all online users:
    Please add a picture description

  8. Then select the blue frame component and use the red frame layout, where the List Widget component is named "online_lw", and the Push Button component is named "addFriend_pb":
    Please add a picture description

  9. At this point, we have completed the UI design for displaying all online user windows. Next, we will design the interface UI related to friends. We first create a class named "Friend" in the TcpClient project as follows:
    Please add a picture description

  10. Then add the following code to "friend.h" in the TcpClient project to store the layout component information of the friend interface 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. Then replace all the codes in "friend.cpp" in the TcpClient project with the following content, which is used to display the main window UI of the friend function:

    #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. So far, we have completed the interface UI design related to friends, which also means that we have completed the framework of the UI design of the friend interface. We can test it below. We only need to add all the contents of "main.cpp" in the TcpClient project Replace with the following:

    #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. Then we started the TcpClient project and found that the friend interface UI can be displayed normally, and clicking the "Show online user button" can also display the interface UI designed by us to display online users, which means that our friend interface UI framework has been built:
    Please add a picture description

7.2 UI design of book sub-interface

  1. We have already designed the friend interface UI, and now we need to design the book interface UI. After the study just now, readers may also find that the design process of the book interface UI should be the same as that of the friend interface UI, and our main purpose is to Complete the design and logic of the friend interface UI, so we will briefly design the book interface UI for the time being. If there is a need or time later, we can redesign it. We only need to create a new class named "Book" in the TcpClient project as follows, indicating that there is such an interface component UI:
    Please add a picture description

7.3 Merge the sub-interface UI into the operation main interface UI

  1. We have completed the design of each sub-interface UI above, and then we will merge each sub-interface UI into the main operation interface UI. First, we add the following code to "opewidget.h" in the TcpClient project:

    #include "friend.h"
    #include "book.h"
    #include <QStackedWidget>
    
    private:
        Friend *m_pFriend;
        Book *m_pBook;
        QStackedWidget *m_pSW;
    
  2. Then replace all the content in "opewidget.cpp" in the TcpClient project with the following content:

    #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. At this point we can test it, we need to replace all the contents of "main.cpp" in the TcpClient project with the following contents:

    #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. Then we started the TcpClient project and found that our function has been realized, that is, when we click the "friend" button, we will jump to the friend sub-interface UI, and when we click the "book" button, we will jump to the book Sub interface UI:
    Please add a picture description

Eight, user login jump function realization

  We have completed the UI design of the main operation interface above. The purpose of designing the UI of the main operation interface is that legitimate users can enter the main interface after logging in. Therefore, the purpose of this section is to complete the function of jumping to the main operation interface when the user logs in. , just follow me step by step.

  1. First of all, we replace all the content in "main.cpp" in the TcpClient project with the following content, because we are going to exchange information between the client and the server, so we don't need to test it here:

    #include "tcpclient.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
          
          
        QApplication a(argc, argv);
        TcpClient w;
        w.show();
        return a.exec();
    }
    
  2. Then add the following code in the public part of "opewidget.h" in the TcpClient project:

    static OpeWidget &getInstance();
    
  3. Then add the following code to "openwidget.cpp" in the TcpClient project:

    // 生成OpeWidget实例对象
    OpeWidget &OpeWidget::getInstance()
    {
          
          
        static OpeWidget instance;
        return instance;
    }
    
  4. Then import the header file of the UI class of the operation main interface that we just wrote into "tcpclient.cpp" in the TcpClient project:

    #include "opewidget.h"
    
  5. Then add the code shown in the red box to the following position in "tcpclient.cpp" in the TcpClient project, use the instantiated object of the UI class of the operation main interface, and jump to the operation main interface after the legal user successfully logs in:
    Please add a picture description

  6. Then we start the TcpServer project and the TcpClient project to test it, we can find that when we log in as a legal user, we can automatically jump to the main operation interface, and there is no problem with all functions, so we have realized the user login jump Turn function:
    Please add a picture description

9. Realization of friend operation function

  So far, our project has basically taken shape, and the following work is to enrich its functions, because we have just completed the UI design of the friend sub-interface, so our work in this section is to enrich the related functions about friend operations, these functions Specifically include: view online friends, search for friends, add friends, refresh online friends, delete friends, private chat, group chat, file sharing. Still the same sentence, as long as you follow me step by step, these functions can be perfectly realized, come on!

9.1 View online friends

  1. The flow diagram of checking online friends is as follows:
    Please add a picture description

  2. First, add the following code to "public" in "tcpclient.h" in the TcpClient project:

    static TcpClient &getInstance();
    QTcpSocket &getTcpSocket();
    
  3. Then add the following code to "tcpclient.cpp" in the TcpClient project:

    // 返回TcpClient实例对象
    TcpClient &TcpClient::getInstance()
    {
          
          
        static TcpClient instance;
        return instance;
    }
    
    // 获取TcpSocket
    QTcpSocket &TcpClient::getTcpSocket()
    {
          
          
        return m_tcpSockey;
    }
    
  4. Then replace the entire contents of "main.cpp" in the TcpClient project with the following:

    #include "tcpclient.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
          
          
        QApplication a(argc, argv);
        TcpClient::getInstance().show();
        return a.exec();
    }
    
  5. Then add the following header file to "friend.cpp" in the TcpClient project:

    #include "protocol.h"
    #include "tcpclient.h"
    
  6. Then replace all the content in the "Friend::showOnline" function in "friend.cpp" in the TcpClient project with the following content:

    // 显示在线用户窗口
    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. Then add the following code to "opedb.h" in the TcpServer project:

    #include <QStringList>
    
    public:
        QStringList handleAllOnline();
    
  8. Then add the following code to "opedb.cpp" in the TcpServer project:

    // 查询所有在线用户的用户名列表
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project:

    // 查看在线用户请求
    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”中的测试内容删掉:
    Please add a picture description

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

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

    • 点击“工具(T)”:
      Please add a picture description

    • 点击“选项(O)…”:
      Please add a picture description

    • 然后按照下图指示的操作步骤进行修改,将最后一个红框中的内容选择为“None”:
      Please add a picture description

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

9.2 搜索好友

  1. 搜索好友的流程示意图如下所示:
    Please add a picture description

  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”的时候,发现可以成功展示其用户名和状态,这也就意味着搜索好友功能已经完成了:
    Please add a picture description

9.3 添加好友

  1. 添加好友的流程示意图如下所示:
    Please add a picture description

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

  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. 可以看到测试数据已经成功插入了:
    Please add a picture description

  10. Then we declare the following function at "public" in "opedb.h" in the TcpServer project to find the relationship between the current user and the user to be added:

    int handleAddFriend(const char *pername,const char *name);
    
  11. Then add the following code to "opedb.cpp" in the TcpServer project to complete the specific function of the function just declared:

    // 查看当前用户和待添加好友之间的关系
    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. Then add the following code to "public" in "mytcpsocket.h" in the TcpServer project:

    QString getName();
    
  13. Then add the following code to "mytcpsocket.cpp" in the TcpServer project to return the user name of the currently requested user:

    #include "mytcpserver.h"
    
    // 返回当前请求的用户用户名
    QString MyTcpSocket::getName()
    {
          
          
        return m_strName;
    }
    
  14. Then add the following code to "public" in "mytcpserver.h" in the TcpServer project:

    void resend(const char *pername,PDU *pdu);
    
  15. Then add the following code to "mytcpserver.cpp" in the TcpServer project to forward the request to add friends to the client:

    // 将添加好友的请求转发给客户端
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to respond to the client's request to add friends:

    // 查看添加好友请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to process the reply from the server about adding friends:

    // 查看添加好友回复
    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. Then declare the following function at "public" in "opedb.h" in the TcpServer project to handle friend addition events:

    void handleAgreeAddFriend(const char *pername, const char *name);
    
  19. Then add the following code to "opedb.cpp" in the TcpServer project, which is to write relevant data into the database after agreeing to add friends:

    // 在同意添加好友之后向数据库中写入相关数据
    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. Then add the following code to "mytcpsocket.cpp" in the TcpServer project to reply to the result of adding friends to the client:

    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. Then we can open the server, and open two clients to test, and found that the added user can pop up a window normally:
    Please add a picture description

  22. When we click "Yes", we find that we have successfully added friends, and the friendship relationship of two users has been saved in the database. At this point, we have completed the function of adding friends:
    Please add a picture description

9.4 Refresh online friends

  1. The specific implementation diagram of the function of refreshing the friend list is as follows:
    Please add a picture description

  2. First, we add a slot function to refresh the friend list at "public slots" in "friend.h" of the TcpClient project:

    void flushFriend();
    
  3. Then in the "Friend::Friend" function in "friend.h" of the TcpClient project, add the following code to associate the signal slot of the refresh friend button:

    connect(m_pFlushFriendPB,SIGNAL(clicked(bool)),this,SLOT(flushFriend()));
    
  4. Then add the following code in "friend.cpp" of the TcpClient project to send a request to refresh the friend list:

    // 刷新好友列表
    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. Then declare the following function at "public" in "opedb.h" of the TcpServer project, which is used to handle the database operation of refreshing the friend list function:

    QStringList handleFlushFriend(const char *name);
    
  6. Then complete the specific content of the function declaration just now in "opedb.cpp" of the TcpServer project. The purpose of this function is to obtain the current user's friend list:

    // 获取当前用户的好友列表
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" of the TcpServer project to handle refreshing online friend requests:

    // 查看刷新在线好友列表请求
    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. Then add the following code in the "public" of "friend.h" in the TcpClient project. This function is used to process the latest online friend list replied by the server:

    void updateFriendList(PDU *pdu);
    
  9. Then add the following code to "friend.cpp" in the TcpClient project to update the latest online friend list:

    // 更新最新在线好友列表
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to handle refreshing the online friend list reply:

    // 查看刷新在线好友列表回复
    case ENUM_MSG_TYPE_FLUSH_FRIEND_RESPOND:
    {
          
          
    	OpeWidget::getInstance().getFriend()->updateFriendList(pdu);
    	break;
    }
    
  11. Then we can test it. After starting the server and client respectively and logging in, we click the "Refresh Friends" button and find that the latest online friends list can be displayed at this time, which also means that our function of refreshing online friends has been completed. finished:
    Please add a picture description

9.5 Delete friends

  1. The schematic diagram of the process of deleting a friend is as follows:
    Please add a picture description

  2. First, add the slot function of the "delete friend" button at the "public slots" in "friend.h" in the "TcpClient" project:

    void deleteFriend();
    
  3. Then add the following function to "Friend::Friend" in "friend.h" in the "TcpClient" project, and associate the slot function with the "Delete Friend" button:

    connect(m_pDelFriendPB,SIGNAL(clicked(bool)),this,SLOT(deleteFriend()));
    
  4. Then add the following function to "friend.cpp" in the "TcpClient" project to send a request to the server to delete a friend:

    // 删除好友
    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. Then declare the following function in the "public" of "opedb.h" in the TcpServer project to handle the operation of deleting the friend database:

    bool handleDelFriend(const char *name,const char *friendName);
    
  6. Then complete the specific function of the function just declared in "opedb.cpp" in the TcpServer project:

    // 数据库删除好友操作
    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. Then add the following content to "mytcpsocket.cpp" in the TcpServer project to handle the client's request to delete a friend:

    // 查看删除好友请求
    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. Then add the following content to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to handle the server's reply to delete friends:

    // 查看删除好友回复
    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. Then we start the server and the two clients with friend relationship separately, click the "Delete friend" button to successfully delete the friend relationship between the two users, and a prompt can be displayed, which means that our delete friend The function has been completed:
    Please add a picture description

9.6 Private Chat

  1. The flow diagram of the private chat function is as follows:
    Please add a picture description

  2. Add a UI interface class named "PrivateChat" to the TcpClient project, and the system will automatically create the corresponding cpp class and h header file for us:
    Please add a picture description

  3. Then create the UI interface of the private chat window according to the following configuration:
    Please add a picture description

  4. Then add a signal slot to the "Send" button, and the system will automatically create the required content for us:
    Please add a picture description

  5. Then add the following content to "privatechat.h" in the TcpClient project:

    #include "protocol.h"
    
    public:
    	static PrivateChat &getInstance();
    	void setChatName(QString strName);
    	void updateMsg(const PDU *pdu);
    
    private:
    	QString m_strChatName;
    	QString m_strLoginName;
    
  6. Then add the following content to "privatechat.cpp" in the TcpClient project to send chat messages:

    #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. Then add the slot function of the private chat function at the public slots in "friend.h" in the TcpClient project:

    void privateChat();
    
  8. Then add the following association to the "Friend::Friend" function in "friend.cpp" in the TcpClient project to associate the slot function of the private chat function with the processing function:

    connect(m_pPrivateChatPB,SIGNAL(clicked(bool)),this,SLOT(privateChat()));
    
  9. Then add the following content to "friend.cpp" in the TcpClient project to complete the private chat request:

    #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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to handle the private chat request from the client:

    // 查看私聊请求
    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. Then add the following header file to "tcpclient.cpp" in the TcpClient project:

    #include "privatechat.h"
    
  12. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to process the server's private chat reply:

     // 查看私聊回复
    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. 然后我们可以启动服务器和两个客户端进行测试,发现可以正常进行聊天消息的传递,这也就意味着我们的私聊功能也完成了:
    Please add a picture description

9.7 群聊

  1. 群聊功能具体实现流程示意图如下所示:
    Please add a picture description

  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. 然后我们来测试一下,发现可以正常发送群聊信息,这也就意味着我们的群聊功能已经完成了:
    Please add a picture description

十、文件操作功能实现

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

10.1 图书界面设计

  1. 我们需要设计的文件操作功能界面的最终效果简图如下所示:
    Please add a picture description

  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. Then we can test it. When we click "Book", the interface we just designed can be displayed, which means that the book interface design has been completed:
    Please add a picture description

10.2 Folder Operation

10.2.1 Create Folder

  1. The schematic diagram of the process of creating a folder is as follows:
    Please add a picture description

  2. First add the following header file to "mytcpsocket.h" in the TcpServer project:

    #include <QDir>
    
  3. Then add the following code to "case ENUM_MSG_TYPE_REGIST_REQUEST" in the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to create a folder:

    qDebug() << "create dir:" << dir.mkdir(QString("./%1").arg(caName));
    
  4. At this point, let's test to see if the newly registered user can successfully create a folder with the same name as the user name:
    Please add a picture description

  5. It can be found that the folder has been successfully created. What has just been completed is the function of creating a folder for a new user. Next, we will complete the function of creating a new folder in this directory after the new user is registered. First, we add the following code to "tcpclient.h" in the TcpClient project:

    public:
        QString curPath();
    
    private:
        QString m_strCurPath;
    
  6. Then add the following code to the "case ENUM_MSG_TYPE_LOGIN_RESPOND" in the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project, the purpose is to use the login user name to save the directory of the current folder:

    m_strCurPath = QString("./%1").arg(m_strLoginName);
    
  7. Then add the following code to "tcpclient.cpp" in the TcpClient project to return the current folder directory:

    // 返回当前文件夹目录
    QString TcpClient::curPath()
    {
          
          
        return m_strCurPath;
    }
    
  8. Then add the following code to "book.h" in the TcpClient project:

    #include "protocol.h"
    
    public slots:
        void createDir();
    
  9. Then add the following code to "book.cpp" in the TcpClient project to create a folder:

    #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. Then add the following code to the "Book::Book" function in "book.cpp" in the TcpClient project to associate the slot function:

    connect(m_pCreateDirPB,SIGNAL(clicked(bool)),this,SLOT(createDir()));
    
  11. Then add the following header file to "mytcpsocket.cpp" in the TcpServer project:

    #include <QDir>
    
  12. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to view the request to create a folder:

    // 查看创建文件夹请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to view the folder creation reply from the server:

    // 查看创建文件夹回复
    case ENUM_MSG_TYPE_CREATE_DIR_RESPOND:
    {
          
          
    	QMessageBox::information(this,"创建文件",pdu->caData);
    	break;
    }
    
  14. Then let's test to see if we can successfully create a new folder under the same directory as the user name:
    Please add a picture description

  15. It can be found that a new folder has been successfully created under the directory with the same user name, which also means that we have successfully implemented the function of creating a folder

10.2.2 View all files

  1. The schematic diagram of the process of viewing all files is as follows:
    Please add a picture description

  2. First, add some test files under the previously created folder for the convenience of subsequent tests:
    Please add a picture description

  3. Then add the following code to "book.h" in the TcpClient project:

    public slots:
        void flushFile();
    
  4. Then add the following code to "book.cpp" in the TcpClient project to send a request to the server to view all files:

    // 查看所有文件
    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. Then add the following code to "protocol.h" in the TcpClient project to save the file information:

    // 文件信息结构体
    struct FileInfo
    {
          
          
        char caFileName[32];    // 文件名字
        int iFileType;          // 文件类型
    };
    
  6. Then add the following code to "protocol.h" in the TcpServer project to save the file information:

    // 文件信息结构体
    struct FileInfo
    {
          
          
        char caFileName[32];    // 文件名字
        int iFileType;          // 文件类型
    };
    
  7. Then add the following header file to "mytcpsocket.cpp" in the TcpServer project:

    #include <QFileInfoList>
    
  8. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to view all file requests:

    // 查看查看所有文件请求
    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. Then add the following code to "openwidget.h" in the TcpClient project:

    public:
        Book *getBook();
    
  10. Then add the following code to "openwidget.cpp" in the TcpClient project:

    // 获取图书对象
    Book *OpeWidget::getBook()
    {
          
          
        return m_pBook;
    }
    
  11. Then use the two pictures in my code as the icons of different files. Of course, readers can also customize the icons, but they must be placed in the directory shown in the figure below, and the name of the icon picture needs to be consistent with the icon in the code. The name of the picture remains the same:
    Please add a picture description

  12. Then add resource files:
    Please add a picture description

  13. Enter a name and click "Choose...":
    Please add a picture description

  14. Add prefix:
    Please add a picture description

  15. Then read the icon file just now into the resource file and save it, because this step has been demonstrated before, so I won’t repeat it here:
    Please add a picture description

  16. Then add the following code to "book.h" in the TcpClient project:

    public:
        void updateFileList(const PDU *pdu);
    
  17. Then add the following code to "book.cpp" in the TcpClient project to update the file list:

    // 更新文件列表
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to view all file replies:

    // 查看查看所有文件回复
    case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND:
    {
          
          
    	OpeWidget::getInstance().getBook()->updateFileList(pdu);
    	break;
    }
    
  19. Then we can test it. When we click "Refresh Files" on the "Books" interface, all the files in this folder can be displayed, which means that our function of viewing all files has been completed:
    Please add a picture description

10.2.3 Delete Folder

  1. The flow diagram of deleting folders is as follows:
    Please add a picture description

  2. First, add the following slot function to declare the deletion folder in "book.h" in the TcpClient project:

    public slots:
        void delDir();
    
  3. Then add the following function to the "Book::Book" function in "book.cpp" in the TcpClient project to associate the slot function:

    connect(m_pDelDirPB,SIGNAL(clicked(bool)),this,SLOT(delDir()));
    
  4. Then add the following code to "book.cpp" in the TcpClient project to complete the operation of deleting the folder:

    // 删除文件夹
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to view the delete folder request:

    // 查看删除文件夹请求
    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. Then add the following code to "TcpClient::recvMsg" in "tcpclient.cpp" in the TcpClient project to view the delete folder reply:

    // 查看删除文件夹回复
    case ENUM_MSG_TYPE_DEL_DIR_RESPOND:
    {
          
          
    	QMessageBox::information(this,"删除文件夹",pdu->caData);
    	break;
    }
    
  7. Then we can test it. When we select the folder and click "Delete Folder", a prompt will pop up that the deletion is successful. Then when we click "Refresh File", we will find that the original deleted folder no longer exists. , which means that the delete folder function has been completed by us:
    Please add a picture description

10.2.4 Renaming files

  1. The schematic diagram of the file renaming process is as follows:
    Please add a picture description

  2. First add the following code to "book.h" in the TcpClient project:

    public slots:
        void renameFile();
    
  3. Then add the following code to the "Book::Book" function in "book.cpp" in the TcpClient project:

    connect(m_pRenamePB,SIGNAL(clicked(bool)),this,SLOT(renameFile()));
    
  4. Then add the following code to "book.cpp" in the TcpClient project to send a request to rename the file:

    // 重命名文件
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to process the rename file request:

    // 查看重命名文件请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to process the rename file reply from the server:

    // 查看重命名文件回复
    case ENUM_MSG_TYPE_RENAME_FILE_RESPOND:
    {
          
          
    	QMessageBox::information(this,"重命名文件",pdu->caData);
    	break;
    }
    
  7. Then we can start the project and test it out:
    Please add a picture description

  8. When we click "Refresh File", we can find that the file has been successfully renamed, which means that our file rename function has been successfully implemented:
    Please add a picture description

10.2.5 Enter folder

  1. The schematic diagram of the process of entering the folder is as follows:
    Please add a picture description

  2. First add the following code to "book.h" in the TcpClient project:

    public slots:
        void enterDir(const QModelIndex &index);
    
  3. Then add the following code to the "Book::Book" function in "book.cpp" in the TcpClient project:

    connect(m_pBookListW,SIGNAL(doubleClicked(QModelIndex)),this,SLOT(enterDir(QModelIndex)));
    
  4. Then add the following code to "book.cpp" in the TcpClient project to send a request to the server to enter the folder:

    // 进入文件夹
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to handle the request to enter the folder:

    // 查看进入文件夹请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to process incoming folder replies from the server:

    // 查看进入文件夹回复
    case ENUM_MSG_TYPE_ENTER_DIR_RESPOND:
    {
          
          
    	QMessageBox::information(this,"进入文件夹",pdu->caData);
    	break;
    }
    
  7. Because after entering the new folder, our current directory will change, so we need to update the current directory. We first add the following code to "book.h" in the TcpClient project:

    public:
        void clearEnterDir();
        QString enterDir();
    
    private:
        QString m_strEnterDir;
    
  8. Then add the following code to "book.cpp" in the TcpClient project:

    // 清空当前保存的目录
    void Book::clearEnterDir()
    {
          
          
        m_strEnterDir.clear();
    }
    
    // 获取当前保存的目录
    QString Book::enterDir()
    {
          
          
        return m_strEnterDir;
    }
    
  9. Then add the following code to the "Book::Book" function in "book.cpp" in the TcpClient project:

    m_strEnterDir.clear();
    
  10. Then replace all "case ENUM_MSG_TYPE_ENTER_DIR_RESPOND" in the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project with the following code:

    // 查看进入文件夹回复
    case ENUM_MSG_TYPE_ENTER_DIR_RESPOND:
    {
          
          
    	OpeWidget::getInstance().getBook()->clearEnterDir();
    	QMessageBox::information(this,"进入文件夹",pdu->caData);
    	break;
    }
    
  11. Then replace all "case ENUM_MSG_TYPE_FLUSH_FILE_RESPOND" in the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project with the following code:

    // 查看查看所有文件回复
    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. Then we start the project to test it. When we double-click a folder, we can enter this folder, which means that our function of entering the folder has been realized:
    Please add a picture description

10.2.6 Back to previous level

  1. 返回上一级的流程示意图如下所示:
    Please add a picture description

  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. 然后我们启动项目来测试一下,当我们点击“返回”的时候,可以从子目录返回到主目录,这也就意味着我们的返回上一级功能已经成功实现了:
    Please add a picture description

10.3 常规文件操作

10.3.1 上传文件

  1. 上传文件的流程示意图如下所示:
    Please add a picture description

  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. 然后我们可以启动项目,上传一个文件测试一下:
    Please add a picture description

  10. 经过测试后发现,文件已经可以成功上传了,这也就意味着我们的上传文件功能已经成功实现了:
    Please add a picture description

10.3.2 删除文件

  1. 删除文件的流程示意图如下所示:
    Please add a picture description

  2. First declare the following function in "book.h" in the TcpClient project:

    public slots:
        void delRegFile();
    
  3. Then add the following function to the "Book::Book" function in "book.cpp" in the TcpClient project to associate the slot function:

    connect(m_pDelFilePB,SIGNAL(clicked(bool)),this,SLOT(delRegFile()));
    
  4. Then add the following code to "book.cpp" in the TcpClient project to send a request to the server to delete files:

    // 删除文件
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project to handle the delete file request from the client:

    // 查看删除文件请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project to process the delete file reply from the server:

    // 查看删除文件回复
    case ENUM_MSG_TYPE_DEL_FILE_RESPOND:
    {
          
          
    	QMessageBox::information(this,"删除文件",pdu->caData);
    	break;
    }
    
  7. Then start the project to test it. We can select a file and click "Delete File":
    Please add a picture description

  8. When we "refresh the file" again, we will find that the selected file has been deleted by us, which means that we have successfully implemented the function of deleting files:
    Please add a picture description

10.3.3 Downloading files

  1. The schematic diagram of the process of downloading files is as follows:
    Please add a picture description

  2. First add the following code to "book.h" in the TcpClient project:

    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. Then add the following code to the "Book::Book" function in "book.h" in the TcpClient project:

    m_bDownload = false;
    
    connect(m_DownLoadPB,SIGNAL(clicked(bool)),this,SLOT(downloadFile()));
    
  4. Then add the following code to "book.cpp" in the TcpClient project:

    // 下载文件
    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. Then add the following code to "mytcpsocket.h" in the TcpServer project:

    #include <QTimer>
    
    public slots:
        void sendFileToClient();
        
    private:
        QTimer *m_pTimer;
    
  6. Then add the following code to the "MyTcpSocket::MyTcpSocket" function in "mytcpsocket.cpp" in the TcpServer project:

    m_pTimer = new QTimer;
    connect(m_pTimer,SIGNAL(timeout()),this,SLOT(sendFileToClient()));
    
  7. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project:

    // 查看下载文件请求
    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. Then add the following code to "mytcpsocket.cpp" in the TcpServer project:

    // 向客户端发送文件数据
    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. Then add the following code to "tcpclient.h" in the TcpClient project:

    private:
        QFile m_file;
    
  10. Then replace all the "TcpClient::recvMsg" functions in "tcpclient.cpp" in the TcpClient project with the following code:

    // 接收服务器信息
    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. 然后我们可以启动两个项目测试一下:
    Please add a picture description

  12. 可以发现,已经下载成功了,这也就意味着我们的下载文件功能已经成功实现了:
    Please add a picture description

10.3.4 分享文件

  1. 分享文件的流程示意图如下所示:
    Please add a picture description

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

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

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

  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. 然后我们来测试一下,首先启动一个服务端和两个客户端,在两个客户端上点击“刷新好友”,要先刷新好友,才能对好友进行分享文件的操作:
    Please add a picture description

  21. 然后将“rose”用户下的“hello”文件夹进行分享:
    Please add a picture description

  22. After selecting "lucy", click "OK":
    Please add a picture description

  23. At this point, both clients have corresponding prompts, we only need to click "Yes" on the receiving end:
    Please add a picture description

  24. Then click "Refresh File" on the "lucy" user client, and you can see that the "hello" folder of the "rose" user and the contents of the folder have been successfully copied to the file directory of the "lucy" user, which means Our file sharing function has been successfully implemented:
    Please add a picture description

10.3.5 Moving files

  1. The schematic diagram of the process of moving files is shown in the figure below:
    Please add a picture description

  2. First add the following code to "book.h" in the TcpClient project:

    public slots:
    	void moveFile();
    	void selectDestDir();
        
    private:
    	QPushButton *m_pMoveFilePB;
    	QPushButton *m_pSelectDirPB;
    	QString m_strMoveFileName;
    	QString m_strMoveFilePath;
    	QString m_strDestDir;
    
  3. Then add the following code to the "Book::Book" function in "book.cpp" in the TcpClient project:

    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. Then add the following code to "book.cpp" in the TcpClient project:

    // 移动文件
    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. Then add the following code to the "MyTcpSocket::recvMsg" function in "mytcpsocket.cpp" in the TcpServer project:

    // 查看移动文件请求
    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. Then add the following code to the "TcpClient::recvMsg" function in "tcpclient.cpp" in the TcpClient project:

    // 查看移动文件回复
    case ENUM_MSG_TYPE_MOVE_FILE_RESPOND:
    {
          
          
        QMessageBox::information(this,"移动文件",pdu->caData);
        break;
    }
    
  7. Then start the server and client respectively for testing, first select the file to be moved, and then click "Move File":
    Please add a picture description

  8. Then select the target directory to move the files to, and click "Target Directory":
    Please add a picture description

  9. When we enter the target directory of the moving file to check, we find that the file has been moved to the target directory, which means that our moving file function has been successfully implemented:
    Please add a picture description


Summarize

  Readers who read this paragraph should have already completed this project. This project has cost me a lot of effort. Due to the limited space (due to too many words, the webpage for blogging is now stuck), I will not repeat it I hope readers can learn knowledge, come on!

Guess you like

Origin blog.csdn.net/IronmanJay/article/details/130718228