一百一十九、简易版网络聊天室实现
119.1 QT实现连接TCP协议
119.1.1 基于TCP的通信流程

 
119.1.2 QT中实现服务器过程
- 使用QTcpServer实例化一个服务器对象
- 设置监听状态,通过listen()函数,可以监听特定的主机,也可以监听所有客户端,端口号可以是系统自动分配的,也可以是指定端口号。
- 如果有客户端发来连接请求,那么服务器就自动发射一个newConnection信号,我们就可以将该信号连接到自定义的槽函数中处理该客户端的操作。
- 此时服务器和客户端已经建起了连接,可以调用nextPandingConnection获取最新连接的客户端套接字,可以将该套接字存放在服务器的客户端容器中。
- 当客户端发来数据时,该客户端就会自动发射一个readyRead信号,我们可以将该信号连接到自定义的槽函数中读取客户端数据。
- 通过read(),readLine(),readAll()读取套接字里的数据,可以通过write()往套接字中写入数据
- 关闭服务器使用close即可
119.1.3 QT中实现客户端过程
- 使用QTcpSocket实例化一个客户端对象
- 将客户端连接到服务器,使用connectToHost, 给定主机地址,端口号
- 如果连接成功,该客户端会自动发射connected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。
- 此时,客户端和服务器已经建立了连接,如果服务端发来数据,那么该客户端会自定发射readyRead信号,可以将该信号连接到自定义的槽函数中读取服务端中数据
- 可以使用read() readLine() readAll()读取套接字中数据,使用write往套接字中写入数据
- 客户端断开与服务器的连接,使用disConnectFromHost, 如果断开成功,客户端会自动发射disconnected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。
119.2 服务器端
119.2.1 UI 界面

119.2.2 qt_server.h
#ifndef QT_SERVER_H
#define QT_SERVER_H
#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QTcpSocket>
#include <QMessageBox>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Qt_Server; }
QT_END_NAMESPACE
class Qt_Server : public QWidget
{
    Q_OBJECT
public:
    Qt_Server(QWidget *parent = nullptr);
    ~Qt_Server();
private slots:
    void on_startBtn_clicked();
public slots:
    //  自定义的 处理连接的槽函数声明
    void newConnection_slot();
    //  自定义的 处理接收数据的槽函数声明
    void readyRead_slot();
private:
    Ui::Qt_Server *ui;
    QTcpServer *server;
    QList<QTcpSocket *> socketList;
};
#endif // QT_SERVER_H
119.2.3 qt_server.cpp
#include "qt_server.h"
#include "ui_qt_server.h"
Qt_Server::Qt_Server(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Qt_Server)
{
    ui->setupUi(this);
    //  给服务器指针创建空间
    server = new QTcpServer(this);
//    socketList = new QList<>();
}
Qt_Server::~Qt_Server()
{
    delete ui;
}
//  点击启动服务器按钮对应的槽函数实现
void Qt_Server::on_startBtn_clicked()
{
    if(ui->startBtn->text() == "启动服务器"){
        //  获取UI界面上输入的端口号
        quint16 port = ui->portEdit->text().toUInt();
        //  使服务器进入监听状态,返回值是bool类型
        if(server->listen(QHostAddress::Any,port)){
            QMessageBox::information(this,"提示","服务器启动成功");
        }else{
            QMessageBox::critical(this,"错误","服务器启动失败");
        }
        //  此时服务器已经进入监听状态,如果有客户端发来链接请求,
        //  则服务器会自动发射一个newConnection信号,需要将该信号连接到自定义的槽函数中处理连接的套接字
        connect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);
        ui->startBtn->setText("已启动服务器");
        ui->startBtn->setStyleSheet("background-color:green");
    }else{
        server->close();
        disconnect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);
        ui->startBtn->setStyleSheet("background-color:white");
        ui->startBtn->setText("启动服务器");
    }
}
//  自定义的 处理连接的槽函数声明
void Qt_Server::newConnection_slot()
{
    qDebug() << "有新客户的连接";
    //  获取最新连接的客户端套接字,并放入容器中,此时客户端与服务器已经建立起连接
    QTcpSocket *s = server->nextPendingConnection();
    socketList.push_back(s);
    //  如果有客户端发送数据,那么此客户端就会自动发射readyRead信号
    //  需要将该信号与自定义的处理接收数据的槽函数连接
    connect(s, &QTcpSocket::readyRead, this, &Qt_Server::readyRead_slot);
}
//  自定义的 处理接收数据的槽函数实现
void Qt_Server::readyRead_slot()
{
    //  移除无效的客户端
    //  遍历所有的客户端
    for(int i=0; i<socketList.count(); i++){
        //  判断每个客户端的状态,返回值是枚举类型
        //  socketList.at(i)->state();
        if(0 == socketList.at(i)->state()){
            //  移除当前客户端,通过下标删除
            socketList.removeAt(i);
        }
    }
    //  遍历容器,找到有需要读取数据的客户端
    for(int i=0; i<socketList.count(); i++){
        //  如果当前客户端的 有效字节数 不为0,代表当前客户端有需要读取的数据
        if(0 != socketList.at(i)->bytesAvailable()){
            //  读取客户端中的数据
            QByteArray msg = socketList.at(i)->readAll();
            //  将读取的数据放到UI界面上
            ui->listWidget->addItem(QString::fromLocal8Bit(msg));
            //  将数据发送给所有客户端
            for (int j=0; j<socketList.count(); j++) {
                socketList.at(i)->write(msg);
            }
        }
    }
}
119.2.4 main.cpp
#include "qt_server.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Qt_Server w;
    w.show();
    return a.exec();
}
119.3 客户端
119.3.1 UI 界面

119.3.2 qt_client.h
#ifndef QT_CLIENT_H
#define QT_CLIENT_H
#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QTcpSocket>
#include <QMessageBox>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Qt_Client; }
QT_END_NAMESPACE
class Qt_Client : public QWidget
{
    Q_OBJECT
public:
    Qt_Client(QWidget *parent = nullptr);
    ~Qt_Client();
public slots:
    void connected_slot();
    void readyRead_slot();
private slots:
    void on_connectBtn_clicked();
    void on_msgBtn_clicked();
    void on_disconnectBtn_clicked();
private:
    Ui::Qt_Client *ui;
    //  定一个客户端对象
    QTcpSocket *socket;
    //  定义一个用户名变量
    QString uname;
};
#endif // QT_CLIENT_H
119.3.3 qt_client.cpp
#include "qt_client.h"
#include "ui_qt_client.h"
Qt_Client::Qt_Client(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Qt_Client)
{
    ui->setupUi(this);
    //  给客户端对象创建空间
    socket = new QTcpSocket(this);
    //  初始化UI界面的组件状态
    ui->msgEdit->setEnabled(false);
    ui->msgBtn->setEnabled(false);
    ui->disconnectBtn->setEnabled(false);
}
Qt_Client::~Qt_Client()
{
    delete ui;
}
void Qt_Client::connected_slot()
{
    //  告诉服务器我进来了
    QString msg = uname + " 来了,快接驾";
    //  将这个消息发送给服务器
    socket->write(msg.toLocal8Bit());
    //  此时说明服务器与客户端已经建立连接
    //  现在将UI界面的组件状态修改一下
    ui->msgEdit->setEnabled(true);
    ui->msgBtn->setEnabled(true);
    ui->disconnectBtn->setEnabled(true);
    ui->unameEdit->setEnabled(false);
    ui->ipEdit->setEnabled(false);
    ui->portEdit->setEnabled(false);
    ui->connectBtn->setEnabled(false);
    //  如果服务器发来数据,客户端会自动发射readyRead信号
    //  因此需要将readyRead信号连接到自定义的槽函数
    //  因为只需要连接一次,所以也是该在构造函数中写连接函数
    connect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);
}
void Qt_Client::readyRead_slot()
{
    //  走到了这一步,说明服务器给客户端发送了消息,现在需要进行读取
    QByteArray msg = socket->readAll();
    //  将这个数据放到UI界面的消息显示框中
    ui->listWidget->addItem(QString::fromLocal8Bit(msg));
}
//  连接服务器按钮 对应的槽函数
void Qt_Client::on_connectBtn_clicked()
{
    //  获取UI界面的IP和PORT,还有uname
    QString ip = ui->ipEdit->text();
    quint16 port = ui->portEdit->text().toUInt();
    uname = ui->unameEdit->text();
    //  使客户端连接服务器
    socket->connectToHost(ip, port);
    //  判断客户端是否成功连接服务器,成功则客户端会自动发射connected信号
    //  将该信号连接到自定义的槽函数中
    //  因为只需要连接一次,所以连接函数应该写在构造函数中
    connect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);
}
void Qt_Client::on_msgBtn_clicked()
{
    //  获取UI界面上输入的内容
    QString msg = uname + " : " + ui->msgEdit->toPlainText();
    //  将消息发送给服务器
    socket->write(msg.toLocal8Bit());
}
void Qt_Client::on_disconnectBtn_clicked()
{
    QString msg = uname + " 走咯,我还会再回来的";
    //  将消息发送给服务器
    socket->write(msg.toLocal8Bit());
    //  断开链接
    socket->close();
    disconnect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);
    disconnect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);
    //  更改UI界面的组件状态
    ui->msgEdit->setEnabled(false);
    ui->msgBtn->setEnabled(false);
    ui->disconnectBtn->setEnabled(false);
    ui->unameEdit->setEnabled(true);
    ui->ipEdit->setEnabled(true);
    ui->portEdit->setEnabled(true);
    ui->connectBtn->setEnabled(true);
}
119.3.4 main.cpp
#include "qt_client.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Qt_Client w;
    w.show();
    return a.exec();
}
一百二十、QT连接数据库
120.1 QT将数据库分为三个层次
- 数据库驱动层:QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorBase、QSqlDriverPlugin
- sql接口层:QSqlDatabase、QSqlQuery、QSqlRecord、QSqlError
- 用户接口层:提供一些模型QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel
120.2 实现数据库操作的相关方法
1.	添加数据库:[static] QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver *driver, const QString &connectionName = QLatin1String(defaultConnection))                                                                
		QSQLITE SQLite version 3 or above
2.	设置数据库名称:void QSqlDatabase::setDatabaseName(const QString &name)
3.	包含数据库:bool QSqlDatabase::contains(const QString &connectionName = QLatin1String(defaultConnection))
4.	打开数据库:bool QSqlDriver::open(const QString &db)
5.	关闭数据库:void QSqlDatabase::close()
6.	错误信息:QSqlError QSqlDatabase::lastError()
7.	sql语句执行:构造一个QSqlQuery类对象,调用其成员函数exec,执行sql语句:bool QSqlQuery::exec(const QString &query)
8.	bool QSqlQuery::next():遍历查询结果的函数
120.3 示例 :
120.3.1 UI 界面

120.3.2 database.h
#ifndef DATABASE_H
#define DATABASE_H
#include <QWidget>
#include <QSqlDatabase> //  数据库管理类
#include <QSqlQuery>    //  数据库操作类
#include <QSqlRecord>   //  数据库记录类
#include <QSqlError>    //  数据库错误类
#include <QMessageBox>  //  消息对话框类
#include <QtDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class DataBase; }
QT_END_NAMESPACE
class DataBase : public QWidget
{
    Q_OBJECT
public:
    DataBase(QWidget *parent = nullptr);
    ~DataBase();
private slots:
    void on_addBtn_clicked();
    void on_showAllEdit_clicked();
    void on_showEdit_clicked();
    void on_deleteBtn_clicked();
    void on_deleteAllBtn_clicked();
private:
    Ui::DataBase *ui;
    //  实例化一个数据库对象
    QSqlDatabase db;
};
#endif // DATABASE_H
120.3.3 database.cpp
#include "database.h"
#include "ui_database.h"
DataBase::DataBase(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::DataBase)
{
    ui->setupUi(this);
    //  判断数据库是否存在
    if(!db.contains())
    {
        //  不存在,则创建数据库
        db = QSqlDatabase::addDatabase("QSQLITE");   //  表示数据库驱动为sqlite3
        //  给刚创建的数据库起名
        db.setDatabaseName("stuInfo.db");
        //  提示用户:数据库创建成功
        QMessageBox::information(this,"提示","数据库创建成功");
    }
    //  打开数据库
    if(!db.open()){
        QMessageBox::critical(this,"错误","数据库无法打开");
        return;
    }
    //  创建数据库表
    QSqlQuery table;
    //  Sql语句
    QString sql = "create table if not exists stu_info_table("
                  "id integer primary key autoincrement,"
                  "numb integer,"
                  "name varchar(20),"
                  "sex varchar(4),"
                  "score integer);";
    if(table.exec(sql)){
        QMessageBox::information(this,"提示","数据库学生表创建成功");
    }else{
        QMessageBox::critical(this,"错误","数据库学生表创建失败");
    }
}
DataBase::~DataBase()
{
    delete ui;
}
//  添加
void DataBase::on_addBtn_clicked()
{
    //  获取UI界面上的信息
    int num = ui->numEdit->text().toUInt();
    QString name = ui->nameEdit->text();
    QString sex = ui->sexEdit->text();
    int score = ui->scoreEdit->text().toUInt();
    //  保证用户输入完整信息
    if(num == 0 || name.isEmpty() || sex.isEmpty() || score == 0){
        QMessageBox::warning(this,"警告","请完善信息");
        return;
    }
    //  将信息存放到数据库学生表中
    QSqlQuery query;
    //  SQL语句
    QString sql = QString("insert into stu_info_table (numb, name, sex, score) "
                          "values(%1,'%2','%3',%4)")
            .arg(num).arg(name).arg(sex).arg(score);
    //  执行SQL语句
    if(query.exec(sql)){
        QMessageBox::information(this,"提示","数据添加成功");
    }else{
        QMessageBox::critical(this,"错误","数据添加失败");
    }
    DataBase::on_showAllEdit_clicked();
}
//  查看所有
void DataBase::on_showAllEdit_clicked()
{
    ui->tableWidget->clear();
    //  将信息存放到数据库学生表中
    QSqlQuery query;
    //  SQL语句
    QString sql = QString("select * from stu_info_table");
    //  执行SQL语句
    if(query.exec(sql)){
        //  将数据库的内容放到UI界面上
        int i = 0;  //  记录行号
        //  用next()遍历
        while (query.next()) {
            for(int j=0; j<query.record().count(); j++){
                ui->tableWidget->setItem(i,j,new QTableWidgetItem(query.value(j+1).toString()));
            }
            i++;
        }
    }
}
//  查询某个学生
void DataBase::on_showEdit_clicked()
{
    QString name = ui->nameEdit_2->text();
    if(name.isEmpty()){
        QMessageBox::information(this, "提示", "请输入要查询的学生姓名");
        return;
    }
    ui->tableWidget->clear();
    QSqlQuery query;
    QString sql = QString("select * from stu_info_table where name='%1';").arg(name);
    if(query.exec(sql)){
        int i=0;
        while (query.next()) {
            //            qDebug() << i;
            for(int j=0; j<query.record().count(); j++){
                ui->tableWidget->setItem(i, j, new QTableWidgetItem(query.value(j+1).toString()));
            }
            i++;
        }
    }else{
        QMessageBox::warning(this, "警告", "查询错误");
        return;
    }
}
void DataBase::on_deleteBtn_clicked()
{
    QString name = ui->nameEdit_2->text();
    if(name.isEmpty()){
        QMessageBox::information(this, "提示", "请输入要删除的学生姓名");
        return ;
    }
    QString sql = QString("delete from stu_info_table where name='%1';").arg(name);
    QSqlQuery query;
    if(query.exec(sql)){
        QMessageBox::information(this, "提示", "删除成功");
        DataBase::on_showAllEdit_clicked();
    }else{
        QMessageBox::warning(this, "警告", "删除失败");
        DataBase::on_showAllEdit_clicked();
    }
    return ;
}
void DataBase::on_deleteAllBtn_clicked()
{
    QString sql = QString("delete from stu_info_table");
    QSqlQuery query;
    if(query.exec(sql)){
        QMessageBox::information(this, "提示", "删除成功");
        DataBase::on_showAllEdit_clicked();
    }else{
        QMessageBox::warning(this, "警告", "删除失败");
        DataBase::on_showAllEdit_clicked();
    }
    ui->tableWidget->clear();
    return ;
}
120.3.4 main.cpp
#include "database.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    DataBase w;
    w.show();
    return a.exec();
}



















