Qt网络编程
0.前置知识
-
Socket是不同主机之间通信的API
IP地址用于区分不同主机/端口用于区分不用应用 -
QHostInfo 可查找与主机名相关联的 IP 地址或与 IP 地址相关联的主机名。 该类提供了两个静态便利函数:一个是异步函数,在找到主机后发出信号;另一个是阻塞函数,返回一个 QHostInfo 对象。
-
QNetworkInterface 表示连接到运行程序所在主机的一个网络接口。
-
QNetworkAddressEntry 每个网络接口可包含零个或多个 IP 地址,这些地址又可与网络掩码和/或广播地址相关联(取决于操作系统的支持情况)。
-
在Qt中,nextPendingConnection() 是 QTcpServer 类的一个成员函数,用于接受待处理的连接。当一个客户端尝试连接到服务器时,连接不会立即被接受,而是首先成为“待处理”状态。使用 nextPendingConnection() 可以取出一个待处理的连接,并返回一个 QTcpSocket 对象,该对象代表了与客户端的通信通道。
以下是 nextPendingConnection() 的一些关键点:
- 阻塞行为:如果当前没有待处理的连接,nextPendingConnection() 会阻塞调用线程,直到一个新的连接到达。
- 接受连接:此函数从内部的待处理连接队列中取出一个连接,并返回一个 QTcpSocket 对象,用以与客户端进行通信。
- 使用场景:在服务器应用程序中,你通常会在一个循环中调用 nextPendingConnection(),以便连续接受来自客户端的连接。
- 在Qt框架中,
QThread::run()
是一个虚函数(virtual function),它是QThread
类的核心函数之一。QThread
类用于Qt的多线程编程,允许你创建和管理线程。
这里是QThread::run()
函数的一些关键点:
- 虚函数:
run()
函数是虚函数,这意味着任何继承自QThread
的类都可以重写(override)这个函数,以实现自己的线程执行逻辑。 - 执行线程:当你启动一个
QThread
对象(使用start()
或exec()
方法)时,run()
函数会在新线程中被调用。这是线程执行的起点。 - 子类化:通常,你不会直接使用
QThread
,而是创建一个继承自QThread
的子类,并在子类中实现自己的run()
方法。 - 线程的主要职责:在
run()
方法中,你编写的代码定义了线程要执行的任务。一旦线程启动,run()
中的代码就会在新线程中运行。 - 线程结束:
run()
方法执行完毕后,线程会自动进入非活动状态。如果使用了QThread::exec()
启动线程,那么exec()
会返回,并且线程对象会被删除,除非它是通过QThread::detach()
被分离的。 - 示例用法:
class MyThread : public QThread
{
protected:
void run() override {
// 在这里实现线程要执行的代码
// 这个函数会在新线程中被调用
}
};
// 在某个地方创建并启动线程
MyThread thread;
thread.start();
在这个示例中,我们创建了一个 MyThread
类,它继承自 QThread
并重写了 run()
方法。然后,我们创建了 MyThread
的一个实例,并调用 start()
方法来启动线程。
- 线程安全:
run()
方法的实现需要考虑到线程安全,因为可能存在多个线程同时访问共享数据的情况。 - 保护级别:
[virtual protected]
表示run()
是一个受保护的虚函数。这意味着它只能在QThread
类或其子类中被重写。
QThread::run()
是实现Qt多线程功能的基础,通过重写这个函数,你可以控制线程的行为和执行的任务。
QSemaphore
是 Qt 框架中的一个类,用于跨线程同步。它提供了一种机制,允许线程等待某个条件成立,或者限制对共享资源的访问数量。QSemaphore
可以用来控制对共享资源的访问,以防止多个线程同时访问这些资源,从而避免竞态条件。
以下是 QSemaphore
的一些基本用法:
-
创建 Semaphore:可以通过指定一个整数值来创建
QSemaphore
,这个值通常被称为“计数”。计数表示同时可以访问共享资源的线程数。QSemaphore semaphore(1); // 创建一个计数为1的信号量
-
等待资源:当一个线程需要访问一个受信号量保护的资源时,它应该调用
QSemaphore
的acquire()
方法。如果信号量的计数大于0,acquire()
方法会减少计数并立即返回。如果计数为0,线程将等待,直到其他线程释放资源。semaphore.acquire(); // 请求资源 // ... 访问共享资源 ... semaphore.release(); // 释放资源
-
释放资源:当线程完成对资源的访问后,它应该调用
QSemaphore
的release()
方法来增加信号量的计数,从而允许其他等待的线程访问资源。semaphore.release(); // 增加计数,允许其他线程访问资源
-
查询当前计数:可以通过
QSemaphore
的available()
方法查询当前信号量的计数。int count = semaphore.available(); // 获取当前计数
QSemaphore
是 Qt 并发编程中的一个重要工具,它帮助开发者管理多线程环境下的资源访问,确保线程安全。在设计多线程应用程序时,合理使用信号量可以避免复杂的同步问题,并提高程序的稳定性和性能。
1.实战代码
1. 获取主机名称以及主机名称对应的IP地址
// 获取主机名称
QString strLocalHostName = QHostInfo::localHostName();
ui->lineEdit->setText(strLocalHostName);
// 根据主机名称获取对应的IP地址
QString strLocalAddress = "";
QHostInfo hostinfo = QHostInfo::fromName(strLocalHostName);
QList<QHostAddress> ipaddresslist = hostinfo.addresses();
if (!ipaddresslist.isEmpty())
{
for (int i = 0; i < ipaddresslist.size(); i++)
{
QHostAddress addresshost = ipaddresslist[i];
if(QAbstractSocket::IPv4Protocol == addresshost.protocol())
{
strLocalAddress = addresshost.toString();
break;
}
}
}
ui->lineEdit_2->setText(strLocalAddress);
2. 返回主机找到的所有网络接口列表 + IP地址对应的信息
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 j = 0; j < entrylist.size(); j++)
{
QNetworkAddressEntry entry = entrylist.at(j);
strTemp = strTemp + "IP地址:" + entry.ip().toString() + "\n";
strTemp = strTemp + "子网掩码:" + entry.netmask().toString() + "\n";
strTemp = strTemp + "广播地址:" + entry.broadcast().toString() + "\n";
}
}
QMessageBox::information(this, "主机所有信息", strTemp, QMessageBox::Yes);
3. 查询域名或者IP地址
查询域名或IP地址类内实现
getDomainName::getDomainName(QWidget *parent)
: QWidget(parent)
, ui(new Ui::getDomainName)
{
ui->setupUi(this);
ui->lineEdit->setText("www.baidu.com");
}
getDomainName::~getDomainName()
{
delete ui;
}
void getDomainName::on_pushButton_clicked()
{
// 主机名称
QString strHostName = ui->lineEdit->text();
ui->textEdit->append("你所查询的主机信息:" + strHostName);
QHostInfo::lookupHost(strHostName, this, &getDomainName::lookupHostInfoFunc);
}
void getDomainName::on_pushButton_2_clicked()
{
ui->textEdit->clear();
}
QString getDomainName::ProtocolTypeName(QAbstractSocket::NetworkLayerProtocol protocolType){
switch (protocolType) {
case QAbstractSocket::IPv4Protocol:
return "IPv4 Protrcol";
case QAbstractSocket::IPv6Protocol:
return "IPv6 Protrcol";
case QAbstractSocket::AnyIPProtocol:
return "Either IPv4 or IPv6 Protrcol";
case QAbstractSocket::UnknownNetworkLayerProtocol:
return "Unknown Protrcol";
default:
break;
}
}
void getDomainName::lookupHostInfoFunc(const QHostInfo &host)
{
QList<QHostAddress> addresslist = host.addresses();
for (int i = 0; i < addresslist.size(); i++)
{
QHostAddress host = addresslist.at(i);
ui->textEdit->append("协议类型:" + ProtocolTypeName(host.protocol()));
ui->textEdit->append("主机IP地址:" + host.toString());
ui->textEdit->append("");
}
}
4. TCP服务器与客户端连接
实现步骤:
- 获取本地IP地址--首先获取hostname(主机名称),通过hostname获取主机IP
- 实现newConnect函数,当有连接时,应用nextPendingConnection()处理对应连接,连接可能有多个,创建不同连接信号与槽函数的对应关系
- 槽函数主要包括 连接 断开连接 读取数据
- 关闭事件需要加入断开连接的操作
TCP服务器端类内实现
MyTcpServer::MyTcpServer(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MyTcpServer)
{
ui->setupUi(this);
this->setWindowTitle("服务器端窗口");
QString strIP = getLocalIpAddress();
ui->comboBox->addItem(strIP);
tcpserver = new QTcpServer(this);
connect(tcpserver, &QTcpServer::newConnection, this, &MyTcpServer::newConnect);
}
MyTcpServer::~MyTcpServer()
{
delete ui;
}
void MyTcpServer::on_pushButton_clicked()
{
QString IP = ui->comboBox->currentText();
quint16 port = ui->spinBox->value();
QHostAddress address(IP);
tcpserver->listen(address, port);
ui->textEdit->append("$$$$$$$$$$开始监听$$$$$$$$$$");
ui->textEdit->append("$$$$$$$$$$服务器地址$$$$$$$$$$" +
tcpserver->serverAddress().toString());
ui->textEdit->append("$$$$$$$$$$服务器端口$$$$$$$$$$" +
QString::number(tcpserver->serverPort()));
ui->pushButton->setEnabled(false);
ui->pushButton_2->setEnabled(true);
}
void MyTcpServer::on_pushButton_2_clicked()
{
if (tcpserver->isListening()){
tcpserver->close();
}
ui->pushButton->setEnabled(true);
ui->pushButton_2->setEnabled(false);
}
void MyTcpServer::on_pushButton_3_clicked()
{
QString strMessage = ui->lineEdit->text();
ui->textEdit->append("[out]:" + strMessage);
ui->lineEdit->clear();
QByteArray str = strMessage.toUtf8();
str.append("\n");
tcpsocket->write(str);
}
void MyTcpServer::clientConnect()
{
// 客户端连接
ui->textEdit->append("**********客户端socket连接**********");
ui->textEdit->append("**********peer address:" + tcpsocket->peerAddress().toString());
ui->textEdit->append("**********peer port:" + QString::number(tcpsocket->peerPort()));
}
void MyTcpServer::clientDisconnect()
{
// 断开连接
ui->textEdit->append("**********客户端socket断开连接**********");
tcpsocket->deleteLater();
}
void MyTcpServer::socketReadData()
{
// 读取数据
while (tcpsocket->canReadLine())
{
ui->textEdit->append("[in]" + tcpsocket->readLine());
}
}
void MyTcpServer::newConnect()
{
tcpsocket = tcpserver->nextPendingConnection();
connect(tcpsocket, &QTcpSocket::connected, this, &MyTcpServer::clientConnect);
clientConnect();
connect(tcpsocket, &QTcpSocket::disconnected, this, &MyTcpServer::clientDisconnect);
connect(tcpsocket, &QTcpSocket::readyRead, this, &MyTcpServer::socketReadData);
connect(tcpsocket, &QTcpSocket::stateChanged, this, &MyTcpServer::socketReadData);
}
void MyTcpServer::closeEvent(QCloseEvent *event)
{
if(tcpserver->isListening())
tcpserver->close();
event->accept();
}
QString MyTcpServer::getLocalIpAddress() // 获取本地Ip地址
{
QString hostname = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostname);
QList<QHostAddress> ipAddressList = hostInfo.addresses();
QString strLocalAddress = "";
if (!ipAddressList.isEmpty())
{
for (int i = 0; i < ipAddressList.size(); i++)
{
QHostAddress addresshost = ipAddressList[i];
if(QAbstractSocket::IPv4Protocol == addresshost.protocol())
{
strLocalAddress = addresshost.toString();
break;
}
}
}
return strLocalAddress;
}
实现步骤:
- 获取服务器IP地址
- 实现槽函数 连接 断开连接 读取数据
客户端类内实现
MyTcpClient::MyTcpClient(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MyTcpClient)
{
ui->setupUi(this);
this->setWindowTitle("客户端窗口");
tcpsocket = new QTcpSocket(this);
QString strIP = getLocalIP();
ui->comboBox->addItem(strIP);
connect(tcpsocket, &QTcpSocket::connected, this, &MyTcpClient::connectFunc);
connect(tcpsocket, &QTcpSocket::disconnected, this, &MyTcpClient::disconnectFunc);
connect(tcpsocket, &QTcpSocket::readyRead, this, &MyTcpClient::socketReadData);
// connect(tcpsocket, &QTcpSocket::)
}
MyTcpClient::~MyTcpClient()
{
delete ui;
}
void MyTcpClient::on_pushButton_clicked()
{
QString addr = ui->comboBox->currentText();
quint16 port = ui->spinBox->value();
tcpsocket->connectToHost(addr, port);
}
void MyTcpClient::on_pushButton_2_clicked()
{
if (tcpsocket->state() == QAbstractSocket::ConnectedState)
{
tcpsocket->disconnectFromHost();
}
}
void MyTcpClient::on_pushButton_3_clicked()
{
QString strMessage = ui->lineEdit->text();
ui->textEdit->append("[out]" + strMessage);
ui->lineEdit->clear();
QByteArray str = strMessage.toUtf8();
str.append("\n");
tcpsocket->write(str);
}
void MyTcpClient::connectFunc()
{
ui->textEdit->append("连接到服务器");
ui->textEdit->append("IP地址:" + tcpsocket->peerAddress().toString());
ui->textEdit->append("端口号:" + QString::number(tcpsocket->peerPort()));
ui->pushButton->setEnabled(false);
ui->pushButton_2->setEnabled(true);
}
void MyTcpClient::disconnectFunc()
{
ui->textEdit->append("断开服务器连接");
ui->pushButton->setEnabled(true);
ui->pushButton_2->setEnabled(false);
}
void MyTcpClient::socketReadData()
{
while(tcpsocket->canReadLine())
{
ui->textEdit->append("[in]" + tcpsocket->readLine());
}
}
void MyTcpClient::closeEvent(QCloseEvent *event)
{
// 如果正在访问则断开连接
if (tcpsocket->state() == QAbstractSocket::ConnectedState)
{
tcpsocket->close();
}
event->accept();
}
QString MyTcpClient::getLocalIP()
{
QString hostname = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostname);
QList<QHostAddress> addressList = hostInfo.addresses();
QString strLocalAddress = "";
if (!addressList.isEmpty())
{
for (int i = 0; i < addressList.size(); i++)
{
QHostAddress addresshost = addressList[i];
if(QAbstractSocket::IPv4Protocol == addresshost.protocol())
{
strLocalAddress = addresshost.toString();
break;
}
}
}
return strLocalAddress;
}
5. UDP连接
实现步骤:
- 只需要实现绑定端口 断开绑定 读取数据 广播 槽函数
- 连接时绑定的是源端口/发送信息发送的时目标端口
- 无需建立连接,只需要根据IP地址及端口号进行信息发送
UDP信息发送类内实现
MyUdp1::MyUdp1(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MyUdp1)
{
ui->setupUi(this);
QString strIP = getLocalIpAddress();
ui->comboBox->addItem(strIP);
udpSocket = new QUdpSocket(this);
connect(udpSocket, &QUdpSocket::readyRead, this, &MyUdp1::socketReadyReadData);
}
MyUdp1::~MyUdp1()
{
delete ui;
}
// 启动服务
void MyUdp1::on_pushButton_clicked()
{
quint16 port = ui->spinBox->value();
// udp连接绑定端口
if(udpSocket->bind(port))
{
ui->textEdit->append("绑定成功!");
ui->pushButton->setEnabled(false);
ui->pushButton_2->setEnabled(true);
}else
{
ui->textEdit->append("绑定失败!");
}
}
void MyUdp1::on_pushButton_2_clicked()
{
udpSocket->abort();
ui->pushButton->setEnabled(true);
ui->pushButton_2->setEnabled(false);
ui->textEdit->append("断开!");
}
void MyUdp1::on_pushButton_3_clicked()
{
QString targetIPAddress = ui->comboBox->currentText();
QHostAddress targetIP(targetIPAddress);
quint16 targetPort = ui->spinBox_2->value();
QString strMessage = ui->lineEdit->text();
QByteArray str = strMessage.toUtf8();
udpSocket->writeDatagram(str, targetIP, targetPort);
ui->textEdit->append("[out:]" + str);
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}
void MyUdp1::on_pushButton_4_clicked()
{
quint16 targetPort = ui->spinBox_2->value();
QString strMessage = ui->lineEdit->text();
QByteArray str = strMessage.toUtf8();
udpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort);
ui->textEdit->append("[广播:]" + str);
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}
void MyUdp1::socketReadyReadData()
{
// 读取接受到的信息
// 若返回true 至少有一个数据需要读取
while (udpSocket->hasPendingDatagrams())
{
QByteArray datagrams;
datagrams.resize(udpSocket->pendingDatagramSize());
QHostAddress address;
quint16 port;
udpSocket->readDatagram(datagrams.data(), datagrams.size(), &address, &port);
QString strs = datagrams.data();
QString peer = "[From to:" + address.toString() + ":" + QString::number(port) + "]";
ui->textEdit->append(peer + strs);
}
}
QString MyUdp1::getLocalIpAddress()
{
QString hostname = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostname);
QList<QHostAddress> addressList = hostInfo.addresses();
QString strIp = "";
if (!addressList.isEmpty())
{
for (int i = 0; i < addressList.size(); i++)
{
QHostAddress address = addressList.at(i);
if (address.protocol() == QAbstractSocket::IPv4Protocol)
{
strIp = address.toString();
break;
}
}
}
return strIp;
}
6. 创建线程并执行函数
- 需要继承
QThread
类,重写run()
函数,run()
函数就是线程中要执行得函数
线程类内实现
MyThreadProcess::MyThreadProcess() {
}
MyThreadProcess::~MyThreadProcess()
{
}
void MyThreadProcess::run()
{
for(int i = 0; i<=5; i++)
{
qDebug() << currentThread() << i << i << i << i;
}
}
创建线程并执行线程内函数
MyThreadWindow::MyThreadWindow(QWidget *parent)
:QDialog(parent)
{
setWindowTitle("QThread操作");
startbutton = new QPushButton("开 始", this);
stopbutton = new QPushButton("停 止", this);
exitbutton = new QPushButton("退 出", this);
QHBoxLayout *hlayout = new QHBoxLayout(this);
hlayout->addWidget(startbutton);
hlayout->addWidget(stopbutton);
hlayout->addWidget(exitbutton);
connect(startbutton, &QPushButton::clicked, this, &MyThreadWindow::onSlotStart);
connect(stopbutton, &QPushButton::clicked, this, &MyThreadWindow::onSlotStop);
connect(exitbutton, &QPushButton::clicked, this, &MyThreadWindow::close);
// test();
}
MyThreadWindow::~MyThreadWindow(){
}
void MyThreadWindow::onSlotStart()
{
// 创建5个线程
for(int i=0;i<MAXSIZE;i++)
{
thread[i] = new MyThreadProcess();
}
//
for (int i = 0; i < MAXSIZE; i++)
{
thread[i]->start();
}
startbutton->setEnabled(false);
stopbutton->setEnabled(true);
}
void MyThreadWindow::onSlotStop()
{
for (int i = 0; i < MAXSIZE; i++)
{
thread[i]->terminate();
thread[i]->wait();
}
startbutton->setEnabled(true);
stopbutton->setEnabled(false);
}
7. 信号量中消费者和生产者简单实现
注意:一点要等线程运行结束才能结束函数的运行
点击查看代码
#include <QThread>
#include <QSemaphore>
#include <QTime>
#include <iostream>
#include <QDebug>
const int datasize = 10;
const int buffersize = 1;
QSemaphore freespace(buffersize);
QSemaphore usedspace(0);
class producer :public QThread
{
protected:
void run ()
{
srand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
srand(NULL);
for (int i = 0; i < datasize; i++)
{
freespace.acquire(); // 获取资源
std::cerr<<i<<":producer-->";
usedspace.release(); // 释放资源
}
}
};
class consumer:public QThread
{
protected:
void run ()
{
for (int i = 0; i < datasize; i++)
{
usedspace.acquire(); // 获取资源
std::cerr<<i<<":consumer\n";
freespace.release(); // 释放资源
}
}
};
int main()
{
producer p;
consumer c;
p.start();
c.start();
p.wait();
c.wait();
}
8. 互斥量
点击查看代码
#include <Qmutex>
#include <QThread>
#include <QString>
#include <QDebug>
#include <QObject>
class ticketSellers :public QObject
{
public:
ticketSellers();
~ticketSellers();
public slots:
void saleFunc();
public:
int *tickets; // 指向资源的指针
QMutex *metx; //
QString sellersname; // 售票员姓名
void stopWork(); // 停止工作
signals:
void onFinished(); // 处理 finished 信号
};
ticketSellers::ticketSellers(){
metx = nullptr;
tickets = nullptr;
}
ticketSellers::~ticketSellers(){
delete metx;
delete tickets;
}
void ticketSellers::saleFunc()
{
while((*tickets)>0)
{
metx->lock();
qDebug() << sellersname << ":" << (*tickets)--;
metx->unlock();
}
}
int main(){
int tickets = 20;
QMutex m;
// 创建线程
QThread th1;
ticketSellers s1;
// 设置线程
s1.sellersname = "1";
s1.metx = &m;
s1.tickets = &tickets;
// 将对象移动到线程
s1.moveToThread(&th1);
QObject::connect(&th1, &QThread::started, &s1, &ticketSellers::saleFunc);
// 创建线程
QThread th2;
ticketSellers s2;
// 设置线程
s2.sellersname = "2";
s2.metx = &m;
s2.tickets = &tickets;
// 将对象移动到线程
s2.moveToThread(&th2);
QObject::connect(&th2, &QThread::started, &s2, &ticketSellers::saleFunc);
th1.start();
th2.start();
th1.wait(); // 等待线程结束
th2.wait();
}
9.多线程
待解决
10.HTTP协议编程实战
- QNetworkReply是QIODevice的子类,用于接受返回的报文信息
- QNetworkRequest(QUrl("https://www.hao123.com"))是用于发送请求
- QNetworkAccessManager 是 Qt 框架中的一个类,它负责管理网络请求。它提供了一个高级的接口来发送网络请求和接收响应,并且可以处理多种类型的网络协议,比如 HTTP、HTTPS 和 FTP。
访问网页数据
#include "mainwindow.h"
#include "ui_mainwindow.h"
// #pragma execution_character_set("utf-8")
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
man = new QNetworkAccessManager(this);
connect(man, &QNetworkAccessManager::finished, this, &MainWindow::replayFinishedFunc);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::replayFinishedFunc(QNetworkReply *reply)
{
QString message = reply->readAll();
ui->textBrowser->setText(message);
reply->deleteLater();
}
void MainWindow::on_pushButton_clicked()
{
ui->label->setText(tr("数据正在下载中,请稍后...."));
man->get(QNetworkRequest(QUrl("https://www.hao123.com")));
}
简易HTTP服务器
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ser = new QTcpServer(this);
connect(ser, &QTcpServer::newConnection, this, &MainWindow::connection);
if(!ser->listen(QHostAddress::Any, 8080))
{
qDebug() << "Web服务器未启动" ;
}
else
{
qDebug() << "Web服务器正常启动,等待客服端连接...." ;
}
}
MainWindow::~MainWindow()
{
delete ui;
tcpsocket->close();
}
void MainWindow::connection()
{
tcpsocket = ser->nextPendingConnection();
while(!(tcpsocket->waitForReadyRead(100)));
char webData[1000];
int sv = tcpsocket->read(webData, 1000);
qDebug() << "正常运行,读取数据信息" << QString(webData);
tcpsocket->write("HTTP/1.1 200 OK\r\n");
tcpsocket->write("Content-Type:text/html\r\n");
tcpsocket->write("Connection:close\r\n");
tcpsocket->write("Refresh:1\r\n\r\n"); // 每秒刷新Web浏览器
tcpsocket->write("<!DOCTYPE>"
"<html>"
"<header>"
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>"
"<title>HttpServer</title>"
"</header>"
"<body>客户端已经连接HttpSever服务器秒数为:");
QByteArray bytesary;
static qint16 icount; // 用于在浏览器上显示的统计访问数字
bytesary.setNum(icount++);
tcpsocket->write(bytesary);
tcpsocket->write("</html>");
tcpsocket->flush();
connect(tcpsocket,&QTcpSocket::disconnected, tcpsocket, &QTcpSocket::deleteLater);
tcpsocket->disconnectFromHost();
}
11. WebSocket协议编程实战
服务器端
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// WS(服务器以非安全模式运行)
// WSSL(服务器以安全模式运行)
websocketserver = new QWebSocketServer(QStringLiteral("testServer"),
QWebSocketServer::NonSecureMode);
connect(websocketserver, &QWebSocketServer::newConnection, this, &MainWindow::getNewConnection);
websocketserver->listen(QHostAddress::Any, 8899);
}
MainWindow::~MainWindow()
{
delete ui;
for (auto socket:websocketList)
{
socket->close();
}
websocketserver->close();
}
void MainWindow::getNewConnection()// 获取新连接
{
if (websocketserver->hasPendingConnections())
{
QWebSocket *websocket = websocketserver->nextPendingConnection();
ui->textEdit_msgList->append(websocket->origin() + "客户端已连接到服务器");
websocketList << websocket;
QListWidgetItem *item = new QListWidgetItem;
item->setText(websocket->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, &MainWindow::receiveMsg);
connect(websocket, &QWebSocket::errorOccurred, this, &MainWindow::errorFunc);
}
}
void MainWindow::receiveMsg(const QString &msg)
{
QJsonDocument jd = QJsonDocument::fromJson(msg.toLatin1().data());
if(jd.isNull())
{
QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
ui->textEdit_msgList->append("收到客户消息[" + websocket->origin() + "]--->" + msg);
}
else
{
QJsonObject jdo = jd.object();
QString dst = jdo["dst"].toString(); // 发送到相应的客户端
for (auto socket:websocketList)
{
if (dst == socket->origin())
{
socket->sendTextMessage(msg);
}
}
}
}
void MainWindow::errorFunc(QAbstractSocket::SocketError error) // 错误处理
{
QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
ui->textEdit_msgList->append(websocket->origin() + "出错" + websocket->errorString());
}
void MainWindow::on_pushButton_send_clicked()
{
QString strText = ui->textEdit_sendMsg->toPlainText().trimmed(); //文本编辑框中获取当前的文本内容,转换为纯文本格式,并去除首尾的空白字符。
if(strText.isEmpty())
{
return;
}
if(ui->radioButton_sendAll->isChecked()) // 群发
{
if(websocketList.isEmpty())
{
return;
}
for (auto socket:websocketList)
{
socket->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;
break;
}
}
if (websocket)
{
websocket->sendTextMessage(strText);
ui->textEdit_msgList->append("服务器端向" + websocket->origin() + "发送--->" + strText);
}
}
}
客户端
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_connect_clicked()
{
if(websocket) // 实现连接与断开服务器
{
// 判断服务器的名字是否为空
if(ui->lineEdit_name->text().trimmed().isEmpty())
{
QMessageBox::critical(this, "错误", "服务器名称不能为空");
return;
}
websocket = new QWebSocket(ui->lineEdit_name->text().trimmed(), QWebSocketProtocol::VersionLatest, this);
connect(websocket ,&QWebSocket::connected, this, [this]()
{
ui->textEdit_Msg->append("已经连接上:" + websocket->peerAddress().toString());
beConnect = true;
ui->pushButton_connect->setText("断开服务器");
});
connect(websocket ,&QWebSocket::disconnected, this, [this]()
{
ui->textEdit_Msg->append("已与" + websocket->peerAddress().toString() + "断开连接");
beConnect = false;
ui->pushButton_connect->setText("连接服务器");
});
connect(websocket, &QWebSocket::errorOccurred, this, &MainWindow::errorFunc);
connect(websocket, &QWebSocket::textMessageReceived, this, &MainWindow::receviedMsgFunc);
}
if (!beConnect)
{
websocket->open(QUrl(ui->lineEdit_IP->text().trimmed())); // 连接服务器
}
else
{
emit websocket->disconnected();
websocket->close();
websocket->deleteLater();
}
}
void MainWindow::on_pushButton_sendMsg_clicked()
{
if (!websocket)
return;
if(!websocket->isValid())
return;
QString strText = ui->textEdit_send->toPlainText().trimmed();
if(strText.isEmpty())
return;
QString strCLient = ui->lineEdit_ToClient->text().trimmed();
if(strCLient.isEmpty())
{
websocket->sendTextMessage(strText);
ui->textEdit_Msg->append("发送消息:" + strText);
}
else
{
QJsonObject jsobj;
jsobj["src"]=websocket->origin();
jsobj["dst"]=strCLient;
jsobj["msg"]=strText;
websocket->sendTextMessage(QString(QJsonDocument(jsobj).toJson(QJsonDocument::Compact)));
ui->textEdit_Msg->append("给客户端" + strCLient + "发送消息:" +strText);
}
ui->textEdit_send->clear();
}
void MainWindow::errorFunc(QAbstractSocket::SocketError error)
{
ui->textEdit_Msg->append(websocket->origin() + "出错" + websocket->errorString());
}
void MainWindow::receviedMsgFunc(const QString &msg)
{
QJsonDocument jd = QJsonDocument::fromJson(msg.toUtf8().data());
if(jd.isEmpty())
{
ui->textEdit_Msg->append("接收消息:" + msg);
}
else
{
QJsonObject jobj = jd.object();
ui->textEdit_Msg->append("接受来自:" + jobj["src"].toString() + "的消息:" + jobj["msg"].toString());
}
}