自学内容网 自学内容网

QT开发--网络编程

第十七章 QT网络编程

        Qt Network模块用于TCP/IP编程,提供HTTP请求、cookies、DNS等功能的C++类。

        使用需在pro文件中添加“QT += network”

tcp通信流程图

17.1 QHostInfo

        QHostInfo类用于查找主机名与IP地址的关联。它提供两种查找方式:

        1、异步查找:调用lookupHost(),传入主机名或IP地址、接收对象和槽。

        查找完成时,槽被调用,结果存储在QHostInfo对象中。

QHostInfo::lookupHost("www.baidu.com", // 主机名或IP地址  
                     this, // 接收查找结果的接收者对象  
                     [](const QHostInfo& info) // 处理查找结果的lambda表达式  
{    
    qDebug() << info.hostName() << info.addresses();  // 输出主机名和IP地址列表  
});

        ‌一个主机名可以有多个关联的IP地址,这是因为DNS系统允许一个主机名映射到多个IP地址。‌ 这种机制称为域名解析冲突,它允许在特定情况下,一个主机名对应多个IP地址‌ 。

​​​​​​​"www.baidu.com"                                 //域名
(QHostAddress("180.101.50.242"),                 //IPV4地址
QHostAddress("180.101.50.188"), 
QHostAddress("240e:e9:6002:15a:0:ff:b05c:1278"),  //ipv6地址
QHostAddress("240e:e9:6002:15c:0:ff:b015:146f")) 

        2、阻塞查找:使用QHostInfo::fromName()函数。

QHostInfo info = QHostInfo::fromName("smtp.qq.com");  
qDebug() << info.hostName() << info.addresses();

        查找失败时,可使用error()errorString()获取错误详情。支持国际化域名(IDNs)。

        要检索本地主机名,使用QHostInfo::localHostName()

17.2 QHostAddress

        QHostAddress类用于保存和操作IPv4或IPv6地址,独立于平台和协议。

        它通常与QTcpSocketQTcpServerQUdpSocket结合使用,以支持网络连接。

        该类提供设置和检索主机地址的方法,如setAddress()、toIPv4Address()、toIPv6Address()和toString(),以及检查地址类型的protocol()方法。

        QHostAddress还定义了一组预定义地址,包括:

枚举描述
QHostAddress::Null空地址对象
QHostAddress::LocalHostIPv4本地主机地址(127.0.0.1)
QHostAddress::localhsotIPv6IPv6本地主机地址(::1)。
QHostAddress::BroadcastIPv4广播地址(255.255.255.255)。
QHostAddress::AnyIPv4IPv4任何地址(0.0.0.0),绑定此地址的套接字仅在IPv4接口上监听。
QHostAddress::AnyIPv6IPv6任何地址(::),绑定此地址的套接字仅在IPv6接口上监听。
QHostAddress::Any双栈任意地址,绑定此地址的套接字将侦听IPv4和IPv6接口。

17.3 QNetworkInterface

        QNetworkInterface类提供了获取主机IP地址和网络接口列表的功能

        每个网络接口可能包含多个IP地址,以及相关的网络掩码和广播地址,这些信息可通过addressEntries()获取。

        若只需IP地址,可使用allAddresses()

        hardwareAddress()用于获取接口的硬件地址。

/*获取主机上所有ip地址*/
QList<QHostAddress> addrList = QNetworkInterface::allAddresses();  
for (const QHostAddress &addr : addrList) {  
    qDebug() << addr.protocol() << addr.toString();  
}
/*获取并遍历主机上所有网络接口*/
QList<QNetworkInterface> networkList = QNetworkInterface::allInterfaces();  
for (const QNetworkInterface &inter : networkList) {  
    if (!inter.isValid()) continue;  
      
    qDebug() << inter.name() << inter.type() << inter.hardwareAddress() << inter.humanReadableName();  
      
    for (const QNetworkAddressEntry &entry : inter.addressEntries()) {  
        qDebug() << entry.ip();  
    }  
}

17.4 QNetworkAddressEntry

        QNetworkAddressEntry类用于存储网络接口上的IP地址及其相关的网络掩码和广播地址

        关键成员函数包括:

        broadcast():返回与IPv4地址和子网掩码相关联的广播地址

        ip():返回网络接口中的IPv4或IPv6地址

        netmask():返回与IP地址相关联的子网掩码

QNetworkAddressEntry entry;  // 假设entry已经被正确初始化  
  
QHostAddress ip = entry.ip();                  //获取Ip地址
QHostAddress netmask = entry.netmask();        //获取子网掩码
QHostAddress broadcast = entry.broadcast();    //获取广播地址
  
qDebug() << "IP:" << ip.toString();  
qDebug() << "Netmask:" << netmask.toString();  
qDebug() << "Broadcast:" << broadcast.toString();

17.5 QAbstractSocket

        QAbstractSocket类QTcpSocketQUdpSocket提供了通用的套接字功能

1. QAbstractSocket基类

        是QTcpSocketQUdpSocket的基类,统一了API。

2. 套接字选择

        可以直接实例化QTcpSocketQUdpSocket

#include <QCoreApplication>  
#include <QTcpSocket>  
#include <QDebug>  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication app(argc, argv);  
  
    // 创建一个QTcpSocket实例  
    QTcpSocket *socket = new QTcpSocket();  
  
    // 连接到服务器(这里使用localhost和端口12345作为示例)  
    socket->connectToHost("127.0.0.1", 12345);  
  
    // 监听连接状态改变信号  
    QObject::connect(socket, &QTcpSocket::connected, []() {  
        qDebug() << "Connected to server";  
          
        // 在连接成功后发送数据(这里发送一个简单的Hello消息)  
        socket->write("Hello, server!\n");  
    });  
  
    // 监听数据可读信号  
    QObject::connect(socket, &QTcpSocket::readyRead, []() {  
        qDebug() << "Data received from server:" << socket->readAll();  
    });  
  
    // 监听连接断开信号(可选)  
    QObject::connect(socket, &QTcpSocket::disconnected, []() {  
        qDebug() << "Disconnected from server";  
          
        // 在这里可以执行一些清理操作,比如删除socket对象(如果不再需要)  
        // 注意:如果socket对象是在局部作用域内创建的(例如在这个main函数中),  
        // 并且你没有将其传递给其他长期存在的对象或线程,那么当main函数结束时,  
        // socket对象将被自动释放(前提是你没有使用new来动态分配它)。  
        // 但是,如果你在其他地方创建了socket对象并希望在这里删除它,  
        // 你应该确保没有其他地方还在使用这个对象。  
    });  
  
    // 进入Qt的事件循环(对于控制台应用程序,这一步是必需的,以便处理信号和槽)  
    return app.exec();  
}

        或者使用本机套接字描述符,创建QAbstractSocket实例,并调用setSocketDescriptor()

#include <QTcpSocket>  
#include <QSocketNotifier>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#include <QDebug>  
  
int main(int argc, char *argv[])  
{  
    // 初始化Qt应用程序(对于非GUI应用程序,这一步可能不是必需的,但在这里包含以防万一)  
    QCoreApplication app(argc, argv);  
  
    // 创建一个本机套接字描述符(这里仅作示例,实际代码中你可能需要在其他地方创建和配置这个套接字)  
    int nativeSocketDescriptor = socket(AF_INET, SOCK_STREAM, 0);  
    if (nativeSocketDescriptor == -1) {  
        qCritical() << "Failed to create native socket descriptor";  
        return -1;  
    }  
  
    // 配置套接字地址和端口(这里使用localhost和任意端口作为示例)  
    sockaddr_in serverAddress;  
    serverAddress.sin_family = AF_INET;  
    serverAddress.sin_port = htons(12345); // 任意端口,应根据实际情况修改  
    inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr); // localhost  
  
    // 连接到服务器(这里仅作示例,实际代码中你可能需要处理连接失败的情况)  
    if (connect(nativeSocketDescriptor, (sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {  
        qCritical() << "Failed to connect to server";  
        close(nativeSocketDescriptor);  
        return -1;  
    }  
  
    // 创建一个QTcpSocket实例(也可以使用QUdpSocket,取决于你的需求)  
    QTcpSocket *socket = new QTcpSocket();  
  
    // 将本机套接字描述符设置为QTcpSocket的底层套接字描述符  
    if (!socket->setSocketDescriptor(nativeSocketDescriptor)) {  
        qCritical() << "Failed to set socket descriptor";  
        delete socket;  
        return -1;  
    }  
  
    // 现在你可以像使用普通的QTcpSocket一样使用这个套接字了  
    // 例如,监听连接状态改变、数据可读等信号,并相应地处理它们  
  
    // 监听连接状态改变(可选)  
    QObject::connect(socket, &QTcpSocket::stateChanged, [](int state) {  
        qDebug() << "Socket state changed:" << state;  
    });  
  
    // 监听数据可读(可选)  
    QObject::connect(socket, &QTcpSocket::readyRead, []() {  
        qDebug() << "Data available:" << socket->readAll();  
    });  
  
    // ... 在这里执行其他操作,例如发送数据、关闭连接等 ...  
  
    // 注意:在Qt应用程序结束时,确保正确地删除并释放所有动态分配的对象和资源  
    // 在这个例子中,由于我们在main函数中创建了socket对象,并且没有在其他地方删除它,  
    // 因此当main函数结束时,socket对象将被自动释放(前提是我们没有将其传递给其他长期存在的对象或线程)。  
    // 然而,在实际的应用程序中,你可能需要更仔细地管理对象的生命周期。  
  
    return app.exec(); // 对于非GUI应用程序,这一步可能不是必需的;但如果你的应用程序有事件循环,则需要调用它  
}

3. 协议差异

  • TCP:可靠、面向流、面向连接。
  • UDP:不可靠、面向数据报、无连接。
  • QAbstractSocket通过connectToHost()为UDP提供虚拟连接。

4. 状态管理

  • 初始状态:UnconnectedState。
  • 连接过程:HostLookupState -> ConnectingState -> ConnectedState。
    •                 //查找主机->请求连接->连接成功
  • 错误处理:触发error()。
  • 状态改变:触发stateChanged()。

5. 数据读写

  • 读取:read()、readLine()、readAll()。
  • 写入:write()。
  • 信号:bytesWritten()。
  • 缓冲区:setReadBufferSize()限制大小。

6. 连接管理

  • 关闭连接:disconnectFromHost()
  • 立即中止:abort()
  • 远程关闭:发出RemoteHostClosedError,然后disconnected()。

7. 对等方信息

peerPort()     //返回连接的对等端(远程)的端口号。  
peerAddress()  //返回连接的对等端(远程)的IP地址。  
peerName()     //返回连接的对等端(远程)的名称,通常是IP地址和端口号的组合。  
  
localPort()    //返回本地套接字的端口号。  
localAddress() //返回本地套接字的IP地址。

8. 阻塞函数

waitForConnected()    // 阻塞当前线程,直到套接字连接成功或超时。  
waitForReadyRead()    // 阻塞当前线程,直到有数据可读或超时。  
waitForBytesWritten() // 阻塞当前线程,直到所有待发送的数据都已写入套接字或超时。  
waitForDisconnected() // 阻塞当前线程,直到套接字断开连接或超时。

17.6 信号

自定义信号

  1. connected() - 成功建立连接后发出。
  2. disconnected() - 套接字断开连接时发出。
  3. error(QAbstractSocket::SocketError socketError) - 发生错误时发出,带有错误类型。
  4. hostFound() - 主机查找成功后发出。
  5. *proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator authenticator) - 需要代理身份验证时发出。
  6. stateChanged(QAbstractSocket::SocketState socketState) - 套接字状态变化时发出,带有新状态。//查找->请求连接->连接建立。

从QIODevice继承的信号

  1. aboutToClose() - 设备即将关闭时发出。
  2. bytesWritten(qint64 bytes) - 向设备写入数据时发出,带有写入的字节数。
  3. readChannelFinished() - 输入流关闭时发出。
  4. readyRead() - 有新数据可读时发出。

从QObject继承的信号

  1. *destroyed(QObject obj = Q_NULLPTR) - 对象销毁前发出。
  2. objectNameChanged(const QString &objectName) - 对象名称更改后发出。

17.7 QTcpServer(监听套接字)

        QTcpServer类是一个基于TCP的服务器类,用于监听并接收传入的TCP连接。

功能概述

  • 监听端口:可以指定端口或自动选择。
  • 监听地址:可以监听特定地址或所有地址。
  • 开始监听:调用listen()方法。
  • 连接信号:每当有新连接时,发出newConnection()信号。
  • 接受连接:调用nextPendingConnection()获取已连接的QTcpSocket
  • 错误处理:使用serverError()errorString()处理错误。
  • 获取信息:使用serverAddress()serverPort()获取监听信息。
  • 停止监听:调用close()方法。
  • 无事件循环:可使用waitForNewConnection()阻塞等待连接。

        listen()本身是非阻塞的,它不会等待连接,而是立即返回。连接的处理是通过信号和槽机制异步完成的。

               waitForNewConnection()方法用于在没有事件循环的情况下,阻塞当前线程直到有新的连接进来或者超时发生。

信号

  • acceptError(QAbstractSocket::SocketError socketError):接受新连接出错时发出
  • newConnection()有新连接可用时发出
// 创建服务器实例  
QTcpServer *server = new QTcpServer(this);  
  
// 监听指定端口(例如1234)  
server->listen(QHostAddress::Any, 1234);  
  
// 连接newConnection信号到槽函数  
connect(server, &QTcpServer::newConnection, this, &MyClass::handleNewConnection);  
  
// 槽函数,处理新连接  
void MyClass::handleNewConnection() {  
    QTcpSocket *clientSocket = server->nextPendingConnection();  
    // 使用clientSocket与客户端通信  
}  
  
// 可选:处理接受错误  
connect(server, &QTcpServer::acceptError, this, &MyClass::handleAcceptError);  
void MyClass::handleAcceptError(QAbstractSocket::SocketError socketError) {  
    // 处理错误,例如记录日志或显示错误信息  
}

17.8 QTcpSocket(通信套接字)

        TCP是一种面向数据流和连接的可靠传输协议,HTTP和FTP等协议都是基于TCP协议的。

        QTcpSocket类继承自QAbstractSocket,用于传输连续的数据流,适合连续数据传输。

        它分为客户端和服务端,即C/S模型。

        QTcpSocket提供读写两个独立的数据流,通过read()write()操作控制。

        读取前可使用bytesAvailable()检查数据是否足够。

        QTcpServer用于处理客户端连接,通过listen()监听连接请求,并在有客户端连接时发出newConnection()信号,可使用QTcpSocket读取客户端数据。

        QIODevice类提供了几个用于读写数据的常用公有函数。

读取函数

/*从设备读取最多maxSize字节到data中,返回读取到的字节数*/
qint64 read(char *data, qint64 maxSize);

/*从设备读取最多maxSize字节,返回QByteArray。*/
QByteArray read(qint64 maxSize);

/*从设备读取所有剩余数据,返回QByteArray*/
QByteArray readAll();

写入函数

/*写入最多maxSize字节的数据到设备中。
  返回实际写入的字节数,出错时返回-1。*/
qint64 write(const char *data, qint64 maxSize);

/*写入以零结尾的8位字符字符串到设备中。
  返回实际写入的字节数,出错时返回-1。*/
qint64 write(const char *data);

/*写入QByteArray的内容到设备中。
  返回实际写入的字节数,出错时返回-1。*/
qint64 write(const QByteArray &byteArray);

17.9 TCP聊天程序

/*服务端*/
#include "mainwindow.h"  // 包含MainWindow类的头文件  
#include "ui_mainwindow.h"  // 包含UI类的头文件,用于设置UI界面  
  
// MainWindow类的构造函数,初始化窗口和服务器  
MainWindow::MainWindow(QWidget *parent)  
    : QMainWindow(parent)  
    , ui(new Ui::MainWindow)  // 初始化UI对象  
{  
    ui->setupUi(this);  // 设置UI界面  
    server = new QTcpServer(this);  // 创建QTcpServer对象  
    // 连接服务器的newConnection信号到MainWindow的onNewConnection槽函数  
    connect(server, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);  
    startServer();  // 启动服务器  
}  
  
// MainWindow类的析构函数,释放UI对象  
MainWindow::~MainWindow()  
{  
    delete ui;  
}  
  
// 启动服务器的方法  
void MainWindow::startServer() {  
    // 尝试在任意地址的1234端口上监听连接  
    if(server->listen(QHostAddress::Any, 1234)) {  
        ui->textEdit->append("Server started on port 1234");  // 服务器启动成功,显示消息  
    } else {  
        ui->textEdit->append("Error: Could not start server");  // 服务器启动失败,显示错误消息  
    }  
}  
  
// 处理新连接的方法  
void MainWindow::onNewConnection() {  
    QTcpSocket *clientSocket = server->nextPendingConnection();  // 获取待处理的客户端连接  
    // 连接客户端的readyRead信号到MainWindow的onReadyRead槽函数  
    connect(clientSocket, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);  
    // 连接客户端的disconnected信号到MainWindow的onDisconnected槽函数  
    connect(clientSocket, &QTcpSocket::disconnected, this, &MainWindow::onDisconnected);  
    clientSockets << clientSocket;  // 将客户端连接添加到客户端列表  
}  
  
// 处理客户端发送数据的方法  
void MainWindow::onReadyRead() {  
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket *>(sender());  // 获取发送数据的客户端连接  
    QString message = clientSocket->readAll();  // 读取客户端发送的数据  
    ui->textEdit->append(message);  // 在文本编辑器中显示接收到的消息  
}  
  
// 处理客户端断开连接的方法  
void MainWindow::onDisconnected() {  
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket *>(sender());  // 获取断开连接的客户端连接  
    clientSockets.removeAll(clientSocket);  // 从客户端列表中移除断开的连接  
    clientSocket->deleteLater();  // 稍后删除客户端连接对象  
}
/*客户端*/
#include "mainwindow.h"  // 包含MainWindow类的头文件  
#include "ui_mainwindow.h"  // 包含UI类的头文件,用于设置UI界面  
  
// MainWindow类的构造函数,初始化窗口和套接字  
MainWindow::MainWindow(QWidget *parent)  
    : QMainWindow(parent)  
    , ui(new Ui::MainWindow)  // 初始化UI对象  
{  
    ui->setupUi(this);  // 设置UI界面  
    socket = new QTcpSocket(this);  // 创建QTcpSocket对象  
    // 连接按钮的clicked信号到MainWindow的onSendMessage槽函数  
    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onSendMessage);  
    // 尝试连接到本地服务器的1234端口  
    socket->connectToHost("127.0.0.1", 1234);  
    // 连接套接字的connected信号到MainWindow的onConnected槽函数  
    connect(socket, &QTcpSocket::connected, this, &MainWindow::onConnected);  
}  
  
// MainWindow类的析构函数,释放UI对象  
MainWindow::~MainWindow()  
{  
    delete ui;  
}  
  
// 发送消息的方法  
void MainWindow::onSendMessage() {  
    // 检查套接字是否处于连接状态  
    if(socket->state() == QAbstractSocket::ConnectedState) {  
        // 发送输入框中的文本到服务器,并在末尾添加换行符  
        socket->write(ui->lineEdit->text().toUtf8() + "\n");  
    }  
    ui->lineEdit->clear();  // 清空输入框  
}  
  
// 处理连接成功的方法  
void MainWindow::onConnected() {  
    // 在文本编辑器中显示连接成功的消息  
    ui->textEdit->append("Connected to server");  
}
客户端发消息,服务端接收

17.9 QUdpSocket(通信套接字) 

        UDP是轻量级、不可靠、无连接的协议,适用于非关键传输。

        QUdpSocket类用于发送和接收UDP数据报,通过IP地址和端口号实现应用间通信。它支持IPv4广播,数据报建议小于512字节,端口号选1024-65535。

  1. MTU限制
    • MTU(Maximum Transmission Unit,最大传输单元):以太网中MTU通常设置为1500字节。当IP层的数据包(包括IP头和数据)超过MTU时,需要进行分片传输。分片会增加传输的复杂性和出错的可能性。
    • IP头和UDP头开销:IP头通常需要20字节,UDP头需要8字节。因此,对于UDP数据报,实际可用的数据负载空间会减少。
    • 避免分片:为了确保UDP数据报不需要分片,建议其大小小于或等于MTU减去IP头和UDP头的开销,即1500 - 20 - 8 = 1472字节。然而,考虑到网络中的不确定性(如路径上的MTU可能小于1500字节),以及可能的IP选项或其他封装开销,512字节通常被认为是一个更安全的上限。
  1. 保留端口号
    • 0-1023:这些端口号被保留给系统或知名应用程序使用,如FTP(21)、Telnet(23)、DNS(53)等。普通用户或应用程序不应使用这些端口号,以避免冲突。
  2. 动态端口号范围
    • 1024-65535:这些端口号被分配给用户进程或应用程序使用。当客户端或服务器需要建立连接时,它们可以从这个范围内选择一个未使用的端口号进行通信。
    • 灵活性:选择1024-65535范围内的端口号可以确保通信的灵活性,因为这些端口号在大多数操作系统和网络环境中都是可用的。


原文地址:https://blog.csdn.net/qq_42190402/article/details/142905697

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!