1.前言
本文包含了目前平台上大多数的文章所采取的本地tcp构建的聊天室项目,也就是同一局域网通信。同时,我在这里提供不同局域网下实现通信的方法,这里用到的方法是客户端发送消息给服务器,然后服务器再发送给另一个局域网的客户,为了减少工作量,我采取的是用群聊的方式同一接收信息。如果需要实现不同局域网的通信,我们得租一个云服务器,这里以华为云示例。文章最后一并展示参考文档与视频链接。
2.本地通信效果展示
1.登录,注册
2.个人界面展示
3.群聊
(为了方便实现不同局域网下的在线人数内容,此处存在小bug)
字体展示后面代码呈上,很简单,这里不做展示。
3.本地通信代码展示
头文件比较乱,加的时候一个个加,提升代码整洁度
1.main.cpp
#include "mainwindow.h"
#include"page.h"
#include <QApplication>
#include<QDebug>
#include "new_login.h"
#include "myself.h"
#include <QtSql/QSqlDatabase>
#include <QSqlQuery>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
//设计数据库名称
db.setDatabaseName("user.db");
//打开数据库
if(db.open())
{
qDebug()<<"数据库开启成功";
}
else
{
qDebug()<<"数据库开启失败";
}
//创建一个存储数据的数据库表
QString cmd = "create table if not exists userinfo (id integer primary key autoincrement ,username varchar(64),password varchar(64),password_plus varchar(64))";
QString cmd2 = QString("insert into userinfo (id,username,password,password_plus) values(%1,'%2','%3','%4')").arg(10000).arg("root").arg("123456").arg("123456");
//执行sql语句
QSqlQuery query;
if(query.exec(cmd))
{
if(query.exec(cmd2))
qDebug()<<"exec 执行成功";
else
qDebug()<<"成功一半";//如果已经出现过执行成功,说明10000id已经写入,这里将不影响
}
else
{
qDebug()<<"exec 执行失败";
}
//Myself x;
new_login x;
x.show();
return a.exec();
}
2.注册功能:
注册功能的实现我使用QT5以上自带的数据库SQLite,你可以尝试使用文本读取,如果只需要聊天功能,可以跳过,直接查看最后的通讯,由于数据库是离线的,只适合用于先注册完后整个文件一起打包,对方才能登陆他数据库中的存储的用户(如果他自己继续注册其实是不影响的)
.h
#ifndef NEW_REGISTER_H
#define NEW_REGISTER_H
#include <QWidget>
#include <QString>
#include <QMessageBox>
#include "mainwindow.h"
namespace Ui {
class new_register;
}
class new_register : public QWidget
{
Q_OBJECT
public:
explicit new_register(QWidget *parent = nullptr);
~new_register();
public slots:
void push_Register_clicked(); //注册按钮按下后触发的事件
void push_Return_clicked(); //返回按钮按下后触发的事件
private:
Ui::new_register *ui;
};
#endif // NEW_REGISTER_H
.cpp
#include "new_register.h"
#include "ui_new_register.h"
#include <QSqlQuery>
new_register::new_register(QWidget *parent) :
QWidget(parent),
ui(new Ui::new_register)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/resource/images/qq.png"));
this->setWindowTitle("注册用户");
// 触发注册按钮的信号槽连接
connect(ui->Register,SIGNAL(clicked()),this,SLOT(push_Register_clicked()));
// 触发返回按钮的信号槽连接
connect(ui->Return,SIGNAL(clicked()),this,SLOT(push_Return_clicked()));
}
new_register::~new_register()
{
delete ui;
}
// 注册按钮触发事件
void new_register::push_Register_clicked()
{
QString username = ui->lineEditname->text();
QString password = ui->lineEditpw->text();
QString password_plus=ui->lineEditpww->text();
QString cmd = QString("insert into userinfo (username,password,password_plus) values('%2','%3','%4')").arg(username) .arg(password) .arg(password_plus);
QString cmd2 = (QString("select id from userinfo where password='%2' and password_plus='%3'").arg(password).arg(password_plus));
QSqlQuery query;
if(query.exec(cmd)&&query.exec(cmd2))
{
while(query.next())
{
QString id = query.value(0).toString();
QMessageBox::information(this, "注册成功",QString("您的id是:'%1'").arg(id));
}
this->close();
}
else
{
QMessageBox::information(this,"注册提示","注册失败");
}
}
// 返回按钮触发事件
void new_register::push_Return_clicked()
{
this->close();
}
.ui
3.登陆功能
.h
#ifndef NEW_LOGIN_H
#define NEW_LOGIN_H
#include <QWidget>
#include <QMessageBox>
#include <QJsonDocument>
#include <QFile>
#include <QDebug>
#include <QJsonObject>
#include <QByteArray>
#include <QToolButton>
#include <QIcon>
namespace Ui {
class new_login;
}
class new_login : public QWidget
{
Q_OBJECT
public:
explicit new_login(QWidget *parent = nullptr);
~new_login();
void read_json(); //读json
void write_json();//写json
void message_init(QString flag1,QString flag2);//根据json内容决定是否填充输入框
signals:
void login(); //登录主界面信号
void close_window(); //关闭登录界面信号
public slots:
void push_clear_clicked(); //重置按钮按下后触发的事件
void push_lg_clicked(); //登录按钮按下后触发的事件
void push_forget_clicked();//按下忘记密码
void push_register_clicked();//按下注册
private:
Ui::new_login *ui;
QString m_username; // 自己设定的账号 这里是照搬照用的两个变量 测试记住账号和密码的 如果
QString m_password; // 自己设定的密码 想使用请自行用数据库的代码把name和pw提取出来赋值
};
#endif // NEW_LOGIN_H
.cpp
#include "new_login.h"
#include "ui_new_login.h"
#include <QSqlTableModel>
#include "new_register.h"
new_login::new_login(QWidget *parent) :
QWidget(parent),
ui(new Ui::new_login)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/resource/images/qq.png"));
this->setWindowTitle("登录ChatRoom");
// connect 3个信号槽
// 触发重置按钮的信号槽连接
connect(ui->push_clear,SIGNAL(clicked()),this,SLOT(push_clear_clicked()));
// 触发登录按钮的信号槽连接
connect(ui->push_lg,SIGNAL(clicked()),this,SLOT(push_lg_clicked()));
// 触发忘记密码
connect(ui->push_forget,SIGNAL(clicked()),this,SLOT(push_forget_clicked()));
// 触发注册
connect(ui->push_register,SIGNAL(clicked()),this,SLOT(push_register_clicked()));
// 发出信号后关闭登录窗口的信号槽连接
connect(this,SIGNAL(close_window()),this,SLOT(close()));
ui->edit_pw->setEchoMode(QLineEdit::Password);//输入的时候就显示圆点
m_username = "tangent";
m_password = "123456";
// 读取json文件
read_json();
}
void new_login::read_json()
{
//打开文件
QFile file(QApplication::applicationDirPath()+"/config.json");
if(!file.open(QIODevice::ReadOnly)) {
qDebug() << "File open failed!";
} else {
qDebug() <<"File open successfully!";
}
QJsonDocument jdc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = jdc.object();
QString save_name_flag=obj.value("SAVE_NAME").toString();
QString save_password_flag=obj.value("SAVE_PASSWORD").toString();
message_init(save_name_flag,save_password_flag);
}
//根据json内容决定是否填充输入框
void new_login::message_init(QString flag1,QString flag2)
{
//qDebug() << flag1 << "^^^" << flag2 ;
if (flag1 == "1")
{
ui->edit_name->setText("tangent");
ui->check_name->setChecked(true);
}
if(flag2 == "1")
{
ui->edit_pw->setText("123456");
ui->check_pw->setChecked(true);
}
}
// 清理输入栏
void new_login::push_clear_clicked()
{
ui->edit_pw->clear();
ui->edit_name->clear();
}
// 登录按钮触发事件
void new_login::push_lg_clicked()
{
/*// 从输入框获取账号
QString name = ui->edit_name->text();
// 从输入框获取密码
QString password = ui->edit_pw->text();
//账号和密码匹配正确
if (name == m_username && password == m_password)
{
emit(login());
emit(close_window());
}
else // 账号或密码错误
QMessageBox::information(this, "Warning","Username or Password is wrong !");*/
QSqlTableModel *model = new QSqlTableModel;
// 从输入框获取账号
QString id = ui->edit_name->text();
int id2=id.toInt();
// 从输入框获取密码
QString password = ui->edit_pw->text();
model->setTable("userinfo");
model->setFilter(QString("id=%1 and password='%3'").arg(id2).arg(password));
model->select();
//检测是否查询到数据
int row = model->rowCount();
if(row>0)
{
Myself * m = new Myself(0,id);
m->show();
this->close();
}
else
{
QMessageBox::information(this, "Warning","UserID or Password is wrong !");
}
delete model;
}
// 忘记密码按钮触发事件
void new_login::push_forget_clicked()
{
findpw * fd = new findpw;
fd->show();
}
// 注册按钮触发事件
void new_login::push_register_clicked()
{
new_register * reg = new new_register;
reg->show();
}
// 更新json文件
void new_login::write_json()
{
QFile file(QApplication::applicationDirPath()+"/config.json");
if(!file.open(QIODevice::WriteOnly)) {
qDebug() << "File open failed!";
} else {
qDebug() <<"File open successfully!";
}
QJsonObject obj;
bool flag = ui->check_name->isChecked();
if(flag == true)
{
obj["SAVE_NAME"] = "1";
}
else
obj["SAVE_NAME"] = "0";
flag = ui->check_pw->isChecked();
if(flag == true)
{
obj["SAVE_PASSWORD"] = "1";
}
else
obj["SAVE_PASSWORD"] = "0";
QJsonDocument jdoc(obj);
file.write(jdoc.toJson());
file.flush();
}
new_login::~new_login()
{
delete ui;
}
config.json
将该文件放入debug的目录下,成功打开文件会显示
.ui
4.个人信息界面
.h
#ifndef MYSELF_H
#define MYSELF_H
#include <QWidget>
#include <QMessageBox>
#include <QJsonDocument>
#include <QFile>
#include "new_login.h"
#include "mainwindow.h"
namespace Ui {
class Myself;
}
class Myself : public QWidget
{
Q_OBJECT
public:
explicit Myself(QWidget *parent = nullptr,QString ID = "");
~Myself();
public slots:
void push_Page_clicked(); //个人信息按钮按下后触发的事件
void push_Chat_clicked(); //进入群聊按钮按下后触发的事件
signals:
void Start_Chat();
private:
Ui::Myself *ui;
QString m_ID;
};
#endif // MYSELF_H
.cpp
#include "myself.h"
#include "ui_myself.h"
Myself::Myself(QWidget *parent,QString ID) :
QWidget(parent),
ui(new Ui::Myself)
{
ui->setupUi(this);
this->m_ID = ID;
this->setWindowIcon(QIcon(":/resource/images/qq.png"));
this->setWindowTitle("用户信息");
// 触发个人信息按钮的信号槽连接
connect(ui->Page,SIGNAL(clicked()),this,SLOT(push_Page_clicked()));
// 触发进入群聊按钮的信号槽连接
connect(ui->Chat,SIGNAL(clicked()),this,SLOT(push_Chat_clicked()));
}
void Myself::push_Page_clicked()
{
page * pa = new page(0,this->m_ID);
pa->show();
pa->setWindowIcon(QIcon(":/resource/images/qq.png"));
}
void Myself::push_Chat_clicked()
{
MainWindow* chat = new MainWindow(0,this->m_ID);
chat->setWindowIcon(QIcon(":/resource/images/qq.png"));
chat->setWindowTitle("相亲相爱一家人");
chat->show();
this->close();
}
Myself::~Myself()
{
delete ui;
}
进入个人信息后:.h
#ifndef PAGE_H
#define PAGE_H
#include <QWidget>
#include <QSqlTableModel>
#include <QMessageBox>
#include <QDebug>
#include <QSqlQuery>
namespace Ui {
class page;
}
class page : public QWidget
{
Q_OBJECT
public:
explicit page(QWidget *parent = nullptr,QString ID = "");
~page();
public slots:
// void updateImage(const QString& name);
void page_exit_clicked();
void page_name_clicked();
void page_person_clicked();
private:
Ui::page *ui;
QString m_ID;
};
#endif // PAGE_H
.cpp
#include "page.h"
#include "ui_page.h"
#include<QLineEdit>
#include<QTextEdit>
page::page(QWidget *parent,QString ID)
: QWidget(parent)
, ui(new Ui::page)
{
ui->setupUi(this);
setWindowTitle("个人信息");
ui->imageLabel->setPixmap(QPixmap(":/resource/images/1.jpg"));
ui->idEdit->setText(ID);
ui->textEdit->setText("这是我的第一个用户");//用同样的方法可以在数据库中加入个性签名的内容 这里不再赘述
QString cmd2 = (QString("select username from userinfo where id=%1").arg(ID));
QSqlQuery query;
if(query.exec(cmd2))
{
qDebug()<<"获取用户名成功";
while(query.next())
{
QString username = query.value(0).toString();
ui->nameEdit->setText(QString("%2").arg(username));
}
}
else
{
qDebug()<<"获取用户名失败";
}
connect(ui->page_exit,SIGNAL(clicked()),this,SLOT(page_exit_clicked()));
connect(ui->page_name,SIGNAL(clicked()),this,SLOT(page_name_clicked()));
connect(ui->page_person,SIGNAL(clicked()),this,SLOT(page_person_clicked()));
}
void page:: page_exit_clicked()
{
this->close();
}
void page::page_name_clicked()
{
}
void page::page_person_clicked()
{
}
page::~page()
{
delete ui;
}
个人界面补充说明:个签和改昵称的操作同样用数据库操作就行,文章末尾会提供数据库增删改查有关内容的资料链接。头像也只有一张哈,可以在文件中吧头像图片名改掉,同时把名称存储到数据库中,同样也方便调用。(自己比较懒,不想写了)
.ui
5.找回密码功能
.h
#ifndef FINDPW_H
#define FINDPW_H
#include <QWidget>
#include <QSqlTableModel>
#include <QMessageBox>
#include <QDebug>
#include <QSqlQuery>
namespace Ui {
class findpw;
}
class findpw : public QWidget
{
Q_OBJECT
public:
explicit findpw(QWidget *parent = nullptr);
~findpw();
public slots:
void push_Findpw_clicked(); //找回密码按钮按下后触发的事件
void push_Return_clicked(); //返回按钮按下后触发的事件
private:
Ui::findpw *ui;
};
#endif // FINDPW_H
.cpp
#include "findpw.h"
#include "ui_findpw.h"
findpw::findpw(QWidget *parent) :
QWidget(parent),
ui(new Ui::findpw)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/resource/images/qq.png"));
this->setWindowTitle("注册用户");
// 触发找回密码按钮的信号槽连接
connect(ui->Findpw,SIGNAL(clicked()),this,SLOT(push_Findpw_clicked()));
// 触发返回按钮的信号槽连接
connect(ui->Return,SIGNAL(clicked()),this,SLOT(push_Return_clicked()));
}
findpw::~findpw()
{
delete ui;
}
void findpw::push_Findpw_clicked()
{
QSqlTableModel *model = new QSqlTableModel;
QString id = ui->lineEditname->text();
QString password_plus=ui->lineEditpww->text();
model->setTable("userinfo");
model->setFilter(QString("id=%1 and password_plus='%3'").arg(id).arg(password_plus));
model->select();
//检测是否查询到数据
int row = model->rowCount();
if(row>0)
{
QString select_sql = (QString("select password from userinfo where id=%1 and password_plus='%3'").arg(id).arg(password_plus));
QSqlQuery sql_query;
if(!sql_query.exec(select_sql))
{
QMessageBox::information(this, "Warning","密码查找失败");
}
else
{
while(sql_query.next())
{
QString password = sql_query.value(0).toString();
QMessageBox::information(this, "密码提示",QString("password:'%2'").arg(password));
}
}
}
else
{
QMessageBox::information(this, "Warning","无此用户!");
}
delete model;
}
// 返回按钮触发事件
void findpw::push_Return_clicked()
{
this->close();
}
.ui
6.核心聊天功能
仅供参考,bug比较多
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QMessageBox>
//---------
#include <QTcpSocket>
#include <QColorDialog>
#include<QTableWidget>
//---------
#include "new_login.h"
#include "myself.h"
#include "page.h"
#include "new_register.h"
#include "findpw.h"
#include <QtSql/QSqlDatabase>
//-------
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr,QString name = "");
~MainWindow();
enum msgType{
Msg,//普通信息
UserEnter,//用户进入
UserLeft//用户离开
};
QString getUserName();//获取用户名
QString getMessage();//获取输入框中的信息
void sendMesssge (msgType type);//根据枚举参数udp向每个端口广播不同信息
void recieveMessage();//根据枚举参数接收udp套接字传递的信息
void userEnter(QString name);//处理用户进入
void userLeft(QString name, QString time);//处理用户离开
signals:
//void nameClicked(const QString& name);
private slots:
void on_send_clicked();//按下了发送键的槽函数
void on_quit_clicked();//按下了退出键的槽函数
//void on_tableWidget_itemClicked(QTableWidgetItem *item);
public slots:
// void showpage(const QString& name);
private:
Ui::MainWindow *ui;
QString userName;//当创建窗口时,用来获取用户名,可以显示在右侧群聊名单上还有传入数据流进行广播
QString m_ID;
quint16 port;//湍口
QTcpSocket * m_t;//tcp套接字
QUdpSocket* udp;//udp套接字
QTableWidget *tableWidget;
};
#endif // MAINWINDOW_H
.cpp 有未删除干净的注释代码,看需求留用。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QList>
#include <QDateTime>
#include<QColorDialog>
#include<QFileDialog>
//--------------
#include <QPixmap>
#include<QTableWidget>
#include"page.h"
//-------------
#include<QTextEdit>
#include "new_login.h"
MainWindow::MainWindow(QWidget *parent, QString name)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
/*-------------
//通过指针创建登录界面类的对象
m_log = new new_login;
//调用登录窗口的show()函数显示登录界面
m_log->show();
// 建立信号槽,到接收到登录界面发来的login()信号后,调用主窗口的show()函数。
connect(m_log,SIGNAL(login()),this,SLOT(show()));
*/
this->m_ID = name;//获得创建窗口时传入的用户ID,这里变量命名有点问题,请忽略
QString cmd = (QString("select username from userinfo where id=%1").arg(m_ID));
QSqlQuery query;
if(query.exec(cmd))
{
qDebug()<<"获取用户名成功";
while(query.next())
{
QString username = query.value(0).toString();
this->userName=(QString("%2").arg(username));
}
}
else
{
qDebug()<<"获取用户名失败";
}
//this->userName = name;
port = 9999;//随便写个8000以上的都行,别超过2^16
//创建客户端对象;
m_t = new QTcpSocket;
//连接服务器
QString ip ="***如果是本地,则使用本地ip,如果是不同局域网,则使用公网ip***";
m_t->connectToHost(QHostAddress(ip),port);
//接收数据
connect(m_t,&QTcpSocket::readyRead,this,&MainWindow::recieveMessage);
connect(m_t,&QTcpSocket::connected,this,[=]{
sendMesssge(UserEnter);//每当槽函数被调用,说明有用户进入
});
//断开服务器的提示
/* connect(m_t,&QTcpSocket::disconnected,this,[=]{
sendMesssge(UserLeft);//每当槽函数被调用,说明有用户离开
m_t->close();
m_t->deleteLater();
});*///此处应该是用注释掉的代码 可是我不知道原因他用不了 于是用了下述代码代替
connect(ui->quit,&QPushButton::clicked,[=]{
sendMesssge(UserLeft);//每当槽函数被调用,说明有用户离开
m_t->close();
m_t->deleteLater();
});
//----------------------------非核心功能描述-----------------------------------------
//更改字体
connect(ui->fontBox,&QFontComboBox::currentFontChanged,this,[=](const QFont &font){
ui->inputBox->setFont(font);
});
//更改字号
connect(ui->sizeBox,static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[=](int index){
bool ok;
int fontSize = ui->sizeBox->itemText(index).toInt(&ok); // 将选中的项转换为int
if(ok){
QFont font = ui->inputBox->font(); // 获取当前字体
font.setPointSize(fontSize); // 设置字体大小
ui->inputBox->setFont(font); // 应用新的字体设置
}
});
//加粗
ui->black->setToolTip(QString("加粗"));
connect(ui->black,&QToolButton::clicked,this,[=]{
QFont font = ui->inputBox->font(); // 获取当前字体
font.setBold(true); // 设置字体加粗
ui->inputBox->setFont(font); // 应用新的字体设置
});
//斜体
ui->italic->setToolTip(QString("斜体"));
connect(ui->italic,&QToolButton::clicked,this,[=]{
QFont font = ui->inputBox->font(); // 获取当前字体
font.setItalic(true); // 设置字体斜体
ui->inputBox->setFont(font); // 应用新的字体设置
});
//下划线
ui->underLine->setToolTip(QString("下划线"));
connect(ui->underLine,&QToolButton::clicked,this,[=]{
QFont font = ui->inputBox->font(); // 获取当前字体
font.setUnderline(true); // 设置字体斜体
ui->inputBox->setFont(font); // 应用新的字体设置
});
//更改字体颜色
ui->color->setToolTip(QString("改变颜色"));
connect(ui->color,&QToolButton::clicked,this,[=]{
//导入QColorDialog控件中的颜色选择
QColor color = QColorDialog::getColor(Qt::white,this,"choos a color");
if(color.isValid()){
ui->inputBox->setTextColor(color);//设置字体颜色
}
});
//清空
ui->clean->setToolTip(QString("清空"));
connect(ui->clean,&QToolButton::clicked,this,[=]{
ui->chatBox->clear();//清空内容
});
sendMesssge(UserEnter);//每当构造函数被调用,说明有用户进入
//保存聊天记录
ui->save->setToolTip(QString("保存"));
connect(ui->save,&QToolButton::clicked,[=](){
if(ui->chatBox->toPlainText().isEmpty())
{
QMessageBox::warning(this,"warning!","warning!the chat content cannot be empty!");
return;
}
else
{
QString filename=QFileDialog::getSaveFileName(this,"保存聊天记录","聊天记录","(*.txt)");
if(!filename.isEmpty())
{
QFile file(filename);
file.open(QIODevice::WriteOnly | QFile::Text);
QTextStream stream(&file);
stream<<ui->inputBox->toPlainText();
file.close();
}
}
});
connect(ui->inputBox,SIGNAL(returnPressed()),ui->send,SLOT(on_send_clicked()));
//connect(ui->inputBox,&QPushButton::clicked,ui->send,&mainwindow::on_send_clicked());
// tableWidget = new QTableWidget;
//connect(this,&MainWindow::nameClicked,this,&MainWindow::showpage);
}
MainWindow::~MainWindow()
{
delete ui;
}
//获得当前聊天窗口的用户名
QString MainWindow::getUserName()
{
return userName;//在类里用成员属性userName,直接返回即可
}
//从输入框中获取输入内容,输入框叫inputBox,获得信息后刷新输入框
QString MainWindow::getMessage()
{
QString msg = ui->inputBox->toHtml();//改成toHtml可以获得不同的字体,大小,颜色
ui->inputBox->clear();//清空输入框
ui->inputBox->setFocus();//将焦点重新拉到输入框
return msg;
}
//发送信息
void MainWindow::sendMesssge(msgType type)
{
QByteArray array;//储存字节数据,udp通信传的是字节数据
QDataStream stream(&array,QIODevice::WriteOnly);//准备写入数据的流
stream << type << this->getUserName();
switch(type)
{
case Msg:
if(ui->inputBox->toPlainText() == "")//判断输入框是否为空
{
QMessageBox::warning(this,"警告","发送的聊天内容不能为空!");
return;
}
stream << this->getMessage();
break;
case UserEnter:
break;
case UserLeft:
break;
}
m_t->write(array);//往服务器塞数据
}
//接收信息
void MainWindow::recieveMessage()
{
QByteArray array = m_t->readAll();//读取数据
//处理数据,将枚举类型和用户名读数来
QDataStream stream(&array,QIODevice::ReadOnly);//创建了一个只读的数据流来读取array中的数据
int mytype;//枚举类型
QString name,msg;//名字,信息
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");//获取时间
stream >> mytype;//从流里读枚举类型
switch (mytype) {
case Msg:
stream >> name >> msg;//从流里读用户名和用户信息
ui->chatBox->setTextColor(QColor(Qt::blue));
ui->chatBox->append("["+name+"]"+time);
ui->chatBox->append(msg);
break;
case UserEnter:
stream >> name;
userEnter(name);
break;
case UserLeft:
stream >> name;
userLeft(name,time);
break;
}
}
//处理用户进入,根据传入的用户名,检查右侧tableWidget是否有和传入的用户名相同的项,没有就说明新用户进入
//创建新的QTableWidgetItem在tableWidget上,通过计算tabalWidget上的行数更新人数
void MainWindow::userEnter(QString name)
{
bool notFound = ui->tableWidget->findItems(name,Qt::MatchExactly).isEmpty();
if(notFound)
{
QTableWidgetItem *table = new QTableWidgetItem(name);
ui->tableWidget->insertRow(0);
ui->tableWidget->setItem(0,0,table);
ui->chatBox->setTextColor(QColor(Qt::gray));
ui->chatBox->append(name+u8"已上线");
ui->online->setText(QString("在线人数:%1").arg(ui->tableWidget->rowCount()));
sendMesssge(UserEnter);
}
}
//处理用户离开,检查tableWidget中是否有一个特定的用户(由name标识)
//如果有,则删除该用户所在的行,并在界面上显示用户离开的消息和更新在线人数。
void MainWindow::userLeft(QString name, QString time)
{
qDebug()<<"进入函数了";
bool notFound = ui->tableWidget->findItems(name,Qt::MatchExactly).isEmpty();
if(!notFound)
{
int row =ui->tableWidget->findItems(name,Qt::MatchExactly).first()->row();
ui->tableWidget->removeRow(row);
ui->chatBox->setTextColor(QColor(Qt::gray));
ui->chatBox->append(time+name+u8"已下线");
ui->online->setText(QString("在线人数:%1").arg(ui->tableWidget->rowCount()));
sendMesssge(UserEnter);
}
}
//按下了发送键,将输入框中的信息发送出去
void MainWindow::on_send_clicked()
{
sendMesssge(Msg);
}
//按下了退出键,关闭当前的聊天窗口,用户退出聊天室,把用户退出的信息广播到聊天框
void MainWindow::on_quit_clicked()
{
this->close();
}
.ui就不分享了,照着一位b站的up写的,变量重新命名了。可以根据喜好更改。
本地ip查找方式:
win+r 输入cmd 打开命令行窗口
输入ipconfig
找到你所连宽带(WLAN)的ipv4的数字,输入mainwindow即可
7.服务器端代码
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QList>
#include <QWidget>
#include<QTcpServer>
#include<QTcpSocket>
namespace Ui {
class TCPserver;
}
class TCPserver : public QWidget
{
Q_OBJECT
public:
explicit TCPserver(QWidget *parent = nullptr);
~TCPserver();
QList<QTcpSocket*> clients;
private slots:
void on_connectbutton_clicked();
void threadslot(QByteArray b);
private:
Ui::TCPserver *ui;
QTcpServer *m_s;
QTcpSocket *m_t;
};
#endif // TCPSERVER_H
tcpserver.cpp
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include"mythread.h"
TCPserver::TCPserver(QWidget *parent) :
QWidget(parent),
ui(new Ui::TCPserver)
{
ui->setupUi(this);
ui->porttext->setText("9999");//监听端口
setWindowTitle(u8"服务器");
//创建服务器套接字对象
m_s=new QTcpServer(this);
//当有客户端连接时触发槽函数
connect(m_s,&QTcpServer::newConnection,this,[=](){
m_t=m_s->nextPendingConnection();
clients.append(m_t);//把不同的客户端套接字对象存在一个list里
mythread *t =new mythread(m_t);
t->start();
connect(t, &mythread::send_to, this, &TCPserver::threadslot);
connect(m_t,&QTcpSocket::disconnected,this,[=](){
clients.removeAll(m_t);
m_t->close();
m_t->deleteLater();
m_t = nullptr;
});
});
}
TCPserver::~TCPserver()
{
delete ui;
}
void TCPserver::on_connectbutton_clicked()
{ unsigned port=ui->porttext->text().toUShort();
m_s->listen(QHostAddress::Any,port);
ui->connectbutton->setDisabled(true);
}
void TCPserver::threadslot(QByteArray b){
//遍历储存客户端套接字的表,将收到的数据发送给所有看客户端
for (QTcpSocket *client : qAsConst(clients)) {
if (client->state() == QAbstractSocket::ConnectedState) {
client->write(b);
}
}
}
mythread.cpp
#include "mythread.h"
mythread::mythread(QTcpSocket *s)
{
socket =s;
}
void mythread::run(){
connect(socket, &QTcpSocket::readyRead, this, &mythread::clientinfooslot);
}
void mythread::clientinfooslot(){
QByteArray ba =socket->readAll();
emit send_to(ba);
}
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include<qthread.h>
#include <QObject>
#include<QTcpSocket>
class mythread : public QThread
{
Q_OBJECT
public:
explicit mythread(QTcpSocket*s);
void run();
signals:
void send_to(QByteArray b);
public slots:
void clientinfooslot();
private:
QTcpSocket *socket;
};
#endif // MYTHREAD_H
注:要先打开服务器,再运行客户端。
4.不同局域网通信效果展示
虽然看起来功能是一样的,但是服务器端的代码实际是运行在我租用的云服务器上的。
实际上代码的更改量并不多,只是把我原先mainwindow里面的ip改为我租用的云服务器的公网ip。
5.不同局域网通信流程
1.租服务器,我租了一个弹性云ECS,一个小型的几十块钱搞定
2.远程登录虚拟机,我装的Windows镜像,里面什么都没有很方便学习,在虚拟机里面安装QT5
安装教程链接:(下载会很慢,耐心等待哦)Qt5.14.2从安装到环境配置到组件更新手把手教程(Win10)_qt 5.14.2-CSDN博客
3.***确认虚拟机中防火墙关闭***
这点很重要,有的时候客户端和服务器连接不上有很大的可能是这个原因。
4.更改云控制台入方向规则
不同大厂的平台上有相应手册,可以查阅
这里以华为云为例
先找到安全组
进去配置端口,添加入方向规则
我们代码中是9999端口,就配置这个端口。
这样就配置完成了,在云服务器中先运行服务器端代码,再在本机中运行客户端代码,可以用多台客户端连接服务器,实现不同局域网下的通信了。
你可以把写好的程序打包为可执行程序发给你的好朋友一起使用哦。
打包教程:QT打包成一个exe文件。(多种方法)_qt打包exe-CSDN博客
6.参考文档
本篇文章是以自我学习为目的发表,如有侵权联系删帖。
代码参照bilibili视频:【QT实现群聊聊天系统】https://www.bilibili.com/video/BV1iy4y1t7PL?vd_source=18b5c3850bf032d57fa1f0edbc66a0a6
【QT快速入门 | 最简单最简洁的QT入门教程 | 嵌入式UI】https://www.bilibili.com/video/BV1N34y1H7x7?vd_source=18b5c3850bf032d57fa1f0edbc66a0a6
数据库处理方案:五分钟教会你在Qt中使用SQLite数据库,非常有用,建议收藏!_qt使用sqlite数据库-CSDN博客
登录设计:
【QT】为自己的QT程序添加一个登录界面_qt登录界面-CSDN博客
求点赞
标签:clicked,QT,void,聊天工具,ui,QString,公网,new,include From: https://blog.csdn.net/2301_81070376/article/details/138976390