前言

QQ是一款优秀的聊天软件,本文将提供主要代码和思路来实现一个类似于QQ群聊的网络聊天软件,大致有以下俩个功能:

采用qt5编写,实现基于UDP的文本聊天功能,和基于TCP的文件传输功能

基本聊天会话功能

文件传输功能实现

一、主界面和聊天窗口

基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊-LMLPHP

#ifndef DRAWER_H
#define DRAWER_H

#include <QToolBox>
#include <QToolButton>
#include <QWidget>
#include "myqq.h"

class Drawer : public QToolBox
{
public:
    Drawer();
private:
    QToolButton *toolBtn1;


    //聊天对象窗口指针
    QWidget *chatWidget1;



private slots:
    // 显示聊天对象窗口
    void showChatWidget1();


    MyQQ *myqq;

};

#endif // DRAWER_H
 	setWindowTitle(tr("My QQ v01"));
    setWindowIcon(QPixmap(":/images/R-C.jpg"));

    toolBtn1 = new QToolButton;
    toolBtn1->setText(tr("冰雪奇缘"));
    toolBtn1->setIcon(QPixmap(":/images/girl1.jpg"));
    toolBtn1->setAutoRaise(true); //设置toolBtn1在显示时自动提升,使得按钮外观更加立体感。
    toolBtn1->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); //设置toolBtn1的按钮样式为图标在文本旁边的形式。
    // 将显示函数与抽屉盒中相对应的用户按钮进行绑定
    //connect(toolBtn1,SIGNAL(clicked()),this,SLOT(showChatWidget1()));
    //connect(toolBtn1, &QToolButton::clicked, this, &QToolBox::showChatWidget1);
    connect(toolBtn1, &QToolButton::clicked, this, &Drawer::showChatWidget1);

二、UDP聊天

原理:如果要进行聊天,则首先要获取所有登录用户的信息,这个功能是通过在每一个用户运行该程序时发送广播实现的,不仅用户登录时要进行广播,而且在用户退出、发送消息时都使用UDP广播来告知所有用户。

#ifndef SERVER_H
#define SERVER_H

#include <QDialog>
#include <QFile>
#include <QTcpServer>
#include <QTime>

namespace Ui {
class Server;
}

class Server : public QDialog
{
    Q_OBJECT

public:
    explicit Server(QWidget *parent = nullptr);
    ~Server();
    void initSrv(); // 初始化服务器
    void refused(); // 关闭服务器

protected:
    void closeEvent(QCloseEvent *);
    void updClntProgress(qint64 numBytes);

private slots:
    void on_Server_accepted();
    void sendMsg(); //发送数据
    void updclntProgress(qint64 numBytes); // 更新进度条

    void on_sOpenBtn_clicked();

    void on_sSendBtn_clicked();

    void on_sCloseBtn_clicked();

private:
    Ui::Server *ui;
    qint16 tPort;
    QTcpServer *tSrv;
    QString fileName;
    QString theFileName;
    QFile *locFile; //待发送的文件
    qint64 totalBytes; //总共要发送的
    qint64 bytesWritten; //已发送的
    qint64 bytesTobeWrite; //待发送的
    qint64 payloadSize;  //被初始化为一个常量
    QByteArray outBlock; // 缓存一次的
    QTcpSocket *clntConn;
    QTime time;

signals:
    void sendFileName(QString fileName);
};

#endif // SERVER_H

#include "server.h"
#include "ui_server.h"

#include <QFile>
#include<QTcpServer>
#include<QTcpSocket>
#include<QMessageBox>
#include <QFileDialog>
#include<QDebug>

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

    setFixedSize(400,207);
    tPort = 5555;
    tSrv = new QTcpServer(this);
    connect(tSrv,&QTcpServer::newConnection,this,&Server::sendMsg);
    initSrv();
}

void Server::initSrv()
{
    payloadSize = 64*1024;
    totalBytes = 0;
    bytesWritten = 0;

    ui->sOpenBtn->setEnabled(true);
    ui->sSendBtn->setEnabled(false);
    tSrv->close();
}

// 发送数据
void Server::sendMsg()
{
    ui->sSendBtn->setEnabled(false);
    clntConn = tSrv->nextPendingConnection();
    connect(clntConn,SIGNAL(bytesWritten(gint64)),this,SLOT(updCIntProgress(qint64)));
    ui->sStatusLabel->setText(tr("开始传送文件 号1 !").arg(theFileName));
    locFile = new QFile(fileName);
    if(!locFile->open((QFile::ReadOnly)))
    {
        QMessageBox::warning(this,tr("应用程序"), tr("无法读取文件号1: n各2").arg(fileName).arg(locFile->errorString()));
        return;
    }
    totalBytes = locFile->size();
    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);

    sendOut.setVersion(QDataStream::Qt_4_7);
    time.start();
    QString curFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);
    sendOut << qint64(0) << qint64((outBlock.size() - sizeof(qint64)*2));

    bytesTobeWrite = totalBytes - clntConn->write(outBlock);
    outBlock.reserve(0);
}

// 更新进度条
void Server::updClntProgress(qint64 numBytes)
{
    // 防止传输大文件产生冻结
    qApp->processEvents();
    bytesWritten += (int)numBytes;
    if(bytesTobeWrite > 0)
    {
        outBlock = locFile->read(qMin(bytesTobeWrite,payloadSize));
        bytesTobeWrite -= (int)clntConn->write(outBlock);
        outBlock.resize(0);
    } else{
        locFile->close();
    }
    ui->progressBar->setMaximum(totalBytes);
    ui->progressBar->setValue(bytesWritten);
    float useTime = time.elapsed();
    double speed = bytesWritten / useTime;
    ui->sStatusLabel->setText(tr("已发送 %1MB (%2MB/s)\n 共%3MB 已用时:%4s\n 估计剩余时间:%5秒")
            .arg(bytesWritten/(1024*1024))
            .arg(bytesWritten / (1024*1024))
            .arg(speed*1000 / (1024*1024),0,'f',0)
            .arg(totalBytes / (1024 * 1024)).arg(useTime/1000,0,'f',0)
            .arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));
    if(bytesWritten == totalBytes)
    {
            locFile->close();
            tSrv->close();
            ui->sStatusLabel->setText(tr("传送文件 %1 成功").arg(theFileName));
    }

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



void Server::on_sOpenBtn_clicked()
{
    fileName = QFileDialog::getOpenFileName(this);

    if(!fileName.isEmpty())
    {
        theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1);
        ui->sStatusLabel->setText(tr("要发送的文件为:%1").arg(theFileName));
        ui->sOpenBtn->setEnabled(false);
        ui->sSendBtn->setEnabled(true);
    }
}


void Server::on_sSendBtn_clicked()
{
    if(!tSrv->listen(QHostAddress::Any,tPort))
    {
        qDebug() << tSrv ->errorString();
        close();
        return;
    }
    ui->sStatusLabel->setText("等待……");
    emit sendFileName(theFileName);
}


void Server::on_sCloseBtn_clicked()
{
   if(tSrv->isListening())
   {
       tSrv->close();
       if(locFile->isOpen())
           locFile->close();
       clntConn->abort();
   }
   close();
}

void Server::closeEvent(QCloseEvent *)
{
    on_sCloseBtn_clicked();
}

void Server::refused()
{
    tSrv->close();
    ui->sStatusLabel->setText(tr("对方拒绝!"));
}

三、TCP文件传输

文件的传输采用TCP来实现,用C/S(客户端/服务器)方式,创建俩个新类,client和server类

server类

基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊-LMLPHP

#ifndef SERVER_H
#define SERVER_H

#include <QDialog>
#include <QFile>
#include <QTcpServer>
#include <QTime>

namespace Ui {
class Server;
}

class Server : public QDialog
{
    Q_OBJECT

public:
    explicit Server(QWidget *parent = nullptr);
    ~Server();
    void initSrv(); // 初始化服务器
    void refused(); // 关闭服务器

protected:
    void closeEvent(QCloseEvent *);
    void updClntProgress(qint64 numBytes);

private slots:
    void on_Server_accepted();
    void sendMsg(); //发送数据
    void updclntProgress(qint64 numBytes); // 更新进度条

    void on_sOpenBtn_clicked();

    void on_sSendBtn_clicked();

    void on_sCloseBtn_clicked();

private:
    Ui::Server *ui;
    qint16 tPort;
    QTcpServer *tSrv;
    QString fileName;
    QString theFileName;
    QFile *locFile; //待发送的文件
    qint64 totalBytes; //总共要发送的
    qint64 bytesWritten; //已发送的
    qint64 bytesTobeWrite; //待发送的
    qint64 payloadSize;  //被初始化为一个常量
    QByteArray outBlock; // 缓存一次的
    QTcpSocket *clntConn;
    QTime time;

signals:
    void sendFileName(QString fileName);
};

#endif // SERVER_H

#include "server.h"
#include "ui_server.h"

#include <QFile>
#include<QTcpServer>
#include<QTcpSocket>
#include<QMessageBox>
#include <QFileDialog>
#include<QDebug>

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

    setFixedSize(400,207);
    tPort = 5555;
    tSrv = new QTcpServer(this);
    connect(tSrv,&QTcpServer::newConnection,this,&Server::sendMsg);
    initSrv();
}

void Server::initSrv()
{
    payloadSize = 64*1024;
    totalBytes = 0;
    bytesWritten = 0;

    ui->sOpenBtn->setEnabled(true);
    ui->sSendBtn->setEnabled(false);
    tSrv->close();
}

// 发送数据
void Server::sendMsg()
{
    ui->sSendBtn->setEnabled(false);
    clntConn = tSrv->nextPendingConnection();
    connect(clntConn,SIGNAL(bytesWritten(gint64)),this,SLOT(updCIntProgress(qint64)));
    ui->sStatusLabel->setText(tr("开始传送文件 号1 !").arg(theFileName));
    locFile = new QFile(fileName);
    if(!locFile->open((QFile::ReadOnly)))
    {
        QMessageBox::warning(this,tr("应用程序"), tr("无法读取文件号1: n各2").arg(fileName).arg(locFile->errorString()));
        return;
    }
    totalBytes = locFile->size();
    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);

    sendOut.setVersion(QDataStream::Qt_4_7);
    time.start();
    QString curFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);
    sendOut << qint64(0) << qint64((outBlock.size() - sizeof(qint64)*2));

    bytesTobeWrite = totalBytes - clntConn->write(outBlock);
    outBlock.reserve(0);
}

// 更新进度条
void Server::updClntProgress(qint64 numBytes)
{
    // 防止传输大文件产生冻结
    qApp->processEvents();
    bytesWritten += (int)numBytes;
    if(bytesTobeWrite > 0)
    {
        outBlock = locFile->read(qMin(bytesTobeWrite,payloadSize));
        bytesTobeWrite -= (int)clntConn->write(outBlock);
        outBlock.resize(0);
    } else{
        locFile->close();
    }
    ui->progressBar->setMaximum(totalBytes);
    ui->progressBar->setValue(bytesWritten);
    float useTime = time.elapsed();
    double speed = bytesWritten / useTime;
    ui->sStatusLabel->setText(tr("已发送 %1MB (%2MB/s)\n 共%3MB 已用时:%4s\n 估计剩余时间:%5秒")
            .arg(bytesWritten/(1024*1024))
            .arg(bytesWritten / (1024*1024))
            .arg(speed*1000 / (1024*1024),0,'f',0)
            .arg(totalBytes / (1024 * 1024)).arg(useTime/1000,0,'f',0)
            .arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));
    if(bytesWritten == totalBytes)
    {
            locFile->close();
            tSrv->close();
            ui->sStatusLabel->setText(tr("传送文件 %1 成功").arg(theFileName));
    }

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



void Server::on_sOpenBtn_clicked()
{
    fileName = QFileDialog::getOpenFileName(this);

    if(!fileName.isEmpty())
    {
        theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1);
        ui->sStatusLabel->setText(tr("要发送的文件为:%1").arg(theFileName));
        ui->sOpenBtn->setEnabled(false);
        ui->sSendBtn->setEnabled(true);
    }
}


void Server::on_sSendBtn_clicked()
{
    if(!tSrv->listen(QHostAddress::Any,tPort))
    {
        qDebug() << tSrv ->errorString();
        close();
        return;
    }
    ui->sStatusLabel->setText("等待……");
    emit sendFileName(theFileName);
}


void Server::on_sCloseBtn_clicked()
{
   if(tSrv->isListening())
   {
       tSrv->close();
       if(locFile->isOpen())
           locFile->close();
       clntConn->abort();
   }
   close();
}

void Server::closeEvent(QCloseEvent *)
{
    on_sCloseBtn_clicked();
}

void Server::refused()
{
    tSrv->close();
    ui->sStatusLabel->setText(tr("对方拒绝!"));
}

Clint类

TCP客户端类,用于接收文件。
基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊-LMLPHP

#ifndef CLIENT_H
#define CLIENT_H

#include <QDialog>
#include <QHostAddress>
#include <QFile>
#include <QTime>
#include <QTcpSocket>


namespace Ui {
class client;
}

class client : public QDialog
{
    Q_OBJECT

public:
    explicit client(QWidget *parent = nullptr);
    ~client();
    void setHostAddr(QHostAddress addr);
    void setFileName(QString name);

protected:
    void closeEvent(QCloseEvent *);


private:
    Ui::client *ui;
    QTcpSocket *tClnt;
    quint16 blockSize;
    QHostAddress hostAddr;
    qint16 tPort;
    qint64 totalBytes;
    qint64 bytesReceived;
    qint64 fileNameSize;
    QString fileName;
    QFile *locFile;
    QByteArray inBlock;
    QTime time;

private slots:
    void newConn(); // 连接到服务器
    void readMsg(); // 读取文件数据
//    void displayErr(QAbstractSocket::SocketError); // 显示错误信息
    void on_cCancleBtn_clicked();
    void on_cCloseBtn_clicked();
};

#endif // CLIENT_H

#include "client.h"
#include "ui_client.h"
#include <QDebug>
#include <QMessageBox>
#include <QTime>

client::client(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::client)
{
    ui->setupUi(this);
    setFixedSize(400,190);
    totalBytes = 0;
    bytesReceived = 0;
    fileNameSize = 0;
    tClnt = new QTcpSocket(this);
    tPort = 5555;
    connect(tClnt, &QTcpSocket::readyRead, this, &client::readMsg);


}


// 连接服务器
void client::newConn()
{
    blockSize = 0;
    tClnt->abort();
    tClnt->connectToHost(hostAddr,tPort);
    time.start();
}

// 发送文件
void client::readMsg()
{
    QDataStream in(tClnt);
    in.setVersion(QDataStream::Qt_4_7);
    float useTime = time.elapsed();
    if (bytesReceived <= sizeof(qint64)*2)
    {
        if ((tClnt->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0))
        {
            in>>totalBytes>>fileNameSize;
            bytesReceived += sizeof(qint64)*2;
        }
        if((tClnt->bytesAvailable() >= fileNameSize) && (fileNameSize != 0))
        {
            in>>fileName;
            bytesReceived +=fileNameSize;
            if(!locFile->open(QFile::WriteOnly))
            {
                QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件%1:\n%2.").arg(fileName).arg(locFile->errorString()));
            }
            return;
        }else
        {
            return;
        }
    }
    if (bytesReceived < totalBytes)
    {
        bytesReceived += tClnt->bytesAvailable();
        inBlock = tClnt->readAll();
        locFile->write(inBlock);
        inBlock.resize(0);
    }
    ui->progressBar->setMaximum(totalBytes);
    ui->progressBar->setValue(bytesReceived);
    double speed = bytesReceived / useTime;
    ui->label_2->setText(tr("已接收 %1MB (%2MB/s)\n 共%3MB 已用时:%4s\n 估计剩余时间:%5秒")
                         .arg(bytesReceived/(1024*1024))
                         .arg(speed*1000 / (1024*1024),0,'f',0)
                         .arg(totalBytes / (1024 * 1024))
                         .arg(useTime/1000,0,'f',0)
                         .arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));
     if(bytesReceived == totalBytes)
     {
         locFile->close();
         tClnt->close();
         ui->label->setText(tr("接收文件 %1 成功").arg(fileName));
     }
}
client::~client()
{
    delete ui;
}

void client::on_cCancleBtn_clicked()
{
    tClnt->abort();
    if(locFile->isOpen())
        locFile->close();

}


void client::on_cCloseBtn_clicked()
{
    tClnt->abort();
    if(locFile->isOpen())
        locFile->close();
    close();
}

void client::closeEvent(QCloseEvent *)
{
    on_cCloseBtn_clicked();
}

最后

至此已完成,读者还可根据自己所需来添加一些拓展功能,更改字体、字号和颜色等等……如果本文对你有所帮助,还请三连支持一下博主!
基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊-LMLPHP

11-23 07:35