1.关于atomic和mutex的区别
2.项目描述
3.自我介绍
您好,我本科专业是环境科学,但因为自己比较喜欢编程,所以从大学期间开始系统学习 C++。目前主要掌握 C++、数据结构、Linux、多线程以及 Qt 开发。C++方面学习过面向对象、STL、内存管理等基础知识,也通过技术博客和项目进行巩固。Linux方面主要学习了常用命令、进程线程以及多线程同步。
项目方面,我做过一个基于 Qt 的音乐播放器,以及一个仿 muduo 的高并发服务器,对 Qt 开发和 Linux 网络编程都有一定实践。实习期间主要负责 Qt 上位机开发,其中比较核心的是 FPGA IQ 数据采集链路,包括 UDP 通信、协议封装、数据包重排、IQ 数据处理、实时显示和数据落盘。目前我希望找 C++/Qt、Linux C++ 或网络编程相关的开发岗位。
4.你为什么专业是环境科学,却来做 C++?
我本科虽然是环境科学,但我在大学期间逐渐发现自己对编程和计算机系统更感兴趣,所以从 C++ 开始自学。最开始主要学习数据结构和 C++ 基础,之后为了真正做项目又继续学习 Linux、网络编程、多线程和 Qt。
我不是只停留在课程学习,而是通过音乐播放器、服务器以及实习项目不断验证自己是否适合这个方向。到实习以后,我实际参与了 FPGA IQ 数据采集和 Qt 上位机开发,也进一步确定自己希望以后从事 C++ 开发。
5.你 Linux 学到了什么?
虽然我不是科班出身,但系统自学了 Linux 开发知识,并且写了 Reactor 网络项目落地。
- Linux 基础命令、Makefile、gcc/gdb 调试,静态库动态库;
- 进程、线程、信号,线程同步互斥:互斥锁、读写锁、自旋锁、信号量;
- 基础 IO、fd、mmap 内存映射,进程间通信 IPC,Ext 文件系统;
- Socket 网络编程,TCP/UDP、HTTP/HTTPS,ARP、DNS、NAT,会 tcpdump 抓包。 我基于这些知识,用 C++ 实现主从 Reactor 高并发 TCP 服务器,使用 epoll IO 多路复用,将 Linux 网络、多线程、IO 相关知识做了实战。
6.你实习主要做什么?
我实习主要负责频谱监测设备的 Qt 上位机开发,其中我负责的核心模块是 IQ 数据采集链路。具体来说,上位机需要接收 FPGA 通过 UDP 发送过来的 IQ 数据,我负责了应用层通信协议的封装和对接,之后实现 UDP 数据接收、数据包重排、IQ 帧重组、数据处理和 Qt Charts 实时显示,同时还负责原始 IQ 数据的文件落盘。
7.你说协议封装,具体怎么做的?
协议主要分成控制命令和数据包两部分。控制方向由上位机根据采集参数组装命令发送给 FPGA;数据方向由 FPGA 按照约定的数据结构封装 IQ 数据。数据包中包含当前包的packet_id、这一帧的total_packets、当前有效数据长度packet_length以及 IQ 数据 payload。
我们和硬件工程师确认这些字段的定义后,上位机根据packet_id把收到的数据放到对应的缓存位置,最后根据total_packets判断一帧是否接收完整。
8.UDP 为什么会乱序?
因为 UDP 本身不提供可靠、有序的传输保证,所以发送端连续发送多个数据报之后,接收端不能假设它们一定按照发送顺序到达。因此我们在应用层协议中增加了packet_id,接收端根据这个序号把数据包放回对应位置,从而完成乱序重排。
9.你们 UDP 有没有考虑丢包?
我们在应用层通过 total_packets、packet_id 和 packet_length 对 IQ 数据进行分包和组帧。UDP 本身不保证可靠传输,所以理论上存在丢包问题。对于实时频谱监测场景,如果为了一个丢失的数据包阻塞等待重传,会增加处理延迟,因此是否重传需要根据应用场景权衡。如果是实时监测,可以选择丢弃当前不完整帧继续处理下一帧;如果是对原始 IQ 数据完整性要求较高的采集场景,则可以结合 FPGA 侧缓存设计 ACK/NACK 和重传机制。
10.你介绍一下你在这个项目中主要负责什么?整个数据链路是怎么样的?
我主要负责 IQ 数据接收和后处理这一部分。FPGA 将一帧原始 IQ 数据分成多个 UDP 数据包发送到上位机,我负责接收并解析数据,根据包序号、总包数和包长度等信息对数据进行重排和组帧,放入应用层缓冲区。之后根据配置的抽取参数对 IQ 数据进行处理,再通过 Qt 的跨线程消息机制将处理后的数据投递到 UI 主线程,使用 Qt Charts 绘制实时 IQ 波形。同时将原始 IQ 数据保存为 BIN 文件,便于后续分析和数据回放。
11.如何判断缺包、如何决定等待还是放弃
一帧为单位,用包序号判断是否收齐0到N-1号包;在一个很短的超时窗口内等待剩余包,超时仍缺则记为不完整帧。不阻碍实时显示,并记录完整率作为质量指标。这个做法和实时业务目标一致,在取舍上更偏向可用性而非传输完美无缺。
12.颜色地址
Pastel Color Tones Color Scheme - Palettes - SchemeColor.com
13.讲一讲什么是信号和槽机制
信号槽是 Qt 元对象系统提供的对象间通信机制,用来解耦。 moc 在编译预处理阶段扫描头文件,生成元对象相关代码。调用 connect 的时候,会把槽函数注册到这个信号对应的回调链表里面,不是全局哈希表。 emit 发射信号的时候,会遍历该信号绑定的所有槽函数,传递参数并执行。 信号是事件发生时对外发出的通知,没有函数体,但可以携带参数,必须写在 signals 下,通过 emit 触发。 槽本质就是成员函数。Qt5 新的函数指针 connect 语法,普通成员函数不需要写 slots 关键字也能作为槽;槽的作用就是收到信号后执行业务逻辑。
14.请问槽函数的执行是同步的还是异步的?
15.你们的设备的协议是如何规定的?
我们项目没有直接使用 HTTP,而是基于 UDP/TCP 之上的自定义二进制应用层协议。代码中的这些 struct 相当于协议报文的数据结构定义,程序根据结构体填充同步字、长度、请求 ID、命令字以及具体参数,然后经过协议封装/序列化,通过 Socket 发送给设备,设备按照相同的字段约定进行解析。
16.你在简历中写的数据完整率是什么意思?你怎么证明是你的分包方案把 90% 提升到了 95%?
原来的数据传输以一帧 IQ 数据为单位,一帧大约 4096 Byte,数据量较大。后来我在协议层增加了帧 ID、包序号、帧长度等字段,将一帧数据拆分成多个较小的数据包进行传输。接收端根据帧 ID 和包序号进行重组,并以完整帧作为统计单位计算数据完整率,使完整率从原来的约 90% 提升到了 95%左右。
我通过实际运行测试,对修改前后的数据完整率进行了对比。在相同测试条件和测试时长下,修改前完整率大约 90%,增加协议字段并进行分包重组后,完整率提升到约 95%。这里的完整率是按照完整 IQ 帧数量与理论应接收帧数量的比例进行统计。
项目中原先通过senddatastruct.h和receivedatastruct.h分别定义上下位机之间的控制命令和接收数据结构;后续通过NewProtocol.h重新定义了一套统一的协议报文,其中Packet_Command负责参数配置,Packet_Data负责分包数据传输。
相比旧协议,新协议最大的变化是协议结构更加统一。旧协议主要按照发送命令和接收数据分别定义结构,新协议则把命令和数据统一封装成标准数据包,并且对数据分包中的总包数、包序号、有效长度进行了明确描述,更方便上位机进行协议解析和IQ数据的组帧处理。
17.你的频谱上位机开发为什么要用dialog而不是widget
18.如何确定一包数据是否完整到达?
19.如何解决cpu问题?
我的实现中每 100ms 检查一次数据更新状态,如果允许绘制,就通过 QtConcurrent 异步执行 paint。paint 内部需要复制采集数据、进行数据抽取以及最大保持、最小保持、平均等轨迹处理,最后再更新 Qt Charts 曲线。因此数据量较大或者刷新频率较高时,CPU 开销主要来自数据处理和图表重绘。为了控制开销,我做了复用缓冲区、预分配以及超过 5000 点时进行 max/min 抽取等优化,同时通过原子标志避免 paint 重入。”