0
点赞
收藏
分享

微信扫一扫

git的学习使用(认识工作区,暂存区,版本区。添加文件的方法)

兵部尚输 2024-10-15 阅读 0

第十七章 QT网络编程

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

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

tcp通信流程图

17.1 QHostInfo

        QHostInfo类用于查找主机名与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"))
QHostInfo info = QHostInfo::fromName("smtp.qq.com");  
qDebug() << info.hostName() << info.addresses();

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

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. 套接字选择

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

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范围内的端口号可以确保通信的灵活性,因为这些端口号在大多数操作系统和网络环境中都是可用的。

举报

相关推荐

0 条评论