Qt串行化数据流QDataStream
QDataStream 是 Qt 提供的二进制串行化工具,用于将数据结构读写到文件或缓冲区中,常用于保存和恢复数据。
Qt串行化数据流QDataStream
Qt 串行化数据流 QDataStream
QDataStream
QDataStream
是 Qt 框架中用于对二进制数据进行序列化和反序列化的类。它提供了一种方便的方法,把各种数据类型(如基本类型、Qt 类型、甚至自定义类型)以平台无关的格式写入或从设备(如文件、内存、网络套接字)中读取。
主要用途
数据持久化:将数据结构以二进制形式写入文件,便于后续读取。
网络传输:在网络通信中对数据进行打包和解包。
跨平台数据交换:保证不同系统间数据的兼容性,避免字节序(大小端)问题。
核心特点
支持 内置数据类型(int、float、QString、QByteArray、QList 等)序列化。
支持用户自定义类型(通过重载操作符)。
自动处理数据的字节序(默认使用大端序,Qt::BigEndian)。
方便的操作符重载
<<
和>>
,类似流式写入和读取。
常用接口示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <QFile>
#include <QDataStream>
#include <QString>
#include <QList>
void writeData(const QString& filename) {
QFile file(filename);
if (!file.open(QIODevice::WriteOnly))
return;
QDataStream out(&file);
out.setVersion(QDataStream::Qt_6_0); // 设置版本,保证兼容性
int number = 42;
QString text = "Hello QDataStream";
QList<int> list = {1, 2, 3, 4, 5};
out << number << text << list; // 序列化写入
}
void readData(const QString& filename) {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly))
return;
QDataStream in(&file);
in.setVersion(QDataStream::Qt_6_0); // 与写入时保持版本一致
int number;
QString text;
QList<int> list;
in >> number >> text >> list; // 反序列化读取
qDebug() << number << text << list;
}
自定义类型支持
如果需要序列化自定义类型,需要重载 <<
和 >>
操作符:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Point {
int x;
int y;
};
QDataStream& operator<<(QDataStream& out, const Point& p) {
out << p.x << p.y;
return out;
}
QDataStream& operator>>(QDataStream& in, Point& p) {
in >> p.x >> p.y;
return in;
}
版本控制:
QDataStream::setVersion()
重要,防止不同 Qt 版本间的兼容性问题。字节序:Qt 默认使用大端字节序,如果与其他系统交互,注意设置。
错误检测:通过
QDataStream::status()
方法检查流状态,判断是否读取成功。
示例:自定义文件格式输入输出
自定义文件格式结构
字节偏移 | 数据内容 | 类型 | 说明 |
---|---|---|---|
0 | 0x4453 | qint16 | 文件魔数,用于识别是 .ds 文件(”DS” 两个字符) |
2 | 0x0100 | qint16 | 文件版本号(v1.0) |
4 | 行数(数据条目数) | qint32 | 总共有几行数据 |
8~… | 每行的三项数据(循环 N 次) | QString + qint32 + double | 分别表示 姓名、年龄、体重 |
1
2
3
4
5
6
7
8
9
[文件头]
- 0x4453 (qint16 魔数)
- 0x0100 (qint16 版本号)
- nCount (qint32 总行数)
[数据部分] × nCount
- QString 姓名
- qint32 年龄
- double 体重
widget.ui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>姓名</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditName"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>年龄</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditAge"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>体重</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditWeight"/>
</item>
<item>
<widget class="QPushButton" name="btnAdd">
<property name="text">
<string>添加行</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="btnDel">
<property name="text">
<string>删除行</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnSaveDS">
<property name="text">
<string>保存DS</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnLoadDS">
<property name="text">
<string>加载DS</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
widget.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget* parent = nullptr);
~Widget();
private slots:
void on_btnAdd_clicked();
void on_btnDel_clicked();
void on_btnSaveDS_clicked();
void on_btnLoadDS_clicked();
void on_listWidget_currentRowChanged(int currentRow);
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include "widget.h"
#include "./ui_widget.h"
#include <QDataStream>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QTextStream>
// 构造函数,初始化 UI 和列表控件选择模式
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 设置列表控件只能单选
ui->listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
}
// 析构函数,释放 UI 资源
Widget::~Widget() {
delete ui;
}
// 点击“添加”按钮的槽函数
void Widget::on_btnAdd_clicked() {
// 获取输入框中的数据
QString strName = ui->lineEditName->text().trimmed();
QString strAge = ui->lineEditAge->text().trimmed();
QString strWeight = ui->lineEditWeight->text().trimmed();
// 检查三个数据项是否为空
if (strName.isEmpty() || strAge.isEmpty() || strWeight.isEmpty()) {
QMessageBox::warning(this, tr("添加行"), tr("请填好姓名、年龄、体重三个数据项再添加。"));
return;
}
// 将年龄和体重字符串转换为数值
qint32 nAge = strAge.toInt();
double dblWeight = strWeight.toDouble();
// 年龄和体重范围校验
if ((nAge < 0) || (nAge > 600)) {
QMessageBox::warning(this, tr("添加行"), tr("年龄数值不对,应该 0~600 "));
return;
}
if (dblWeight < 0.1) {
QMessageBox::warning(this, tr("添加行"), tr("体重数值不对,至少 0.1kg "));
return;
}
// 构造用于显示的字符串行
QString strAll;
QTextStream tsLine(&strAll);
tsLine << strName << "\t" << nAge << "\t" << Qt::fixed << qSetRealNumberPrecision(2) << dblWeight;
// 添加到 listWidget 控件中
ui->listWidget->addItem(strAll);
}
// 点击“删除”按钮的槽函数
void Widget::on_btnDel_clicked() {
// 获取当前选中行
int nCurRow = ui->listWidget->currentRow();
if (nCurRow < 0) return; // 未选中任何行则不操作
// 删除该行
ui->listWidget->takeItem(nCurRow);
}
// 点击“保存 DS 文件”按钮的槽函数
void Widget::on_btnSaveDS_clicked() {
int nCount = ui->listWidget->count();
// 无数据不保存
if (nCount < 1) return;
// 弹出保存文件对话框
QString strFileName = QFileDialog::getSaveFileName(this, tr("保存为 DS 文件"), tr("."), tr("DS files(*.ds);;All files(*)"));
if (strFileName.isEmpty()) return;
// 打开文件写入
QFile fileOut(strFileName);
if (!fileOut.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("无法打开文件"), tr("无法打开要写入的文件:") + fileOut.errorString());
return;
}
// 使用 QDataStream 写入二进制数据
QDataStream dsOut(&fileOut);
dsOut.setVersion(QDataStream::Qt_6_9); // 设置版本,确保兼容性
// 写入文件头部(魔数 + 版本 + 行数)
dsOut << qint16(0x4453); // 'D''S' 文件标识
dsOut << qint16(0x0100); // 文件版本 1.0
dsOut << qint32(nCount); // 总行数
// 写入每行的姓名、年龄、体重数据
QString strCurName;
qint32 nCurAge;
double dblCurWeight;
for (int i = 0; i < nCount; i++) {
QString strLine = ui->listWidget->item(i)->text();
QTextStream tsLine(&strLine);
tsLine >> strCurName >> nCurAge >> dblCurWeight;
dsOut << strCurName << nCurAge << dblCurWeight;
}
// 写入完成提示
QMessageBox::information(this, tr("保存DS文件"), tr("保存为 .ds 文件成功!"));
}
// 点击“加载 DS 文件”按钮的槽函数
void Widget::on_btnLoadDS_clicked() {
// 弹出打开文件对话框
QString strFileName = QFileDialog::getOpenFileName(this, tr("打开DS文件"), tr("."), tr("DS files(*.ds);;All files(*)"));
if (strFileName.isEmpty()) return;
// 打开文件
QFile fileIn(strFileName);
if (!fileIn.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("打开DS文件"), tr("打开DS文件失败: ") + fileIn.errorString());
return;
}
// 创建数据流读取文件
QDataStream dsIn(&fileIn);
// 读取头部数据
qint16 nDS;
qint16 nVersion;
qint32 nCount;
dsIn >> nDS >> nVersion >> nCount;
// 检查标识是否合法
if (0x4453 != nDS) {
QMessageBox::warning(this, tr("打开文件"), tr("指定的文件不是 .ds 文件类型,无法加载。"));
return;
}
// 检查版本是否支持
if (0x0100 != nVersion) {
QMessageBox::warning(this, tr("打开文件"), tr("指定的 .ds 文件格式版本不是 1.0,暂时不支持。"));
return;
} else {
dsIn.setVersion(QDataStream::Qt_6_9); // 设置流版本
}
// 检查行数是否有效
if (nCount < 1) {
QMessageBox::warning(this, tr("打开文件"), tr("指定的 .ds 文件内数据行计数小于 1,无数据加载。"));
return;
}
// 清空原有内容
ui->listWidget->clear();
// 读取每行数据
QString strCurName;
qint32 nCurAge;
double dblCurWeight;
for (int i = 0; i < nCount; i++) {
// 检查流状态
if (dsIn.status() != QDataStream::Ok) {
qDebug() << tr("第 %1 行读取前的状态出错:%2").arg(i).arg(dsIn.status());
break;
}
// 读取数据项
dsIn >> strCurName >> nCurAge >> dblCurWeight;
// 构造显示字符串
QString strLine = tr("%1\t%2\t%3").arg(strCurName).arg(nCurAge).arg(dblCurWeight, 0, 'f', 2);
ui->listWidget->addItem(strLine);
}
// 提示完成
QMessageBox::information(this, tr("加载DS文件"), tr("加载DS文件完成!"));
}
// 列表控件选中项变化的槽函数
void Widget::on_listWidget_currentRowChanged(int currentRow) {
if (currentRow < 0) return; // 无选中项
// 读取该行数据
QString strLine = ui->listWidget->item(currentRow)->text();
QTextStream tsLine(&strLine);
// 拆分成三个字段
QString strName;
int nAge;
double dblWeight;
tsLine >> strName >> nAge >> dblWeight;
// 显示到输入框中
ui->lineEditName->setText(strName);
ui->lineEditAge->setText(tr("%1").arg(nAge));
ui->lineEditWeight->setText(tr("%1").arg(dblWeight));
}
示例:串行化
widget.ui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>源头端口</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditSrcPort"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>目的端口</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditDstPort"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>报文消息</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditMsg"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="btnAddUDP">
<property name="text">
<string>添加UDP包</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDelUDP">
<property name="text">
<string>删除UDP包</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>保存UDP</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnLoad">
<property name="text">
<string>加载UDP</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
widget.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
// 设置结构体按 1 字节对齐,避免编译器自动填充字节造成结构体大小不一致
#pragma pack(1)
// 定义一个 UDP 报文的结构体,用于模拟 UDP 数据包的头部与数据部分
struct UDPPacker {
quint16 m_srcPort; // 源端口号
quint16 m_dstPort; // 目标端口号
quint16 m_length; // 报文长度(包含头部8字节 + 数据长度)
quint16 m_checksum; // UDP 校验和(本示例中未进行实际计算,默认为0)
QByteArray m_data; // 报文数据内容
// 声明两个友元函数,用于支持 QDataStream 的序列化和反序列化操作
friend QDataStream& operator<<(QDataStream& stream, const UDPPacker& udp);
friend QDataStream& operator>>(QDataStream& stream, UDPPacker& udp);
};
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget; // 前向声明 UI 类,避免包含 ui_widget.h(由 uic 自动生成)
}
QT_END_NAMESPACE
// 主窗口类,继承自 QWidget
class Widget : public QWidget {
Q_OBJECT
public:
// 构造函数和析构函数
Widget(QWidget* parent = nullptr);
~Widget();
private slots:
// 添加一个 UDP 报文到列表
void on_btnAddUDP_clicked();
// 删除选中的 UDP 报文
void on_btnDelUDP_clicked();
// 保存 UDP 报文列表到文件
void on_btnSave_clicked();
// 从文件加载 UDP 报文列表
void on_btnLoad_clicked();
// 当前列表选中项变化时触发,用于更新显示到输入框
void on_listWidget_currentRowChanged(int currentRow);
private:
Ui::Widget* ui; // UI 指针,用于访问界面控件
};
#endif // WIDGET_H
widget.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "widget.h"
#include "./ui_widget.h"
#include <QDataStream> // 用于对象串行化(序列化)和反串行化
#include <QDebug> // 用于调试输出
#include <QFile> // 文件操作类
#include <QFileDialog> // 文件选择对话框
#include <QIntValidator> // 整数范围验证器,用于端口号输入
#include <QListWidgetItem> // 用于操作列表控件的行
#include <QMessageBox> // 消息弹窗
// 构造函数:初始化界面和控件设置
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this); // 设置 UI
ui->listWidget->setSelectionMode(QAbstractItemView::SingleSelection); // 列表只能单选
// 设置两个端口输入框只能输入 0-65535 之间的整数
QIntValidator* valSrc = new QIntValidator(0, 65535);
ui->lineEditSrcPort->setValidator(valSrc);
QIntValidator* valDst = new QIntValidator(0, 65535);
ui->lineEditDstPort->setValidator(valDst);
}
// 析构函数:释放 UI
Widget::~Widget() {
delete ui;
}
// 重载运算符:将 UDP 包写入数据流中(用于保存)
QDataStream& operator<<(QDataStream& stream, const UDPPacker& udp) {
stream << udp.m_srcPort;
stream << udp.m_dstPort;
stream << udp.m_length;
stream << udp.m_checksum;
stream.writeRawData(udp.m_data.data(), udp.m_data.size()); // 写入报文数据部分
return stream;
}
// 重载运算符:从数据流中读取 UDP 包(用于加载)
QDataStream& operator>>(QDataStream& stream, UDPPacker& udp) {
stream >> udp.m_srcPort;
stream >> udp.m_dstPort;
stream >> udp.m_length;
stream >> udp.m_checksum;
int nMsgLen = udp.m_length - 8; // UDP 包数据区长度 = 总长度 - 头部长度
char* buff = new char[nMsgLen]; // 分配内存缓冲区
stream.readRawData(buff, nMsgLen); // 读入数据区
udp.m_data.setRawData(buff, nMsgLen); // 设置 QByteArray 的原始数据指针
return stream;
}
// 添加 UDP 包按钮点击槽函数
void Widget::on_btnAddUDP_clicked() {
QString strSrcPort = ui->lineEditSrcPort->text().trimmed();
QString strDstPort = ui->lineEditDstPort->text().trimmed();
QString strMsg = ui->lineEditMsg->text().trimmed();
// 若任一输入为空,提示警告
if (strSrcPort.isEmpty() || strDstPort.isEmpty() || strMsg.isEmpty()) {
QMessageBox::warning(this, tr("添加包"), tr("请先填写两个端口和消息字符串。"));
return;
}
UDPPacker udp;
QByteArray baMsg = strMsg.toUtf8(); // 消息转为字节数组
udp.m_srcPort = strSrcPort.toUShort(); // 源端口
udp.m_dstPort = strDstPort.toUShort(); // 目的端口
udp.m_length = 8 + baMsg.size(); // 总长度 = 头部 + 数据
udp.m_checksum = 0; // UDP 校验和可省略为 0
udp.m_data = baMsg; // 设置数据字段
QByteArray baAll;
QDataStream dsOut(&baAll, QIODevice::ReadWrite); // 将数据写入内存字节数组
dsOut << udp;
QString strAll = baAll.toHex(); // 转为十六进制字符串显示
ui->listWidget->addItem(strAll); // 添加到列表控件中
}
// 删除 UDP 包按钮点击槽函数
void Widget::on_btnDelUDP_clicked() {
int nCurRow = ui->listWidget->currentRow(); // 获取当前行号
if (nCurRow < 0) return; // 无选中项,直接返回
ui->listWidget->takeItem(nCurRow); // 删除该行项
}
// 保存按钮槽函数
void Widget::on_btnSave_clicked() {
int nCount = ui->listWidget->count(); // 获取 UDP 包数量
if (nCount < 1) return;
QString strFileName = QFileDialog::getSaveFileName(this, tr("保存UDP文件"), tr("."), tr("UDP files(*.udp);;All files(*)"));
if (strFileName.isEmpty()) return;
QFile fileOut(strFileName);
if (!fileOut.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("保存UDP文件"), tr("打开要保存的文件失败:") + fileOut.errorString());
return;
}
QDataStream dsOut(&fileOut); // 输出数据流
dsOut << qint32(nCount); // 首先写入包个数
UDPPacker udpCur;
for (int i = 0; i < nCount; i++) {
QString strHex = ui->listWidget->item(i)->text(); // 提取十六进制字符串
QByteArray baCur = QByteArray::fromHex(strHex.toUtf8()); // 转为字节数组
QDataStream dsIn(baCur);
dsIn >> udpCur; // 提取结构体
dsOut << udpCur; // 写入文件
}
QMessageBox::information(this, tr("保存UDP包"), tr("保存UDP包到文件完毕!"));
}
// 加载按钮槽函数
void Widget::on_btnLoad_clicked() {
QString strFileName = QFileDialog::getOpenFileName(this, tr("打开UDP文件"), tr("."), tr("UDP files(*.udp);;All files(*)"));
if (strFileName.isEmpty()) return;
QFile fileIn(strFileName);
if (!fileIn.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("打开UDP文件"), tr("打开指定UDP文件失败:") + fileIn.errorString());
return;
}
QDataStream dsIn(&fileIn); // 输入数据流
qint32 nCount;
dsIn >> nCount; // 读取包数量
if (nCount < 1) {
QMessageBox::warning(this, tr("加载UDP包"), tr("指定UDP文件内数据包计数小于1,无法加载。"));
return;
}
ui->listWidget->clear(); // 清空列表控件
UDPPacker udpCur;
for (int i = 0; i < nCount; i++) {
if (dsIn.status() != QDataStream::Ok) {
qDebug() << tr("读取第 %1 个数据包前的状态错误:%2").arg(i).arg(dsIn.status());
break;
}
dsIn >> udpCur;
QByteArray baCur;
QDataStream dsOut(&baCur, QIODevice::ReadWrite); // 把 UDP 包写回字节流
dsOut << udpCur;
QString strHex = baCur.toHex(); // 转为十六进制字符串
ui->listWidget->addItem(strHex); // 添加显示
}
QMessageBox::information(this, tr("加载UDP包"), tr("加载文件中的UDP包完成!"));
}
// 当前行变更槽函数:选中某行时,更新界面显示
void Widget::on_listWidget_currentRowChanged(int currentRow) {
if (currentRow < 0) return;
QString strHex = ui->listWidget->item(currentRow)->text();
QByteArray baAll = QByteArray::fromHex(strHex.toUtf8());
QDataStream dsIn(baAll);
UDPPacker udp;
dsIn >> udp; // 提取结构体
// 更新界面三个编辑框的内容
ui->lineEditSrcPort->setText(tr("%1").arg(udp.m_srcPort));
ui->lineEditDstPort->setText(tr("%1").arg(udp.m_dstPort));
ui->lineEditMsg->setText(QString::fromUtf8(udp.m_data));
}
本文由作者按照 CC BY 4.0 进行授权