当前位置: 首页 > article >正文

使用QT编写有图形界面的TCP局域网聊天室(app)

服务器:

实现方法:

1.使用QTcpServer类实例化一个对象,就得到了一个服务器端
2.调用该类对象的成员函数 listen 将服务器启动监听,该函数会进行绑定ip和端口号
    ip地址可以指定也可以由系统自动绑定,端口号也可以自己指定和由系统自动指定
3.当有客户端发来连接请求后,该服务器就会自动发射一个newConnection的信号
    我们可以将该信号绑定到自定义的槽函数中完成相关逻辑
4.可以使用类中的成员函数 nextPenddingConnection 可以获取最新连接的客户端套接字
5.可以使用该客户端套接字进行数据收发
    read、readLine、readAll读取数据
    write发送数据
6.当服务器收到客户端的消息后,该服务器会自动发射一个readyRead的信号
    可以将该信号连接到对应的槽函数中,处理客户端发来的消息
7.调用成员函数close关闭监听

头文件:

// 防止头文件被重复包含
#ifndef WIDGET_H
#define WIDGET_H

// 包含必要的Qt类库
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
#include <QMessageBox>
#include <QDebug>

// 开始Qt命名空间,以使用Qt的类和函数
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

// Widget类,继承自QWidget,实现一个基于TCP的服务器
class Widget : public QWidget
{
    Q_OBJECT

public:
    // 构造函数,初始化服务器
    Widget(QWidget *parent = nullptr);
    // 析构函数,释放资源
    ~Widget();

private slots:
    // 当“启动服务器”按钮被点击时的槽函数
    void on_startSerBtn_clicked();
    // 当有新的客户端连接时的槽函数
    void newConnection_slot();
    // 当客户端有数据可读时的槽函数
    void readyRead_slot();

private:
    // Ui::Widget是用于管理界面组件的类
    Ui::Widget *ui;
    // QTcpServer用于监听客户端的连接请求
    QTcpServer *server;
    // 保存所有已连接客户端的socket列表
    QList <QTcpSocket*> socketList;

};
#endif // WIDGET_H

主程序:
 

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化一个服务器对象
    server = new QTcpServer(this); // 在堆区申请一个服务器
}

Widget::~Widget()
{
    delete ui;
}

// 启动服务器按钮对应的槽函数
void Widget::on_startSerBtn_clicked()
{
    if (ui->startSerBtn->text() == "启动服务器")
    {
        // 执行启动服务器的的动作
        // 获取ui界面上的端口号
        quint16 port = ui->portEdit->text().toUInt();
        // 启动监听: bool listen(const QHostAddress &address=QHostAddress::Any,quint16 port =0);
        if (!server->listen(QHostAddress::Any, port))
        {
            QMessageBox::critical(this, "错误", "服务器启动失败");
            return;
        }
        // 程序执行至此,表示服务器启动成功
        QMessageBox::information(this, "成功", "服务器启动成功");
        // 将行编辑器设置为不可用
        ui->portEdit->setEnabled(false);
        // 将按钮文本内容设置为关闭服务器
        ui->startSerBtn->setText("关闭服务器");

        // 此时,如果有客户端发来连接请求,那么该服务器就会自动发送一个newConnection的信号
        // 我们可以将该信号连接到自定义的槽函数中,处理后续操作
        connect(server, &QTcpServer::newConnection, this, &Widget::newConnection_slot);
    }
    else
    {
        // 执行关闭服务器的动作

        // 将行编辑器设置成可用状态
        ui->portEdit->setEnabled(true);
        // 将按钮文本内容设置为启动服务器
        ui->startSerBtn->setText("启动服务器");
    }
}

// 自定义处理newConnection信号的槽函数的实现
void Widget::newConnection_slot()
{
    qDebug() << "有新的客户端发来连接请求了";

    // 可以通过成员函数 nextPendingConnection函数获取最新连接的客户端套接字的地址
    // 函数原型:QTcpSocket *newPendingConnection();
    // 无参函数
    // 返回值:最新的一个连接的套接字地址
    QTcpSocket *s = server->nextPendingConnection();

    // 将该套接字放入客户端链表中
    socketList.push_back(s);

    // 程序执行至此,一个服务器可以对应多个客户端,已经建立了连接
    // 此时,如果有某个客户端发来数据,那么该客户端套接字就会自动发送一个readyRead的信号
    // 我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    connect(s, &QTcpSocket::readyRead, this, &Widget::readyRead_slot);
}

// 自定义处理readyRead信号的槽函数的实现
void Widget::readyRead_slot()
{
    // 1.遍历链表中所有的客户端,如果客户端的状态为未连接,则直接从链表中移除
    for (int i = 0; i < socketList.size(); i++)
    {
        // 判断当前套接字 socketList[i]是否时效
        // 函数原型:SockState state() const;
        // 功能:返回当前套接字状态
        // 返回值为0时,表示改套接字时未连接状态
        if (socketList[i]->state() == 0)
        {
            // 将改套接字移除出链表
            socketList.removeAt(i);
        }
    }
    for (int i = 0; i < socketList.count(); i++)
    {
        if (socketList[i]->bytesAvailable() != 0)
        {
            // 读取当前套接字内容
            QByteArray sjrhsk = socketList[i]->readAll();
            // 显示到ui界面上
            ui->msglistWidget->addItem(QString::fromLocal8Bit(sjrhsk));
            // 将消息发送给其他客户端

            // 将收到的消息,全部发给其他客户端
            for (int j = 0; j < socketList.length(); j++)
            {
                if (i != j) // 防止自己发给自己
                {
                    socketList[j]->write(sjrhsk);
                }
            }
        }
    }
}

客户端:

客户端:
1.使用该类实例化一个对象,就创建了一个客户端
2.使用该类对象的成员函数 connectToHost 向服务器发送连接请求
    如果连接服务器成功,那么该客户端套接字就会自动发射一个 connected 的信号
    可以将该信号连接到自定义的槽函数中处理相关逻辑
3.可以使用该客户端套接字进行数据收发
    read、readLine、readAll读取数据
    write发送数据
4.如果客户端收到服务器发来的消息后,该客户端就会自动发射一个readyRead的信号
可以将该信号连接到对应的槽函数中,处理客户端发来的消息
5.调用成员函数 disconnectFromHost 断开跟服务器的连接

头文件:

// 确保头文件Widget.h只被包含一次
#ifndef WIDGET_H
#define WIDGET_H

// 包含必要的Qt库文件
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
#include <QString>
#include <QMessageBox>
#include <QDebug>

// 开始使用Qt命名空间,以避免全局命名冲突
QT_BEGIN_NAMESPACE
namespace Ui
{
    class Widget;
}
QT_END_NAMESPACE

// Widget类,继承自QWidget,实现了网络通信功能
class Widget : public QWidget
{
    Q_OBJECT

public:
    // 构造函数,初始化Widget对象
    Widget(QWidget *parent = nullptr);

    // 析构函数,释放Widget对象占用的资源
    ~Widget();

private slots:
    // 当连接按钮被点击时的槽函数
    void on_connectbtn_clicked();

    // 当发送按钮被点击时的槽函数
    void on_sendbtn_clicked();

    // 连接成功时的槽函数
    void connected_slot();

    // 准备读取数据时的槽函数
    void readyread_slot();

    // 断开连接时的槽函数
    void disconnect_slot();

private:
    // 用于设置UI界面
    Ui::Widget *ui;

    // TCP客户端套接字
    QTcpSocket *client;

    // 用户名
    QString username;
};

#endif // WIDGET_H

主程序:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化客户端对象
    client = new QTcpSocket(this);
    connect(client, &QTcpSocket::readyRead, this, &Widget::readyread_slot);
    connect(client, &QTcpSocket::connected, this, &Widget::connected_slot);
    connect(client, &QTcpSocket::disconnected, this, &Widget::disconnect_slot);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_connectbtn_clicked()
{
    if (ui->connectbtn->text() == "连接服务器")
    {
        QString ip = ui->ipedit->text();              // IP地址
        quint16 port = ui->portedit->text().toUInt(); // 端口号
        username = ui->nameedit->text();              // 用户名
        // 调用套接字成员函数,连接服务器
        // 函数原型:void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
        // 参数1:要被连接的服务器ip地址
        // 参数2:服务器的端口号
        // 参数3:默认为可读可写
        // 返回值:无
        client->connectToHost(ip, port);
        ui->connectbtn->setText("断开连接");
    }
    else
    {
        // 执行断开连接的操作
        // 准备发送消息给服务器
        QString msg = username + ": 离开聊天室";
        client->write(msg.toLocal8Bit());

        // 断开连接
        client->disconnectFromHost();
        ui->connectbtn->setText("连接服务器");
    }
}

void Widget::on_sendbtn_clicked()
{
    // 组织要发送的消息
    QString msg = username + ":" + ui->messageedit->toPlainText();

    // 将消息发送给服务器
    client->write(msg.toLocal8Bit());

    // 将消息展示到自己界面上
    // 准备一个QListWidgetItem类的对象
    QListWidgetItem *sjrhsk = new QListWidgetItem(msg);
    sjrhsk->setTextAlignment(Qt::AlignRight); // 将文本右对齐
    ui->msgListWidget->addItem(sjrhsk);

    // 清空消息发送框的内容
    // ui->msgEdit->clear();
}
// 处理connected信号的槽函数的定义
void Widget::connected_slot()
{
    QMessageBox::information(this, "成功", "连接成功");
    // 将相关组件禁用
    ui->ipedit->setEnabled(false);
    ui->nameedit->setEnabled(false);
    ui->portedit->setEnabled(false);
    // 向服务器发送一条消息
    QString msg = username + ": 进入聊天室";
    client->write(msg.toLocal8Bit());
}
// 自定义处理readyRead信号的槽函数
void Widget::readyread_slot()
{
    // 从套接字中读取数据
    QByteArray msg = client->readAll();

    // 将读取下来的数据展示到ui界面上
    ui->msgListWidget->addItem(QString::fromLocal8Bit(msg));
}
// 自定义处理disconnect信号的槽函数的定义
void Widget::disconnect_slot()
{
    QMessageBox::information(this, "提示", "成功断开与服务器的连接");

    // 将相关组件启用
    ui->ipedit->setEnabled(true);
    ui->nameedit->setEnabled(true);
    ui->portedit->setEnabled(true);
}


http://www.kler.cn/a/309014.html

相关文章:

  • 华为数通HCIA系列第5次考试-【2024-46周-周一】
  • 常见的噪声模型+图像中噪声模型的估计+常见的滤波方法(C++)
  • SCUI Admin + Laravel 整合
  • 本地编译ChatNio的问题解决
  • XML Schema 字符串数据类型
  • 数据分析那些事儿——时间片轮转实验
  • QT使用事件事件和绘制事件实现简易时钟
  • java自定义注解
  • 数据结构——二叉搜索树
  • linux-网络管理-防火墙配置
  • 面试真题-TCP的三次握手
  • STM32外设-0.96寸OLED显示屏
  • [数据集][目标检测]男女性别检测数据集VOC+YOLO格式9769张2类别
  • AI重塑视觉体验:将图像与视频转化为逼真可编辑的3D虚拟场景
  • 使用jackson将xml和对象、List相互转换
  • 碰撞检测 | 图解线段几何与线段相交检测原理(附ROS C++可视化)
  • pandas读取Excel保留空格与数字前置0
  • UNI-APP 富文本编辑器,可以对图片、文字格式进行编辑和混排。
  • python之openpyxl模块——实现Excel表格的处理(万字教学,全网最全,超详细!)
  • 深度解析代理IP地址与端口:定义及高效获取方法
  • 硬件工程师笔试面试——保险丝
  • 运维面试题1
  • 微信小程序中实现类似于 ECharts 的图表渲染及优化
  • Java项目实战II基于Java+Spring Boot+MySQL的作业管理系统设计与实现(源码+数据库+文档)
  • OpenAI o1——人工智能推理能力的飞跃,助力高级问题解决
  • Spark处理结构化数据:DataFrame、DataSet、SparkSQL