第十七章 QT网络编程
Qt Network模块用于TCP/IP编程,提供HTTP请求、cookies、DNS等功能的C++类。
使用需在pro文件中添加“QT += network”。

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地址,独立于平台和协议。
它通常与QTcpSocket、QTcpServer和QUdpSocket结合使用,以支持网络连接。
该类提供设置和检索主机地址的方法,如setAddress()、toIPv4Address()、toIPv6Address()和toString(),以及检查地址类型的protocol()方法。
QHostAddress还定义了一组预定义地址,包括:
枚举 | 描述 |
---|---|
QHostAddress::Null | 空地址对象 |
QHostAddress::LocalHost | IPv4本地主机地址(127.0.0.1) |
QHostAddress::localhsotIPv6 | IPv6本地主机地址(::1)。 |
QHostAddress::Broadcast | IPv4广播地址(255.255.255.255)。 |
QHostAddress::AnyIPv4 | IPv4任何地址(0.0.0.0),绑定此地址的套接字仅在IPv4接口上监听。 |
QHostAddress::AnyIPv6 | IPv6任何地址(::),绑定此地址的套接字仅在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类为QTcpSocket和QUdpSocket提供了通用的套接字功能。
1. QAbstractSocket基类:
是QTcpSocket和QUdpSocket的基类,统一了API。
2. 套接字选择:
可以直接实例化QTcpSocket或QUdpSocket。
#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 信号
自定义信号
- connected() - 成功建立连接后发出。
- disconnected() - 套接字断开连接时发出。
- error(QAbstractSocket::SocketError socketError) - 发生错误时发出,带有错误类型。
- hostFound() - 主机查找成功后发出。
- *proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator authenticator) - 需要代理身份验证时发出。
- stateChanged(QAbstractSocket::SocketState socketState) - 套接字状态变化时发出,带有新状态。//查找->请求连接->连接建立。
从QIODevice继承的信号
- aboutToClose() - 设备即将关闭时发出。
- bytesWritten(qint64 bytes) - 向设备写入数据时发出,带有写入的字节数。
- readChannelFinished() - 输入流关闭时发出。
- readyRead() - 有新数据可读时发出。
从QObject继承的信号
- *destroyed(QObject obj = Q_NULLPTR) - 对象销毁前发出。
- 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。
- 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字节通常被认为是一个更安全的上限。
- 保留端口号:
- 0-1023:这些端口号被保留给系统或知名应用程序使用,如FTP(21)、Telnet(23)、DNS(53)等。普通用户或应用程序不应使用这些端口号,以避免冲突。
- 动态端口号范围:
- 1024-65535:这些端口号被分配给用户进程或应用程序使用。当客户端或服务器需要建立连接时,它们可以从这个范围内选择一个未使用的端口号进行通信。
- 灵活性:选择1024-65535范围内的端口号可以确保通信的灵活性,因为这些端口号在大多数操作系统和网络环境中都是可用的。