QT开发--网络编程
第十七章 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范围内的端口号可以确保通信的灵活性,因为这些端口号在大多数操作系统和网络环境中都是可用的。
原文地址:https://blog.csdn.net/qq_42190402/article/details/142905697
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!