QT C++ 基于TCP通信的网络聊天室

news2024/11/23 12:31:11

一、基本原理及流程

1)知识回顾(C语言中的TCP流程)

2)QT中的服务器端/客户端的操作流程

二、代码实现

1)服务器

.ui

.pro

在pro文件中添加network库

.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>  //服务器头文件
#include <QTcpSocket>  //客户端头文件
#include <QList>       //链表头文件,用于存放客户端容器
#include <QMessageBox> //消息对话框类
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_startBtn_clicked(); //自定义启动按钮的槽函数
    void newConnection_slot();  //自定义处理newConnection信号的槽函数
    void readyRead_slot();      //自定义处理reayRead信号的槽函数

private:
    Ui::Widget *ui;

    //定义服务器指针
    QTcpServer *server;
    //定义客户端指针链表容器
    QList<QTcpSocket *> clientList;

};
#endif // WIDGET_H

.main

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

.cpp

#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_startBtn_clicked()
{
    //1、获取ui界面上的端口号
    quint16 port = ui->portEdit->text().toUInt();

    //2、将服务器设置成监听状态
    //bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
    //参数1:要监听的主机地址,如果是any,表示监听所有主机地址,也可以给特定的主机地址进行监听
    //参数2:通过指定的端口号进行访问服务器,如果是0,表示由服务器自动分配,如果非0,则表示指定端口号  (quint16 == unsignal int)
    //返回值:成功返回真,失败返回假
    if(!server->listen(QHostAddress::Any,port))
    {
        QMessageBox::critical(this,"失败","服务器启动失败");  //默认按钮为ok
        return;
    }else
    {
        QMessageBox::information(this,"成功","服务器启动成功");
    }
    //此时表明服务器启动成功,并对客户端连接进行监听
    //如果有客户端向服务器发来连接请求,那么该服务器就会自动发射一个newConnection
    //我们可以将该信号连接到对应的槽函数中处理相关逻辑
    connect(server,&QTcpServer::newConnection,this,&Widget::newConnection_slot);
}

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

    //获取最新连接的客户端套接字
    //函数原型:[virtual] QTcpSocket *QTcpServer::nextPendingConnection()
    //参数:无
    //返回值:最新连接客户端套接字的指针
    QTcpSocket *s = server->nextPendingConnection();

    //将获取的套接字存放到客户端容器中
    clientList.push_back(s);

    //此时,客户端就和服务器建立起联系了
    //如果该套接字有数据向服务器发送过来,那么该套接字就会自动发射一个ready read信号
    //我们可以将该信号连接到自定义的槽函数中,然后处理相关逻辑
    connect(s, &QTcpSocket::readyRead, this, &Widget::readyRead_slot);
}

//关于readyRead信号对应槽函数的实现
void Widget::readyRead_slot()
{
    //删除客户端链表中的无效客户端套接字
    for(int i=0;i<clientList.count();i++)
    {
        //判断套接字的状态
        //函数原型:SocketState state() const;
        //功能:返回客户端套接字的状态
        //参数:无
        //返回值:客户端的状态,如果结果为0,表示未连接
        if(clientList[i]->state() == 0)
        {
            clientList.removeAt(i);     //将下标为i的客户端的套接字从链表中移除
        }
    }

    //遍历所有客户端,查看是哪个客户端发来数据
    for(int i=0 ; i<clientList.count() ; i++)
    {
        //函数原型:qint64 bytesAvailable() const override;
        //功能:返回当前客户端套接字中的可读数据字节个数
        //参数:无
        //返回值:当前客户端待读的字节数,如果该数据为0,表示无待读数据
        if(clientList[i]->bytesAvailable() != 0)
        {
            //读取当前客户端的相关数据,并返回一个字节数组
            //参数:无
            //返回值:数据的字节数组
            QByteArray msg = clientList[i]->readAll();

            //将数据展示到ui界面上
            ui->msgList->addItem(QString::fromLocal8Bit(msg));

            //将接收到的该消息,发送给所有客户端
            for(int j = 0 ; j<clientList.count() ; j++)
            {
                clientList[j]->write(msg);
            }
        }
    }
}

2)客户端

.ui

.pro

在pro文件中添加network库

.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpSocket>  //客户端头文件
#include <QMessageBox> //

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_connectBtn_clicked();
    void connected_slot();        //自定义处理connected信号的槽函数
    void readyRead_slot();        //自定义处理readyRead信号的槽函数
    void on_sendBtn_clicked();    //自定义处理发送按钮的槽函数
    void disconnect_slot();       //自定义处理disconnectFromHost信号的函数
    void on_disConnectBtn_clicked();

private:
    Ui::Widget *ui;

    //定义一个客户端指针
    QTcpSocket *socket;
    //用户名
    QString userName;     //由于多个槽函数需要用到该用户名,所以将用户名设置为类的私有成员

};
#endif // WIDGET_H

.main

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

.cpp

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //将发送区域的组件设置为不可用状态
    ui->sendBtn->setEnabled(false);
    ui->msgEdit->setEnabled(false);
    ui->disConnectBtn->setEnabled(false);

    //给客户端指针实例化空间
    socket = new QTcpSocket(this);

    //如果连接服务器成功,该客户端就会发射一个connected的信号
    //我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    //由于该连接只需要连接一次,所以,写在构造函数中即可
    connect(socket,&QTcpSocket::connected,this,&Widget::connected_slot);

    //客户端与服务器连接成功后,如果服务器向客户端发来数据,那么该客户端就会自动发送一个readyRead信号
    //我们只需要
    connect(socket,&QTcpSocket::readyRead,this,&Widget::readyRead_slot);

    //当客户端与服务器断开连接后,该客户端就会发射一个disconnected的信号
    //我们可以将该信号与自定义的槽函数连接
    //由于只需要连接一次,所以将该连接写到构造函数中即可
    //点击断开连接按钮,
    connect(socket,&QTcpSocket::disconnected,this,&Widget::disconnect_slot);
}

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

//连接服务器按钮对应的槽函数
void Widget::on_connectBtn_clicked()
{
    //获取ui界面的信息
    userName = ui->userNameEdit->text();           //获取用户名
    QString hostName = ui->ipEdit->text();         //获取主机地址
    quint16 port = ui->portEidt->text().toUInt();  //获取端口号

    //调用函数连接到主机
    //[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port)
    //参数1:服务器的主机地址
    //参数2:端口号
    //返回值:无
    socket->connectToHost(hostName,port);

    //如果连接服务器成功,该客户端就会发射一个connected的信号
    //我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    //由于该连接只需要连接一次,所以,写在构造函数中即可
}

//关于处理connected信号的槽函数的定义
void Widget::connected_slot()
{
    QMessageBox::information(this,"成功","连接服务器成功");

    //设置组件的可用状态
    ui->msgEdit->setEnabled(true);
    ui->sendBtn->setEnabled(true);
    ui->disConnectBtn->setEnabled(true);
    ui->userNameEdit->setEnabled(false);
    ui->ipEdit->setEnabled(false);
    ui->portEidt->setEnabled(false);
    ui->connectBtn->setEnabled(false);

    //顺便向服务器发送一条消息,说:***:进入聊天室
    QString msg = userName + ":进入聊天室";
    socket->write(msg.toLocal8Bit());
}

//关于readyRead信号对应槽函数的实现
void Widget::readyRead_slot()
{
    //读取该客户端中的数据
    QByteArray msg = socket->readAll();

    //将数据展示在ui界面
    ui->msgList->addItem(QString::fromLocal8Bit(msg));

}

//发送按钮对应的槽函数
void Widget::on_sendBtn_clicked()
{
    //获取ui界面中的编辑的文本内容
    QString m = ui->msgEdit->text();

    //整合要发送的消息
    QString msg = userName + ":" + m;
    socket->write(msg.toLocal8Bit());

    //将消息编辑器中的内容清空
    ui->msgEdit->clear();
}

//断开服务器按钮对应的槽函数
void Widget::on_disConnectBtn_clicked()
{
    //准备要发送的信息
    QString msg = userName + ":离开聊天室";
    socket->write(msg.toLocal8Bit());

    //调用成员函数disconnectFromHost
    //函数原型:virtual void disconnectFromHost();
    //功能:断开客户端与服务器的连接
    //参数:无
    socket->disconnectFromHost();

    //当客户端与服务器断开连接后,该客户端就会发射一个disconnected的信号
    //我们可以将该信号与自定义的槽函数连接
    //由于只需要连接一次,所以将该连接写到构造函数中即可

}

//关于disconnectFromHost信号对应槽函数的实现
void Widget::disconnect_slot()
{
    QMessageBox::information(this,"退出","断开成功");
    //设置组件的可用状态
    ui->msgEdit->setEnabled(false);
    ui->sendBtn->setEnabled(false);
    ui->disConnectBtn->setEnabled(false);
    ui->userNameEdit->setEnabled(true);
    ui->ipEdit->setEnabled(true);
    ui->portEidt->setEnabled(true);
    ui->connectBtn->setEnabled(true);
}

三、效果展示

       

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/975126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Netty—FuturePromise

Netty—Future&Promise 一、JDK原生 Future二、Netty包下的 Future三、Promise1、使用Promise同步获取结果2、使用Promise异步获取结果.3、使用Promise同步获取异常 - sync & get4、使用Promise同步获取异常 - await5、使用Promise异步获取异常 在异步处理时&#xff0…

uniapp - 倒计时组件-优化循环时间倒计时

使用定时器的规避方法 为了避免定时器误差导致倒计时计算错误&#xff0c;可以采用一些规避方法&#xff0c;比如将倒计时被中断时的剩余时间记录下来&#xff0c;重新开启定时器时再将这个剩余时间加到新的计算中。同时&#xff0c;为了避免定时器延迟&#xff0c;可以在每次执…

Golang 新手经常踩的坑

1、 Golang 新手经常踩的坑 1.1 前言 Go 是一门简单有趣的编程语言&#xff0c;与其他语言一样&#xff0c;在使用时不免会遇到很多坑&#xff0c;不过它们大多不是 Go 本身的设计缺陷。如果你刚从其他语言转到 Go&#xff0c;那这篇文章里的坑多半会踩到。 如果花时间学习官…

风向变了!智能汽车何以「降本」

随着软件定义汽车的概念逐步落地&#xff0c;以及底盘、动力、座舱、智驾、车身等不同域&#xff08;分布式或者混合式&#xff09;的功能更新迭代和融合&#xff0c;汽车行业正在意识到&#xff1a;底层硬件架构重构的迫切性。 事实上&#xff0c;早在2016年&#xff0c;作为传…

客户端发现pod并与之通信

客户端发现pod并与之通信 pod需要一种寻找其他pod的方法来使用其他pod提供的服务&#xff0c;不像在没有Kubernetes的世界&#xff0c;系统管理员要在用户端配置文件中明确指出服务的精确IP地址 或者主机名来配置每个客户端应用&#xff0c;但同样的方法在Kubernetes中不适用 …

富硒虫草肉丸系列新品上市—虫草可以“享”着吃

2023年9月4日&#xff0c;鸿祥食品(汕尾市)有限公司探索研发的富硒虫草肉丸全系列新品惊喜亮相&#xff0c;为消费者带来全新的滋补体验。五款以富硒虫草为主要原料&#xff0c;分别以猪肉、鲜虾、牛肉、墨鱼以及牛筋为新食品原料加工而成的虫草类健康新品--“享着丸”系列&…

yolov5运行过程遇到的小问题(随时更新)

1.关于git的问题 解决办法&#xff1a;插入下面代码 import os os.environ["GIT_PYTHON_REFRESH"] "quiet"2.页面太小无法完成操作 解决办法: 如果不好使再考虑降低Batch_Size大小或者调整虚拟内存可用硬盘空间大小&#xff01;&#xff08;调整虚拟内存…

公信力不是儿戏:政府与非营利组织如何利用爱校对提升信息质量

公信力是政府和非营利组织成功的基础&#xff0c;而这种公信力大多来源于对外发布的信息的准确性和可靠性。在这个方面&#xff0c;“爱校对”展现了它的强大能力和实用性。以下我们将具体探讨这两种组织如何通过使用爱校对来提升他们的信息质量。 政府&#xff1a;公开与透明&…

【strapi系列】strapi在登录时调用api/auth/local获取token接口一直报401、403、400错误的问题解决

文章目录 问题描述解决403 forbidden问题解决401 (Unauthorized) error问题调用认证接口需用注意的事项&#xff0c;解决400问题 问题描述 strapi在调用api/auth/local登录接口时&#xff0c;一直报403 forbidden 或 401 (Unauthorized) error问题。 这个接口的作用其实就是使…

02-Linux-IO多路复用之select、poll和epoll详解

前言&#xff1a; 在linux系统中&#xff0c;实际上所有的 I/O 设备都被抽象为了文件这个概念&#xff0c;一切皆文件&#xff0c;磁盘、网络数据、终端&#xff0c;甚至进程间通信工具管道 pipe 等都被当做文件对待。 在了解多路复用 select、poll、epoll 实现之前&#xff…

手写Mybatis:第19章-二级缓存

文章目录 一、目标&#xff1a;二级缓存二、设计&#xff1a;二级缓存三、实现&#xff1a;二级缓存3.1 工程结构3.2 二级缓存类图3.3 二级缓存队列3.3.1 FIFI缓存策略3.3.2 事务缓存3.3.3 事务管理3.3.4 修改一级缓存 3.4 缓存执行器3.4.1 执行器接口3.4.2 执行器抽象基类3.4.…

STM32CUBEMX_创建时间片轮询架构的软件框架

STM32CUBEMX_创建时间片轮询架构的软件框架 说明&#xff1a; 1、这种架构避免在更新STM32CUBEMX配置后把用户代码清除掉 2、利用这种时间片的架构可以使得代码架构清晰易于维护 创建步骤&#xff1a; 1、使用STM32CUBEMX创建基础工程 2、新建用户代码目录 3、构建基础的代码框…

uniapp制作——交友盲盒

在小程序端可以有很多好玩的小玩意&#xff0c;目前网上比较有趣的就是有一个交友盲盒&#xff0c;能抽出和找出对象的一个有趣的小程序&#xff0c;所以今天给大家带来用uniapp搭建的交友盲盒&#xff0c;大家再根据自己的情况去搭建自己的后端和数据库来完成自己的一个小项目…

实现Android APK瘦身99.99%

摘要&#xff1a; 如何瘦身是 APK 的重要优化技术。APK 在安装和更新时都需要经过网络下载到设备&#xff0c;APK 越小&#xff0c;用户体验越好。本文作者通过对 APK 内在机制的详细解析&#xff0c;给出了对 APK 各组成成分的优化方法及技术&#xff0c;并实现了一个基本 APK…

制药行业GMP是什么?

药品制造是一项极其关键的行业&#xff0c;它直接涉及到人民的健康和生命安全。因此&#xff0c;确保药品质量、安全性和有效性至关重要。为了实现这一目标&#xff0c;全球范围内都实施了药品生产质量管理规范&#xff0c;通常被简称为GMP&#xff08;Good Manufacturing Prac…

Java8新特性stream和parallelStream有什么区别

1 stream和parallelStream的区别 1.Stream 是在 Java8 新增的特性&#xff0c;普遍称其为流&#xff1b;它不是数据结构也不存放任何数据&#xff0c;其主要用于集合的逻辑处理。 2.Stream流是一个集合元素的函数模型&#xff0c;它并不是集合&#xff0c;也不是数据结构&…

是真卷?还是内耗?这次面试造的火箭着实有点离谱

大家好&#xff0c;我是冰河~~ 都说面试造火箭&#xff0c;工作拧螺丝&#xff0c;不过这次面试造的着实有点离谱&#xff01; 事情是这样的&#xff0c;昨天在冰河的知识星球微信群里&#xff0c;一名小伙伴发了一张面试常州一家公司的面试题的图片&#xff0c;瞬间吸引了我…

十个响应式页面项目

十个响应式页面项目 上接 53 个特效&#xff1a; 53 个 CSS 特效 153 个 CSS 特效 253 个 CSS 特效 3&#xff08;完&#xff09; 照例&#xff0c;预览地址在 http://www.goldenaarcher.com/html-css-js-proj/&#xff0c;git 地址&#xff1a; https://github.com/Goldena…

Seata处理分布式事务之1.7.0

https://blog.csdn.net/zhang33565417/article/details/122768300 1.5.0之后版本发生了很大改变 1.seata安装 1.1官网地址 http://seata.io/zh-cn/ 1.2下载地址 https://github.com/seata/seata/releases 下载的是seata-server-1.7.0.zip 1.3seata相关配置的修改 seata-…

Python学习 -- Math模块和Random模块

math 模块提供了许多数学函数&#xff0c;用于执行各种数学运算。以下是一些常用的 math 函数以及相应的示例代码&#xff1a; math.sqrt(x): 计算平方根。 import math x 25 square_root math.sqrt(x) print(f"√{x} {square_root}") math.pow(x, y): 计算 x …