首页 > 其他分享 >基于QT的TCP本地与不同局域网(使用公网)的简易聊天工具

基于QT的TCP本地与不同局域网(使用公网)的简易聊天工具

时间:2024-05-29 23:03:49浏览次数:28  
标签:clicked QT void 聊天工具 ui QString 公网 new include

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

相关文章

  • 为QT程序增加版本等属性信息
    1.在pro文件中增加VERSION=1.0.0.0,编译后,会在debug或release目录下生成xxx_resource.rc文件,使用编辑器打开进行编辑;2.在pro文件中增加RC_FILE=xxx_resource.rc,VERSION自动失效,编译后,即可增加属性信息。注意:由QT自动生成的RC文件默认使用UTF-8编码,属性信息中若含中文,windows......
  • 使用qtranslator遇到的坑
    需求:使用Qt+C++的项目支持中/英文界面Qt:QTranslator类头文件:#include<qtranslator.h>要翻译的语句都要加上tr("待翻译的内容")不属于Qt部分的类需要继承QObject后使用tr();classCGlobalVariant:QObject{ Q_OBJECT}生成ts文件网上找到生成ts文件的一种方法是使......
  • pyqt Qtreeview分层控件
    pyqtQtreeview分层控件介绍效果代码介绍QTreeView是PyQt中的一个控件,它用于展示分层数据,如目录结构、文件系统等。QTreeView通常与模型(如QStandardItemModel、QFileSystemModel或自定义模型)一起使用,以管理数据和提供视图如何显示数据的规则。效果代码from......
  • 本地项目放到公网访问
    要将本地的Vue.js项目部署到公网以便他人访问,你需要遵循以下步骤。这个过程涉及到前端和后端的知识,包括使用Node.js作为服务器,并可能使用Nginx或Apache等Web服务器软件来处理静态文件。步骤1:确保项目构建完成首先,确保你的Vue.js项目已经构建完成。在项目根目录......
  • MQTT和kafka搭配使用 集成 emq iot 物联网
    MQTT历史MQTT协议于1999年发明,用于石油和天然气行业。工程师需要一种协议来实现最小带宽和最小电池损耗,以通过卫星监控石油管道。最初,该协议被称为MessageQueuingTelemetryTransport(消息队列遥测传输),得名于首先支持其初始阶段的IBM产品MQ系列。2010年,IBM发布了......
  • 临阵磨枪之公网资产自查常见高风险漏洞——如何快速自查目标资产是否存在常见漏洞
    总纲收束公网资产的攻击面常见资产类型分类公网域名公网出口IP公众号、小程序名单应用APP程序名称供应链——内部资产涉及框架、中间件、服务器等。整理公网资产出入口地址锚定资产的具体公网入口,便于配合资产表单快速定位漏洞地址,进行应急处理。自查往届HVV频发漏洞,......
  • QGIS开发笔记(三):Windows安装版二次开发环境搭建(下):将QGis融入QtDemo,添加QGis并加载tif遥
    前言  使用QGis的目的是进行二次开发,或者说是融入我们的应用(无人车、无人船、无人机),本片描述搭建QGis二次基础开发环境,由于实在是太长了,进行了分篇:上半部分:主要是安装好后,使用QtCreator可以使用QGIs的apps下的Qt使用对应的编译器编译不带qgis的空工程。下半部分:在上半......
  • Qt的延时函数 实现精准延时(转)
    voidQUIHelper::delay(intmsec){//这个最准/*非阻塞方式延时,现在很多人推荐的方法*/QEventLooploop;QTimer::singleShot(msec,&loop,SLOT(quit()));loop.exec();}voidQUIHelper::deferred(intmsec){//这个最准QTimertimer;timer.setTim......
  • QT C++实现表头添加组件
    QTableWidget在表头添加组件QTableWidget的表头有一个类QHeaderView是专门处理表头的类,要在表头添加一个类似QCheckBox的组件需要重写一个类并继承QHeaderView。QTableWidget中添加复选框并实现全选居中:自定义类MyHeaderView并继承QHeaderView自定义类MyTableWidget并继承QTa......
  • 【Qt秘籍】[005]-Qt第一次实战-运行
    一、如何创建文件?        当我们打开QtCreator,你会发现整个界面类目繁多。现在,让我们直接开始新建一个项目。1.点击左上角的“文件”==>点击“新建文件或项目”2.如图,选择“Application”==>“QtWifgetsapplication”==>“Choose...” 3.如图,选择填写名称和......