2023东软IM学习记录

qt

打开:home->szq->tools->QtCreator->bin->qtcreator

1.基础

basic

QT打开项目提示 no valid settings file could be found qt

 no valid settings file could be found qt中文意思是
 找不到有效的设置文件。
 此时,只需要删除项目中的.user 文件,再打开,打开后再点configure project,重新配置,重新构建,就可以了。

error: Unknown module(s) in QT: core5compat问题解决方法

在qt安装目录下找到maintenanceTool

选择更新组件,然后添加对应版本的Qt 5 Compatibility Module

信号槽【信号与槽】

信号槽是qt框架中的事件处理机制。信号对应的就是事件,槽对应的就是事件的处理动作。信号槽实际就算设计模式种的观察者模式。事件的捕捉和信号的发射都是由qt实现框架内部实现的。

信号的本质是事件(也是函数-事件发生,发出信号) 信号由窗口发出,信号是一个被实例化的对象(对象内部可以进行相关事件的发生,发出对应的信号)

槽函数本质是对信号的处理动作 槽函数也应该是属于某一个对象的【接收者】

两者之间建立某种关系才能进行信号处理–connect–信号发出者地址,信号地址;信号接收者指针,槽函数地址;

原来去年研究的qt的文档那些函数就是信号槽。

ui对应的就是ui那个文件:

主要是signal和slots【它们是各自属于各自对象的一个函数(一部分)】

位置:23小学期/qt代码

观察者模式

观察者订阅被观察者的情况

object:抽象基类

 class observer : public QObject
 {
     Q_OBJECT
 ​
 public:
     explicit observer(QObject* parent = nullptr);
     virtual void update(float temperature) = 0;//观察者接收消息后执行
 };
 观察者通过定义一个纯虚函数 update(float temperature) 来接收被观察者发出的消息。当被观察者状态发生变化时,它会调用观察者的 update 函数,并将变化的信息传递给它。
 在这个特定的代码中,observer 类是一个抽象基类,因为它包含纯虚函数。这意味着你不能直接创建 observer 类的实例,而是需要从它派生出具体的子类来实现 update 函数,以便在状态变化时执行相应的操作。
 所以就是后续需要创建具体的观察者类
 ​
 class Reciever : public observer
 {
 public:
     void update(float temperature) override
     {
         qDebug() << "Temperature is now:" << temperature << "degrees Celsius.";
     }
 };
 抽象类vs接口:
 成员和实现:抽象类可以包含成员变量和非纯虚函数的实现,而接口只能包含纯虚函数。
 继承:一个类可以同时继承多个接口,但只能继承一个抽象类。
 状态:抽象类可以有状态(成员变量),而接口不包含状态。
 多重继承:接口常用于多重继承的情况,抽象类则在单一继承环境中使用较多。
 在上下文中,observer 类更接近于抽象类而不是接口。虽然它是一个基类,定义了一个纯虚函数 update(float temperature),这是抽象类的典型特征。接口通常是只包含纯虚函数,而不包含任何成员变量或非纯虚函数。
 这里observer也可视为一个接口

subject

 void subject::addObserver(observer* observer)
 {
     observers.append(observer);
 }
 observers 是一个成员变量,它是 subject 类中的一个属性。根据您的代码,observers 是一个容器(可能是数组、列表或其他类似容器),用于存储观察者对象的指针。在观察者模式中,被观察者对象维护一个观察者列表,以便在状态发生变化时通知所有观察者。observers 变量就是用来存储这些观察者对象的容器。

main:

  QObject::connect(&timer, &QTimer::timeout, [&]() {
         float temperature =  QRandomGenerator::global()->bounded(30);
         weatherStation.notifyObservers(temperature);
     });
     lambda函数

2.qt实现界面并连接sql+通信

connect_sql文件

仔细看了眼,发现label拼错了

但是实现了之后也报错

报错QMetaObject::connectSlotByName: No matching signal for on_pushButtonQuery_clicked()

这种报错通常发生在你通过 Qt Designer 的 "Edit Signals/Slots" 功能将信号连接到槽函数时,Qt 会尝试根据信号的名称自动连接到对应名称的槽函数。如果信号与槽函数的名称不匹配,就会出现这种错误。

 这个报错是正常的,可能表示正在监听

但是结果只有一行数据,会不会是label只能显示一个数据?

换了testbrowser并不能够,只能出现两个数据。

真实的问题是append,当textBrowser也用append就会只有一个数据sk,

是setText的问题 textBrowser用这个也值显现一个,但是用append显示两个。但是理应所有的数据都展现呀,stu的age列并未展现

如何给数据库设置密码:

SQLite 并没有内置的用户管理系统,所以它本身只提供了密码的设置,没有直接支持指定用户的功能

user_version是设置数据库版本,可以选择其他数字

csdn上用qt实现用户注册登录界面

Qt设计精美的登录注册界面(包含SQLite数据库应用)qt登录注册界面连接数据库桃豆豆的博客-CSDN博客

有sqlite和tcp

研究通信

服务器和客户端是什么?要一起实现吗?怎么实现通信?

 服务器端: 服务器是一个提供服务的计算机程序或设备。在网络通信中,服务器通常监听某个特定的网络端口,等待客户端的连接请求。服务器负责接受客户端的连接、处理请求、提供服务并发送响应。服务器可以是独立的实体,也可以是一个运行在服务器计算机上的程序。
 ​
 客户端: 客户端是发送请求并接收响应的计算机程序或设备。在网络通信中,客户端连接到服务器,发送请求,等待响应并处理响应。客户端可以是独立的实体,也可以是一个运行在客户端计算机上的程序。
 ​
 服务器是处理客户端的请求的设备。
 服务器端和客户端不一定要在同一个 Qt 程序内。它们可以是不同的程序,甚至运行在不同的计算机上。在某些情况下,你可能希望将服务器和客户端分开部署,以实现更好的可扩展性和性能。如果你想在同一个程序内同时运行服务器端和客户端,你可以在程序中创建一个服务器实例和多个客户端实例。然而,需要注意的是,这样的设计可能会导致程序结构变得复杂,并且需要考虑多线程、并发处理和资源管理等问题。在实际情况中,通常会将服务器和客户端分开开发和部署,以实现更清晰的架构和更好的可维护性。
 Asio 是一个 C++ 库,用于异步 I/O 和网络编程。它由 Boost C++ 库提供,被设计用于高效、可扩展的网络和异步操作。Asio 提供了一种基于事件驱动的编程模型,适用于各种网络应用,包括服务器和客户端。
 ​
 Socket: 在计算机网络编程中,Socket(套接字)是一种抽象概念,用于描述计算机之间进行通信的一种机制。它可以看作是一种接口,允许不同计算机或进程之间通过网络发送和接收数据。Socket 提供了一种编程接口,允许开发者使用不同的网络协议(如 TCP、UDP)在计算机之间进行通信。
 ​
 使用 TCP 套接字:
 使用 TCP 套接字可以实现可靠的、面向连接的通信。这种方式适用于需要确保数据传输可靠性的场景,如即时消息的可靠传递。
 ​
 服务器端: 在你的应用中,你需要创建一个服务器程序来监听局域网内的连接请求。服务器会处理客户端的连接和消息传递。在局域网内,服务器可以通过 IP 地址或者主机名来监听连接请求。
 ​
 客户端: 在每台客户端计算机上,你需要创建一个客户端程序来连接服务器,并在连接建立后进行消息的收发。客户端之间也可以通过 IP 地址或主机名来连接服务器。
 ​
简单通信

QT5实现简单的TCP通信(包括客户端和服务端)qt5 tcp 客户端Li_Monster的博客-CSDN博客

好像服务器和客户端是分离的

报错 QTcpServer:No such file or directory—没有QT+=network

画时序图采用iodraw

换了个例子理解:

QT -- TcpSocket实例,使用Qt中的tcp通信协议,构建客户端和服务端,实现局域网通信软件功能qt实现局域网网上通信Freedom_Bule的博客-CSDN博客

换:Qt网络编程实现TCP通信qt tcp喝水怪~的博客-CSDN博客

服务端任何时候只能被动等待连接

继承自Qwidget,和mainwindow有什么区别

实现了!!

服务器可以给客户端发消息吗?ip是在干什么呢?是只要端口一样就能通信吗?

之后看看进阶:

研究代码:

头文件保护:#ifndef #define #endif

包含的头文件:QWidget、QTcpServer、QT_BEGIN_NAMESPACE和QT_END_NAMESPACE这些宏用于将与qt相关的代码置于qt命名空间内

什么是命名空间

避免命名冲突和组织代码的机制
命名空间将代码元素分组,使得相同名字的元素可以存在于不同的命名空间中而不会发生冲突。通过在代码中指定命名空间,可以将标识符的作用范围限定在特定的命名空间中,从而确保不同部分的代码可以共存而不干扰彼此
Utility::SomeClass lib1Obj;
OtherUtility::SomeClass lib2Obj;

标识符?
这里的"myVariable"、"myFunction"和"MyClass"都是标识符,它们用来标识不同的程序元素(变量、函数和类)。通过使用命名空间,你可以在不同的命名空间中使用相同的标识符名字,从而将这些标识符的作用范围限定在特定的命名空间内,避免了它们之间的冲突。
namespace FirstLibrary {
    class Utility {
        // ...
    };
}

namespace SecondLibrary {
    class Utility {
        // ...
    };
}
utility相同但是不冲突。

Ui命名空间的前向声明:

namespace Ui{ class Widget; }这个前向声明表明有一个命名空间Ui包含一个名为Widget的类,这个类的实际定义是由Qt的用户界面编译器(uic)基于一个‘.ui’文件自动生成的

什么是前向声明?

Widget类声明:

‘class Widget : public QWidget’这个类声明定义了一个名为‘Widget’的类,它继承自QWidget类

共有和私有部分:

‘Q_OBJECT’这个宏表示这个类使用了Qt的元对象系统,允许其使用信号和槽

‘public’这个部分包含了类的公共成员,包括构造函数、析构函数及其他公共方法

什么是析构函数

公共成员:

Widget(QWidget *parent = nullptr):Widget类的构造函数,初始化了类成员并设置TCP服务器??

‘~Widget()’Widget类的析构函数,在对象呗销毁时,清理任何资源

私有槽:

  • void onNewConnect():这个私有槽似乎处理来自客户端的新连接请求。

  • void onSendBackMsg():这个私有槽似乎将反馈信息发送回新连接的客户端。

  • void onReadMsg():这个私有槽似乎读取客户端发送到服务器的数据。

私有成员:

  • Ui::Widget *ui:指向由 uic 生成的 UI 定义的指针。

  • QTcpServer* m_tcpServer:指向 QTcpServer 实例的指针,表示 TCP 服务器。

  • QTcpSocket* m_tcpSocket:指向 QTcpSocket 实例的指针,表示用于与客户端通信的 TCP 套接字。

总之,这段代码定义了一个 Widget 类,它似乎使用 Qt 框架实现了一个基本的 TCP 服务器。实际函数(槽)的实现应该在相应的 .cpp 文件中。

套接字到底是什么?
为什么要分.cpp和.h文件

TCP简介

3.QT多线程

减少界面卡顿

默认情况下qt只有一个线程(会不会所以只显示一个结果)

在线程在计数的时候,拖动窗口会出现无响应的状态,且数数期间无法在界面更新数据,所以要将计数放到子线程,拖动窗口和更新就能得到响应。

主线程:默认线程,窗口线程,UI,负责窗口事件处理,窗口控件数据更新(取数和设置)。

子线程:后台业务逻辑相关,无法实现窗口控件的更新,需要通过信号槽传给主线程,再由主线程更新窗口控件数据,子线程无法直接访问ui。

修改文件名:qt右键重命名无法把小写文件名改为大写,

通常情况下,将类的声明放在 .h 文件中,将实现放在 .cpp 文件中,而主要的应用程序逻辑则通常放在 main.cpp 文件中

简单实现:

能够输出东西并且拖动窗口。

首先,我们创建一个简单的多线程任务,在工作线程中计算数字的平方。然后,我们使用Qt信号槽机制在主线程中更新UI界面以显示计算的结果。

实现了多线程–子线程进行计算,主线程更新控件数据并且同时实现窗口响应。

【其实完全可以看书和附带的代码】

3.1初步

代码研究–thread_1

这整个程序是一个什么样的结构?各文件之间有什么关系?这样组合有什么目的?

mainwindow在程序中代表主线程。workerthread代表自定义的工作线程类,用于处理实际的计算任务,以避免在主线程中阻塞UI响应。
这是一个典型的多线程应用程序结构,其中主线程负责管理UI,工作线程负责执行耗时的任务。
通过使用workerthread,我们在主线程中启动了一个独立的工作线程,实现并行处理,这样使得用户界面保持响应,不会被阻塞在计算任务中。

研究workerthread.h代码

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
这是头文件的预处理指令,用于防止头文件被多次包含
#ifndef检查是否被定义,若未定义,则#define定义。

#include <QThread>
引入QThread类的头文件,QThread是Qt中用于多线程编程的基类。
class WorkerThread : public QThread
{
    Q_OBJECT
定义了一个类WorkerThread,它继承自 QThread。
Q_OBJECT 宏用于启用 Qt 的元对象系统,使得类能够使用 Qt 的信号槽机制等特性。

signals:
    void resultReady(int result);
定义了一个信号,用于在工作线程计算完成后,通知主线程并传递计算结果。
protected:
    void run() override;
重写 QThread 中的 run() 函数,在这个函数中,你可以定义工作线程实际要执行的任务逻辑。在这个例子中,run() 函数将在工作线程中执行计算任务。
【定义工作线程的任务】
#endif // WORKERTHREAD_H
头文件结束的标志,用于标记头文件的结束。这里是 WORKERTHREAD_H 宏定义的结束。

什么是元对象系统?

什么是宏定义?

完全通信

客户端

chatdialog:

So, in this case, the approach of periodically fetching chat history is being used to achieve a form of real-time communication by regularly checking for and displaying new messages as they arrive.
 connect(timer,SIGNAL(timeout()),this,SLOT(getchathistory()));
    timer->start(500);
    

发送消息

发送消息:
tcpSocket->abort();//取消已有链接
        tcpSocket->connectToHost(hostip, hosthost);//链接服务器
        QString ip = tcpSocket->peerAddress().toString().section(":",3,3);
        int port = tcpSocket->peerPort();
        QString str = QString("[%1:%2]").arg(ip).arg(port);
        qDebug() << str ;//打印对方主机的IP和端口
        
连接超时就退出登录需要重新登录:
if(!tcpSocket->waitForConnected(30000))
        {
            QMessageBox::warning(this, "Warning!", "网络错误", QMessageBox::Yes);
            this->close();
            user.islogin = false;
            client *cli = new client();
            cli->show();
        }
连接成功:
{//服务器连接成功
            //第一个是时间,第二个发送的ID,第三个是接受的ID,第四个是内容
            QString nowstr = QDateTime::currentDateTime().currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
            QString message = QString("chat_send##%1##%2##%3##%4").arg(nowstr).arg(user.id).arg(otheruser.id).arg(ui->lineEdit_sendmessage->text());
            tcpSocket->write(message.toUtf8());//将信息以UTF-8的格式写入网络连接
            tcpSocket->flush();//刷新
            ui->lineEdit_sendmessage->clear();
        }
        

聊天记录

请求获取消息:
QString message = QString("chat_history##%1##%2").arg(user.id).arg(otheruser.id);
        tcpSocket->write(message.toUtf8());
        tcpSocket->flush();
 
     if(QString(buffer).section("##",0,0)==QString("chat_history_ok"))检查是否收到聊天记录?




connect(tcpSocket,&QTcpSocket::readyRead,[=](){ 在这段代码中,[=](){...} 就是一个 lambda 函数的定义,其中:

= 表示通过值捕获,表示该 lambda 函数可以访问在其定义时可见的所有外部变量。
() 是 lambda 函数的参数列表。在这里我们没有参数,所以为空。
{...} 是 lambda 函数的主体,即具体的代码逻辑。
所以,connect(tcpSocket, &QTcpSocket::readyRead, [=](){...}) 的含义是,当 tcpSocket 接收到数据时,会执行 lambda 函数中定义的代码逻辑来处理接收到的数据。在这里,这个 lambda 函数用于解析聊天历史记录数据并更新用户界面。




因此,QString(buffer).section("##", 0, 0) 就是从接收到的数据中提取出第一个以 "##" 分隔的部分,从这里可以理解为提取出标识部分。
整个 if 语句就是在检查提取出来的标识是否与 "chat_history_ok" 相等,以确定接收到的数据是否是聊天历史记录的标识。如果相等,说明接收到的数据是聊天历史记录的数据。
int num = QString(buffer).section("##", 1, 1).toInt();
这表示从字符串 buffer 中查找第一个出现的 "##" 分隔符,然后提取出这个分隔符后面的内容。由于 startIndex 和 endIndex 都是1,所以提取的内容是分隔符后面的部分。
即在 "chat_history_ok##num" 中的 "num" 部分

## 分隔的方式不是 TCP Socket 自动生成的,而是根据你的代码和服务器之间的通信协议来约定的。在这种情况下,你的代码和服务器之间约定使用 ## 作为消息中不同部分的分隔符,以便在接收数据时可以将其拆分成不同的字段。




QDateTime time = QDateTime::fromString( QString(buffer).section("##",rownum*3+2,rownum*3+2),"yyyy-MM-dd hh:mm:ss.zzz");
行代码的作用是从接收的数据中提取时间字符串,然后使用特定的日期时间格式将其转换为 QDateTime 对象,以便你可以在界面上显示或处理这个时间。
QDateTime::fromString(...): 这是一个静态成员函数,用于将字符串转换为 QDateTime 对象。
QString(buffer).section("##",rownum*3+2,rownum*3+2): 这一部分将接收的数据转换为 QString 对象,并使用分隔符 "##" 将其分割为多个部分。其中 rownum*3+2 表示从数据中选择第 rownum*3+2 个部分,这个部分应该是时间的字符串表示。
qDebug() << time.toString(); 的目的是输出经过转换的时间对象 time 的字符串表示形式,以便你在调试过程中验证时间的正确性。
if(QString(buffer).section("##",rownum*3+3,rownum*3+3).toInt()==user.id
.section("##", rownum*3+3, rownum*3+3): 这是使用字符串方法 .section() 来从字符串中提取一个特定的部分。"##" 是分隔符,表示要将字符串按照 "##" 进行分割。rownum*3+3 是起始索引,表示要从字符串的哪个位置开始提取。rownum*3+3 是结束索引,表示要提取的部分的结尾位置。因此,这一部分代码实际上是从字符串中提取出特定的片段。
用于区分当前用户和其他用户
chatshow = "("+timeshow+")" + idshow + QString(buffer).section("##",rownum*3+4,rownum*3+4) +"\n" + chatshow;
时间+id+内容



所以本质存储形式是chat_history_ok##num##time##消息
"chat_history_ok" 是特定的标识,表示这是聊天历史记录数据。
"num" 表示消息的数量,即历史记录中包含的消息数量。
"time" 是每条消息的时间戳。
"消息" 是实际的聊天消息内容

client

客户端构造函数:

client::client(QWidget *parent)指明client类的构造函数的名称是client,接受一个指向父级窗口的指针作为参数。这个参数是用来告诉构造函数这个新创建的窗口是作为哪个已存在的窗口的子窗口存在的。通过这个参数,父窗口可以管理子窗口,使得它们的生命周期和行为能够协调一致。
    : QMainWindow(parent)部分是构造函数的初始化列表。在这里,我们在调用 QMainWindow 的构造函数时,将之前提到的 parent 参数传递给它。这样做的目的是将新创建的 client 窗口与父级窗口建立关联。
    这部分表示该构造函数继承自 QMainWindow,这意味着在构造 client 类的对象时,会先调用 QMainWindow 的构造函数来初始化相关的属性和功能。parent 是一个指针,它指向一个父级窗口,即在创建 client 类的对象时,你可以传递一个已存在的窗口对象作为参数,这个已存在的窗口就会成为新创建窗口的父窗口。
    
    , ui(new Ui::client)这部分是在构造函数初始化列表中,使用 ui 这个指针来指向刚刚创建的 Ui::client 对象。这样,在构造函数中就可以通过 ui 来访问用户界面中的各个部件。
    ui 就是一个指向与用户界面相关的实例的指针,它的类型是 Ui::client,表示与 client 类相关的用户界面。
    这行代码并不是直接创建用户界面(UI)对象。实际上,它是在构造函数中初始化了一个指向用户界面的指针。这个指针指向的是由 Qt Designer 自动生成的 Ui::client 类的对象,这个对象包含了用户界面中各个部件的指针。
    
{
    ui->setupUi(this);
    tcpSocket = new QTcpSocket();
}


extern userinfo user;
extern QString hostip;
extern int hosthost;
作为全局变量在其他文件中定义的,然后通过 extern 关键字引用到当前文件中。


 QString ip = tcpSocket->peerAddress().toString().section(":",3,3);
        int port = tcpSocket->peerPort();
获取连接的主机的ip的最后一位和其端口,这样做可能是为了确保连接的是预期的主机,而不是硬编码在代码中。  

QString loginmessage = QString("login##%1##%2").arg(ui->lineEdit_username->text()).arg(ui->lineEdit_pwd->text());
在这里,%1 和 %2 是格式化字符串中的占位符。通过使用 .arg() 方法,你可以将这些占位符替换为实际的值。具体来说,%1 会被替换为 ui->lineEdit_username->text() 所返回的文本,而 %2 则会被替换为 ui->lineEdit_pwd->text() 所返回的文本。这就允许你在构建字符串时将变量的值插入到字符串中的指定位置。          
tcpSocket->write(loginmessage.toUtf8());
            tcpSocket->flush(); 
loginmessage 是一个字符串,通过 .toUtf8() 方法将其转换为字节序列(QByteArray),然后使用 tcpSocket->write() 发送这些字节数据到服务器端,实现了与服务器的通信。 tcpSocket->flush() 用于确保缓冲区中的数据被立即发送出去,而不是等待缓冲区满或连接关闭时再发送。这是确保数据及时传输的一种方式。

connect(tcpSocket,&QTcpSocket::readyRead,[=](){//当tcpSocket接收到数据(readyRead 信号触发)时,会执行 lambda 函数内的代码块来处理服务器返回的数据。
                QByteArray buffer = tcpSocket->readAll();先通过 tcpSocket->readAll() 读取服务器返回的数据,
                if(QString(buffer).section("##",0,0)==QString("login successed"))然后使用 QString(buffer).section("##",0,0) 来提取数据的标识部分(比如 "login successed" 或 "login error")。
                {//登陆成功
                    user.id=QString(buffer).section("##",1,1).toInt();
                    user.name = ui->lineEdit_username->text();
                    user.islogin = true;
                    this->close();
                    home *hom = new home();
                    hom->show();
                }
                else if(QString(buffer).section("##",0,0)==QString("login error"))
                {
                    if(QString(buffer).section("##",1,1)==QString("no_user"))
                    {//用户不存在
                        QMessageBox::warning(this, "Warning!", "用户不存在", QMessageBox::Yes);
                        ui->lineEdit_username->clear();
                        ui->lineEdit_pwd->clear();
                        ui->lineEdit_username->setFocus();调用 setFocus() 函数会使用户名输入框获得焦点,即光标会出现在用户名输入框中,用户可以直接在该输入框内输入文本,而不需要手动点击该输入框。这样可以提升用户体验,方便用户直接进行输入操作。
                    }
                    else if(QString(buffer).section("##",1,1)==QString("errpwd"))
                    {
                        QMessageBox::warning(this, "Warning!", "密码错误", QMessageBox::Yes);
                        ui->lineEdit_pwd->clear();
                        ui->lineEdit_pwd->setFocus();
                    }
                }
            });

home.cpp

connect(timer,SIGNAL(timeout()),this,SLOT(Createdfriendlist()));
这行代码的作用是将定时器的 timeout() 信号与 home 类中的 Createdfriendlist() 槽函数连接起来。当定时器定时器的时间间隔到达时,会自动触发 timeout() 信号,从而调用 Createdfriendlist() 槽函数,执行在该槽函数中定义的代码,例如刷新好友列表。
void home::Createdfriendlist()
home类的成员函数


enable是在干什么?ui->pushButton_startchat->setEnabled(false);
在这段代码中,setEnabled(false) 是用来禁用(或使不可用)Qt GUI 控件的方法。在你提到的这段代码中,ui->pushButton_startchat 是一个按钮控件,setEnabled(false) 的调用会将该按钮设置为不可用状态,即用户无法点击或与之交互。

这段代码可能是在某种逻辑条件下被调用,目的是根据条件来控制按钮的可用性。在这种情况下,setEnabled(false) 的调用可以防止用户在特定情况下点击按钮。当条件满足时,你可能会看到类似的代码使用 setEnabled(true),以使按钮重新变为可用状态,让用户能够与之交互。这种操作可以帮助控制用户界面的交互流程。

void home::Createdfriendlist()
Createdfriendlist 函数是用来更新用户界面的好友列表以及相关按钮的状态,以反映服务器上的数据。

server

  tcpServer->listen(QHostAddress::Any,8888);
  因为client的端口是8888,所以能监听所有client
  
  sqlquery.prepare("select * from people where name = :name");
                sqlquery.bindValue(":name",QString(buffer).section("##",1,1));
在这里,:name 是一个命名绑定变量。在SQL查询中,绑定变量允许您将值动态地插入到查询中,而不必直接在查询字符串中嵌入这些值。绑定变量的目的是防止SQL注入等安全问题,同时也可以更方便地处理不同的查询值。  
在你的代码中,:name 用于表示一个待填充的变量,它将与实际的值进行绑定。通过使用 bindValue 函数,你可以将实际的值绑定到这个变量上,然后查询将使用这个值来执行相应的数据库操作。【就是把读取到的name和这个:name绑定】


所以,db.setDatabaseName("./people.db"); 不是在新建数据库,而是在设置一个已经存在的数据库连接的名称。如果数据库连接 db 还没有创建,你需要先创建它并设置数据库类型为 "QSQLITE"。

原理?

我们的服务器和客户端都是在自己的电脑上的,所以服务器的 ip 为 127.0.0.1。

cmd中输入:ipconfig,即可显示IP地址。 通过端口号(port)来确定一台计算机中特定的网络程序。 一台机器的端口号可以在0~65535之间。下载文件,浏览网页,QQ聊天,这些程序对应不同的端口,信息传输到本机时,根据端口号来进行分类,用不同的程序来处理数据。

##

在cmd中输入:netstat -an,即可显示本机使用了哪些端口。 其中冒号后面的数字,即为端口号。 常用应用程序端口号:21–FTP协议,22–SSH安全登录,23–Telnet,25–SMTP协议,80–HTTP协议 0~1024的端口最好不要使用

实现

目的:关闭客户端实现下线功能

client:

void home::closeEvent(QCloseEvent *event)
{
    timer->stop();
    tcpSocket = new QTcpSocket();
    tcpSocket->abort();//取消已有链接
    tcpSocket->connectToHost(hostip, hosthost);//链接服务器
    if(!tcpSocket->waitForConnected(30000))
    {
        this->close();
        user.islogin = false;
    }
    else
    {//服务器连接成功
        QString message = QString("logout##%1").arg(user.id);
        tcpSocket->write(message.toUtf8());
        tcpSocket->flush();
        QMessageBox::warning(this, "Success", "下线成功", QMessageBox::Yes);
    }
}

server:

 else if("logout" == QString(buffer).section("##",0,0))
            {
                db.setDatabaseName("./people.db");
                db.open();
                QSqlQuery sqlquery;
                sqlquery.prepare("update people set islogin=0 where id = :id");
                sqlquery.bindValue(":id",QString(buffer).section("##",1,1));
                sqlquery.exec();

                //更新服务器界面
                ui->listWidget->clear();
                sqlquery.prepare("select * from people where islogin = 1");
                sqlquery.exec();
                if(sqlquery.next())
                {
                    QString userid = sqlquery.value(0).toString();
                    QString username = sqlquery.value(1).toString();
                    QString userip = sqlquery.value(3).toString();
                    //qDebug()<<userid;
                    ui->listWidget->insertItem(0,"用户ID:"+userid+",用户昵称:"+username+",用户IP:"+userip);
                    int rownum = 1;
                    while (sqlquery.next())
                    {
                        QString userid = sqlquery.value(0).toString();
                        QString username = sqlquery.value(1).toString();
                        QString userip = sqlquery.value(3).toString();
                        ui->listWidget->insertItem(rownum,"用户ID:"+userid+",用户昵称:"+username+",用户IP:"+userip);
                        rownum++;
                    }
                }
                else
                {
                    ui->listWidget->clear();
                    ui->listWidget->insertItem(0,tr("当前无在线用户"));
                }
            }
            

聊天记录更新不及时

void GuiChatWindow::on_pushButton_close_clicked()
{
    qDebug()<<is_open_chatdialog;
    
    is_open_chatdialog = false;
    qDebug()<<is_open_chatdialog;
    this->close();
    timer->stop();
}

void GuiChatWindow::closeEvent(QCloseEvent *event)
{
    is_open_chatdialog = false;
    timer->stop();
}

解决可以同时登录相同用户的问题

设置登录的时候检查是否已经在线,如果用户已在线就提醒“该用户已在线”呢

sqlquery.prepare("select * from people where name = :name AND islogin = 1");
                        sqlquery.bindValue(":name",QString(buffer).section("##",1,1));
                        sqlquery.exec();
                        if(sqlquery.next())//查询在线列表,若已经在线,
                        {//查找到该用户
                            tcpSocket[0]->write(QString("login error##online_already").toUtf8());
                            tcpSocket[0]->flush();
                            db.close();
                        }
else if(QString(buffer).section("##",1,1)==QString("online_already"))
                    {
                        QMessageBox::warning(this, "Warning!", "用户已在线!", QMessageBox::Yes);
                        ui->lineEdit->clear();
                        ui->lineEdit_2->clear();
                        ui->lineEdit_2->setFocus();
                    }

实现关闭服务器的时候所有用户的下线

重新打开服务器并不能出现已在线的用户

关闭服务器也不能让所有下线。

登录了一个之后才会更新在线是为什么
好像是因为这个tcpSocket[0] = tcpServer->nextPendingConnection();

或许用户已在线然后自动退出?

可以不自动退出吗?但是列表也没有更新

下线是会更新的

多线程?

要的事件循环和处理逻辑似乎在一个单独的主线程中。每当有新的连接进来时,都会创建一个新的QTcpSocket来处理客户端请求,并且连接的数据读取和处理都在这个QTcpSocket的槽函数中完成。尽管这样的方式可以工作,但是如果同时有多个客户端连接时,可能会阻塞主线程,影响服务器的响应性能。

图片

根据您提供的代码,`server` 类似乎是一个用于管理服务器的类。如果您想要在服务器端支持图片传输的功能,那么您需要对服务器的代码进行适当的调整。

**发送图片到服务器:**

当客户端发送图片时,服务器需要接收图片数据,然后根据接收的数据进行处理。您可以将图片数据保存在数据库中,也可以直接将图片保存在服务器的文件系统中。

以下是向服务器发送图片数据的大致步骤:

1. 在客户端,将图片加载到 `QImage` 对象中,然后将图像数据编码为字节数组。

2. 将图片字节数组发送到服务器,您可以使用与发送消息类似的方法。

3. 在服务器端,当接收到数据后,判断数据是否为图片数据,然后将图片数据保存到数据库或文件系统中。

**从服务器接收图片:**

当客户端需要接收图片时,服务器需要从数据库或文件系统中获取图片数据,然后将图片数据发送给客户端。

以下是从服务器接收图片数据的大致步骤:

1. 在客户端,向服务器发送请求以获取图片数据。

2. 在服务器端,根据客户端的请求,从数据库或文件系统中获取图片数据。

3. 将图片数据编码为字节数组,然后发送给客户端。

4. 在客户端,接收到图片数据后,将字节数组解码为 `QImage` 对象,并在用户界面上显示图片。

需要注意的是,上述步骤涉及到很多细节,包括数据传输的协议、图片的存储方式等。您可能需要对服务器的代码进行适当的修改和扩展,以支持图片的发送和接收功能。另外,要确保在发送和接收图片数据时进行适当的错误处理,以保证系统的稳定性和可靠性。

头文件和库都很重要

currentImageName = fileName.right(fileName.size()
                                                 - fileName.lastIndexOf('/')-1);
                             
                             
                             假设你有一个完整的文件路径,类似于 "/path/to/your/image.jpg"。你想从这个路径中提取出文件名,即 "image.jpg"。

这段代码的目的就是实现这个操作。让我们逐步解释它:

fileName.lastIndexOf('/'): 这一部分会找到最后一个斜杠字符 '/' 在文件路径中的位置。对于 "/path/to/your/image.jpg",这个表达式将返回最后一个斜杠 '/' 在字符串中的索引位置,即 13。

fileName.size() - fileName.lastIndexOf('/') - 1: 这一部分计算了文件名部分的长度。fileName.size() 返回整个字符串的长度,然后从中减去最后一个斜杠后面的索引位置,再减去 1,就得到了文件名部分的长度。在我们的例子中,计算是 24 - 13 - 1 = 10。

fileName.right(...): 这个函数从字符串的右侧开始截取一段子字符串。括号中的参数表示要截取的子字符串的长度。所以,fileName.right(10) 会返回 "image.jpg"。
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_6);

// 写入数据到 sendOut
sendOut << qint64(0) << qint64(0) << imageData;

在这里,sendOut 是一个 QDataStream 对象,它与 outBlock 缓冲区相关联,可以通过串行化操作将数据写入 outBlock。
sendOut << qint64(0) << qint64(0) << imageData;

sendOut << qint64(0):将一个 qint64 类型的整数值 0 写入到 sendOut 数据流中,表示此处先占位用来存放总大小信息。qint64 是 Qt 提供的一种整数类型,用来表示有符号的 64 位整数。

sendOut << qint64(0):同样地,将另一个 qint64 类型的整数值 0 写入到 sendOut 数据流中,表示此处占位用来存放图像大小信息。

sendOut << imageData:将图像数据(经过编码后的)写入到 sendOut 数据流中。在这里,imageData 是一个 QByteArray,它包含了图像的编码数据,即实际要传输的图像内容。

outBlock 存储了完整的图片数据,而 message 只包含了关于图片的一些元信息,比如图片的文件名。具体来说:

outBlock 是一个数据缓冲区,其中存储了完整的图片数据。
message 则是用于聊天记录的消息字符串,其中包含了关于图片的元信息,如文件名。
所以message得被接收并识别为文本并显示到聊天记录内:在接收端,需要解析接收到的消息,识别出是否是图片消息,并将图片消息的元信息(如文件名)显示在聊天记录中。

你是通过将图像数据作为纯文本字符串发送,并希望在聊天记录中显示这些图像。由于在 HTML 中直接显示纯文本图像数据并不简单,因此需要将纯文本图像数据转换为 Base64 编码,并以图像标签的形式嵌入到 HTML 中。

聊天界面图片通信的实现【失败】:

本来以outBlock传输无法解决在另外的客户端显示,但是为了以聊天记录的形式显示,采用Base64编码,换成string的形式传输,并在qlabel显示,但是还是没能实现显示。【合理怀疑是通信的问题】

client->guichatwindow.cpp:

void GuiChatWindow::getchathistory()
QString message = QString("chat_history##%1##%2").arg(user.id).arg(otheruser.id);
        tcpSocket->write(message.toUtf8());
        tcpSocket->flush();
        connect(tcpSocket,&QTcpSocket::readyRead,[=](){
            QByteArray buffer = tcpSocket->readAll();
            if(QString(buffer).section("##",0,0)==QString("chat_history_ok"))
            {
                QString chatshow = "";
                int num = QString(buffer).section("##",1,1).toInt();
                for(int rownum = 0;rownum < num ;rownum++)
                {
                    QDateTime time = QDateTime::fromString( QString(buffer).section("##",rownum*3+2,rownum*3+2),"yyyy-MM-dd hh:mm:ss.zzz");
                    qDebug()<<time.toString();
                    QString timeshow = time.toString("MM-dd hh:mm:ss");
                    qDebug()<<timeshow;
                    QString idshow = "";

                    if(QString(buffer).section("##",rownum*3+3,rownum*3+3).toInt()==user.id)
                    {//我自己发送的消息
                        idshow = " 我:";
                    }
                    else
                    {
                        idshow =" "+ otheruser.name + ":";
                    }
//                    QString content = QString(buffer).section("##", rownum * 3 + 4, rownum * 3 + 4);
//                    if (content.startsWith("图片:"))
//            {
//                        QString imageData = QString(buffer).section("##", rownum * 3 + 5, rownum * 3 + 5);
//                        QByteArray decodedImageData = QByteArray::fromBase64(imageData.toLatin1());

//                        QImage receivedImage;
//                        receivedImage.loadFromData(decodedImageData);

//                        // 在聊天记录中显示图片
//                         chatshow += getImageMessageContent(receivedImage) + "\n";
//                    }
//                    else
//                    {
                        // 非图片消息
                        chatshow = "("+timeshow+")" + idshow + QString(buffer).section("##",rownum*3+4,rownum*3+4) +"\n" + chatshow;
                    //}

                }
                ui->textBrowser_msgShow->setHtml(chatshow);
            }
            else if(QString(buffer).section("##",0,0)==QString("chat_history_error"))
            {
                ui->textBrowser_msgShow->setText("无消息记录");
            }
其他函数
//图片
void GuiChatWindow::openFile()
{
    fileName = QFileDialog :: getOpenFileName(this);
    if(!fileName.isEmpty())
    {
        currentImageName = fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);

        ui->clientStatusLabel->setText(tr("打开 %1 成功!").arg(currentImageName));
        ui->sendButton->setEnabled(true);
    }

}
void GuiChatWindow::send()
{
    startTransfer();
}

QByteArray GuiChatWindow::getImageData(const QImage &image)
{
    QByteArray imageData;
    QBuffer buffer(&imageData);
    image.save(&buffer, "png");
    imageData = imageData.toBase64();

    return imageData;

}


void GuiChatWindow::startTransfer()
{
//    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
//    sendOut.setVersion(QDataStream::Qt_6_5);

//    QImage image(fileName);
//    QString imageData = getImageData(image);
//    //占位
//    sendOut<<qint64(0)<<qint64(0)<<imageData;// 保留总大小信息空间、图像大小信息空间,然后输入图像信息

//    totalBytes += outBlock.size();
//    sendOut.device()->seek(0);

//    sendOut<<totalBytes<<qint64(outBlock.size()-sizeof(qint64)*2);
    tcpSocket->abort();//取消已有链接
    tcpSocket->connectToHost(hostip, hosthost);//链接服务器

    if(!tcpSocket->waitForConnected(30000))
    {
        QMessageBox::warning(this, "Warning!", "网络错误", QMessageBox::Yes);
        this->close();
    }
    else{
    qDebug() << "File Name:" << fileName;

    QImage image(fileName);
    QByteArray imageData = getImageData(image);

    // 发送图像数据
    QString nowstr = QDateTime::currentDateTime().currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
    QString msg_p = QString("chat_send##%1##%2##%3##%4").arg(nowstr).arg(user.id).arg(otheruser.id).arg("图片:"+imageData.toBase64());
    //qDebug() << "msg_p:" << msg_p;//成功Base64编码
    tcpSocket->write(msg_p.toUtf8());
    tcpSocket->flush();

    //label
//    QPixmap pixmap = QPixmap::fromImage(image);
//    imageLabel->setPixmap(pixmap);
//    imageLabel->setScaledContents(true);


    bool success = pixmap.load(fileName);

    if (success) {
        // 图片加载成功,可以将 pixmap 设置给 QLabel 或进行其他操作
        ui->imageLabel->setPixmap(pixmap);
        ui->imageLabel->setScaledContents(true); // 让图片自适应 Label 大小
    } else {
        qDebug() << "Failed to load image from" << fileName;
        QFile file(fileName);
        pixmap.loadFromData(file.readAll());

    }





//    //添加到聊天记录中
//    QString imageMessage = "图片:"+currentImageName;//其实可以只要这一小节
//    //imageMessage += "##" + QString(imageData); // 将二进制数据转换为QString
//    //QString nowstr = QDateTime::currentDateTime().currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
//    QString message = QString("chat_send##%1##%2##%3##%4").arg(nowstr).arg(user.id).arg(otheruser.id).arg(imageMessage);
//    qDebug()<<message;
//    tcpSocket->write(message.toUtf8());
//    tcpSocket->flush();


//    tcpSocket->write(outBlock);

//    outBlock.resize(0);
    ui->clientStatusLabel->setText(tr("传送文件 %1 成功").arg(currentImageName));
//    totalBytes = 0;

    ui->sendButton->setEnabled(false); // 禁用发送按钮,直到新文件被选择

    // 清除文件名,等待新的文件选择
    //fileName.clear();
    //currentImageName.clear();
    }



}


void GuiChatWindow::on_openButton_clicked()
{
    ui->clientStatusLabel->setText(tr("状态:等待打开文件!"));
    openFile();

}

// 发送按钮
void GuiChatWindow::on_sendButton_clicked()
{
    send();
}


QString GuiChatWindow::getImageMessageContent(const QImage &image)
{
    QPixmap pixmap = QPixmap::fromImage(image);
    return createImageHtml(pixmap);
}

QString GuiChatWindow::createImageHtml(const QPixmap &pixmap)
{
    QString imageBase64 = imageToBase64(pixmap);
    return QString("<img src='data:image/png;base64,%1'>").arg(imageBase64);
}

QString GuiChatWindow::imageToBase64(const QPixmap &pixmap)
{
    QByteArray byteArray;
    QBuffer buffer(&byteArray);
    buffer.open(QIODevice::WriteOnly);
    pixmap.save(&buffer, "PNG");
    return QString::fromLatin1(byteArray.toBase64().data());
}

猜你喜欢

转载自blog.csdn.net/m0_62153438/article/details/132600200