Qt网络编程

0.前置知识

  1. Socket是不同主机之间通信的API
    IP地址用于区分不同主机/端口用于区分不用应用

  2. QHostInfo 可查找与主机名相关联的 IP 地址或与 IP 地址相关联的主机名。 该类提供了两个静态便利函数:一个是异步函数,在找到主机后发出信号;另一个是阻塞函数,返回一个 QHostInfo 对象。

  3. QNetworkInterface 表示连接到运行程序所在主机的一个网络接口。

  4. QNetworkAddressEntry 每个网络接口可包含零个或多个 IP 地址,这些地址又可与网络掩码和/或广播地址相关联(取决于操作系统的支持情况)。

  5. 在Qt中,nextPendingConnection() 是 QTcpServer 类的一个成员函数,用于接受待处理的连接。当一个客户端尝试连接到服务器时,连接不会立即被接受,而是首先成为“待处理”状态。使用 nextPendingConnection() 可以取出一个待处理的连接,并返回一个 QTcpSocket 对象,该对象代表了与客户端的通信通道。
    以下是 nextPendingConnection() 的一些关键点

  • 阻塞行为:如果当前没有待处理的连接,nextPendingConnection() 会阻塞调用线程,直到一个新的连接到达
  • 接受连接:此函数从内部的待处理连接队列中取出一个连接,并返回一个 QTcpSocket 对象,用以与客户端进行通信。
  • 使用场景:在服务器应用程序中,你通常会在一个循环中调用 nextPendingConnection(),以便连续接受来自客户端的连接。
  1. 在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多线程功能的基础,通过重写这个函数,你可以控制线程的行为和执行的任务。
  1. QSemaphore 是 Qt 框架中的一个类,用于跨线程同步。它提供了一种机制,允许线程等待某个条件成立,或者限制对共享资源的访问数量。QSemaphore 可以用来控制对共享资源的访问,以防止多个线程同时访问这些资源,从而避免竞态条件。

以下是 QSemaphore 的一些基本用法:

  • 创建 Semaphore:可以通过指定一个整数值来创建 QSemaphore,这个值通常被称为“计数”。计数表示同时可以访问共享资源的线程数。

    QSemaphore semaphore(1); // 创建一个计数为1的信号量
    
  • 等待资源:当一个线程需要访问一个受信号量保护的资源时,它应该调用 QSemaphoreacquire() 方法。如果信号量的计数大于0,acquire() 方法会减少计数并立即返回。如果计数为0,线程将等待,直到其他线程释放资源。

    semaphore.acquire(); // 请求资源
    // ... 访问共享资源 ...
    semaphore.release(); // 释放资源
    
  • 释放资源:当线程完成对资源的访问后,它应该调用 QSemaphorerelease() 方法来增加信号量的计数,从而允许其他等待的线程访问资源。

    semaphore.release(); // 增加计数,允许其他线程访问资源
    
  • 查询当前计数:可以通过 QSemaphoreavailable() 方法查询当前信号量的计数。

    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服务器与客户端连接

实现步骤:

  1. 获取本地IP地址--首先获取hostname(主机名称),通过hostname获取主机IP
  2. 实现newConnect函数,当有连接时,应用nextPendingConnection()处理对应连接,连接可能有多个,创建不同连接信号与槽函数的对应关系
  3. 槽函数主要包括 连接 断开连接 读取数据
  4. 关闭事件需要加入断开连接的操作
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;
}

实现步骤:

  1. 获取服务器IP地址
  2. 实现槽函数 连接 断开连接 读取数据
客户端类内实现
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连接

实现步骤:

  1. 只需要实现绑定端口 断开绑定 读取数据 广播 槽函数
  2. 连接时绑定的是源端口/发送信息发送的时目标端口
  3. 无需建立连接,只需要根据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. 创建线程并执行函数

  1. 需要继承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();
}

image

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();
}

image

9.多线程

待解决

10.HTTP协议编程实战

  1. QNetworkReply是QIODevice的子类,用于接受返回的报文信息
  2. QNetworkRequest(QUrl("https://www.hao123.com"))是用于发送请求
  3. 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());
    }
}
posted @ 2024-07-05 22:34  xjx111  阅读(85)  评论(0编辑  收藏  举报