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

一、基本原理及流程

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);
}

三、效果展示

       

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
### 回答1: 为了使用Qt框架实现局域网聊天室,我们可以使用UDP协议进行通信。UDP是一种面向无连接的传输协议,适用于传输不需要高可靠性和有序到达的数据。 首先,我们需要创建一个UDP的Socket,并绑定到一个合适的端口。接下来,我们可以使用Qt提供的信号与槽机制来处理接收到的消息。 为了实现聊天室功能,我们需要至少两个应用实例,一个用作服务器,另一个用作客户端。当客户端启动时,它将向服务器发送一个加入聊天室的请求。 当服务器接收到一个加入请求时,它可以将发送者的IP地址和端口存储在一个列表中,以便之后可以将消息广播给所有的聊天室成员。 当一个客户端发送一条消息时,它将将消息发送给服务器服务器收到消息后,将消息广播给所有已连接的客户端。客户端接收到广播后,可以将消息显示在聊天窗口中。 需要注意的是,UDP是无连接的协议,因此在实现聊天室时,需要处理丢包和乱序的问题。可以使用一些技术,如序列号、心跳检测等来解决这些问题。 此外,为了提高用户体验,我们还可以在聊天室中实现一些额外的功能,如私聊、文件传输等。 总之,通过使用Qt和UDP协议,我们可以很容易地实现一个简单的局域网聊天室。使用UDP协议可以简化通信过程,并提供轻量级的通信方式。不过也要注意到UDP的不可靠性,需要在实现中考虑丢包和乱序问题。 ### 回答2: Qt是一个跨平台的C++应用程序开发框架,可以用于开发各种类型的应用程序,包括局域网聊天室。首先要实现局域网聊天室,需要使用UDP协议进行通信,因为UDP是无连接的、不可靠的传输协议,适合于实时性要求较高的应用。 在Qt中,可以使用QUdpSocket类来实现UDP通信。首先,需要创建一个QUdpSocket对象,并绑定到本地IP和端口上。接下来,可以通过该对象的bind函数将socket绑定到指定的本地IP和端口上。然后,可以使用该对象的readDatagram函数接收其他主机发送过来的消息,并使用writeDatagram函数发送消息给其他主机。 在局域网聊天室中,需要实现多个主机之间的消息传递。可以使用多线程来处理消息接收和发送的操作,以免阻塞主线程。可以创建一个接收线程,通过QUdpSocket来接收其他主机发送的消息,并将接收到的消息发送给主线程进行处理。同时,可以创建一个发送线程,用于向其他主机发送消息。 在主线程中,可以使用Qt的信号和槽机制来处理接收到的消息。当接收线程接收到消息后,可以通过信号将消息传递给主线程,并在主线程中的槽函数中进行处理,例如显示在用户界面上。当用户在界面上输入消息后,可以通过发送线程将消息发送给其他主机。 总结起来,使用Qt的QUdpSocket类可以方便地实现局域网聊天室。通过创建多线程,利用信号和槽机制处理接收和发送的消息,可以实现实时的消息传递。使用UDP协议可以满足聊天室的实时性要求。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值