Qt直接提供网络编程模块,基于TCP/IP客户端和服务器端相关各种类。TCP通信(QTcpSocket/QTcpServer).UDP通信(QUdpSocket)。还有部分实现HTTP、FTP等网络相关协议的高级类。如QNetworkRequest/QNetworkAccessManager(HTTP)等;
它的网络编程模块提供网络承载管理类,提供基于安全套接字协议(SSL)的安全网络通信类。
我们开发过程中在项目配置文件引入:QT += network
QHostInfo 类为主机查找提供静态函数,主要用来查询主机信息、包含主机名、ip地址、DNS域名等信息;
QNetworkInterface类,主要获取主机所有ip地址和网络接口列表信息
demo历程
.h
#ifndef GETHOSTNAMEIPINFO_H
#define GETHOSTNAMEIPINFO_H
#include <QDialog>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
namespace Ui { class GetHostNameIPInfo; }
QT_END_NAMESPACE
class GetHostNameIPInfo : public QDialog
{
Q_OBJECT
public:
GetHostNameIPInfo(QWidget *parent = nullptr);
~GetHostNameIPInfo();
void GetHostNameAndIpAddress(); // 获取主机名称和IP地址
private slots:
void on_pushButton_GetHostNameIP_clicked();
void on_pushButton_GetHostInfo_clicked();
private:
Ui::GetHostNameIPInfo *ui;
};
#endif // GETHOSTNAMEIPINFO_H
.c
#include "gethostnameipinfo.h"
#include "ui_gethostnameipinfo.h"
GetHostNameIPInfo::GetHostNameIPInfo(QWidget *parent)
: QDialog(parent)
, ui(new Ui::GetHostNameIPInfo)
{
ui->setupUi(this);
}
GetHostNameIPInfo::~GetHostNameIPInfo()
{
delete ui;
}
void GetHostNameIPInfo::GetHostNameAndIpAddress() // 获取主机名称和IP地址
{
// 获取主机名称
QString StrLocalHostName=QHostInfo::localHostName();
ui->lineEdit_hostname->setText(StrLocalHostName);
// 根据主机名称获取对应的IP地址
QString StrLocalIpAddress="";
QHostInfo hostinfo=QHostInfo::fromName(StrLocalHostName);
QList<QHostAddress> ipaddresslist=hostinfo.addresses();
if(!ipaddresslist.isEmpty())
{
for (int i=0;i<ipaddresslist.size();i++)
{
QHostAddress addresshost=ipaddresslist.at(i);
if(QAbstractSocket::IPv4Protocol==addresshost.protocol())
{
StrLocalIpAddress=addresshost.toString();
break;
}
}
}
ui->lineEdit_hostip->setText(StrLocalIpAddress);
}
void GetHostNameIPInfo::on_pushButton_GetHostNameIP_clicked()
{
GetHostNameAndIpAddress();
}
void GetHostNameIPInfo::on_pushButton_GetHostInfo_clicked()
{
QString strTemp="";
// 返回主机所找到的所有网络接口的列表
QList<QNetworkInterface> netlist=QNetworkInterface::allInterfaces();
for(int i=0;i<netlist.size();i++)
{
QNetworkInterface interfaces=netlist.at(i);
strTemp=strTemp+"设备名称:"+interfaces.name()+"\n"; // 获取设备名称
strTemp=strTemp+"硬件地址:"+interfaces.hardwareAddress()+"\n"; // 获取硬件地址
QList<QNetworkAddressEntry> entrylist=interfaces.addressEntries(); // 遍历每一个IP地址对应信息
for (int k=0;k<entrylist.count();k++)
{
QNetworkAddressEntry etry=entrylist.at(k);
strTemp=strTemp+"IP地址:"+etry.ip().toString()+"\n";
strTemp=strTemp+"子网掩码:"+etry.netmask().toString()+"\n";
strTemp=strTemp+"广播地址:"+etry.broadcast().toString()+"\n";
}
}
QMessageBox::information(this,"主机所有信息",strTemp,QMessageBox::Yes);
}
一、TCP协议
1.传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
2.TCP拥塞控制算法(也称AIMD)算法。该算法主要包括四个主部分:慢启动、拥塞避免、快速重传和快速恢复。
3.TCP通信必须建立TCP连接(客户端和服务器),Qt提供QTcpSocket类和QTcpServer类专门用于建立TCP通信程序。服务器端用QTcpServer监听端口及建立服务器;QTcpSocket用于建立连接后使用套接字(socket)进行通信。
QIODevice 父类 派生出QUdpSocket和QTcpSOcket;QTcpSocket派生出QStcpSocket和QSslSocket ;而QTcpServer是从QObject继承的类用于服务器网络监听,创建socket连接。
server服务器
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer> // 专门用于建立TCP连接并传输数据信息
#include <QtNetwork> // 此模块提供开发TCP/IP客户端和服务器的类
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui
// 自定义如下
private:
QTcpServer *tcpserver; //TCP服务器
QTcpSocket *tcpsocket;// TCP通讯socket
QString GetLocalIpAddress(); // 获取本机的IP地址
private slots:
void clientconnect();
void clientdisconnect();
void socketreaddata();
void newconnection();
void on_pushButton_Start_clicked();
void on_pushButton_Stop_clicked();
void on_pushButton_Send_clicked();
protected:
void closeEvent(QCloseEvent *event);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <stdexcept>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString localip = GetLocalIpAddress();
ui->comboBoxip->addItem(localip);
ui->comboBoxip->addItem("192.168.66.158");
tcpServer = new QTcpServer(this);
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(new_connect()));
}
MainWindow::~MainWindow()
{
delete ui;
}
//获取本机IP地址
QString MainWindow::GetLocalIpAddress()
{
//查询主机名称
QString hostName = QHostInfo::localHostName();
//根据主机名称查找它本身的IP
QHostInfo hostinfp = QHostInfo::fromName(hostName);
QString localip = "";
//返回主机ip地址
QList<QHostAddress> addressList = hostinfp.addresses();
if(!addressList.isEmpty())
{
for(QHostAddress list:addressList)
{
if(list.protocol() == QAbstractSocket::IPv4Protocol)
{
localip = list.toString();
break;
}
}
}
return localip;
}
void MainWindow::client_connect()
{
//客户端连接
ui->plainTextEdit->appendPlainText("************客户端socket连接************");
ui->plainTextEdit->appendPlainText("************peer address:"+tcpSocket->peerAddress().toString());
ui->plainTextEdit->appendPlainText("************peer port :"+QString::number(tcpSocket->peerPort()));
}
void MainWindow::client_dis_connect()
{
//客户端断开连接
ui->plainTextEdit->appendPlainText("************客户端socket断开连接************");
tcpSocket->deleteLater();
}
void MainWindow::socket_read_data()
{
//读取数据
while(tcpSocket->canReadLine())
{
ui->plainTextEdit->appendPlainText("[in]"+tcpSocket->readLine());
}
}
void MainWindow::new_connect()
{
//将下一个挂起的连接作为已连接的QTcpSocket对象返回。
//套接字是作为服务器的子节点创建的,这意味着当QTcpServer对象被销毁时,它将被自动删除。在使用完对象后显式删除它仍然是一个好主意,以避免浪费内存。
//如果在没有挂起连接的情况下调用此函数,则返回0。
//注意:返回的QTcpSocket对象不能在其他线程中使用。如果您想使用来自另一个线程的传入连接,则需要重写incomingConnection()。
tcpSocket = tcpServer->nextPendingConnection();
//qDebug()<<tcpSocket<<"hahahhaha";
connect(tcpSocket,SIGNAL(connected()),this,SLOT(client_connect()));
client_connect();
connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(client_dis_connect()));
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(socket_read_data()));
connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(OnSocketStateChanged(QAbstractSocket::SocketState)));
}
void MainWindow::on_pushButtonstart_clicked()
{
QString ip = ui->comboBoxip->currentText(); //组合框当前的值
quint16 port = ui->spinBoxport->value();//这个属性保存旋转框的值
QHostAddress address(ip);
tcpServer->listen(address,port); //Tcpserver监听
ui->plainTextEdit->appendPlainText("$$$$$$$$$$开始监听$$$$$$$$$$");
ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器地址$$$$$$$$$"+tcpServer->serverAddress().toString());
ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器端口号$$$$$$$$"+QString::number(tcpServer->serverPort()));
ui->pushButtonstart->setEnabled(false);
ui->pushButtonstop->setEnabled(true);
}
void MainWindow::on_pushButtonstop_clicked()
{
if(tcpServer->isListening())
{
tcpServer->close();
ui->pushButtonstart->setEnabled(true);
ui->pushButtonstop->setEnabled(true);
ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器已关闭$$$$$$$$");
}
/* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
* 因为 socket 未断开,还在监听上一次端口
*/
if(tcpSocket->state() == tcpSocket->ConnectedState)
{
tcpSocket->disconnectFromHost();
}
}
void MainWindow::on_pushButtonsend_clicked()
{
if(NULL == tcpSocket)
return;
//tcpSocket->ConnectedState 等价于 QAbstractSocket::ConnectedState
if(tcpSocket->state() == tcpSocket->ConnectedState)
{
QString strMsg = ui->lineEditinputmsg->text();
ui->plainTextEdit->appendPlainText("[out]:"+strMsg);
ui->lineEditinputmsg->clear(); //清除文本信息
QByteArray str = strMsg.toUtf8();
str.append("\n");
tcpSocket->write(str);
}
}
/*
void MainWindow::closeEvent(QCloseEvent *event):
这是一个事件处理函数,用于处理主窗口的关闭事件。当用户尝试关闭主窗口时,这个函数会被触发。
if (tcpServer->isListening()):
这行代码检查一个名为 tcpServer 的对象是否正在监听传入的连接。isListening 是 Qt 的 QTcpServer 类的成员函数,用于检查服务器是否正在监听。
tcpServer->close():
如果 tcpServer 正在监听传入的连接(isListening 返回 true),则通过调用 close 方法来关闭服务器。这会停止服务器接受新的连接请求。
event->accept():
最后,通过调用 accept 方法,设置事件对象的 accept 标志为 true,表示主窗口可以正常关闭。这是必要的,因为在某些情况下,你可能希望阻止窗口关闭,例如在某些数据未保存的情况下。
在这种情况下,你可以将 accept 标志设置为 false,以阻止窗口关闭。
*/
void MainWindow::closeEvent(QCloseEvent *event)
{
//如果服务器当前正在侦听传入的连接,则返回true;否则返回false。
if(tcpServer->isListening())
tcpServer->close();
//设置事件对象的accept标志,相当于调用setAccepted(true)。
//表示接收关闭请求
event->accept();
}
void MainWindow::on_pushButtonclear_clicked()
{
ui->plainTextEdit->clear();
}
clicent客户端
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtNetwork>
#include <QHostInfo>
#include <QHostAddress>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QTcpSocket *tcpClient; //tcpClient
QString GetLocalIp(); //获取�?机IP地址
protected:
void closeEvent(QCloseEvent *event);
private slots:
void connect_func();
void dis_connect_func();
void socket_read_data();
void on_pushButtonstart_clicked();
void on_pushButtonstop_clicked();
void on_pushButtonsend_clicked();
void on_pushButtonclear_clicked();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->widget);
QString hostIp = GetLocalIp();
ui->comboBoxip->addItem(hostIp);
//ui->comboBoxip->addItem("192.168.66.158");
tcpClient = new QTcpSocket(this);
connect(tcpClient,SIGNAL(connected()),this,SLOT(connect_func()));
connect(tcpClient,SIGNAL(disconnected()),this,SLOT(dis_connect_func()));
connect(tcpClient,SIGNAL(readyRead()),this,SLOT(socket_read_data()));
}
MainWindow::~MainWindow()
{
delete ui;
}
QString MainWindow::GetLocalIp()
{
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIp = "";
QList<QHostAddress> addressList = hostInfo.addresses();
if(!addressList.isEmpty())
{
for(QHostAddress list:addressList)
{
if(list.protocol() == QAbstractSocket::IPv4Protocol)
{
localIp = list.toString();
break;
}
}
}
return localIp;
}
void MainWindow::on_pushButtonstart_clicked()
{
/* 如果连接状态还没有连接 */
if(tcpClient->state() != QAbstractSocket::ConnectedState)
{
QString addr = ui->comboBoxip->currentText();
quint16 port = ui->spinBoxport->value();
tcpClient->connectToHost(addr,port);
}
}
void MainWindow::on_pushButtonstop_clicked()
{
if(tcpClient->state() == QAbstractSocket::ConnectedState)
{
//关闭套接字的I/O设�?�,并调用disconnectFromHost()来关�?套接字的连接�?
tcpClient->disconnectFromHost(); //关闭套接字的连接
tcpClient->close();//�?己加�?
}
}
void MainWindow::on_pushButtonsend_clicked()
{
if(NULL == tcpClient)
return;
if(tcpClient->state() == tcpClient->ConnectedState)
{
QString strMsg = ui->lineEditinputmsg->text();
ui->plainTextEdit->appendPlainText("[out]:"+strMsg);
ui->lineEditinputmsg->clear();
//发送字节数�?
tcpClient->write(strMsg.toUtf8().append("\n"));
}
}
void MainWindow::on_pushButtonclear_clicked()
{
ui->plainTextEdit->clear();
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if(tcpClient->state() == QAbstractSocket::ConnectedState)
{
tcpClient->disconnectFromHost(); //关闭套接�?
}
event->accept();
}
void MainWindow::connect_func()
{
ui->plainTextEdit->appendPlainText("**********已经连接到服务器�?**********");
ui->plainTextEdit->appendPlainText("**********peer address:"+tcpClient->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**********peer port:"+QString::number(tcpClient->peerPort()));
ui->pushButtonstart->setEnabled(false);
ui->pushButtonstop->setEnabled(true);
}
void MainWindow::dis_connect_func()
{
ui->plainTextEdit->appendPlainText("**********�?开服务器连�?**********");
ui->pushButtonstart->setEnabled(true);
ui->pushButtonstop->setEnabled(true);
}
void MainWindow::socket_read_data()
{
//如果�?以从套接字�?�取一行数�?,则返回true;否则返回false�?
while(tcpClient->canReadLine())
{
ui->plainTextEdit->appendPlainText("[in]:"+tcpClient->readLine());
}
}
二、UDP协议基础知识
1.UDP(用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,用于可靠性要求不高的场合。两个应用程序之间进行UDP通信不需要先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。
2. UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟较小、数据传输效率高,适合可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。
3.UDP 报头由4 个域组成,其中每个域各占用2 个字节,具体包括源端口号、目标端口号、数据包长度、校验值。端口号有效范围0--65535,假设端口号大于49151 的端口都代表动态端口。
4、QUdpSocket 类从QAbstractSocket 类继承,基本跟QTcpSocket共用大部分的接口函数,主要区别在于QUdpSocket 以数据报传输数据, 不是以连续的数据流, 发送方发送数据报使用函数
QUdpSocket::writeDataGram(),数据报长度一般不超过512 个字节,每个数据报包含发送方和接收方的IP 地址和端口等数据信息。
5、UDP 数据接收使用QUdpSocket::bind()函数绑定端口,用于接收传入的数据报,当有数据报传入发射readyRead()信号,使用ReadDatagram()函数来读取接收数据报。UDP 消息传送有单播、广播和组播三种模式。
单播:一个UDP 客户端发出数据报只发送到另一个指定地址和端口的UDP 客户端(一对一的数据传输)。
广播:一个UDP 客户端发出的数据,在同一个网络范围内其它所有UDP 客户端都可以收到。
组播(多播):UDP 客户端加入到另一个组播IP 地址指定的多播组,成员向组播地址发送的数据报组内成员都可以接收到。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket> // 用于发送和接收UDP数据报
#include <QtNetwork>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButtonStart_clicked();
void on_pushButtonStop_clicked();
void on_pushButtonSend_clicked();
void on_pushButtonBroadcast_clicked();
void on_pushButtonClear_clicked();
private:
Ui::MainWindow *ui;
QUdpSocket *udpSocket;
QString getLocalIpAddress();//获取本机的IP地址
//自定义槽函数
private slots:
void socketReadyReadData(); //读取socket传输数据信息
void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->centralwidget);
QString strIp = getLocalIpAddress();
ui->comboBoxTargetIp->addItem(strIp);
udpSocket = new QUdpSocket(this); //专门用于连接客户端的UDP套接字
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(socketReadyReadData()));
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}
MainWindow::~MainWindow()
{
delete ui;
}
//启动服务
void MainWindow::on_pushButtonStart_clicked()
{
quint16 port = ui->spinBoxBind->value(); //本机udp端口
/* 绑定端口需要在 socket 的状态为 UnconnectedState */
if (udpSocket->state() != QAbstractSocket::UnconnectedState)
udpSocket->close();
if(udpSocket->bind(port))
{
ui->plainTextEditMsg->appendPlainText("**********服务启动成功**********");
ui->plainTextEditMsg->appendPlainText("**********绑定端口号:"+QString::number(udpSocket->localPort()));
ui->pushButtonStart->setEnabled(false);
ui->pushButtonStop->setEnabled(true);
}
else
{
ui->plainTextEditMsg->appendPlainText("**********服务启动失败**********");
}
}
void MainWindow::on_pushButtonStop_clicked()
{
udpSocket->abort(); //解绑 不需要监听了
ui->pushButtonStart->setEnabled(true);
ui->pushButtonStop->setEnabled(true);
ui->plainTextEditMsg->appendPlainText("**********停止服务**********");
}
void MainWindow::on_pushButtonSend_clicked()
{
//获取 目标的IP地址
QString targetIpAddress = ui->comboBoxTargetIp->currentText();
QHostAddress targetAddress(targetIpAddress);
//获取 目标的端口号
quint16 targetPort = ui->spinBoxTarget->value();
QString targetMsg = ui->lineEditMsg->text();
QByteArray str = targetMsg.toUtf8();
//发送数据
udpSocket->writeDatagram(str,targetAddress,targetPort);
ui->plainTextEditMsg->appendPlainText("[out]:"+targetMsg);
ui->lineEditMsg->clear();
ui->lineEditMsg->setFocus(); //将光标焦点定位到编辑框控件
}
void MainWindow::on_pushButtonBroadcast_clicked()
{
//获取 目标的端口号
quint16 targetPort = ui->spinBoxTarget->value();
QString targetMsg = ui->lineEditMsg->text();
QByteArray str = targetMsg.toUtf8();
//发送数据
udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);
ui->plainTextEditMsg->appendPlainText("[broadcast]:"+targetMsg);
ui->lineEditMsg->clear();
ui->lineEditMsg->setFocus(); //将光标焦点定位到编辑框控件}
}
void MainWindow::on_pushButtonClear_clicked()
{
ui->plainTextEditMsg->clear();
}
QString MainWindow::getLocalIpAddress()
{
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString strIp = "";
QList<QHostAddress> addressList = hostInfo.addresses();
if(!addressList.isEmpty())
{
for(QHostAddress list:addressList)
{
if(list.protocol() == QAbstractSocket::IPv4Protocol)
{
strIp = list.toString();
break;
}
}
}
return strIp;
}
void MainWindow::socketReadyReadData()
{
//读取接受到的数据报信息
while(udpSocket->hasPendingDatagrams())//如果至少有一个数据报等待读取,则返回true;否则返回false
{
QByteArray datagrams;
//将字节数组的大小设置为size bytes。
//pendingDatagramSize():返回第一个挂起的UDP数据报的大小。如果没有可用的数据报,这个函数返回-1。
datagrams.resize(udpSocket->pendingDatagramSize());
QHostAddress pAddress;
quint16 pPort;
//接收不大于maxSize字节的数据报,并将其存储在data中。发送方的主机地址和端口存储在*address和*port中(除非指针为0)。
//成功时返回数据报的大小;否则返回-1。
udpSocket->readDatagram(datagrams.data(),datagrams.size(),&pAddress,&pPort);
QString strs = datagrams.data();
QString data = "[From to:"+pAddress.toString()+":"+QString::number(pPort)+"]"+strs+"\n";
ui->plainTextEditMsg->appendPlainText(data);
}
}
/* socket 状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
ui->plainTextEditMsg->appendPlainText("scoket 状态:BoundState");
break;
default:
break;
}
}
三、Http协议基本知识
1.http:超文本传输协议,是一个简单的请求-响应协议,它通常运行在TCP之上。规定WWW服务器与浏览器之间信息传递规范,是二者共同遵守的协议。
2.http工作原理:HTTP是基于客户/服务器模式,且面向连接。
头文件(QNetworkRequest(请求对象)/QNetworkAccessManager(发送请求))
HTTP事务处理流程:
①:客户端与服务器端建立连接
②:客户端向服务器端请求
③:服务器端接受请求,并根据请求返回相应的文件作为应答
④:客户与服务器关闭连接
这段代码是通过HTTP请求获取百度网页,显示在页面上
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtNetwork> //提供编写TCP、IP客户端和服务器的类
#include <QUrl> //提供接口使用URLS
//它允许您创建和发送网络请求(例如HTTP请求)以访问远程资源,如Web页面、API端点或下载文件。
class QNetworkAccessManager;
/*
当您使用 QNetworkAccessManager 发送请求后,它将返回一个 QNetworkReply 对象,该对象包含了从服务器接收到的数据和其他与响应相关的信息。
您可以使用 QNetworkReply 读取响应的数据,例如文本、二进制数据或JSON,也可以获取响应的状态码、头信息等。
QNetworkReply 也支持异步操作,通常通过信号和槽机制来处理响应数据的读取和处理。
*/
class QNetworkReply; //此类是QIODevice的子类
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QNetworkAccessManager *mgr; //
public slots:
void replayFinishedFunc(QNetworkReply*);
private slots:
void on_pushButtonGetData_clicked();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("http应用测试");
mgr = new QNetworkAccessManager(this);
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(replayFinishedFunc(QNetworkReply*)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::replayFinishedFunc(QNetworkReply *reply) //响应
{
QString strall = reply->readAll(); // 读取数据
ui->textBrowser->setText(strall); // 显示数据
ui->label_disp->setText("数据下载成功!");
reply->deleteLater(); //删除对象
}
void MainWindow::on_pushButtonGetData_clicked()
{
ui->label_disp->setText("数据正在下载中,请耐心等待....");
mgr->get(QNetworkRequest(QUrl("http://www.baidu.com")));
}
四、WebSocket基础知识
WebSocket是一种通过单个TCP连接提供的全双工通信信道网络技术。它可用于客户端应用程序和服务端应用程序;记得加这个 QT+= websocket
服务器端
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QAbstractSocket>
#include <QJsonDocument> //提供读取和写入JSON文档的相关方法
#include <QJsonObject> //JSON对象
class QWebSocket;
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
//自定义
class QWebSocketServer *websocketserver;
QList<QWebSocket*> websocketlist; //存储客户端
void getNewConnect();//获取新的客户端连接
void receiveMsg(const QString &msg);
public slots:
void onErrorFunc(QAbstractSocket::SocketError);
private slots:
void on_pushButton_senddata_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
setContentsMargins(0,5,5,5);
//WS (服务器非安全模式运行)
//SSL (安全模式运行)
websocketserver = new QWebSocketServer(QStringLiteral("testServer"),QWebSocketServer::NonSecureMode,this);
connect(websocketserver,&QWebSocketServer::newConnection,this,&Widget::getNewConnect);
websocketserver->listen(QHostAddress::Any,8899);
}
Widget::~Widget()
{
delete ui;
for (auto socket:websocketlist)
{
socket->close();
}
websocketserver->close();
}
void Widget::getNewConnect()
{
//hasPendingConnections():如果服务器有挂起的连接返回true;否则返回false。
if(websocketserver->hasPendingConnections())
{
QWebSocket *websocket = websocketserver->nextPendingConnection();
ui->textEdit_msgList->append(websocket->origin()+"客户端已连接上服务器");
websocketlist<<websocket;
QListWidgetItem *item = new QListWidgetItem;
item->setText(websocket->origin()); //origin():返回当前连接源
ui->listWidget_client->addItem(item); //将连接的客户端添加到客户端列表控件
connect(websocket,&QWebSocket::disconnected,this,[websocket,this]
{
ui->textEdit_msgList->append(websocket->origin()+"客户端已退出");
websocketlist.removeOne(websocket);
for (int i =0;i<ui->listWidget_client->count();i++)
{
QListWidgetItem *item = ui->listWidget_client->item(i);
if(item->text()==websocket->origin())
{
ui->listWidget_client->removeItemWidget(item);
delete item;
break;
}
}
websocket->deleteLater();
});
connect(websocket,&QWebSocket::textMessageReceived,this,&Widget::receiveMsg);
connect(websocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(QAbstractSocket::SocketError));
}
}
void Widget::receiveMsg(const QString &msg)
{
QJsonDocument jd = QJsonDocument::fromJson(msg.toLatin1().data());
if(jd.isEmpty())
{
//sender() 函数用于获取当前信号的发送者对象,通常在槽函数内部使用
QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
ui->textEdit_msgList->append("收到客户端消息["+websocket->origin()+"]---->"+msg);
}
else
{
QJsonObject jdo = jd.object();
qDebug()<<jdo;
QString dst = jdo["dst:"].toString();
for (auto socket:websocketlist)
{
if(dst == socket->origin())
socket->sendTextMessage(msg);
}
}
}
void Widget::onErrorFunc(QAbstractSocket::SocketError error)
{
QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
//origin():返回当前原点。errorString():返回最近发生的错误的人类可读的描述
ui->textEdit_msgList->append(websocket->origin()+"出错"+websocket->errorString());
}
void Widget::on_pushButton_senddata_clicked()
{
//trimmed() 返回从开始和结束处删除空白的字符串。toPlainText()获取纯文本
QString strtext = ui->textEdit->toPlainText().trimmed();
if(strtext.isEmpty())
return;
if(ui->radioButton_sendall->isChecked()) //群发
{
if(websocketlist.size()==0)
return;
for (auto sock:websocketlist)
{
sock->sendTextMessage(strtext);
}
ui->textEdit_msgList->append("服务器给所有连接者发送:"+strtext);
}
else
{
if(!ui->listWidget_client->currentItem())
return;
QString strcurrent = ui->listWidget_client->currentItem()->text();
QWebSocket *websocket = nullptr;
for(auto socket:websocketlist)
{
if(socket->origin()==strcurrent)
{
websocket = socket;
}
}
if(websocket)
{
websocket->sendTextMessage(strtext);
ui->textEdit_msgList->append("服务器给["+websocket->origin()+"]发送--->"+strtext);
}
ui->textEdit->clear();
}
}
客户端
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QAbstractSocket>
#include<QWebSocket>
#include<QJsonDocument>
#include<QJsonObject>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
class QWebSocket *websocket; //套接字
void receivedMsgFunc(const QString &msg); //接收消息
bool bConnect = false;
private slots:
void onerrorFunc(QAbstractSocket::SocketError error);
void on_pushButton_start_clicked();
void on_pushButton_sendmsg_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
websocket = nullptr;
}
Widget::~Widget()
{
delete ui;
if(websocket)
websocket->close();
}
void Widget::receivedMsgFunc(const QString &msg)
{
QJsonParseError error;
QJsonDocument jsd = QJsonDocument::fromJson(msg.toUtf8().data(),&error);
if(jsd.isNull()) //如果解析失败 则直接显示
{
ui->textEdit_msglist->append(msg);
}
else
{
QJsonObject jsobj = jsd.object();
ui->textEdit_msglist->append("收到来自"+jsobj["SRC"].toString()+"的消息"+jsobj["msg"].toString());
}
}
void Widget::onerrorFunc(QAbstractSocket::SocketError error)
{
ui->textEdit_msglist->append(websocket->origin()+"出错"+websocket->errorString());
}
void Widget::on_pushButton_start_clicked()
{
if(!websocket) //实现连接与断开服务器
{
//判断服务器名称是否为空
if(ui->lineEdit_name->text().trimmed().isEmpty())
{
QMessageBox::critical(this,"错误","服务器名称为空",QMessageBox::Yes);
return;
}
websocket = new QWebSocket(ui->lineEdit_name->text().trimmed(),QWebSocketProtocol::VersionLatest,this);
//连接服务器
connect(websocket,&QWebSocket::connected,this,[this]
{
ui->textEdit_msglist->append("已经连接上"+websocket->peerAddress().toString());
bConnect = true;
ui->pushButton_start->setText("断开服务器");
});
//断开服务器
connect(websocket,&QWebSocket::disconnected,this,[this]
{
ui->textEdit_msglist->append("已"+websocket->peerAddress().toString()+"断开连接");
bConnect = false;
ui->pushButton_start->setText("连接服务器");
});
//连接发生的错误
connect(websocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(onerrorFunc(QAbstractSocket::SocketError)));
//接收消息
connect(websocket,&QWebSocket::textMessageReceived,this,&Widget::receivedMsgFunc);
}
if(!bConnect)
websocket->open(QUrl(ui->lineEdit_serveraddress->text().trimmed()));
else
{
websocket->close();
websocket->deleteLater();
websocket=nullptr;
}
}
void Widget::on_pushButton_sendmsg_clicked()
{
if(!websocket)
return;
//如果套接字已准备好读写,则返回true;否则返回false。
if(!websocket->isValid())
return;
//获取发送数据的信息
QString str = ui->textEdit_senddata->toPlainText().trimmed();
if(str.isEmpty())
return;
//获取客户端名称
QString strclient = ui->lineEdit_sendmsg->text().trimmed();
if(strclient.isEmpty())
{
websocket->sendTextMessage(str);
ui->textEdit_msglist->append("发送消息"+str);
}
else
{
QJsonObject json;
json["src"] = websocket->origin();
json["dst"] = strclient;
json["msg"] = str;
websocket->sendTextMessage(QString(QJsonDocument(json).toJson()));
ui->textEdit_msglist->append("给客户端"+strclient+"发送消息:"+str);
}
ui->textEdit_senddata->clear();
}