代码链接:https://download.csdn.net/download/qq_53198674/88549159?spm=1001.2014.3001.5503
本次是基于Qt Creater开发的一个聊天室,因为之前用C语言写过一个聊天室,但是没有界面,就像是一个毛坯房,于是在学习了一段时间的Qt之后发现,Qt也同样能开发服务器和客户端,于是就像补一下前面的坑。
下面是展示环节👇
客户端:
服务器:
怎么样,有没有一种给毛坯房贴上瓷砖的感觉呢?
言归正传:这次的聊天室主要实现了:发消息、查看聊天记录、广播等功能(在此为喜欢二次开发的小伙伴提供思路:发送表情包、进行文件传输),运行后需选择IP以及端口号和用户名,以便区分发送者(这里附上聊天截图)
连接前是无法点击发送和消息记录按钮的,连接后无法更改IP地址以及端口和用户名,同时可以点击发送和消息记录。而连接后服务器会提示新连接已建立,当用户断开后同样会提示已断开(这里附上图片)
连接后会自动创建库和建表(即数据库的创建操作和建表操作)每发送一条消息就会在本地数据库中进行记录(即数据库的插入操作),同时可根据下拉框选择查询方式(模糊查询)
可根据三种情况进行记录的查询
这里附上主要代码:
服务器:
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
setWindowFlags(Qt::WindowStaysOnTopHint);
// 创建服务器对象
server = new QTcpServer(this);
// 开启监听服务
bool result = server->listen(QHostAddress::Any,8887);
if(result == false)
{
QMessageBox::critical(this,"错误",server->errorString());
return;
}
else
{
printMessage("服务器开始监听,端口号8887");
connect(server,SIGNAL(newConnection()),
this,SLOT(newConnectionSlot()));
}
}
Dialog::~Dialog()
{
// 如果监听服务正在开启中,则关闭
if(server->isListening())
server->close();
delete ui;
}
void Dialog::printMessage(QString msg)
{
QString dt = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->textBrowser->append(dt);
ui->textBrowser->append(msg);
ui->textBrowser->append("");
}
void Dialog::newConnectionSlot()
{
// 获得服务器端的连接类对象
socket = server->nextPendingConnection();
QTextStream output(socket);
if(list.size() == 20)
{
qDebug() << "聊天室满员";
output << QString("聊天室已达人数上限,连接失败!");
socket->close();
return;
}
list.append(socket);
// 断开连接检测的信号槽
connect(socket,SIGNAL(disconnected()),
this,SLOT(disconnectedSlot()));
// 接受消息信号槽的连接
connect(socket,SIGNAL(readyRead()),
this,SLOT(readyReadSlot()));
// Qt称呼对面的绿蛋为peer(伙伴)
// 获得peer得IP地址和Port
QString ip = socket->peerAddress().toString();
quint16 port = socket->peerPort();
QString text = "新连接已建立 ";
text = text.append('\n').append("IP:") + ip.append(" Port:") + QString::number(port);
printMessage(text);
}
void Dialog::disconnectedSlot()
{
// 拿到发射者
socket = (QTcpSocket*)sender();
for(int i = 0;i<list.size();i++)
{
if(socket == list.at(i))
{
// 获得peer得IP地址和Port
QString ip = socket->peerAddress().toString();
quint16 port = socket->peerPort();
QString text = "连接已断开 ";
text = text.append('\n').append("IP:") + ip.append(" Port:") + QString::number(port);
printMessage(text);
}
}
}
void Dialog::readyReadSlot()
{
for(int i=0;i<list.size();i++)
{
if(list.at(i)->isReadable() && list.at(i)->bytesAvailable()>0)
{
qDebug() << "第" << i << "个客户端发的消息";
// 方法一:读取所有内容
QByteArray buffer = list.at(i)->readAll();
for(int m=0;m<list.size();m++)
{
// 转发
list.at(m)->write(buffer);
}
}
}
}
客户端:
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// 创建播放类对象
movie = new QMovie(":/new/prefix1/res/yes (1).gif");
// 给组件设置播放内容
ui->label_5->setMovie(movie);
// 开始播放
movie->start();
ui->pushButtonSend->setEnabled(false);
ui->pushButtonHistory->setEnabled(false);
connect(ui->pushButtonHistory,SIGNAL(clicked()),
this,SLOT(openHistorySlot()));
connect(ui->pushButtonConn,SIGNAL(clicked()),
this,SLOT(btnConnClickedSlot()));
connect(ui->pushButtonSend,SIGNAL(clicked()),
this,SLOT(btnSendClickedSlot()));
client = new QTcpSocket(this);
// 连接状态信号槽
connect(client,SIGNAL(connected()),
this,SLOT(connectedSlot()));
connect(client,SIGNAL(disconnected()),
this,SLOT(disconnectedSlot()));
connectToDB();
}
Dialog::~Dialog()
{
// 解除信号槽的连接
disconnect(client,SIGNAL(disconnected()),
this,SLOT(disconnectedSlot()));
// 如果连接则断开
if(client->isOpen())
client->close();
delete ui;
}
void Dialog::btnConnClickedSlot()
{
// 先获得用户输入的IP地址和端口号Port
QString ip = ui->lineEditIP->text();
int port = ui->spinBoxPort->value();
name = ui->lineEditName->text();
// TODO 验证IP有效性
// 验证IP有效性
QRegularExpression ipRegex("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$");
QRegularExpressionMatch match = ipRegex.match(ip);
if (!match.hasMatch()) {
QMessageBox::warning(this, "错误", "请输入有效的IP地址");
return;
}
if(name == "")
{
QMessageBox::warning(this,"提示","请输入用户名!");
return;
}
// 接受消息信号槽的连接
connect(client,SIGNAL(readyRead()),
this,SLOT(readyReadSlot()));
// 连接到服务器
client->connectToHost(ip,port);
}
void Dialog::btnSendClickedSlot()
{
QString dt = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//获得用户输入的消息
QString msg = ui->lineEditSend->text();
msg = dt + '\n' + name + ":" + msg + '\n';
if(msg == "")
{
QMessageBox::warning(this,"提示","请输入内容!");
return;
}
// 方法一:使用QByteArray
QByteArray buffer = msg.toUtf8();
client->write(buffer);
// 清空输入框
ui->lineEditSend->clear();
}
void Dialog::connectedSlot()
{
// 屏蔽按钮和输入
ui->pushButtonConn->setEnabled(false);
ui->lineEditIP->setEnabled(false);
ui->lineEditName->setEnabled(false);
ui->spinBoxPort->setEnabled(false);
ui->pushButtonConn->setText("已连接");
// 恢复发送按钮
ui->pushButtonSend->setEnabled(true);
ui->pushButtonHistory->setEnabled(true);
}
void Dialog::disconnectedSlot()
{
// 恢复按钮和输入
ui->pushButtonConn->setEnabled(true);
ui->lineEditIP->setEnabled(true);
ui->lineEditName->setEnabled(true);
ui->spinBoxPort->setEnabled(true);
ui->pushButtonConn->setText("连接");
// 屏蔽发送按钮
ui->pushButtonSend->setEnabled(false);
ui->pushButtonHistory->setEnabled(false);
// 弹窗提示
QMessageBox::warning(this,"提示",client->errorString());
}
void Dialog::readyReadSlot()
{
// 方法一:读取所有内容
QByteArray buf = client->readAll();
QString text(buf); // 转换为字符串
qDebug() << text;
QStringList parts = text.split('\n');
if (parts.size() >= 2) {
QString datetime = parts[0]; // 第一部分:日期+时间
QString nameAndMessage = parts[1]; // 第三部分:姓名和消息
// 进一步拆分姓名和消息
QStringList nameAndMessageParts = nameAndMessage.split(":");
if (nameAndMessageParts.size() >= 2) {
QString name = nameAndMessageParts[0]; // 姓名
QString message = nameAndMessageParts[1]; // 消息
// 存入数据库-------------------
// 预处理的SQL语句
QString sql = "INSERT INTO group_history VALUES(?,?,?);";
// 先预处理SQL语句
QSqlQuery sq;
sq.prepare(sql);
// 绑定参数
sq.addBindValue(datetime);
sq.addBindValue(name);
sq.addBindValue(message);
// 执行SQL语句
if(!sq.exec())
{
// 获得错误信息对象
QSqlError info = sq.lastError();
// 获得错误信息文本
QString text = info.text();
QMessageBox::warning(this,"通知",text);
}
}
}
// 输出
ui->textBrowser->append(text);
}
void Dialog::openHistorySlot()
{
// 创建MyDialog对象
d = new History(this);
d->show();
// 屏蔽按钮
// ui->pushButtonHistory->setEnabled(false);
}
void Dialog::connectToDB()
{
// 获得数据库连接对象
db = QSqlDatabase::addDatabase("QSQLITE");
// 设置数据库名称
db.setDatabaseName("chat_history.db");
// 打开数据库连接
if(db.open())
{
qDebug() << "数据库连接成功";
createTable();
}
else
{
// 获得错误信息对象
QSqlError info = db.lastError();
// 获得错误信息文本
QString text = info.text();
// 展示
QMessageBox::critical(this,"错误",text);
}
}
void Dialog::createTable()
{
QString sql = QString("CREATE TABLE group_history(\
time TEXT,\
name TEXT,\
message TEXT\
);");
// 数据库操作类对象
QSqlQuery sq;
// 执行SQL语句
if(sq.exec(sql))
{
qDebug() << "创建数据表成功";
}
else
{
// 获得错误信息对象
QSqlError info = sq.lastError();
// 获得错误信息文本
QString text = info.text();
qDebug() << "建表失败" << text;
}
}
消息记录:
#include "history.h"
#include "ui_history.h"
History::History(QWidget *parent) :
QDialog(parent),
ui(new Ui::History)
{
ui->setupUi(this);
connect(ui->btnSelect,SIGNAL(clicked()),
this,SLOT(btnSelectSlot()));
}
History::~History()
{
delete ui;
}
void History::selectAll()
{
QString sql = "SELECT * FROM group_history";
QSqlQuery sq;
if(sq.exec(sql))
{
// 清空上次显示
ui->textBrowser->clear();
while (sq.next()) // 向后移动
{
QString time = sq.value("time").toString(); // 按照序号取出
QString name = sq.value("name").toString(); // 按照名字取出
QString message = sq.value("message").toString();
// 组装
QString space = " ";
QString record = time + space + name + space + message;
// 展示
ui->textBrowser->append(record);
}
}
else
{
// 获得错误信息对象
QSqlError info = sq.lastError();
// 获得错误信息文本
QString text = info.text();
QMessageBox::warning(this,"通知",text);
}
}
void History::selectPrecise()
{
if(ui->comboBox->currentText()== "全部")
{
selectAll();
}
else if(ui->comboBox->currentText()== "关键字")
{
QString word = ui->lineEdit->text();
if(word == "")
{
QMessageBox::warning(this,"提示","请输入查询内容!");
return;
}
QString sql = "SELECT * FROM group_history WHERE message LIKE ?";
QSqlQuery sq;
sq.prepare(sql);
sq.addBindValue(word.prepend("%").append("%")); // 前后加通配符
if(sq.exec())
{
// 清空上次显示
ui->textBrowser->clear();
while(sq.next()) // 向后移动
{
QString time = sq.value("time").toString(); // 按照序号取出
QString name = sq.value("name").toString(); // 按照名字取出
QString message = sq.value("message").toString();
// 组装
QString space = " ";
QString record = time + space + name + space + message;
// 展示
ui->textBrowser->append(record);
}
}
else
{
// 获得错误信息对象
QSqlError info = sq.lastError();
// 获得错误信息文本
QString text = info.text();
QMessageBox::warning(this,"通知",text);
}
}
else if(ui->comboBox->currentText()== "日期")
{
QString dt = ui->lineEdit->text();
if(dt == "")
{
QMessageBox::warning(this,"提示","请输入查询内容!");
return;
}
QString sql = "SELECT * FROM group_history WHERE time LIKE ?";
QSqlQuery sq;
sq.prepare(sql);
sq.addBindValue(dt.prepend("%").append("%")); // 前后加通配符
if(sq.exec())
{
// 清空上次显示
ui->textBrowser->clear();
while(sq.next()) // 向后移动
{
QString time = sq.value("time").toString(); // 按照序号取出
QString name = sq.value("name").toString(); // 按照名字取出
QString message = sq.value("message").toString();
// 组装
QString space = " ";
QString record = time + space + name + space + message;
// 展示
ui->textBrowser->append(record);
}
}
else
{
// 获得错误信息对象
QSqlError info = sq.lastError();
// 获得错误信息文本
QString text = info.text();
QMessageBox::warning(this,"通知",text);
}
}
}
void History::btnSelectSlot()
{
selectPrecise();
}
主要用到的库:
#include <QTcpSocket> // 连接类
#include <QTextStream> // 文本流类
#include <QDateTime> // 日期时间类
#include <QRegularExpression> // 正则表达式
#include <QMovie> //播放类
#include <QMessageBox> //弹窗类
#include <QSqlDatabase> // 连接类
#include <QSqlQuery> // 操作类
#include <QSqlError> // 错误信息类
#include <QDebug> // 输出类
#include <QTcpServer> // 服务器类