欢迎小伙伴的点评✨✨,相互学习c/c++应用开发。🍳🍳🍳
博主🧑🧑 本着开源的精神交流Qt开发的经验、将持续更新续章,为社区贡献博主自身的开源精神👩🚀
目录
前言
本章节会给大家带来Qt5 网络与通信—— 基于 TCP 的网络聊天室程序实例详解。
一、基于 TCP 的网络聊天室程序概述
传输控制协议 (Transmission Control Protocol, TCP) 是一种可靠、面向连接、面向数据流的传输协议,许多高层应用协议(包括 HTTP 、 FTP 等)都是以它为基础的, TCP 非常适合数据的连续传输。
TCP 与 UDP 的差别见下表
比较项 | TCP | UDP |
---|---|---|
是否连接 | 面向连接 | 无连接 |
传输可靠性 | 可靠 | 不可靠 |
流量控制 | 提供 | 不提供 |
工作方式 | 全双工 | 可以是全双工 |
应用场合 | 大量数据 | 少量数据 |
速度 | 慢 | 快 |
1.1、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 的网络聊天室程序会在应用程序开发中经常用到的。