目录C/C部分口述版1. 面向对象和面向过程区别2. 类和对象3. C三大特性封装继承多态4. C11新特性智能指针右值引用和移动语义5. 虚函数和虚表6. STL容器vectorlistmap数据结构与算法口述版顺序表和链表栈和队列快排和归并快排归并排序动态规划Linux网络编程口述版多进程和多线程进程间通信线程同步互斥锁 mutex读写锁 rwlock条件变量 condition信号量 semaphoresocket编程I/O复用selectpollepoll面试核心epoll两种触发模式高频select、poll、epoll区别面试总结版网络协议口述版TCP和UDP区别三次握手四次挥手TIME_WAITMySQL口述版B树索引事务隔离级别redo log 和 undo logRedis口述版Redis基本类型Redis持久化缓存问题缓存穿透缓存击穿缓存雪崩Git口述版分布式口述版ZookeeperKafka自我介绍项目一项目描述每个模块1. 用户注册登录怎么做的2. 商品发布/查询/更新/下架怎么做3. 秒杀功能怎么做4. Shell并发测试怎么做相关问题1. 为什么采用三层架构2. 为什么秒杀要先Redis再MySQL3.Lua脚本为什么原子4. 如果Redis扣成功了但MySQL事务失败怎么办5. 为什么删除缓存而不是更新缓存6.为什么用Lua。7.Redis缓存三剑客8.校园交易这个项目http怎么接收的项目二项目描述每个模块1. 棋盘渲染怎么做2. 国际象棋规则怎么实现3. TCP通信怎么做相关问题1. 为什么用TCP不用UDP2. 信号槽机制本质是什么3. 为什么Qt适合这个项目4.关于项目问了对qt的基本了解专业术语第一次面试总结1.c和c的区别2.虚函数表3.对网络的了解4.多线程5.线程同步C/C部分口述版1. 面向对象和面向过程区别“面向过程的话更偏向于步骤化开发比如先写登录函数、再写查询函数、再写下单函数核心是过程。面向对象的话更偏向于把数据和行为封装成对象比如用户类、订单类让对象自己完成自己的行为。我觉得面向对象最大的优点就是代码复用性高、维护方便比较适合大型项目开发。”2. 类和对象“类可以理解成一个模板对象就是类实例化出来的实体。比如我定义一个 User 类里面有用户名、密码这些成员变量还有登录、注册这些成员函数然后真正创建出来的 user1、user2 就是对象。”3. C三大特性封装“封装就是把数据和操作数据的方法放到一起同时隐藏内部实现。比如类里面会把成员变量设成 private对外提供接口访问这样安全性更高也降低了模块之间的耦合。”继承“继承主要是代码复用。比如我有一个 Animal 父类Dog 和 Cat 可以直接继承 Animal 的一些公共属性和方法这样不用重复写。”多态“多态就是同一个接口不同对象会有不同表现。我理解最核心的是虚函数。比如父类指针指向子类对象调用同一个函数时运行时会根据实际对象类型执行对应函数。底层其实是通过虚函数表和虚函数指针实现的。”4. C11新特性智能指针“智能指针主要是解决内存泄漏问题。像 unique_ptr 是独占所有权一个资源只能有一个指针管理。shared_ptr 是引用计数多个指针共享同一个资源。weak_ptr 主要是解决 shared_ptr 循环引用问题它不会增加引用计数。”右值引用和移动语义“右值引用我理解主要是为了支持移动语义。以前对象传递可能会发生深拷贝性能开销比较大。C11 引入移动语义之后可以直接把资源所有权转移过去而不是重新拷贝一份。比如 vector 很大的时候用 move 可以减少大量内存复制提高性能。”5. 虚函数和虚表“只要类里面有虚函数编译器就会给这个类生成虚函数表。对象里面会保存一个虚函数指针调用虚函数时会先通过 vptr 找到 vtable再找到真正函数地址。这个机制其实就是 C 多态底层实现原理。”6. STL容器vector“vector 底层是动态数组支持随机访问尾插效率比较高。但是扩容的时候会重新申请空间并拷贝数据。”list“list 底层是双向链表插入删除效率高但是不支持随机访问。”map“map 底层一般是红黑树所以插入、删除、查询复杂度都是 O(logn)并且 key 会自动排序。”数据结构与算法口述版顺序表和链表“顺序表底层是连续内存所以查询快但是插入删除可能需要移动元素。链表的话节点不连续插入删除比较快但是查找慢。”栈和队列“栈是先进后出像函数调用、括号匹配都会用到。队列是先进先出比如消息队列、广度优先搜索这些场景。”快排和归并快排“快速排序核心是 partition 分区思想选一个基准值把比它小的放左边比它大的放右边然后递归处理。平均时间复杂度是 O(nlogn)。归并排序“归并排序是典型分治思想。先不断拆分数组再把两个有序数组合并起来时间复杂度稳定 O(nlogn)而且是稳定排序。”动态规划“动态规划我理解核心就是一个问题可以拆成很多重复子问题。一般步骤是先定义 dp 数组含义再写状态转移方程然后初始化和确定遍历顺序。”Linux网络编程口述版多进程和多线程“多进程资源独立稳定性更高但是进程间通信成本高。多线程共享内存切换开销更小所以并发性能更好但是需要考虑线程安全问题。”进程间通信“常见的 IPC 我了解管道、共享内存、消息队列、信号量这些。管道共享内存共享内存就是映射一段能被其他进程所访问的内存这段内存由一个进程创建但是多个进程可以访问。共享内存是最快的IPC方式他是针对其他进程间通信方式运行效率低而专门设计的。消息队列消息队列是有消息的链表存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限制等缺点。套接字适用于不同机器间进程通信在本地可作为两个进程间通信的方式。信号用于通知接收进程某个时间已经发生比如按下ctrlC就是信号信号量信号量就是一个计数器可以用来控制多个进程对共享资源的访问。常用一种锁的机制实现进程、线程的临界区的同步及互斥访问。共享内存速度最快因为直接映射同一块内存。socket 不仅能本机通信还能跨网络通信。”线程同步互斥锁 mutex“互斥锁是最基础的线程同步方式。它的核心思想就是同一时间只能有一个线程访问共享资源。比如多个线程同时对 count 操作如果不加锁结果可能不正确。”互斥锁适合写操作比较多的场景实现简单但如果锁竞争严重会影响性能读写锁 rwlock“读写锁适合读多写少场景。它允许多个线程同时读写线程独占所以相比普通互斥锁并发性能更高。”举例“比如缓存系统大部分请求只是查询数据很少修改数据这时候用读写锁比较合适。”读写锁本质上是对读操作做了共享优化条件变量 condition“条件变量一般不是单独使用的而是配合互斥锁使用。它主要解决线程等待某个条件成立的问题。”信号量 semaphore“信号量本质是一个计数器用来控制资源数量socket编程“服务端一般流程是socket 创建套接字bind 绑定端口listen 监听accept 接收连接然后 read/write 通信。”I/O复用“我理解 I/O 复用本质上就是一个线程同时监听多个 socket。以前如果一个连接对应一个线程在高并发场景下线程数量会非常多性能开销很大。所以 Linux 提供了 select、poll、epoll 这些 I/O 多路复用机制让一个线程可以管理很多连接。”select“select 是最早的 I/O 复用模型。它的核心做法是把需要监听的 fd 放进 fd_set 集合里然后调用 select 阻塞等待。”缺点面试重点1fd数量有限“默认最大监听 1024 个 fd。”2每次都要拷贝集合“用户态和内核态之间会频繁拷贝 fd_set。”3需要遍历所有fd“即使只有一个 fd 就绪也需要遍历整个集合所以效率比较低。”“select 的问题主要是连接数越大遍历成本越高。”poll“poll 本质上和 select 差不多也是轮询。不过它解决了 select 的 fd 数量限制问题。”poll优点“没有 1024 限制。”poll缺点“虽然没有数量限制但本质还是需要遍历所有 fd所以高并发性能还是一般。”epoll面试核心“epoll 是 Linux 下高性能 I/O 复用模型也是现在高并发服务器最常用的方案。”epoll 为什么快高频1事件通知机制“select 和 poll 每次都要遍历所有 fd。而 epoll 是哪个 fd 就绪内核直接通知用户态。不用全量遍历。”2减少数据拷贝“epoll 使用 mmap 共享内存减少了用户态和内核态之间的数据拷贝。”3底层数据结构优化“我了解 epoll 底层红黑树管理所有 fd就绪链表保存活跃事件所以查找和插入效率都比较高。”epoll两种触发模式高频1. LT 水平触发默认“只要缓冲区还有数据就会一直通知。”特点简单不容易漏数据2. ET 边缘触发“只有状态变化时才通知一次。”特点效率更高必须一次性把数据读完一般要配合非阻塞IOselect、poll、epoll区别面试总结版对比selectpollepollfd数量限制1024无限制无限制数据结构位图数组红黑树是否遍历全部fd是是否性能较低一般高适合场景小并发中等并发高并发最后一段非常适合面试结尾“我理解 select、poll、epoll 本质都是 I/O 多路复用。区别主要在于select 和 poll 都需要遍历所有 fd而 epoll 采用事件驱动机制只处理活跃连接所以在高并发场景下性能更好。现在像 Redis、Nginx 这些高性能服务器底层都大量使用 epoll。”网络协议口述版TCP和UDP区别“TCP 是面向连接的可靠传输有流量控制和拥塞控制。UDP 是无连接的速度快但是不保证可靠性。像视频直播、语音通信一般会用 UDP。”三次握手“第一次客户端发送 SYN。第二次服务端返回 SYNACK。第三次客户端再回复 ACK。主要目的就是确认双方收发能力正常并建立连接。”四次挥手“因为 TCP 是全双工通信所以双方都需要单独关闭连接因此是四次挥手。”TIME_WAIT“TIME_WAIT 我理解主要有两个作用一个是防止最后 ACK 丢失。另一个是防止旧连接的数据影响新的连接。”MySQL口述版B树索引“MySQL InnoDB 默认使用 B树索引。它比较适合磁盘存储减少磁盘 IO而且范围查询效率也很高。”事务隔离级别“我了解四种隔离级别读未提交、读已提交、可重复读、串行化。MySQL 默认是可重复读。”redo log 和 undo log“redo log 主要保证事务持久性。undo log 主要用于事务回滚还有 MVCC。”Redis口述版Redis基本类型“Redis 我比较常用的是 string、hash、list、set、zset 这几种。”Redis持久化“RDB 是快照方式。AOF 是记录写命令一般数据安全性更高。”缓存问题缓存穿透“缓存穿透就是查数据库里根本不存在的数据。解决方法一般是布隆过滤器或者缓存空对象。”缓存击穿“缓存击穿是热点 key 突然失效大量请求直接打到数据库。一般会加互斥锁或者热点数据不过期。”缓存雪崩“缓存雪崩是大量 key 同时过期。一般会给过期时间加随机值或者做 Redis 集群。”Git口述版“Git 我平时主要用于团队协作开发。常用操作像git clonegit addgit commitgit pushgit pull还有分支管理比如git branchgit checkoutgit merge如果代码冲突的话我一般会先解决冲突再重新提交。”分布式口述版Zookeeper“Zookeeper 我了解它主要是做分布式协调服务。比如注册中心分布式锁配置管理它底层类似树形目录结构。”Kafka“Kafka 是消息队列适合高并发、高吞吐场景。像异步削峰、日志收集这些都会用到。”自我介绍面试官您好我叫王俞涵目前就读于西安工业大学软件工程专业是一名大三生。在校期间我主要学习 C/C 后端开发与 Linux 服务端开发方向系统学习了计算机网络、操作系统、数据结构、TCP/IP、Linux 网络编程等核心课程。平时主要使用 C、Linux、MySQL、Redis 等技术完成项目开发对 socket 网络编程、多线程、高并发、缓存设计这些内容也有一定的掌握。项目方面我独立完成过一个基于 C Drogon 框架开发的校园闲置交易平台后端系统。项目中实现了用户登录、商品发布、订单创建以及秒杀模块并且引入 Redis 做热点数据缓存通过 Lua 脚本和 MySQL 事务解决秒杀超卖问题对接口性能和并发一致性做了一些优化。另外我还开发过一个基于 Qt 和 TCP Socket 的在线双人国际象棋系统主要负责网络通信、协议设计以及棋局同步功能实现了双方实时对战和断线状态处理。平时也会借助 AI 工具提高开发效率比如使用 Cursor、ChatGPT 辅助代码设计和问题排查。项目一项目描述我做的是一个基于 C Drogon 框架开发的校园闲置交易平台后端系统。项目主要是模拟校园二手交易场景提供用户注册登录商品发布商品查询订单创建秒杀下单这些核心功能。整体技术栈主要用了CDrogonMySQLRedisLinux 项目整体采用的是Controller / Service / DAO三层架构。这样做的目的是把接口处理业务逻辑数据访问解耦。后期维护和扩展会更方便。比如Controller层主要负责接收HTTP请求参数校验返回JSON 。Service层负责秒杀逻辑订单逻辑 缓存逻辑。DAO层负责和MySQL交互。项目里我觉得最核心的部分是秒杀高并发场景下的库存一致性问题。因为如果多个用户同时下单。很容易出现库存已经没了但订单还创建成功也就是超卖。所以这里我采用了Redis Lua脚本 MySQL事务的方案。具体流程大概是用户请求秒杀后先在Redis里通过Lua脚本进行库存原子预减。Lua脚本会判断库存是否大于0 扣减库存。因为Redis执行Lua脚本是原子的所以不会出现并发竞争问题。只有Redis扣减成功后才会继续执行数据库事务。数据库事务里同时完成扣减MySQL库存创建订单。这样保证库存和订单数据一致。最后如果成功再删除商品缓存避免缓存脏数据。另外项目里还用了Cache Aside 模式。因为商品详情属于高频查询场景。如果每次都访问MySQL数据库压力会比较大。所以优先查Redis。Redis没有再查MySQL并回写缓存。商品更新或下架后主动删除缓存保证数据一致性。最后我还通过Shell脚本模拟并发秒杀。验证库存是5时。最终只有5个订单成功创建。保证不会超卖。每个模块1.用户注册登录怎么做的用户模块这块我主要是通过 Drogon 提供的 HTTP 接口实现的。比如注册的时候前端会传用户名 密码 这些信息。Controller 接收到请求后会先做参数校验。然后进入 Service 层。Service 里会先查数据库判断用户名是否已经存在。如果不存在再把用户信息写入 MySQL。登录逻辑的话会先根据用户名查数据库。然后校验密码是否正确。验证成功之后返回对应结果。因为项目主要还是偏后端逻辑所以认证这块我做的是比较基础的账号密码登录2.商品发布/查询/更新/下架怎么做商品发布本质上就是新增商品数据。前端传商品信息后。Controller接收请求。然后Service层处理业务。最后DAO层写入MySQL。商品表里我设计了商品名称 价格 库存 status状态 这些字段。商品查询这里我加了 Redis 缓存。因为商品详情属于高频读场景。如果每次都查 MySQL。数据库压力会比较大。所以查询的时候会先查 Redis。如果 Redis 里有。直接返回。如果没有再查 MySQL。然后把结果回写 Redis。这个其实就是Cache Aside 模式。商品更新的时候我会先更新 MySQL。更新成功后删除 Redis 缓存。因为如果直接更新缓存。可能会出现缓存更新失败。或者并发覆盖问题。所以我这里采用的是删除缓存。让后续查询自动重建缓存。商品下架我做的是逻辑下架。不是直接删除数据库数据。因为真实业务里通常不会真的删数据。所以我在商品表设计了status字段。下架时只是把状态改成“已下架”。查询商品时只查询status正常的数据。3.秒杀功能怎么做秒杀功能主要是解决高并发下的超卖问题。因为如果多个用户同时下单。可能都会读到库存还有。最后导致超卖。所以我这里设计的是先 Redis。后 MySQL。具体流程是用户请求过来之后。先在 Redis 查询库存。然后通过 Lua 脚本完成判断库存 扣减库存 这两个操作。因为 Lua 在 Redis 里执行是原子的。所以不会出现并发问题。如果 Redis 扣减失败。说明库存没了。直接返回秒杀失败。如果 Redis 扣减成功。才继续执行 MySQL事务。事务里同时完成数据库扣库存 创建订单 这样保证订单和库存一致。最后事务成功后会删除商品缓存。避免缓存脏数据。4. Shell并发测试怎么做写了 Shell 脚本模拟并发请求。比如库存设置为5。然后同时发很多请求。最后我会去数据库验证是不是最终只有5个订单成功创建。这个主要是为了验证高并发下不会超卖。相关问题1.为什么采用三层架构因为如果业务逻辑直接写在Controller里。后期项目一大代码会非常混乱。所以我拆分成ControllerServiceDAO。这样职责更清晰。后期比如数据库替换。或者业务逻辑修改。影响会更小。2.为什么秒杀要先Redis再MySQL因为Redis性能远高于MySQL。高并发下如果所有请求直接打数据库。数据库压力会很大。所以Redis先抗住高并发请求。MySQL负责最终数据落库。3.Lua脚本为什么原子Redis执行Lua脚本时会把整个脚本作为一个整体执行。中间不会插入其他命令。所以库存判断和扣减可以一次完成。避免并发问题。4.如果Redis扣成功了但MySQL事务失败怎么办目前这个项目里如果MySQL事务失败。我会回滚数据库事务。同时补偿Redis库存。把库存加回去。避免Redis和MySQL数据不一致。5.为什么删除缓存而不是更新缓存因为删除缓存实现更简单。一致性更好。如果直接更新缓存可能更新失败并发覆盖 所以实际项目里很多都会采用更新数据库后删除缓存。让后续查询自动重建缓存。6.为什么用Lua。我项目里 Lua 脚本主要是为了保证库存判断和库存扣减的原子性。因为如果不用 Lua。而是先GET库存再decr扣库存中间可能会被其他请求插入。比如两个线程同时读到库存还有1。最后都扣成功。这样就超卖了。所以我把判断库存 扣减库存 写到一个 Lua 脚本里。 Redis 一次执行完成。因为 Redis 执行 Lua 脚本时整个脚本不会被其他命令打断。所以天然保证原子性。7.Redis缓存三剑客1.缓存穿透查不存在的数据。每次都打数据库。解决布隆过滤器缓存空值2.缓存击穿热点Key突然过期。大量请求同时打数据库。解决互斥锁热点永不过期3.缓存雪崩大量Key同时过期。解决随机过期时间Redis集群8.校园交易这个项目http怎么接收的我这个项目里 HTTP 请求主要是通过 Drogon 提供的 Controller 路由机制接收的。比如用户请求GET /product/1这种接口请求到达服务端之后Drogon会根据配置好的路由自动分发到对应Controller的处理函数。比如商品查询接口。我会在 Controller 里定义对应的 GET 接口。然后 Drogon 收到 HTTP 请求后。会自动调用对应函数。函数里再获取URL参数JSON数据请求头这些信息。处理完业务逻辑后。再通过 JSON 格式返回结果。9. 为什么选择Drogon而不是其他C Web框架Drogon的哪些特性对秒杀场景特别有帮助Drogon是高性能异步非阻塞框架基于proactor模型epoll 线程池类似Golang的netpoller适合IO密集型如大量数据库/Redis请求。对秒杀场景特别有用的特性协程支持可以用同步风格写异步代码避免回调地狱方便在秒杀接口中串行调用“Redis预减 - MySQL事务 - 返回结果”。内置ORM自动连接池、事务管理减少手写SQL的出错风险。高性能HTTP解析基于http_parser每秒可处理数万请求。插件化过滤器可以轻松实现限流、鉴权等中间件。10.如果Redis崩溃了库存状态会丢失吗如果Redis未开启RDB/AOF持久化重启后库存全丢。解决方案使用Redis Cluster 持久化或秒杀活动前将初始库存从MySQL加载到Redis并定期备份。更严格的做法Redis只做预扣最终以MySQL为准。11. Redis预减后MySQL还扣库存吗如何保证一致性会扣。Redis预减是为了快速过滤超卖请求真正扣减仍在MySQL事务中完成UPDATE products SET stockstock-1 WHERE id? AND stock0。一致性保证先Redis预减成功再进入MySQL事务扣减。如果MySQL事务失败比如网络异常、行锁超时需要回滚Redis重新INCR库存。可以用Lua脚本回滚或发送延迟消息补偿。最终以MySQL为准通过定时任务对账MySQL剩余库存 Redis剩余库存不匹配时修复。12. 如果超卖排查步骤检查MySQLproducts.stock最终值是否为负数。检查Redis Lua脚本是否没有原子执行比如用了多条Redis命令而非eval。检查MySQL隔离级别默认Repeatable Read可能引起幻读是否用了SELECT ... FOR UPDATE检查代码中是否有并发路径下两次扣库存比如业务逻辑重复调用。查看Drogon日志是否有事务回滚但忘记回滚Redis。项目二项目描述这个项目是基于 C 和 Qt5 开发的双人在线国际象棋系统。整体采用TCP Socket 的 C/S 架构。主要实现了双人联机 棋局同步 图形界面渲染 回合计时 将死判定等功能。技术上主要用了Qt Widgets QPainter QTcpSocket 信号槽机制 项目里我主要负责棋盘逻辑 网络通信 状态同步 这部分棋盘部分我采用8x8数组建模。因为国际象棋本身就是固定棋盘。数组访问效率高。实现起来也比较方便网络通信部分采用TCP Socket。因为棋局同步要求数据可靠 顺序正确 如果丢包或者乱序。双方棋局状态就会不一致。所以更适合TCP。另外为了同步双方状态。我设计了一套简单协议。比如MOVEATUPG分别代表移动 吃子 升变 客户端收到消息后更新本地棋盘状态。项目里我觉得比较难的地方是完整棋规实现。比如王车易位 将军检测 升变 和棋判定 这些规则状态比较复杂。需要不断校验当前棋局是否合法。每个模块1.棋盘渲染怎么做棋盘界面这部分。我主要是通过 Qt Widgets 和 QPainter 实现的。因为Qt本身比较适合做桌面GUI。棋盘我用了8x8数组建模。数组每个位置对应一个棋子状态。然后通过QPainter动态绘制棋盘和棋子。2.国际象棋规则怎么实现规则实现这部分其实是项目里比较复杂的地方。因为不同棋子的移动规则都不一样。比如兵 马 象 车 走法都不同。所以我会针对每种棋子单独判断当前移动是否合法。另外还实现了升变 王车易位 将军检测 这些特殊规则。尤其将军判断会复杂一点。因为需要判断当前移动后。自己的王是否暴露在攻击范围内。3. TCP通信怎么做网络通信部分我采用的是QTcpServer 和 QTcpSocket。整体是一个C/S架构。服务端主要负责转发消息 同步双方状态 客户端负责棋盘显示 用户操作 双方通信时我设计了一套简单协议。比如MOVEATUPG这些指令。分别代表移动 吃子 升变 客户端落子后。会把棋子位置变化发送给服务端。服务端再同步给另一方。这样保证双方棋盘状态一致。相关问题1.为什么用TCP不用UDP因为棋局同步要求可靠 有序 如果UDP丢包。或者乱序。会导致双方棋局状态错误。所以TCP更适合2.信号槽机制本质是什么Qt信号槽本质上是一种观察者模式。对象之间通过signal和slot进行解耦通信。Qt内部通过元对象系统MOC实现。3.为什么Qt适合这个项目因为Qt跨平台 UI能力强 事件驱动成熟同时Qt封装了Socket 定时器 绘图 开发效率比较高。4.关于项目问了对qt的基本了解我对QT的了解主要是结合项目学习的。项目过程中我主要接触了QT Widgets信号槽机制QPainterQTcpSocket这些模块。我自己的理解是QT本质上是一个基于C的跨平台开发框架它不仅能做GUI界面其实也封装了很多比如网略通信线程定时器文件操作这些功能开发效率会比纯Linux API高很多。项目里界面部分主要用QT Widgets和QPainter比如棋盘绘制棋子渲染高亮提示。网略通信部分我用了QTcpServer 和 QTcpSocket实现双人联机。双方落子后会通过socket同步棋盘状态。另外我觉得QT一个比较大的特点是信号槽机制它可以让不同模块之间解耦。比如用户点击棋子后界面会发送对应信号。棋局逻辑模块再处理这样代码结构会比较清晰。整体来说我觉得QT的优点主要是开发效率高跨平台模块比较完整事件驱动机制成熟。比较适合GUI和客户端程序开发5. 为什么选择Qt Widgets而不是QMLQPainter绘制大量棋子时如何保证性能选择Qt Widgets的原因项目是回合制棋类游戏不需要QML的动画特效和流畅滑动Widgets足够。Widgets的信号槽机制成熟与后端逻辑网络、定时器集成更简单。对C开发者更友好不需要学习QML的声明式语法。性能优化QPainter绘制64个棋子和棋盘即使每帧重绘也没有性能问题1ms。避免频繁重绘只在棋子移动、选中、提示等状态改变时调用update()不进行定时重绘。双缓冲Qt默认开启无需额外处理。预渲染棋子棋子图片加载一次后缓存在QPixmap中每次绘制直接drawPixmap。6. 如何实现双端棋局状态同步移动指令发送后对端如何校验合法性同步流程玩家A在本地移动棋子本地先校验规则通过isLegalMove()。如果合法A发送MOV指令给服务器或直接给对端。B收到指令后再次执行规则校验防止作弊或网络异常。B校验通过后更新本地棋盘刷新界面。B发送确认包ACK给A。防作弊对端必须完全重算合法性不信任客户端的任何移动。断线重连每10步保存一次完整棋盘状态到文件重连时发送最近保存的棋盘。7.请详细说明你如何实现王车易位规则好的王车易位是我在实现国际象棋规则时比较有挑战的一个点。我把它分成判断条件和执行动作两部分判断条件方面我考虑了四个维度第一王和对应的车都不能移动过所以我用布尔变量记录它们的状态。第二王和车之间的所有格子必须是空的不能有其他棋子阻挡。第三也是最关键的王的原始位置、经过的位置、以及目标位置都不能被对方棋子攻击也就是说王不能在被将军的情况下易位也不能穿过被攻击的格子。第四当前必须是该玩家的回合且游戏不能处于结束状态。条件都满足后执行易位分两步先移动王王翼易位时王从e列移动到g列后翼易位时移动到c列。再移动车车从角落移动到王的另一侧王翼易位的车从h列移到f列后翼易位的车从a列移到d列。移动完成后把原来的位置清空并更新王和车的移动标记。还有一个辅助函数是判断格子是否被攻击我会遍历对方所有棋子看有没有棋子能合法走到这个目标格子。这样就能保证王在易位过程中的安全性。”专业术语1. Drogon是一个基于 C 的 Web 后端框架。有点类似 Java 里的 SpringBoot。主要用来开发HTTP服务 RESTful API 这些后端接口。2.RESTful AP本质上就是一种比较规范的 HTTP接口设计风格。比如查数据新增数据修改数据删除数据3.Redis本质上是一个基于内存的高性能缓存数据库。因为数据放在内存里。所以比 MySQL 快很多。4.原子性可以理解成“一个操作要么全部成功要么全部失败”。中间不会被打断。比如Redis执行Lua脚本。就是原子的。5.Qt是一个 C 图形界面开发框架。可以用来开发桌面软件 GUI程序 网络程序 它封装了很多功能。开发效率比较高。6.Qt Widgets可以理解成Qt里的各种界面组件。比如按钮 窗口 输入框 这些。我的棋盘界面就是基于 Widgets 做的。7. QPainter是 Qt 的绘图工具。我项目里主要用它绘制棋盘和棋子。包括棋盘格子 棋子图片 高亮效果8. TCP Socket本质上就是网络通信接口。TCP负责可靠传输。Socket负责程序之间通信。项目里客户端和服务端通信。就是通过 TCP Socket。9. C/S架构客户端 / 服务端架构。比如国际象棋项目里Qt界面是客户端。服务器负责同步双方状态。10. QTcpServer / QTcpSocketQTcpServer 主要负责监听客户端连接。相当于服务器。QTcpSocket 负责真正收发数据。客户端和服务端通信都靠它。第一次面试总结1.c和c的区别c是面向过程的c是既能面向过程又能面向对象的多范式语言编程范式c纯粹的面向过程程序围绕“函数”和“数据结构”设计数据和处理数据的方式是分开的。c支持面向对象类封装继承多态泛型模版和函数式编程数据和操作数据的方法被封装在“类”中类和对象c没有“类”的概念。只能使用struct定义结构体且结构体中只能包含数据。c支持class和struct。支持访问控制继承多态c不支持函数重载c支持函数重载。c不支持继承和多态c支持2.虚函数表虚函数表vtable是c实现动态多态的核心机制。它是一个存储虚函数地址的数组每个含有虚函数的类都有一个全局唯一的虚函数表而每个该类对象内部都会有一个隐藏的指针vptr指向只个表。当通过基类指针或引用调用虚函数时编译器会生成这样的代码先取出对象的vptr再根据vptr找到虚函数表最后从表中取出对应位置的函数地址并调用。这就是动态绑定--函数地址在运行时才确定。3.对网络的了解理解比较深入的是TCP/IP协议栈和Linux环境下的网络编程从协议栈自下而上来看;网络层主要关注ip路由和分片实际工作中MTU问题比较常见传输层是我的重点TCP的可靠传输依赖序列号/确定应答/超时重传效率机制用滑动窗口做流量控制用慢启动/拥塞避免/快重传/快恢复做拥塞控制。应用层里面HTTP协议最常用比较了解管道化多路复用和头部压缩解决队头阻塞的原理4.多线程线程是 CPU 调度单位共享进程资源比进程更轻量。多线程能利用多核、提升响应性但会引入数据竞争、死锁等问题。常用工具包括互斥锁std::mutex、条件变量std::condition_variable、读写锁std::shared_mutex、原子变量std::atomic。RAII 风格的 lock_guard 能避免忘记释放锁。条件变量必须用 while 循环检查条件来防止虚假唤醒。5.线程同步线程同步是为了解决多个线程同时访问共享资源时出现的数据不一致问题。核心思路是让线程按一定的顺序访问共享资源避免竞态条件。常用的线程同步机制有这几种1. 互斥锁最基础的手段保证同一时刻只有一个线程进入临界区。2. 读写锁适合读多写少的场景比如配置表、缓存。多个读线程可以同时持有读锁但写锁是独占的。3. 条件变量用于线程间等待某个条件成立典型应用是生产者-消费者队列。必须配合 unique_lock 使用wait() 需要传入条件判断防止虚假唤醒。4. 信号量控制同时访问某资源的线程数量比如连接池限制最大并发数。5. 原子操作硬件级别的原子操作无锁、性能高。适合简单的计数、标志位
自我面试总结版(会持续更新)
目录C/C部分口述版1. 面向对象和面向过程区别2. 类和对象3. C三大特性封装继承多态4. C11新特性智能指针右值引用和移动语义5. 虚函数和虚表6. STL容器vectorlistmap数据结构与算法口述版顺序表和链表栈和队列快排和归并快排归并排序动态规划Linux网络编程口述版多进程和多线程进程间通信线程同步互斥锁 mutex读写锁 rwlock条件变量 condition信号量 semaphoresocket编程I/O复用selectpollepoll面试核心epoll两种触发模式高频select、poll、epoll区别面试总结版网络协议口述版TCP和UDP区别三次握手四次挥手TIME_WAITMySQL口述版B树索引事务隔离级别redo log 和 undo logRedis口述版Redis基本类型Redis持久化缓存问题缓存穿透缓存击穿缓存雪崩Git口述版分布式口述版ZookeeperKafka自我介绍项目一项目描述每个模块1. 用户注册登录怎么做的2. 商品发布/查询/更新/下架怎么做3. 秒杀功能怎么做4. Shell并发测试怎么做相关问题1. 为什么采用三层架构2. 为什么秒杀要先Redis再MySQL3.Lua脚本为什么原子4. 如果Redis扣成功了但MySQL事务失败怎么办5. 为什么删除缓存而不是更新缓存6.为什么用Lua。7.Redis缓存三剑客8.校园交易这个项目http怎么接收的项目二项目描述每个模块1. 棋盘渲染怎么做2. 国际象棋规则怎么实现3. TCP通信怎么做相关问题1. 为什么用TCP不用UDP2. 信号槽机制本质是什么3. 为什么Qt适合这个项目4.关于项目问了对qt的基本了解专业术语第一次面试总结1.c和c的区别2.虚函数表3.对网络的了解4.多线程5.线程同步C/C部分口述版1. 面向对象和面向过程区别“面向过程的话更偏向于步骤化开发比如先写登录函数、再写查询函数、再写下单函数核心是过程。面向对象的话更偏向于把数据和行为封装成对象比如用户类、订单类让对象自己完成自己的行为。我觉得面向对象最大的优点就是代码复用性高、维护方便比较适合大型项目开发。”2. 类和对象“类可以理解成一个模板对象就是类实例化出来的实体。比如我定义一个 User 类里面有用户名、密码这些成员变量还有登录、注册这些成员函数然后真正创建出来的 user1、user2 就是对象。”3. C三大特性封装“封装就是把数据和操作数据的方法放到一起同时隐藏内部实现。比如类里面会把成员变量设成 private对外提供接口访问这样安全性更高也降低了模块之间的耦合。”继承“继承主要是代码复用。比如我有一个 Animal 父类Dog 和 Cat 可以直接继承 Animal 的一些公共属性和方法这样不用重复写。”多态“多态就是同一个接口不同对象会有不同表现。我理解最核心的是虚函数。比如父类指针指向子类对象调用同一个函数时运行时会根据实际对象类型执行对应函数。底层其实是通过虚函数表和虚函数指针实现的。”4. C11新特性智能指针“智能指针主要是解决内存泄漏问题。像 unique_ptr 是独占所有权一个资源只能有一个指针管理。shared_ptr 是引用计数多个指针共享同一个资源。weak_ptr 主要是解决 shared_ptr 循环引用问题它不会增加引用计数。”右值引用和移动语义“右值引用我理解主要是为了支持移动语义。以前对象传递可能会发生深拷贝性能开销比较大。C11 引入移动语义之后可以直接把资源所有权转移过去而不是重新拷贝一份。比如 vector 很大的时候用 move 可以减少大量内存复制提高性能。”5. 虚函数和虚表“只要类里面有虚函数编译器就会给这个类生成虚函数表。对象里面会保存一个虚函数指针调用虚函数时会先通过 vptr 找到 vtable再找到真正函数地址。这个机制其实就是 C 多态底层实现原理。”6. STL容器vector“vector 底层是动态数组支持随机访问尾插效率比较高。但是扩容的时候会重新申请空间并拷贝数据。”list“list 底层是双向链表插入删除效率高但是不支持随机访问。”map“map 底层一般是红黑树所以插入、删除、查询复杂度都是 O(logn)并且 key 会自动排序。”数据结构与算法口述版顺序表和链表“顺序表底层是连续内存所以查询快但是插入删除可能需要移动元素。链表的话节点不连续插入删除比较快但是查找慢。”栈和队列“栈是先进后出像函数调用、括号匹配都会用到。队列是先进先出比如消息队列、广度优先搜索这些场景。”快排和归并快排“快速排序核心是 partition 分区思想选一个基准值把比它小的放左边比它大的放右边然后递归处理。平均时间复杂度是 O(nlogn)。归并排序“归并排序是典型分治思想。先不断拆分数组再把两个有序数组合并起来时间复杂度稳定 O(nlogn)而且是稳定排序。”动态规划“动态规划我理解核心就是一个问题可以拆成很多重复子问题。一般步骤是先定义 dp 数组含义再写状态转移方程然后初始化和确定遍历顺序。”Linux网络编程口述版多进程和多线程“多进程资源独立稳定性更高但是进程间通信成本高。多线程共享内存切换开销更小所以并发性能更好但是需要考虑线程安全问题。”进程间通信“常见的 IPC 我了解管道、共享内存、消息队列、信号量这些。管道共享内存共享内存就是映射一段能被其他进程所访问的内存这段内存由一个进程创建但是多个进程可以访问。共享内存是最快的IPC方式他是针对其他进程间通信方式运行效率低而专门设计的。消息队列消息队列是有消息的链表存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限制等缺点。套接字适用于不同机器间进程通信在本地可作为两个进程间通信的方式。信号用于通知接收进程某个时间已经发生比如按下ctrlC就是信号信号量信号量就是一个计数器可以用来控制多个进程对共享资源的访问。常用一种锁的机制实现进程、线程的临界区的同步及互斥访问。共享内存速度最快因为直接映射同一块内存。socket 不仅能本机通信还能跨网络通信。”线程同步互斥锁 mutex“互斥锁是最基础的线程同步方式。它的核心思想就是同一时间只能有一个线程访问共享资源。比如多个线程同时对 count 操作如果不加锁结果可能不正确。”互斥锁适合写操作比较多的场景实现简单但如果锁竞争严重会影响性能读写锁 rwlock“读写锁适合读多写少场景。它允许多个线程同时读写线程独占所以相比普通互斥锁并发性能更高。”举例“比如缓存系统大部分请求只是查询数据很少修改数据这时候用读写锁比较合适。”读写锁本质上是对读操作做了共享优化条件变量 condition“条件变量一般不是单独使用的而是配合互斥锁使用。它主要解决线程等待某个条件成立的问题。”信号量 semaphore“信号量本质是一个计数器用来控制资源数量socket编程“服务端一般流程是socket 创建套接字bind 绑定端口listen 监听accept 接收连接然后 read/write 通信。”I/O复用“我理解 I/O 复用本质上就是一个线程同时监听多个 socket。以前如果一个连接对应一个线程在高并发场景下线程数量会非常多性能开销很大。所以 Linux 提供了 select、poll、epoll 这些 I/O 多路复用机制让一个线程可以管理很多连接。”select“select 是最早的 I/O 复用模型。它的核心做法是把需要监听的 fd 放进 fd_set 集合里然后调用 select 阻塞等待。”缺点面试重点1fd数量有限“默认最大监听 1024 个 fd。”2每次都要拷贝集合“用户态和内核态之间会频繁拷贝 fd_set。”3需要遍历所有fd“即使只有一个 fd 就绪也需要遍历整个集合所以效率比较低。”“select 的问题主要是连接数越大遍历成本越高。”poll“poll 本质上和 select 差不多也是轮询。不过它解决了 select 的 fd 数量限制问题。”poll优点“没有 1024 限制。”poll缺点“虽然没有数量限制但本质还是需要遍历所有 fd所以高并发性能还是一般。”epoll面试核心“epoll 是 Linux 下高性能 I/O 复用模型也是现在高并发服务器最常用的方案。”epoll 为什么快高频1事件通知机制“select 和 poll 每次都要遍历所有 fd。而 epoll 是哪个 fd 就绪内核直接通知用户态。不用全量遍历。”2减少数据拷贝“epoll 使用 mmap 共享内存减少了用户态和内核态之间的数据拷贝。”3底层数据结构优化“我了解 epoll 底层红黑树管理所有 fd就绪链表保存活跃事件所以查找和插入效率都比较高。”epoll两种触发模式高频1. LT 水平触发默认“只要缓冲区还有数据就会一直通知。”特点简单不容易漏数据2. ET 边缘触发“只有状态变化时才通知一次。”特点效率更高必须一次性把数据读完一般要配合非阻塞IOselect、poll、epoll区别面试总结版对比selectpollepollfd数量限制1024无限制无限制数据结构位图数组红黑树是否遍历全部fd是是否性能较低一般高适合场景小并发中等并发高并发最后一段非常适合面试结尾“我理解 select、poll、epoll 本质都是 I/O 多路复用。区别主要在于select 和 poll 都需要遍历所有 fd而 epoll 采用事件驱动机制只处理活跃连接所以在高并发场景下性能更好。现在像 Redis、Nginx 这些高性能服务器底层都大量使用 epoll。”网络协议口述版TCP和UDP区别“TCP 是面向连接的可靠传输有流量控制和拥塞控制。UDP 是无连接的速度快但是不保证可靠性。像视频直播、语音通信一般会用 UDP。”三次握手“第一次客户端发送 SYN。第二次服务端返回 SYNACK。第三次客户端再回复 ACK。主要目的就是确认双方收发能力正常并建立连接。”四次挥手“因为 TCP 是全双工通信所以双方都需要单独关闭连接因此是四次挥手。”TIME_WAIT“TIME_WAIT 我理解主要有两个作用一个是防止最后 ACK 丢失。另一个是防止旧连接的数据影响新的连接。”MySQL口述版B树索引“MySQL InnoDB 默认使用 B树索引。它比较适合磁盘存储减少磁盘 IO而且范围查询效率也很高。”事务隔离级别“我了解四种隔离级别读未提交、读已提交、可重复读、串行化。MySQL 默认是可重复读。”redo log 和 undo log“redo log 主要保证事务持久性。undo log 主要用于事务回滚还有 MVCC。”Redis口述版Redis基本类型“Redis 我比较常用的是 string、hash、list、set、zset 这几种。”Redis持久化“RDB 是快照方式。AOF 是记录写命令一般数据安全性更高。”缓存问题缓存穿透“缓存穿透就是查数据库里根本不存在的数据。解决方法一般是布隆过滤器或者缓存空对象。”缓存击穿“缓存击穿是热点 key 突然失效大量请求直接打到数据库。一般会加互斥锁或者热点数据不过期。”缓存雪崩“缓存雪崩是大量 key 同时过期。一般会给过期时间加随机值或者做 Redis 集群。”Git口述版“Git 我平时主要用于团队协作开发。常用操作像git clonegit addgit commitgit pushgit pull还有分支管理比如git branchgit checkoutgit merge如果代码冲突的话我一般会先解决冲突再重新提交。”分布式口述版Zookeeper“Zookeeper 我了解它主要是做分布式协调服务。比如注册中心分布式锁配置管理它底层类似树形目录结构。”Kafka“Kafka 是消息队列适合高并发、高吞吐场景。像异步削峰、日志收集这些都会用到。”自我介绍面试官您好我叫王俞涵目前就读于西安工业大学软件工程专业是一名大三生。在校期间我主要学习 C/C 后端开发与 Linux 服务端开发方向系统学习了计算机网络、操作系统、数据结构、TCP/IP、Linux 网络编程等核心课程。平时主要使用 C、Linux、MySQL、Redis 等技术完成项目开发对 socket 网络编程、多线程、高并发、缓存设计这些内容也有一定的掌握。项目方面我独立完成过一个基于 C Drogon 框架开发的校园闲置交易平台后端系统。项目中实现了用户登录、商品发布、订单创建以及秒杀模块并且引入 Redis 做热点数据缓存通过 Lua 脚本和 MySQL 事务解决秒杀超卖问题对接口性能和并发一致性做了一些优化。另外我还开发过一个基于 Qt 和 TCP Socket 的在线双人国际象棋系统主要负责网络通信、协议设计以及棋局同步功能实现了双方实时对战和断线状态处理。平时也会借助 AI 工具提高开发效率比如使用 Cursor、ChatGPT 辅助代码设计和问题排查。项目一项目描述我做的是一个基于 C Drogon 框架开发的校园闲置交易平台后端系统。项目主要是模拟校园二手交易场景提供用户注册登录商品发布商品查询订单创建秒杀下单这些核心功能。整体技术栈主要用了CDrogonMySQLRedisLinux 项目整体采用的是Controller / Service / DAO三层架构。这样做的目的是把接口处理业务逻辑数据访问解耦。后期维护和扩展会更方便。比如Controller层主要负责接收HTTP请求参数校验返回JSON 。Service层负责秒杀逻辑订单逻辑 缓存逻辑。DAO层负责和MySQL交互。项目里我觉得最核心的部分是秒杀高并发场景下的库存一致性问题。因为如果多个用户同时下单。很容易出现库存已经没了但订单还创建成功也就是超卖。所以这里我采用了Redis Lua脚本 MySQL事务的方案。具体流程大概是用户请求秒杀后先在Redis里通过Lua脚本进行库存原子预减。Lua脚本会判断库存是否大于0 扣减库存。因为Redis执行Lua脚本是原子的所以不会出现并发竞争问题。只有Redis扣减成功后才会继续执行数据库事务。数据库事务里同时完成扣减MySQL库存创建订单。这样保证库存和订单数据一致。最后如果成功再删除商品缓存避免缓存脏数据。另外项目里还用了Cache Aside 模式。因为商品详情属于高频查询场景。如果每次都访问MySQL数据库压力会比较大。所以优先查Redis。Redis没有再查MySQL并回写缓存。商品更新或下架后主动删除缓存保证数据一致性。最后我还通过Shell脚本模拟并发秒杀。验证库存是5时。最终只有5个订单成功创建。保证不会超卖。每个模块1.用户注册登录怎么做的用户模块这块我主要是通过 Drogon 提供的 HTTP 接口实现的。比如注册的时候前端会传用户名 密码 这些信息。Controller 接收到请求后会先做参数校验。然后进入 Service 层。Service 里会先查数据库判断用户名是否已经存在。如果不存在再把用户信息写入 MySQL。登录逻辑的话会先根据用户名查数据库。然后校验密码是否正确。验证成功之后返回对应结果。因为项目主要还是偏后端逻辑所以认证这块我做的是比较基础的账号密码登录2.商品发布/查询/更新/下架怎么做商品发布本质上就是新增商品数据。前端传商品信息后。Controller接收请求。然后Service层处理业务。最后DAO层写入MySQL。商品表里我设计了商品名称 价格 库存 status状态 这些字段。商品查询这里我加了 Redis 缓存。因为商品详情属于高频读场景。如果每次都查 MySQL。数据库压力会比较大。所以查询的时候会先查 Redis。如果 Redis 里有。直接返回。如果没有再查 MySQL。然后把结果回写 Redis。这个其实就是Cache Aside 模式。商品更新的时候我会先更新 MySQL。更新成功后删除 Redis 缓存。因为如果直接更新缓存。可能会出现缓存更新失败。或者并发覆盖问题。所以我这里采用的是删除缓存。让后续查询自动重建缓存。商品下架我做的是逻辑下架。不是直接删除数据库数据。因为真实业务里通常不会真的删数据。所以我在商品表设计了status字段。下架时只是把状态改成“已下架”。查询商品时只查询status正常的数据。3.秒杀功能怎么做秒杀功能主要是解决高并发下的超卖问题。因为如果多个用户同时下单。可能都会读到库存还有。最后导致超卖。所以我这里设计的是先 Redis。后 MySQL。具体流程是用户请求过来之后。先在 Redis 查询库存。然后通过 Lua 脚本完成判断库存 扣减库存 这两个操作。因为 Lua 在 Redis 里执行是原子的。所以不会出现并发问题。如果 Redis 扣减失败。说明库存没了。直接返回秒杀失败。如果 Redis 扣减成功。才继续执行 MySQL事务。事务里同时完成数据库扣库存 创建订单 这样保证订单和库存一致。最后事务成功后会删除商品缓存。避免缓存脏数据。4. Shell并发测试怎么做写了 Shell 脚本模拟并发请求。比如库存设置为5。然后同时发很多请求。最后我会去数据库验证是不是最终只有5个订单成功创建。这个主要是为了验证高并发下不会超卖。相关问题1.为什么采用三层架构因为如果业务逻辑直接写在Controller里。后期项目一大代码会非常混乱。所以我拆分成ControllerServiceDAO。这样职责更清晰。后期比如数据库替换。或者业务逻辑修改。影响会更小。2.为什么秒杀要先Redis再MySQL因为Redis性能远高于MySQL。高并发下如果所有请求直接打数据库。数据库压力会很大。所以Redis先抗住高并发请求。MySQL负责最终数据落库。3.Lua脚本为什么原子Redis执行Lua脚本时会把整个脚本作为一个整体执行。中间不会插入其他命令。所以库存判断和扣减可以一次完成。避免并发问题。4.如果Redis扣成功了但MySQL事务失败怎么办目前这个项目里如果MySQL事务失败。我会回滚数据库事务。同时补偿Redis库存。把库存加回去。避免Redis和MySQL数据不一致。5.为什么删除缓存而不是更新缓存因为删除缓存实现更简单。一致性更好。如果直接更新缓存可能更新失败并发覆盖 所以实际项目里很多都会采用更新数据库后删除缓存。让后续查询自动重建缓存。6.为什么用Lua。我项目里 Lua 脚本主要是为了保证库存判断和库存扣减的原子性。因为如果不用 Lua。而是先GET库存再decr扣库存中间可能会被其他请求插入。比如两个线程同时读到库存还有1。最后都扣成功。这样就超卖了。所以我把判断库存 扣减库存 写到一个 Lua 脚本里。 Redis 一次执行完成。因为 Redis 执行 Lua 脚本时整个脚本不会被其他命令打断。所以天然保证原子性。7.Redis缓存三剑客1.缓存穿透查不存在的数据。每次都打数据库。解决布隆过滤器缓存空值2.缓存击穿热点Key突然过期。大量请求同时打数据库。解决互斥锁热点永不过期3.缓存雪崩大量Key同时过期。解决随机过期时间Redis集群8.校园交易这个项目http怎么接收的我这个项目里 HTTP 请求主要是通过 Drogon 提供的 Controller 路由机制接收的。比如用户请求GET /product/1这种接口请求到达服务端之后Drogon会根据配置好的路由自动分发到对应Controller的处理函数。比如商品查询接口。我会在 Controller 里定义对应的 GET 接口。然后 Drogon 收到 HTTP 请求后。会自动调用对应函数。函数里再获取URL参数JSON数据请求头这些信息。处理完业务逻辑后。再通过 JSON 格式返回结果。9. 为什么选择Drogon而不是其他C Web框架Drogon的哪些特性对秒杀场景特别有帮助Drogon是高性能异步非阻塞框架基于proactor模型epoll 线程池类似Golang的netpoller适合IO密集型如大量数据库/Redis请求。对秒杀场景特别有用的特性协程支持可以用同步风格写异步代码避免回调地狱方便在秒杀接口中串行调用“Redis预减 - MySQL事务 - 返回结果”。内置ORM自动连接池、事务管理减少手写SQL的出错风险。高性能HTTP解析基于http_parser每秒可处理数万请求。插件化过滤器可以轻松实现限流、鉴权等中间件。10.如果Redis崩溃了库存状态会丢失吗如果Redis未开启RDB/AOF持久化重启后库存全丢。解决方案使用Redis Cluster 持久化或秒杀活动前将初始库存从MySQL加载到Redis并定期备份。更严格的做法Redis只做预扣最终以MySQL为准。11. Redis预减后MySQL还扣库存吗如何保证一致性会扣。Redis预减是为了快速过滤超卖请求真正扣减仍在MySQL事务中完成UPDATE products SET stockstock-1 WHERE id? AND stock0。一致性保证先Redis预减成功再进入MySQL事务扣减。如果MySQL事务失败比如网络异常、行锁超时需要回滚Redis重新INCR库存。可以用Lua脚本回滚或发送延迟消息补偿。最终以MySQL为准通过定时任务对账MySQL剩余库存 Redis剩余库存不匹配时修复。12. 如果超卖排查步骤检查MySQLproducts.stock最终值是否为负数。检查Redis Lua脚本是否没有原子执行比如用了多条Redis命令而非eval。检查MySQL隔离级别默认Repeatable Read可能引起幻读是否用了SELECT ... FOR UPDATE检查代码中是否有并发路径下两次扣库存比如业务逻辑重复调用。查看Drogon日志是否有事务回滚但忘记回滚Redis。项目二项目描述这个项目是基于 C 和 Qt5 开发的双人在线国际象棋系统。整体采用TCP Socket 的 C/S 架构。主要实现了双人联机 棋局同步 图形界面渲染 回合计时 将死判定等功能。技术上主要用了Qt Widgets QPainter QTcpSocket 信号槽机制 项目里我主要负责棋盘逻辑 网络通信 状态同步 这部分棋盘部分我采用8x8数组建模。因为国际象棋本身就是固定棋盘。数组访问效率高。实现起来也比较方便网络通信部分采用TCP Socket。因为棋局同步要求数据可靠 顺序正确 如果丢包或者乱序。双方棋局状态就会不一致。所以更适合TCP。另外为了同步双方状态。我设计了一套简单协议。比如MOVEATUPG分别代表移动 吃子 升变 客户端收到消息后更新本地棋盘状态。项目里我觉得比较难的地方是完整棋规实现。比如王车易位 将军检测 升变 和棋判定 这些规则状态比较复杂。需要不断校验当前棋局是否合法。每个模块1.棋盘渲染怎么做棋盘界面这部分。我主要是通过 Qt Widgets 和 QPainter 实现的。因为Qt本身比较适合做桌面GUI。棋盘我用了8x8数组建模。数组每个位置对应一个棋子状态。然后通过QPainter动态绘制棋盘和棋子。2.国际象棋规则怎么实现规则实现这部分其实是项目里比较复杂的地方。因为不同棋子的移动规则都不一样。比如兵 马 象 车 走法都不同。所以我会针对每种棋子单独判断当前移动是否合法。另外还实现了升变 王车易位 将军检测 这些特殊规则。尤其将军判断会复杂一点。因为需要判断当前移动后。自己的王是否暴露在攻击范围内。3. TCP通信怎么做网络通信部分我采用的是QTcpServer 和 QTcpSocket。整体是一个C/S架构。服务端主要负责转发消息 同步双方状态 客户端负责棋盘显示 用户操作 双方通信时我设计了一套简单协议。比如MOVEATUPG这些指令。分别代表移动 吃子 升变 客户端落子后。会把棋子位置变化发送给服务端。服务端再同步给另一方。这样保证双方棋盘状态一致。相关问题1.为什么用TCP不用UDP因为棋局同步要求可靠 有序 如果UDP丢包。或者乱序。会导致双方棋局状态错误。所以TCP更适合2.信号槽机制本质是什么Qt信号槽本质上是一种观察者模式。对象之间通过signal和slot进行解耦通信。Qt内部通过元对象系统MOC实现。3.为什么Qt适合这个项目因为Qt跨平台 UI能力强 事件驱动成熟同时Qt封装了Socket 定时器 绘图 开发效率比较高。4.关于项目问了对qt的基本了解我对QT的了解主要是结合项目学习的。项目过程中我主要接触了QT Widgets信号槽机制QPainterQTcpSocket这些模块。我自己的理解是QT本质上是一个基于C的跨平台开发框架它不仅能做GUI界面其实也封装了很多比如网略通信线程定时器文件操作这些功能开发效率会比纯Linux API高很多。项目里界面部分主要用QT Widgets和QPainter比如棋盘绘制棋子渲染高亮提示。网略通信部分我用了QTcpServer 和 QTcpSocket实现双人联机。双方落子后会通过socket同步棋盘状态。另外我觉得QT一个比较大的特点是信号槽机制它可以让不同模块之间解耦。比如用户点击棋子后界面会发送对应信号。棋局逻辑模块再处理这样代码结构会比较清晰。整体来说我觉得QT的优点主要是开发效率高跨平台模块比较完整事件驱动机制成熟。比较适合GUI和客户端程序开发5. 为什么选择Qt Widgets而不是QMLQPainter绘制大量棋子时如何保证性能选择Qt Widgets的原因项目是回合制棋类游戏不需要QML的动画特效和流畅滑动Widgets足够。Widgets的信号槽机制成熟与后端逻辑网络、定时器集成更简单。对C开发者更友好不需要学习QML的声明式语法。性能优化QPainter绘制64个棋子和棋盘即使每帧重绘也没有性能问题1ms。避免频繁重绘只在棋子移动、选中、提示等状态改变时调用update()不进行定时重绘。双缓冲Qt默认开启无需额外处理。预渲染棋子棋子图片加载一次后缓存在QPixmap中每次绘制直接drawPixmap。6. 如何实现双端棋局状态同步移动指令发送后对端如何校验合法性同步流程玩家A在本地移动棋子本地先校验规则通过isLegalMove()。如果合法A发送MOV指令给服务器或直接给对端。B收到指令后再次执行规则校验防止作弊或网络异常。B校验通过后更新本地棋盘刷新界面。B发送确认包ACK给A。防作弊对端必须完全重算合法性不信任客户端的任何移动。断线重连每10步保存一次完整棋盘状态到文件重连时发送最近保存的棋盘。7.请详细说明你如何实现王车易位规则好的王车易位是我在实现国际象棋规则时比较有挑战的一个点。我把它分成判断条件和执行动作两部分判断条件方面我考虑了四个维度第一王和对应的车都不能移动过所以我用布尔变量记录它们的状态。第二王和车之间的所有格子必须是空的不能有其他棋子阻挡。第三也是最关键的王的原始位置、经过的位置、以及目标位置都不能被对方棋子攻击也就是说王不能在被将军的情况下易位也不能穿过被攻击的格子。第四当前必须是该玩家的回合且游戏不能处于结束状态。条件都满足后执行易位分两步先移动王王翼易位时王从e列移动到g列后翼易位时移动到c列。再移动车车从角落移动到王的另一侧王翼易位的车从h列移到f列后翼易位的车从a列移到d列。移动完成后把原来的位置清空并更新王和车的移动标记。还有一个辅助函数是判断格子是否被攻击我会遍历对方所有棋子看有没有棋子能合法走到这个目标格子。这样就能保证王在易位过程中的安全性。”专业术语1. Drogon是一个基于 C 的 Web 后端框架。有点类似 Java 里的 SpringBoot。主要用来开发HTTP服务 RESTful API 这些后端接口。2.RESTful AP本质上就是一种比较规范的 HTTP接口设计风格。比如查数据新增数据修改数据删除数据3.Redis本质上是一个基于内存的高性能缓存数据库。因为数据放在内存里。所以比 MySQL 快很多。4.原子性可以理解成“一个操作要么全部成功要么全部失败”。中间不会被打断。比如Redis执行Lua脚本。就是原子的。5.Qt是一个 C 图形界面开发框架。可以用来开发桌面软件 GUI程序 网络程序 它封装了很多功能。开发效率比较高。6.Qt Widgets可以理解成Qt里的各种界面组件。比如按钮 窗口 输入框 这些。我的棋盘界面就是基于 Widgets 做的。7. QPainter是 Qt 的绘图工具。我项目里主要用它绘制棋盘和棋子。包括棋盘格子 棋子图片 高亮效果8. TCP Socket本质上就是网络通信接口。TCP负责可靠传输。Socket负责程序之间通信。项目里客户端和服务端通信。就是通过 TCP Socket。9. C/S架构客户端 / 服务端架构。比如国际象棋项目里Qt界面是客户端。服务器负责同步双方状态。10. QTcpServer / QTcpSocketQTcpServer 主要负责监听客户端连接。相当于服务器。QTcpSocket 负责真正收发数据。客户端和服务端通信都靠它。第一次面试总结1.c和c的区别c是面向过程的c是既能面向过程又能面向对象的多范式语言编程范式c纯粹的面向过程程序围绕“函数”和“数据结构”设计数据和处理数据的方式是分开的。c支持面向对象类封装继承多态泛型模版和函数式编程数据和操作数据的方法被封装在“类”中类和对象c没有“类”的概念。只能使用struct定义结构体且结构体中只能包含数据。c支持class和struct。支持访问控制继承多态c不支持函数重载c支持函数重载。c不支持继承和多态c支持2.虚函数表虚函数表vtable是c实现动态多态的核心机制。它是一个存储虚函数地址的数组每个含有虚函数的类都有一个全局唯一的虚函数表而每个该类对象内部都会有一个隐藏的指针vptr指向只个表。当通过基类指针或引用调用虚函数时编译器会生成这样的代码先取出对象的vptr再根据vptr找到虚函数表最后从表中取出对应位置的函数地址并调用。这就是动态绑定--函数地址在运行时才确定。3.对网络的了解理解比较深入的是TCP/IP协议栈和Linux环境下的网络编程从协议栈自下而上来看;网络层主要关注ip路由和分片实际工作中MTU问题比较常见传输层是我的重点TCP的可靠传输依赖序列号/确定应答/超时重传效率机制用滑动窗口做流量控制用慢启动/拥塞避免/快重传/快恢复做拥塞控制。应用层里面HTTP协议最常用比较了解管道化多路复用和头部压缩解决队头阻塞的原理4.多线程线程是 CPU 调度单位共享进程资源比进程更轻量。多线程能利用多核、提升响应性但会引入数据竞争、死锁等问题。常用工具包括互斥锁std::mutex、条件变量std::condition_variable、读写锁std::shared_mutex、原子变量std::atomic。RAII 风格的 lock_guard 能避免忘记释放锁。条件变量必须用 while 循环检查条件来防止虚假唤醒。5.线程同步线程同步是为了解决多个线程同时访问共享资源时出现的数据不一致问题。核心思路是让线程按一定的顺序访问共享资源避免竞态条件。常用的线程同步机制有这几种1. 互斥锁最基础的手段保证同一时刻只有一个线程进入临界区。2. 读写锁适合读多写少的场景比如配置表、缓存。多个读线程可以同时持有读锁但写锁是独占的。3. 条件变量用于线程间等待某个条件成立典型应用是生产者-消费者队列。必须配合 unique_lock 使用wait() 需要传入条件判断防止虚假唤醒。4. 信号量控制同时访问某资源的线程数量比如连接池限制最大并发数。5. 原子操作硬件级别的原子操作无锁、性能高。适合简单的计数、标志位