用户国营 发表于 2025-3-28 20:14:17

Qt的网络编程

Qt的网络编程

使用 Qt 的网络编程 API 须要先在 .pro 文件中添加 network 模块。Qt 的模块提供了静态库和动态库两个版本。
   不默认包罗网络等其他模块,是为了使 Qt 天生的可实验程序更加轻量化。
#.pro
QT                        +=core gui network
#添加到如上位置
Qt 封装了本身的网络编程接口,将 Qt 的信号和槽的机制运用到了 Qt 的网络编程接口上,这些接口的使用方法与 C 语言原生的套接字编程接口区别较大,尤其是信号和槽的机制使得 Qt 服务器设计可很少使用甚至不使用多线程,也能实现正常的服务器功能。但由于服务器一般都不会做成图形化界面,大概说不会使用 Qt 进行编写,也就无所谓 Qt 的服务器设计了。
1. UDP Socket

Qt 提供了两个类,用 QUdpSocket 来表现一个 UDP 的 socket 文件,用 QNetworkDatagram 来表现一个 UDP 数据报。
QUdpSocket:
声明类型阐明对标原生 APIbind(constQHostAddress&, quint16)方法绑定指定的端标语。bind()QNetworkDatagram receiveDatagram()方法读取一个 UDP 数据报。recvfrom()writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报。sendtoreadyRead信号在收到数据并预备就绪后触发。无   quint16 是 Qt 提供的短整型类型,由于 C++ 并未规定 short int 的大小,于是 Qt 为了解耦自创了本身的短整型类型。
QNetworkDatagram:
声明类型阐明对标原生 APIQNetworkDatagram(const QByteArray&,const QHostAddress&,quint16)构造函数构造一个 UDP 数据报,通常用于发送数据时使用。无QByteArray data()方法获取数据报内部持有的数据。无senderAddress()方法获取数据报中包罗的对端 IP 地址。无senderPort()方法获取数据报中包罗的对端端标语。无   QString 提供了一个 QByteArray 的构造,可以使用 QString 直接赋值。
1.1 UDP回显服务器

留意 Qt 的信号和槽提供了一种类似于多路复用 IO 的机制,当对端发送数据到来,本机预备读取时,就会触发 readyRead 信号并调用相干槽函数(自定义)。
1.1.1 服务器

mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkDatagram>
#include <QUdpSocket>

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;
    QUdpSocket* socket;

    void processRequest();
    QString process(const QString& request);

};
#endif // MAINWINDOW_H

mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    socket = new QUdpSocket(this);

    this->setWindowTitle("服务器");

    connect(socket,&QUdpSocket::readyRead,this,&MainWindow::processRequest);

    bool ret = socket->bind(QHostAddress::Any,6666);
    if(!ret)
    {
      QMessageBox::critical(this,"端口号绑定失败",socket->errorString());
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::processRequest()
{
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();

    const QString& response = process(request);

    QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);

    QString log = "[" + requestDatagram.senderAddress().toString()+ ":" +QString::number(requestDatagram.senderPort())+"] req: "
                  +request+"resp: "+response;
    ui->listWidget->addItem(log);
}
QString MainWindow::process(const QString& request)
{
    return request;
}

1.1.2 客户端

mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkDatagram>
#include <QUdpSocket>


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

private:
    Ui::MainWindow *ui;
    QUdpSocket* socket;
    void processResponse();

};
#endif // MAINWINDOW_H

mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    socket = new QUdpSocket(this);

    this->setWindowTitle("客户端");

    connect(socket,&QUdpSocket::readyRead,this,&MainWindow::processResponse);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::processResponse()
{

    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    ui->listWidget->addItem("服务器:"+response);
}

void MainWindow::on_pushButton_clicked()
{
    if(ui->lineEdit->text()=="")
    {
      return;
    }
    const QString& text = ui->lineEdit->text();
    QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress("127.0.0.1"),6666);

    socket->writeDatagram(requestDatagram);

    ui->listWidget->addItem("客户端:"+text);
    ui->lineEdit->setText("");
}


2. TCP Socket

Qt 提供了两个类,用 QTcpServer 来监听端口,获取客户端毗连。用 QTcpSocket 来实现客户端和服务器之间的数据交换。
QTcpServer:
声明类型阐明对标原生 APIlisten(constQHostAddress&, quint16 port)方法绑定指定的地址和端标语,并开始监听。bind() 和 listen()nextPendingConnection()方法从系统中获取一个已经创建好的 TCP 毗连。返回一个 QTcpSocket 表现这个客户端的毗连,通过这个 socket 对象完成和客户端之间的通讯。accept()newConnection信号有新的客户端创建毗连好之后触发。无,类似于多路复用 QTcpSocket
声明类型阐明对标原生 APIQBytearray readAll()方法读取当前接收缓冲区中的全部数据,返回 QBytearray 对象。read()write(const QByteArray&)方法把数据写入 socket 中。write()deleteLater方法临时把 socket 对象标记为无效。Qt 会在下个事件循环中析构开释该对象。无readyRead信号有数据到达并预备就绪时就触发。无disconnected信号毗连断开时触发。无 2.1 TCP回显服务器

留意,回显服务器的设计没有考虑到粘包问题,在现实的服务器设计中,应当自定义应用层协议,在报文里写好本次发送的数据包总大小,并使用一个充足大的字节数组作为缓冲区来接收,然后按照协议的方法解析报文。
2.1.1 服务器

mainwindow.h
留意服务器是使用 QTcpServer 类型作为成员变量监听套接字,当接收到新的毗连之后,再创建 QTcpSocket 变量作为通讯的套接字使用。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>

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;
    QTcpServer* tcpServer;
    void processConnection();
    QString process(const QString request);
};
#endif // MAINWINDOW_H

mainwindow.cpp:
每有一个毗连来到,服务器就会有一个 QTcpSocket* 对象,随着客户端越来越多,如果不开释就会开释就会导致严峻的内存泄漏问题。但是手动 delete 并不可行,因为槽函数都是依赖这个对象来进行操作的,最好使用 Qt 提供的 deleteLater() 接口,它会在下次事件循环时,将对象开释掉。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QTcpSocket>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("服务器");

    tcpServer = new QTcpServer(this);

    connect(tcpServer,&QTcpServer::newConnection,this,&MainWindow::processConnection);

    bool ret = tcpServer->listen(QHostAddress::Any,6666);
    if(!ret)
    {
      QMessageBox::critical(this,"服务器启动失败!",tcpServer->errorString());
      exit(1);
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::processConnection()
{
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log ="[" + clientSocket->peerAddress().toString()+ ":" + QString::number(clientSocket->peerPort())+"]客户端上线!";
    ui->listWidget->addItem(log);

    connect(clientSocket,&QTcpSocket::readyRead,this,[=]()
    {
      QString request = clientSocket->readAll();
      const QString& response = process(request);
      clientSocket->write(response.toUtf8());
      QString log = "[" + clientSocket->peerAddress().toString()+":"+QString::number(clientSocket->peerPort())+"] req: "+request
                      +",resp: "+response;
      ui->listWidget->addItem(log);
    });

    connect(clientSocket,&QTcpSocket::disconnected,this,[=]()
    {
      QString log = "[" + clientSocket->peerAddress().toString()+":"+QString::number(clientSocket->peerPort())+"]客户端下线!";
      ui->listWidget->addItem(log);
      //手动释放 clientSocket,使用Qt提供的方法,在下一次事件循环时释放
      clientSocket->deleteLater();

    });
}

QString MainWindow::process(const QString request)
{
    return request;
}

2.1.2 客户端

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpSocket>

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

private:
    Ui::MainWindow *ui;
    QTcpSocket* socket;
};
#endif // MAINWINDOW_H

mainwindow.cpp
connectToHost() 是在进行三次握手。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");

    socket = new QTcpSocket(this);
    socket->connectToHost("127.0.0.1",6666);
    connect(socket,&QTcpSocket::readyRead,this,[=]()
    {
      QString response = socket->readAll();
      ui->listWidget->addItem(" 服务器:"+response);
    });

    bool ret=socket->waitForConnected();
    if(!ret)
    {
      QMessageBox::critical(this,"服务器连接出错",socket->errorString());
      exit(1);
    }

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    const QString& text = ui->lineEdit->text();
    if(text=="")
    {
      return;
    }
    socket->write(text.toUtf8());
    ui->listWidget->addItem("客户端:"+text);
    ui->lineEdit->setText("");
}
3. HTTP

Qt 提供了 HTTP 客户端的库,但没有提供服务器的库。Qt 的 HTTP 库并没有像浏览器那样解析 HTML ,进行类似于网页浏览的操作。一般用于通过 HTTP 从服务器获取数据或向服务器提交数据。
Qt 的 API 主要是三个类包罗的,QNetworkAccessManager、QNetworkRequest、QNetworkReply 。
QNetworkAccessManager 提供了 HTTP 的焦点操作:
方法阐明QNetworkReply get(constQNetworkRequest&)发起一个 HTTP GET 哀求。post(const QNetworkRequest&,const QByteArray&)发起一个 HTTP POST 哀求。 QNetworkRequest 表现一个 HTTP 哀求:
留意 QNetworkRequest 是不包罗 body 的,body 须要通过 setHeader() 来包罗。
方法阐明QNetworkRequest(constQUrl&)通过 URL 构造一个 HTTP 哀求。setHeader(QNetworkRequest::KnownHeaders header,const QVariant& value)设置哀求头。   QVariant 表现一个类型可变的值,类似于 C 语言中的 void* 。
QNetworkRequest::KnownHeaders 是一个罗列变量,常用取值:
取值阐明ContentTypeHeader描述 body 的类型。ContentLengthHeader描述 body 的长度。LocationHeader用于重定向报文中指定重定向地址。CookieHeader设置 cookie。UserAgentHeader设置 User-Agent。 QNetworkReply 表现一个 HTTP 响应:
方法阐明error()获取出错状态。errorString()获取出错缘故原由的文本。readAll()读取响应 body。header(QNetworkRequest::KnownHeaders header)读取指定 header 的值。   留意 QNetworkReply 的方法一般都是非壅闭的,即实验流不会等候方法获取哀求或响应完成后再实验剩下的代码,Qt 通过提供了 finished 信号,这个信号会在客户端收到完备的响应数据之后触发。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Qt的网络编程