基于Qt框架实现网络聊天室项目

1、实现的效果

2、需求分析 

2.1服务器

  • 基于TCP协议创建服务器
  • 响应客户端的连接请求
  • 实时接收所有客户端发来的聊天消息 保存(显示)聊天消息,并转发消息给其它客户端
  • 注:服务器要及时检查和客户端连接状态,如发现有客户端已经断开连接,需要删除通信套接字

2.1客户端

  • 创建TCP套接字
  • 配置服务器的IP、端口和聊天室昵称(可选)
  • 向服务器发送连接请求
  • 获取用户输入的聊天消息,发送到服务器
  • 实时接收服务器转发的聊天消息,并显示

3、项目框架

4、UI界面设计

        服务器界面

        客户端界面

5、相关代码

5.1服务器

Service.pro

QT       += network  // 网络相关模块

serviceDialog.h 头文件

#ifndef SERVICEDIALOG_H
#define SERVICEDIALOG_H

#include <QDialog>
#include <QTcpServer>   // tcp连接
#include <QTcpSocket>   // tcp套接字
#include <QTimer>      // 定时器
#include <QDebug>

namespace Ui {
class ServiceDialog;
}

class ServiceDialog : public QDialog
{
    Q_OBJECT

public:
    explicit ServiceDialog(QWidget *parent = 0);
    ~ServiceDialog();

private slots:
    // 创建服务器按钮对应的槽函数
    void on_createButton_clicked();

    // 响应客户端连接请求的槽函数
    void onNewConnection();

    // 接收客户端消息的槽函数
    void onReadyRead();

    // 转发聊天消息给其它客户端
    void sendMessage(const QByteArray & buf);

    // 定时器到时后将执行的槽函数
    void onTimeout(void);


private:
    Ui::ServiceDialog *ui;

    QTcpServer tcpService;  // 服务器对象
    quint16 port;           // 服务器端口
    QList<QTcpSocket *> tcpClientList; //容器:保存所有和客户端通信的套接字
    QTimer timer ;  //定时器

};

#endif // SERVICEDIALOG_H

serviceDialog.cpp 源文件

#include "servicedialog.h"
#include "ui_servicedialog.h"

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

    //当有客户端向服务器发送连接请求,发送信号:newConnection
    connect(&tcpService, SIGNAL(newConnection()),
            this, SLOT(onNewConnection()));

    //定时器到时发送信号:timeout
    connect(&timer, SIGNAL(timeout()),
            this, SLOT(onTimeout()));

}


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


// 创建服务器按钮对应的槽函数
void ServiceDialog::on_createButton_clicked()
{
    // 获取服务器端口
    port = ui->protEdit->text().toShort();

    // 设置服务器IP和端口
    if(tcpService.listen(QHostAddress::Any, port) == true)
    {
        qDebug() << "创建服务器成功!";
        // 显示创建服务器消息
        ui->listWidget->addItem("创建服务器成功");
        ui->listWidget->scrollToBottom();

        // 禁用"创建服务器按钮"和"端口输入"
        ui->createButton->setEnabled(false);
        ui->protEdit->setEnabled(false);

        // 开启定时器
        timer.start(3000);

    }
    else
    {
        qDebug() << "创建服务器失败";
    }


}



// 响应客户端连接请求的槽函数
void ServiceDialog::onNewConnection()
{
     // 获取和客户端通信的套接字
     QTcpSocket * tcpClient = tcpService.nextPendingConnection();
     // 保存套接字到容器
     tcpClientList.append(tcpClient);
     // 当客户端向服务器发送消息时,通信套接字发送信号:readyRead
     connect(tcpClient, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

}

// 接收客户端消息的槽函数
void ServiceDialog::onReadyRead()
{
    // 遍历容器哪个客户端给服务器发送了消息
    for(int i=0; i<tcpClientList.size(); i++)
    {
        // bytesAvailable:获取当前套接字等待读取消息字节数
        // 返回0表示没有消息; 返回>0,说明当前套接字有消息到来
        if(tcpClientList.at(i)->bytesAvailable())
        {
            // 读取消息并保存
            QByteArray buf = tcpClientList.at(i)->readAll();

            // 显示聊天消息
            ui->listWidget->addItem(buf);
            ui->listWidget->scrollToBottom();

            // 转发消息给所有在线客户端
            sendMessage(buf);

        }
    }
}

// 转发聊天消息给其它客户端
void ServiceDialog::sendMessage(const QByteArray & buf)
{
    for(int i=0; i<tcpClientList.size(); i++)
    {
        tcpClientList.at(i)->write(buf);
    }

}


// 定时器到时后将执行的槽函数
void ServiceDialog::onTimeout(void)
{
    // 遍历检查容器中保存的客户端通信套接字是否已经断开链接,如果是则删除
    for(int i=0; i<tcpClientList.size(); i++)
    {
        // 客户端通信套接字是否已经断开链接
        if(tcpClientList.at(i)->state() == QAbstractSocket::UnconnectedState)
        {
            tcpClientList.removeAt(i); // 删除
            --i ;
        }
    }
}

/*
    --i 的原因: 容器中的每一个套接字都读取到
    容器: s1 s2 s4 s5
    下标: 0  1  2  3  4
 */




5.2客户端

Client.pro

QT       += network  // 网络相关模块

clientDialog.h 头文件

#ifndef CLIENTDIALOG_H
#define CLIENTDIALOG_H

#include <QDialog>
#include <QTcpSocket>    // 创建套接字
#include <QHostAddress>  // 服务器IP地址
#include <QMessageBox>
#include <QDebug>

namespace Ui {
class ClientDialog;
}

class ClientDialog : public QDialog
{
    Q_OBJECT

public:
    explicit ClientDialog(QWidget *parent = 0);
    ~ClientDialog();

private slots:
    // 发送按钮对应的槽函数
    void on_sendButton_clicked();

    // 连接服务器按钮对应的槽函数
    void on_connectButton_clicked();

    // 和服务器连接成功时执行的槽函数
    void onConnected();

    // 和服务器断开连接时执行的槽函数
    void onDisConnected();

    // 接收聊天消息的槽函数
    void onReadyRead();

    // 网络异常执行的槽函数
    void onError();


private:
    Ui::ClientDialog *ui;

    bool status ;           // 标识客户端状态: 在线/离线
    QTcpSocket tcpSocket;   // 和服务器通信的套接字
    QHostAddress serviceIP; // 服务器地址
    quint16 servicePort ;   // 服务器端口
    QString username ;      // 聊天室昵称

};

#endif // CLIENTDIALOG_H

clientDialog.cpp 源文件

#include "clientdialog.h"
#include "ui_clientdialog.h"

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

    // 初始化
    status = false ; //离线

    connect(&tcpSocket, SIGNAL(connected()), this, SLOT(onConnected()));
    connect(&tcpSocket, SIGNAL(disconnected()), this, SLOT(onDisConnected()));
    connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead())) ;
    connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError()));

}

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

 // 发送按钮对应的槽函数
void ClientDialog::on_sendButton_clicked()
{
    // 获取用户输入聊天消息
    QString msg = ui->messageEdit->text();
    if(msg == "")
    {
        return ;
    }

    msg = username + ": " + msg;
    // 发送聊天消息
    tcpSocket.write(msg.toUtf8());

    // 清空消息输入框
    ui->messageEdit->clear();

}

// 连接服务器按钮对应的槽函数
void ClientDialog::on_connectButton_clicked()
{
    // 如果当前是离线状态,则建立和服务器连接
    if(status == false)
    {
        // 获取服务器IP
        serviceIP.setAddress(ui->serviceIpEdit->text());
        // 获取服务器端口
        servicePort = ui->servicePortEdit->text().toShort() ;
        // 获取聊天室昵称
        username = ui->usernameEdit->text();
        // 向服务器发送连接请求
        // 成功发送信号:connected
        // 失败发送信号:error
        tcpSocket.connectToHost(serviceIP, servicePort);
    }
    else  // 如果当前是在线状态,则断开和服务器连接
    {
        //向服务发送离开聊天室的提示消息
        QString msg = username + ":离开了聊天室!";
        tcpSocket.write(msg.toUtf8());

        // 关闭和服务器连接,发送信号: disconnected
        tcpSocket.disconnectFromHost();
    }

}

// 和服务器连接成功时执行的槽函数
void ClientDialog::onConnected()
{
    status = true ;  // 在线
    ui->sendButton->setEnabled(true);       // 恢复按钮状态
    ui->serviceIpEdit->setEnabled(false);   // 禁用IP
    ui->servicePortEdit->setEnabled(false); // 禁用Port
    ui->usernameEdit->setEnabled(false);    // 禁用昵称
    ui->connectButton->setText("离开聊天室");

    // 向服务器发送进入聊天室提示消息
    QString msg = username + ":进入了聊天室";
    // toUtf8:QString转换QByteArray
    tcpSocket.write(msg.toUtf8());
}

// 和服务器断开连接时执行的槽函数
void ClientDialog::onDisConnected()
{
    status = false;     // 离线
    ui->sendButton->setEnabled(false);      // 禁用按钮
    ui->serviceIpEdit->setEnabled(true);     // 恢复IP
    ui->servicePortEdit->setEnabled(true);   // 恢复Port
    ui->usernameEdit->setEnabled(true);     // 恢复昵称
    ui->connectButton->setText("连接服务器");

}

// 接收聊天消息的槽函数
void ClientDialog::onReadyRead()
{
    if(tcpSocket.bytesAvailable())
    {
        // 接收消息
        QByteArray buf = tcpSocket.readAll();
        // 显示消息
        ui->listWidget->addItem(buf);
        ui->listWidget->scrollToBottom();
    }
}


// 网络异常执行的槽函数
void ClientDialog::onError()
{
    // errorString():获取网络异常的原因
    QMessageBox::critical(this,"ERROR",tcpSocket.errorString());
}

6、补充

有兴趣的小伙伴可以添加以下功能,完善项目。

  • 优化界面
  • 容错处理
  • 增加功能:登录窗口、私聊、文件发送等

未完待续

有疑问或者有更好的项目,小伙伴们可以留言一起交流讨论!!

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值