Qt5开发从入门到精通——第十篇三节(Qt5 网络与通信—— 基于 TCP 的网络聊天室程序)

欢迎小伙伴的点评✨✨,相互学习c/c++应用开发。🍳🍳🍳
博主🧑🧑 本着开源的精神交流Qt开发的经验、将持续更新续章,为社区贡献博主自身的开源精神👩‍🚀

前言

本章节会给大家带来Qt5 网络与通信—— 基于 TCP 的网络聊天室程序实例详解。

一、基于 TCP 的网络聊天室程序概述

传输控制协议 (Transmission Control Protocol, TCP) 是一种可靠、面向连接、面向数据流的传输协议,许多高层应用协议(包括 HTTP 、 FTP 等)都是以它为基础的, TCP 非常适合数据的连续传输。
TCP 与 UDP 的差别见下表

比较项TCPUDP
是否连接面向连接无连接
传输可靠性可靠不可靠
流量控制提供不提供
工作方式全双工可以是全双工
应用场合大量数据少量数据
速度

1.1、TCP 工作原理

如下图所示, TCP 能够为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错地送达网络上的其他计算机。因此,对可靠性要求高的数据通信系统往往使用 TCP 传输数据,但在正式收发数据前,通信双方必须首先建立连接。

请求
应答
TCP客户端
TCP服务器

1.2、TCP 编程模型

下面介绍基于 TCP 的经典编程模型, TCP 客户端与服务器间的交互时序如图下所示。
在这里插入图片描述
首先启动服务器,一段时间后启动客户端,它与此服务器经过三次握手后建立连接。此后的一段时间内,客户端向服务器发送一个请求,服务器处理这个请求,并为客户端发回一个响应。这个过程一直待续下去,直到客户端为服务器发一个文件结束符,并关闭客户端连接,接着服务器也关闭服务器端的连接,结束运行或等待一个新的客户端连接。
Qt 中通过 QTcpSocket 类和 QTcpServer 类实现 TCP 的编程 。下面介绍如何实现一个基
于 TCP 的网络聊天室应用,它同样也由客户端和服务器两部分组成。

二、效果实例

图一
在这里插入图片描述

三、原码实例

3.1、工程文件已经上传GitHub 使用命令直接拉取即可

git clone   https://github.com/dhn111/Qt_TCP.git

3.2、首先先在新建好的工程文件xxx.pro文件中加入

QT       += network

server.h

#ifndef SERVER_H
#define SERVER_H

#include <QTcpServer>
#include <QObject>
#include "tcpclientsocket.h"
class Server : public QTcpServer
{   Q_OBJECT
public:
    Server(QObject *parent=0,int port=0);
    QList<TcpClientSocket*> tcpClientSocketList;
signals:
    void updateServer(QString,int);
public slots:
    void updateClients(QString,int);
    void slotDisconnected(int);
protected:
    void incomingConnection(int socketDescriptor);
};

#endif // SERVER_H

tcpclient.h

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QDialog>
#include <QListWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QGridLayout>
#include <QHostAddress>
#include <QTcpSocket>
class TcpClient : public QDialog
{
    Q_OBJECT
public:
    TcpClient(QWidget *parent= 0,Qt::WindowFlags f=0);
    ~TcpClient();
private:
    QListWidget *contentListWidget;
    QLineEdit *sendLineEdit;
    QPushButton *sendBtn;
    QLabel *userNameLabel;
    QLineEdit *userNameLineEdit;
    QLabel *serverIPLabel;
    QLineEdit *serverIPLineEdit;
    QLabel *portLabel;
    QLineEdit *portLineEdit;
    QPushButton *enterBtn;
    QGridLayout *mainLayout;


    bool status;
    int port;
    QHostAddress *serverIP;
    QString userName;
    QTcpSocket *tcpSocket;


public slots:
    void slotEnter();
    void slotConnected();
    void slotDisconnected();
    void dataReceived () ;
    void slotSend();
};

#endif // TCPCLIENT_H

tcpclientsocket.h

#ifndef TCPCLIENTSOCKET_H
#define TCPCLIENTSOCKET_H

#include <QTcpSocket>
#include <QObject>
class TcpClientSocket : public QTcpSocket
{
    Q_OBJECT  //添加宏 (Q OBJECT) 是为了实现信号与槽的通信
public:
    TcpClientSocket(QObject *parent=0);
signals:
    void updateClients (QString, int);
    void disconnected(int);
protected slots:
    void dataReceived ();
    void slotDisconnected();
};

#endif // TCPCLIENTSOCKET_H

tcpserver.h

#ifndef TEPSERVER_H
#define TEPSERVER_H

#include <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include "server.h"

class TcpServer : public QDialog
{
    Q_OBJECT  //添加宏 (Q_OBJECT) 是为了实现信号与槽的通信

public:
    TcpServer(QWidget *parent = 0,Qt::WindowFlags f=0);
    ~TcpServer();
private:
    QListWidget *ContentListWidget;
    QLabel *PortLabel;
    QLineEdit *PortLineEdit;
    QPushButton *CreateBtn;
    QGridLayout *mainLayout;

    int port;
    Server *server;
public slots:
    void slotCreateServer();
    void updateServer (QString, int);
};

#endif // TEPSERVER_H

main.cpp

#include "tcpserver.h"
#include "tcpclient.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QFont font ("ZYSongl8030", 18);
    a.setFont(font);

    TcpServer w;
    TcpClient w1,w2;

    w1.show();
    w2.show();
    w.show();

    return a.exec();
}

server.cpp

#include "server.h"

Server::Server(QObject *parent,int port) :QTcpServer(parent)
{
    listen(QHostAddress::Any,port);/*其中, listen(QHostAddress: :Any,port)在指定的端口对任意地址进行监听。
QHostAdclress 定义了几种特殊的 IP 地址,如 QHostAdclress: :Null 表示一个空地址;
QHostAdclress: :LocalHost 表示 1Pv4 的本机地址 127.0.0.1; QHostAdclress::LocalHostIPv6 表示 1Pv6
的本机地址; QHostAdclress: :Broadcast 表示广播地址 255.255.255.255; QHostAdclress::Any 表示
1Pv4 的任意地址 0.0.0.0; QHostAdclress::AnyIPv6 表示 1Pv6 的任意地址。*/
}

void Server::incomingConnection(int socketDescriptor)
{
    TcpClientSocket *tcpClientSocket=new TcpClientSocket(this);
    /*创建一个新的 TcpClientSocket与客户端通信 。*/
    connect(tcpClientSocket,SIGNAL(updateClients(QString,int)),
    this, SLOT (updateClients (QString, int))); //连接 TcpClientSocket 的 updateClients 信号 。
    connect(tcpClientSocket,SIGNAL(disconnected(int)),this,
    SLOT(slotDisconnected(int))); //连接 TcpClientSocket 的 disconnected 信号 。
    tcpClientSocket->setSocketDescriptor(socketDescriptor);
    //将新创建的 TcpClientSocket的套接字描述符指定为参数 socketDescriptor 。
    tcpClientSocketList.append(tcpClientSocket); //将 tcpClientSocket 加入客户端套接字列表以便管理 。
}

/*updateClients()函数将任意客户端发来的信息进行广播,保证聊
天室的所有成员均能看到其他人的发言。*/
void Server::updateClients(QString msg,int length)
{
    emit updateServer (msg, length); //发出 updateServer 信号,用来通知服务器对话框更新相应的显示状态 。
    for (int i=0; i<tcpClientSocketList. count(); i++) /*实现信息的广播 tcpClientSocketList 中保存
了所有与服务器相连的 TcpClientSocket 对象。*/
    {
        QTcpSocket *item= tcpClientSocketList.at(i);
        if (item->write (msg. toLatin1(), length) !=length)
        {
            continue;
        }

    }
}

/*slotDisconnectedQ函数实现从 tcpClientSocketList 列表中将断开
连接的 TcpClientSocket 对象删除的功能。*/
void Server::slotDisconnected(int descriptor)
{
    for (int i=0; i<tcpClientSocketList. count(); i++)
    {
        QTcpSocket *item= tcpClientSocketList.at(i);
        if (item->socketDescriptor () ==descriptor)
        {
            tcpClientSocketList.removeAt(i);
            return;
        }
    }
    return;

}

tcpclient.cpp

#include "tcpclient.h"
#include <QMessageBox>
#include <QHostInfo> ,
TcpClient::TcpClient(QWidget *parent,Qt::WindowFlags f)
: QDialog(parent,f)
{
    setWindowTitle(tr("TCP Client"));
    contentListWidget = new QListWidget;
    sendLineEdit = new QLineEdit;
    sendBtn = new QPushButton(tr("发送")) ;
    userNameLabel = new QLabel(tr(" 用户名:"));
    userNameLineEdit = new QLineEdit;
    serverIPLabel = new QLabel(tr("服务器地址:"));
    serverIPLineEdit = new QLineEdit;
    portLabel = new QLabel(tr("端口:"));
    portLineEdit = new QLineEdit;
    enterBtn= new QPushButton(tr("进入聊天室")) ;
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(contentListWidget,0,0,1,2);
    mainLayout->addWidget(sendLineEdit,1,0);
    mainLayout->addWidget(sendBtn,1,1);
    mainLayout->addWidget(userNameLabel,2,0);
    mainLayout->addWidget(userNameLineEdit,2,1);
    mainLayout->addWidget(serverIPLabel,3,0);
    mainLayout->addWidget(serverIPLineEdit,3,1);
    mainLayout->addWidget(portLabel,4,0);
    mainLayout->addWidget(portLineEdit,4,1);
    mainLayout->addWidget(enterBtn,5,0,1,2);


    status= false;
    port= 8010;
    portLineEdit->setText(QString::number(port));
    serverIP =new QHostAddress();
    connect (enterBtn, SIGNAL (clicked()), this, SLOT (slotEnter ()));
    connect (sendBtn, SIGNAL (clicked()), this, SLOT (slotSend ()));
    sendBtn->setEnabled(false);
}

TcpClient::~TcpClient()
{

}
/*槽函数 slotEnter()实现了进入和离开聊天室的功能*/
void TcpClient::slotEnter ()
{
    if (!status) /*表示当前的状态, true 表示已经进入聊天室, false 表示已经离开聊天
室。这里根据 status 的状态决定是执行“进入“还是“离开”的操作。*/
    {
        /*完成输入合法性检验*/
        QString ip = serverIPLineEdit->text ();
        if (! serverIP->setAddress (ip)) /*用来判断给定的 IP 地址能否被正确解析。*/
        {
            QMessageBox::information(this,tr("error"),tr("server ip addresserror!"));
            return;
        }
        if (userNameLineEdit->text () =="")
        {
            QMessageBox::information(this,tr("error"),tr("User name error!"));
            return;
        }


        userName=userNameLineEdit->text();
        /*创建了一个 QTcpSocket 类对象,并将信号/槽连接起来*/
        tcpSocket = new QTcpSocket(this);
        connect(tcpSocket,SIGNAL(connected()),this,SLOT(slotConnected()));
        connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
        connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
        tcpSocket->connectToHost(*serverIP,port); /*与 TCP 服务器端连接,连接成功后发出connected()信号。*/
        status=true;

    }else
    {
        int length=0;
        QString msg=userName+tr(" :Leave Chat Room"); //构造一条离开聊天室的消息。
        if((length=tcpSocket->write(msg.toLatin1(),msg.length())) !=msg.length()) /*通知服务器端以上构造的消息。*/
        {
            return;
        }
        tcpSocket->disconnectFromHost(); /*与服务器断开连接,断开连接后发出 disconnected()信号。*/
        status=false;
    }
}

/*槽函数 slotConnected()为 connected()信号的响应槽,当与服务器连
接成功后,客户端构造一条进入聊天室的消息,并通知服务器。*/
void TcpClient::slotConnected()
{
    sendBtn->setEnabled(true);
    enterBtn->setText(tr(" 离开")) ;
    int length=0;
    QString msg=userName+tr(":Enter Chat Room");
    if ((length=tcpSocket->write (msg. toLatin1() ,msg. length())) !=msg.length())
    {
        return;
    }
}
void TcpClient::slotSend ()
{
    if(sendLineEdit->text()=="")
    {
        return;
    }
    QString msg=userName+":"+sendLineEdit->text();
    tcpSocket->write(msg.toLatin1() , msg.length()) ;
    sendLineEdit->clear () ;
}
void TcpClient::slotDisconnected()
{
    sendBtn->setEnabled(false);
    enterBtn->setText(tr(" 进入聊天室")) ;
}

/*当有数据到来时,触发源文件 "tcpclient.cpp" 的 dataReceived()函数,从套接字中将有效数
据取出并显示,*/
void TcpClient::dataReceived ()
{
    while(tcpSocket->bytesAvailable()>0)
    {
        QByteArray datagram;
        datagram.resize(tcpSocket->bytesAvailable());
        tcpSocket->read (datagram.data(), datagram. size());
        QString msg=datagram.data ();
        contentListWidget->addItem(msg.left(datagram.size()));
    }


}

tcpclientsocket.cpp

#include "tcpclientsocket.h"

TcpClientSocket::TcpClientSocket(QObject *parent)
{
    /*readyRead()是 QIODevice
的 signal, 由 QTcpSocket 继承而来。 QIODevice 是所有输入/输出设备的一个抽象类,其中定义
了基本的接口,在 Qt 中, QTcpSocket 也被看成一个 QIODevice, readyRead()信号在有数据到来
时发出。*/
    connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived ()));
    /*disconnected()信号在断开连接时发出。*/
    connect (this, SIGNAL (disconnected()), this, SLOT (slotDisconnected ()));
}

/*当有数据到来时,触发 dataReceived()函数,从套接字中将有效数据取出,然后发出
updateClients()信号。 updateClients()信号是通知服务器向聊天室内的所有成员广播信息。*/
void TcpClientSocket::dataReceived()
{
    while (bytesAvailable () >0)
    {
        int length= bytesAvailable();
        char buf[1024];
        read(buf,length);
        QString msg=buf;
        emit updateClients (msg, length);

    }


}

/**/
void TcpClientSocket::slotDisconnected()
{
    emit disconnected(this->socketDescriptor());
}

tcpserver.cpp

#include "tcpserver.h"

TcpServer::TcpServer(QWidget *parent,Qt::WindowFlags f)
    : QDialog(parent,f)
{
    setWindowTitle (tr ("T_CP Server")) ;
    ContentListWidget = new QListWidget;
    PortLabel = new QLabel(tr(" 端口: ")) ;
    PortLineEdit = new QLineEdit;
    CreateBtn = new QPushButton(tr(" 创建聊天室")) ;
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(ContentListWidget,0,0,1,2);
    mainLayout->addWidget(PortLabel,1,0);
    mainLayout->addWidget(PortLineEdit,1,1);
    mainLayout->addWidget(CreateBtn,2,0,1,2);
    port=8010;
    PortLineEdit->setText(QString::number(port));
    connect (CreateBtn, SIGNAL (clicked()), this, SLOT (slotCreateServer()));
}

TcpServer::~TcpServer()
{

}

void TcpServer::slotCreateServer()
{
    server = new Server (this, port);  //创建一个 Server 对象
    connect(server,SIGNAL(updateServer(QString,int)),this,
    SLOT (updateServer (QString, int)));     // 将 Server 对象的 updateServerO信号与相应的槽函数进行连接。
    CreateBtn->setEnabled(false);
}

void TcpServer::updateServer(QString msg,int length)
{
    ContentListWidget->addItem(msg.left(length));
}

四、总结

Qt5 网络与通信—— 基于 TCP 的网络聊天室程序会在应用程序开发中经常用到的。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东.'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值