地平线 嵌入式测试开发 一面 面经
1.自我介绍
面试官好,我是[姓名],[学校][专业][年级]。我主要的技术方向是C++开发和软件测试。
技术栈方面,我熟悉C++多线程、网络编程、数据库操作,也了解Python和Shell脚本。测试方面,我有功能测试、性能测试、自动化测试的实践经验,会使用JMeter、Postman等工具。
项目经验上,我独立完成过一个多人聊天系统,从设计到开发到测试都是自己做的。这个过程让我既理解了开发的思维,也培养了测试的意识。特别是在测试阶段,我发现了很多开发时没注意到的问题,比如并发场景下的数据竞争、极端情况下的内存泄漏等。
我认为测试开发是一个很有价值的岗位,既要懂技术能写代码,又要有测试思维能发现问题。希望能加入贵公司,在这个方向上深入发展。
2.你的聊天系统是如何实现一对多通信的?底层线程是怎么管理的?
我的聊天系统支持一对一和群聊两种模式。一对多通信主要是指群聊功能,具体实现是这样的:
服务器维护了一个群组映射表,key是群ID,value是该群的所有成员ID列表。当用户A发送群消息时,服务器收到消息后,查询这个群的成员列表,然后遍历列表,给每个在线成员转发消息。
// 群组数据结构
std::map<int, std::set<int>> group_members; // 群ID -> 成员ID集合
std::map<int, int> user_fd; // 用户ID -> socket fd
// 群消息处理
void handle_group_message(int group_id, const Message& msg) {
std::lock_guard<std::mutex> lock(group_mutex);
auto it = group_members.find(group_id);
if (it != group_members.end()) {
for (int user_id : it->second) {
if (user_fd.count(user_id)) { // 用户在线
send_message(user_fd[user_id], msg);
}
}
}
}
线程管理方面,我用的是线程池模式。服务器启动时创建固定数量的工作线程(我设置的是CPU核心数的2倍,比如8核机器就创建16个线程)。这些线程在整个服务器运行期间都存在,不会销毁。
主线程使用epoll监听所有客户端连接,当某个socket有数据可读时,主线程将这个任务(包含socket fd和事件类型)放入任务队列,工作线程从队列中取任务执行。任务队列用互斥锁保护,用条件变量实现阻塞等待。
// 线程池初始化
void init_thread_pool(int num_threads) {
for (int i = 0; i < num_threads; i++) {
workers.emplace_back([this] {
while (running) {
Task task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return !tasks.empty() || !running;
});
if (!running) break;
task = tasks.front();
tasks.pop();
}
handle_task(task);
}
});
}
}
服务器关闭时,先设置running标志为false,然后通知所有工作线程(condition.notify_all()),最后join等待所有线程退出。这样保证了线程的优雅退出,不会有资源泄漏。
3.项目中哪些部分是你自己实现的?用了哪些第三方库?
核心功能基本都是我自己实现的。网络通信部分,我直接使用Linux的socket API和epoll,没有用第三方网络库。多线程部分使用C++11的std::thread和同步原语(mutex、condition_variable),也是标准库的。消息序列化我用的是nlohmann/json这个第三方库,因为自己实现JSON解析比较复杂,用现成的库更可靠。
数据库部分我用的是MySQL,通过MySQL C API连接,SQL语句都是自己写的。为了提高性能,我实现了一个简单的数据库连接池,预先创建多个连接,使用时从池中取,用完归还,避免频繁创建销毁连接的开销。
class ConnectionPool {
std::queue<MYSQL*> pool;
std::mutex mtx;
int max_size;
public:
MYSQL* get_connection() {
std::lock_guard<std::mutex> lock(mtx);
if (pool.empty()) {
return create_new_connection();
}
MYSQL* conn = pool.front();
pool.pop();
return conn;
}
void return_connection(MYSQL* conn) {
std::lock_guard<std::mutex> lock(mtx);
if (pool.size() < max_size) {
pool.push(conn);
} else {
mysql_close(conn);
}
}
};
日志部分我用的是spdlog库,因为它性能好,支持异步日志,不会阻塞主业务。配置文件解析用的是yaml-cpp。除此之外,基本都是自己实现的。
4.项目开发过程中最有成就感的是什么?
最有成就感的是解决了一个并发场景下的bug。当时做压力测试,模拟100个用户同时登录并发送消息,发现服务器偶尔会崩溃,但很难复现。我通过添加详细日志,发现崩溃发生在访问用户连接映射表的时候,是一个多线程并发访问的问题。
具体原因是:线程A在遍历用户列表发送消息,同时线程B检测到某个用户断开连接,从列表中删除了这个用户,导致线程A访问了已删除的元素,引发崩溃。
我的解决方案是使用读写锁(shared_mutex)保护这个映射表。读操作(查询用户、遍历列表)使用共享锁,多个线程可以同时读;写操作(添加、删除用户)使用独占锁,保证互斥。这样既保证了线程安全,又不会过度影响性能。
std::shared_mutex user_map_mutex;
std::map<int, UserInfo> user_map;
// 读操作
void send_to_user(int user_id, const Message& msg) {
std::shared_lock<std::shared_mutex> lock(user_map_mutex);
auto it = user_map.find(user_id);
if (it != user_map.end()) {
send_message(it->second.fd, msg);
}
}
// 写操作
void remove_user(int user_id) {
std::unique_lock<std::shared_mutex> lock(user_map_mutex);
user_map.erase(user_id);
}
修复后再次压力测试,运行了24小时没有崩溃。这个过程让我深刻理解了多线程编程的复杂性,也学会了如何系统性地排查并发问题。
5.项目中遇到的最大困难是什么?如何解决的?
最大的困难是前端开发。我主要是做后端的,对前端不太熟悉,但聊天系统需要一个界面让用户使用。一开始我想用Qt做桌面客户端,但发现Qt的学习曲线比较陡,而且跨平台部署比较麻烦。
后来我改用Web前端,学习了HTML、CSS、JavaScript和WebSocket。我的学习方法是:先看MDN文档了解基础概念,然后找一个简单的聊天室教程跟着做,理解基本流程,最后根据自己的需求修改和扩展。
遇到不懂的问题,我会先在Stack Overflow上搜索,看看别人是怎么解决的。如果还是不明白,就去看相关的技术博客或视频教程。比如WebSocket的使用,我看了好几篇文章才理解它和HTTP的区别,以及如何在前后端建立连接。
// 前端WebSocket连接
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = function() {
console.log('连接成功');
};
ws.onmessage = function(event) {
const msg = JSON.parse(event.data);
displayMessage(msg);
};
ws.send(JSON.stringify({
type: 'chat',
content: 'Hello'
}));
最终我用了大概两周时间,实现了一个基本可用的Web界面,支持登录、发送消息、显示历史记录等功能。虽然界面不够美观,但功能是完整的。这个经历让我理解了全栈开发的不易,也提升了我快速学习新技术的能力。
6.如何验证自己的学习效果?结合项目说明
我验证学习效果的方法主要是实践和测试。学完一个知识点后,我会立即在项目中应用,看能否解决实际问题。
比如学习WebSocket时,我先写了一个最简单的demo,客户端和服务器互相发送消息,验证连接是否正常。然后逐步增加功能,比如心跳保活、断线重连、消息确认等。每增加一个功能,我都会测试各种场景:正常情况、异常情况、边界情况。
具体到WebSocket的断线重连,我测试了这些场景:
- 正常断开(客户端主动关闭):验证服务器能否正确检测并清理资源
- 异常断开(拔网线):验证客户端能否自动重连
- 服务器重启:验证客户端能否重连并恢复会话
- 网络抖动(频繁断开重连):验证系统的稳定性
// 断线重连实现
function connect() {
ws = new WebSocket('ws://localhost:8080');
ws.onclose = function() {
console.log('连接断开,3秒后重连');
setTimeout(connect, 3000);
};
ws.onerror = function(error) {
console.error('连接错误:', error);
ws.close();
};
}
通过这些测试,我发现了很多问题,比如重连时没有重新发送登录信息、心跳包没有正确处理等。修复这些问题后,我对WebSocket的理解就更深入了。
另外,我还会写技术笔记,总结学到的知识点和遇到的坑。过一段时间回顾笔记,看能否快速回忆起来,这也是检验学习效果的方法。
7.你对项目做了哪些测试?具体测试了什么功能?
我对项目做了比较全面的测试,主要包括功能测试、性能测试、压力测试三个方面。
功能测试方面,我测试了所有的核心功能:
- 用户注册登录:测试正常注册、重复用户名、密码错误、SQL注入等场景
- 一对一聊天:测试消息发送、接收、离线消息存储、消息顺序等
- 群聊功能:测试创建群组、加入退出、群消息广播、成员列表同步等
- 好友管理:测试添加好友、删除好友、好友列表显示等
- 历史消息:测试消息存储、查询、分页加载等
每个功能我都设计了测试用例,包括正常流程和异常流程。比如发送消息的测试用例:
1. 正常发送:用户A给在线的用户B发消息,B能收到 2. 离线发送:用户A给离线的用户B发消息,B上线后能收到 3. 空消息:发送空内容,服务器应该拒绝 4. 超长消息:发送超过限制长度的消息,服务器应该截断或拒绝 5. 特殊字符:发送包含特殊字符的消息,验证是否正确处理
性能测试方面,我主要测试了响应时间和吞吐量:
- 消息延迟:从发送到接收的时间,正常情况下在50ms以内
- 登录耗时:从发起登录到返回结果的时间,正常在100ms以内
- 数据库查询:历史消息查询的耗时,1000条消息在200ms以内
压力测试方面,我使用Python脚本模拟多个客户端:
import th
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

查看18道真题和解析