2024秋招服务器开发笔试题复盘:从C++到高并发设计

发布时间:2026/8/29 23:36:06
2024秋招服务器开发笔试题复盘:从C++到高并发设计 1. 笔试全景畅游2024秋招服务器开发岗到底考什么每年八月底到九月初都是秋招最焦灼的时候。我今年投了畅游的服务器开发工程师岗笔试做完出来第一感觉就是这公司是真的想招能直接上手写业务的人不是在考场里跟你玩脑筋急转弯。整张卷子覆盖了C基础、网络编程、操作系统、数据库、并发场景设计还有一个很实在的系统设计题题量不算夸张但每一道都需要你真正写过代码、调过bug才能答得顺畅。先说说整体感受方便后来的学弟学妹预估复习重心。畅游这套2024秋招笔试题目大致可以分成四个模块第一部分是C语言特性与内存模型大概占了五到六道选择题考察点多在RAII、智能指针、虚函数表、移动语义这些地方难度中等偏上属于那种你只看过八股文能对一半但真动手写过项目才能全对的水平。第二部分是计算机网络和操作系统考了TCP状态迁移、select/poll/epoll的差异、进程线程协程的区别、用户态内核态切换等这些是服务器开发的标配地盘没什么冷门偏题但题干喜欢套一个游戏业务场景比如“聊天服务器出现大量TIME_WAIT怎么处理”“排行榜服务为什么不能用多线程”这就需要你灵活迁移知识点。第三部分是编程题两道算法加一道综合设计。算法题一道是链表相关的常规题另一道是带有限制条件的场景模拟题都不需要背复杂模板但很考察边界处理能力。综合设计题反而是整张卷子里最有区分度的题目让你设计一个游戏房间管理服务从协议设计、存储选型、并发模型到异常处理都要写清楚基本就是直接把你丢到多人对战游戏的服务器开发场景里。最后还有一道开放题问的是“当线上服务器CPU持续飙高你会怎么排查”这题没有标准答案但回答的逻辑链条和实操细节很能反映一个人的真实项目经验。整体来说这套笔试题出得很务实既没有纯粹的脑筋急转弯也没有故意刁难人的偏题怪题更看重候选人对服务器开发核心链路的理解深度。2. 计网与OS考点游戏服务器开发的地基2.1 TCP状态机与TIME_WAIT高频变形题服务器开发笔试里TCP三次握手四次挥手几乎是必考题畅游也不例外。选择题里给了四个TCP状态迁移的图让你挑出哪个是错误的四个选项长得特别像第一次见这种题的人很容易被绕进去。如果复习时只看过“SYN_SENT、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、TIME_WAIT、CLOSED”这些状态名字而没有真正画过状态迁移图做起来会很费劲。笔试里真正有区分度的是这道场景题一个游戏聊天服务器高峰期出现了大量TIME_WAIT连接你该如何处理。这道题考察的是TCP主动关闭连接一方进入TIME_WAIT、要等2MSL才能彻底释放这个知识点。在游戏服务器开发里短连接场景下如果服务器主动断开连接TIME_WAIT会大量堆积导致端口被占用、新连接无法建立。常规的解决思路是让客户端主动断开连接把TIME_WAIT留在客户端一侧或者开启SO_REUSEADDR允许端口复用调小MSL时间在某些场景下也能缓解但生产环境不建议动这个参数。我在答题时把内核参数net.ipv4.tcp_tw_reuse和tcp_timestamps的搭配也写了进去这算是一个加分项因为很多人知道tcp_tw_reuse但不知道它必须配合TCP时间戳选项才能生效。如果是长连接服务器大量TIME_WAIT还可能是连接管理没做好比如心跳超时时间设置得过短导致频繁重建连接。2.2 网络模型选型从select到epoll的进化逻辑选择题里有一道我觉得出得特别好给出select、poll、epoll三个网络模型的描述让你判断哪个说法是错误的。四个选项分别涉及文件描述符上限、触发模式、内核态用户态数据拷贝、以及时间复杂度基本把三种模型的底层机制都过了一遍。如果是新手我建议不要只看结论要理解它们各自的实现思路。select的问题在于它用固定长度的位图来管理文件描述符默认上限是1024而且每次调用都要把整个fd_set从用户态拷贝到内核态内核再线性扫描找出就绪的事件所以数量一大性能就崩。poll改用pollfd数组替代了位图突破了1024的上限但每次调用依然要全量拷贝全量扫描用户态内核态切换的开销并没有减少。epoll就聪明得多它在内核中维护了一个事件表通过epoll_ctl注册关注的事件避免了每次调用时的全量拷贝就绪的事件通过回调机制挂到就绪链表上用户用epoll_wait取走时内核只拷贝真正就绪的fd复杂度从O(n)降到了O(就绪数)。游戏服务器跟普通Web服务器最大的不同是连接状态变化频繁玩家上线、下线、切场景都会导致连接断开如果某个写操作失败还需要临时注册EPOLLOUT事件这些动态修改需求是select和poll很难高效处理的。所以现在市面上自研的MMO游戏服务器网络层基本都跑在epoll上配合线程池做读写分离。笔试时遇到类似的网络模型题不光要答出区别最好再补充一句“游戏场景下连接数量大且动态变化频繁epoll的事件驱动机制更合适”阅卷人会知道你是有真实场景认知的。2.3 进程、线程、协程在游戏服务器里的分工经典问题“进程和线程的区别”畅游也考了但题干包装得比较巧妙在一个游戏服务器里一个进程多个线程和多个进程单线程两种架构哪种更适合做房间对战服务。这题没有绝对正确答案考察的是你对业务模型的理解。我的思路是这样的多进程架构的隔离性更强一个进程崩溃不会拖垮整个服务但进程间通信开销大内存无法天然共享做战斗同步这种高频低延迟场景会吃力多线程架构共享进程内内存通信成本低但一个线程的野指针或内存踩踏可能直接带崩整个进程。对于畅游这种以多人对战游戏为主营业务的公司房间对战服务往往追求低延迟和高吞吐多线程配合无锁队列或细粒度锁是更务实的方案。协程这个选项是作为干扰项出现的题干里说“协程能替代线程解决并发问题”这个说法其实有坑。协程是用户态调度的并发原语切换开销极小但它本质上是单线程内的并发如果代码块里有阻塞式系统调用整个线程还是会被卡住。正确的理解是协程解决了高并发下的CPU上下文切换开销问题但没解决IO阻塞问题所以在游戏服务器里经常是“协程异步IO”组合使用或者用协程处理非阻塞逻辑把日志落盘、数据库访问这类阻塞操作放到独立线程池里。3. 编程题实战从C内存到高并发设计3.1 虚函数、智能指针与移动语义C组的送分与送命题畅游这套笔试题的选择题部分C相关的题目是重头戏大概占了三分之一。先说一道我印象深刻的送分题一个有虚函数的类sizeof等于多少。这个考点很基础只要知道虚表指针是8字节64位系统、内存对齐规则就能算出来。但题目加了点花样类里有一个int和一个char很多人会忽略内存对齐直接把3个成员大小加起来正确答案应该是16字节而不是13字节。这种题就是那种你做的时候觉得简单出分对答案才傻眼的类型。智能指针考了两道一道问shared_ptr的循环引用怎么解决另一道问weak_ptr能不能直接解引用访问对象。循环引用的解法是用weak_ptr打破环这个几乎人人都会背但第二道题就有点陷阱了weak_ptr不能直接解引用需要先调用lock()升级为shared_ptr才能访问因为weak_ptr本身不拥有对象直接访问会面临对象已经被释放的野指针风险。如果你平时只写Java不写C这类题很容易凭感觉选错。移动语义那道题我记不太清原题了只记得给了一段代码问调用过程触发了多少次拷贝构造、多少次移动构造选项有“2次拷贝0次移动”、“1次拷贝1次移动”这些组合。这道题的核心在于搞清楚函数返回值优化和std::move的真正作用。C11之后vector等容器插入临时对象时会优先调用移动构造但因为返回值优化很多情况下编译器会直接构造到目标位置一次拷贝都不用。我建议复习vector的push_back和emplace_back区别时把源码层面实现一起看掉这种题本质上是考这些细节。3.2 算法题链表操作与有状态模拟编程题第一道是单链表相关的常规题给一个链表每K个节点一组反转不足K个保持原样。这题LeetCode上就有原题难点在于K个节点反转后这一组反转出来的头尾节点要正确对接上原来的链表。我在笔试时用了递归实现因为递归天然处理了每一组的独立反转逻辑代码短不容易出错但边界条件要对比如链表长度为K的倍数时最后一组反转后末尾要置空。这道题虽然简单但我建议笔试时不要上来就写代码先在草稿纸上画一下指针变化的图确认prev、next、start三个指针的移动顺序再写代码。画图这个过程能帮你避免九成以上“运行时指针指向错误”的低级bug。第二道算法题比较有意思是一个带状态机的模拟题一个玩家可以处于空闲、匹配中、战斗中三种状态给定一系列操作序列包括匹配请求、匹配成功、战斗开始、战斗结束要求判断这些操作序列是否合法并输出每个操作后的玩家状态。核心考点是状态机合法性校验比如“匹配中”状态不能再次发起匹配“战斗中”状态不能收到匹配成功回调。用状态转移矩阵做会比较清晰非法状态转移直接返回错误码。这类题考的是逻辑严谨性和状态管理的习惯其实也在暗示游戏服务器里玩家状态管理有多重要。3.3 综合设计题游戏房间管理服务的完整设计这题值得展开讲讲因为它是整套笔试题里最像“真实工作内容”的一道。题目给了个背景你要设计一个支持多人对战的房间管理服务玩家可以创建房间、加入房间、退出房间、开始游戏要求你画出核心数据结构、设计协议、说明并发控制方案还要考虑异常场景。我当时的回答框架是这样的。存储层用Redis保存房间元数据和玩家列表因为房间信息需要跨服务器共享而且创建房间、加入房间的QPS很高用Redis能扛住压力。房间内每个玩家的具体坐标位置、血量等高频变化的数据不往Redis写只保留在进程内存里由战斗服自行维护。这里有个设计要点房间状态机要定义清楚WAITING等待中、PLAYING游戏中、CLOSED已关闭只有WAITING状态下的房间允许玩家加入开始游戏时状态机从WAITING迁移到PLAYING此时所有非房主玩家会被锁定不能再操作。协议设计上用自定义的二进制协议替代JSON头部固定12字节包含magic number、消息长度、消息类型、序列号消息体用protobuf编码。为什么不用JSON因为房间内玩家频繁上报操作指令JSON的解析耗时和带宽占用都太高了。序列号这个字段很关键客户端发起的创建房间请求和服务器回包通过序列号对应避免乱序导致逻辑错误。并发控制是这道题的另一个核心考点。同一个房间的操作要保证串行设计上可以采用分房间加锁每个房间有一把独立的锁而不是全局一把大锁。玩家加入房间时先获取该房间的写锁修改玩家列表后释放开始游戏时需要保证所有玩家数据一致要加一个全局的游戏开始协调锁。更进阶的方案是按房间号做一致性哈希把不同房间的请求分发到不同的处理线程上这样房间之间天然并行同一个房间的请求永远落在同一个线程里不需要加锁。异常场景至少要覆盖这几种房主中途退出时要把房主权限转移给其他玩家或者直接解散房间并通知所有成员玩家加入房间时房间已满要返回明确错误码一条消息重复发送比如玩家断线重连后重新发送之前未确认的操作指令需要根据序列号做去重数据库宕机后房间数据要从本地镜像和Redis备份中恢复。这些异常场景往往是设计的加分项能写出三点以上的候选人说明真的有线上系统意识。4. 典型笔试题复盘与易错点排查4.1 一道网络编程题的错误示范与修正笔试里有一道很经典的编程题用epoll实现一个简易的TCP echo server收到的数据原样返回给客户端要求支持多连接并发。这题看似简单但恰恰是区分“背过epoll接口”和“真的写过网络编程”的分水岭。错误示范一循环里调用epoll_wait收到事件后调用recv读取数据但如果客户端一次发送的数据超过了缓冲区recv只读了一部分就开始echo客户端收到不完整数据后可能直接协议错乱。正确做法是把每个连接关联一个应用层接收缓冲区和发送缓冲区recv到的数据先追加到接收缓冲区然后从接收缓冲区取完整协议帧再把响应写入发送缓冲区下次epoll_wait返回该连接可写时再真正发送出去。错误示范二在accept的时候没有处理EMFILE错误。文件描述符耗尽时accept会返回EMFILE如果忽略这个错误后续新连接全部无法建立但服务本身还显示活着这是线上最容易踩的坑。正确做法是提前预留一个空闲fd检测到EMFILE时先关掉预留fdaccept成功后再立刻打开新的fd作为预留这样既能接受新连接又能保持系统有富余fd。错误示范三对非阻塞socket的recv返回值处理不当。recv返回-1不一定是出错还要看errno是不是EAGAIN或EWOULDBLOCK如果是这俩说明当前没有数据可读应该继续使用epoll等待下一次事件而不是当作异常处理。如果自作主张在EAGAIN时直接关闭连接那么客户端只要稍微慢几毫秒发包连接就被服务端莫名其妙地关了。4.2 选择题中最容易失分的隐蔽考察点畅游这套卷子有四五道选择题我做完对答案发现是错在知识盲区或者概念混淆上这里专门列出来给后面笔试的人提个醒。一道考的是RAII与异常安全。代码里有一个unique_ptr成员变量和一个裸指针成员变量构造函数中裸指针分配资源后抛出了异常问析构函数会不会被调用。答案是如果对象构造函数没执行完析构函数不会被调用所以裸指针指向的资源会泄漏而unique_ptr自己的析构会正常执行。如果你在构造函数里既用了裸指针又用了智能指针资源泄漏就悄悄发生了RAII的“二不原则”必须记住要么全用RAII管要么保证异常边界处理干净。另一道考的是C静态变量初始化顺序。代码里有两个编译单元的全局静态对象一个的构造依赖另一个问程序启动时是否一定按期望顺序初始化。答案是不同编译单元的静态对象初始化顺序在标准里未定义如果想控制初始化顺序可以用函数内static局部对象替代全局静态对象第一次调用时才初始化。这道题对做服务器开发的C工程师来说非常重要因为服务里大量的单例对象都是静态的跨模块依赖时经常性成线上偶现bug。数据库相关的一道选择题也容易翻车。题目说一个表有联合索引(a, b, c)问哪些查询条件能命中索引。正确答案是只有遵循最左前缀原则的查询比如a、a和b、a和b和c能命中只查b或者只查c都走不了索引。但题干问答的是“下列哪个查询不能使用该联合索引”选项中有一个看似友好的“WHERE a 1 AND c 3”这个其实也能用索引因为最左前缀a满足了虽然c不是索引前缀MySQL会先通过a定位到一批数据再在回表后过滤c。很多背了“最左前缀”口诀但没理解本质的人会误以为这个查询完全不能走索引。4.3 排查题的答题范式CPU飙高怎么答才显功力最后那道开放题“线上服务器CPU持续飙高如何排查”我总结了一套答题范式笔试现场压着字数写满了。第一步是先确认现象用top命令看是哪个进程的CPU占用高记录是用户态CPU高还是内核态CPU高用户态高通常指向业务代码死循环或密集计算内核态高指向系统调用频繁或网络软中断打满。第二步是定位线程用top -Hp 进程号看具体哪个线程占CPU最高把线程号转成十六进制后用jstack或gdb attach上去抓线程栈。第三步是分析线程栈看热点是否集中在某个函数上比如字符串解析、内存拷贝、加锁自旋等。如果栈非常深且多次抓取都在同一个位置基本可以断定是死循环或锁竞争。第四步做对比验证把高CPU线程的核心转储导出来用perf top采集内核和用户态的热点事件精确到是哪条指令导致的CPU飚高。这题还有一个容易被忽略的加分点别忘了先排除“偶发”还是“持续”。如果只是偶发飙高可能是GC暂停、日志大批量刷盘引起的如果是持续飙高则多半是代码级问题。答题时把这个排查思路拆成“确认现象—定位线程—分析线程栈—验证结论”四步而且每一步都说出具体用什么命令阅卷人一眼就能看出你是真排查过线上问题的。5. 过来人的备考建议与避坑清单5.1 优先级排序C和网络编程是生命线从畅游这套笔试题倒推前期准备的重点我建议按优先级来安排时间。第一优先级是C语言本身尤其是内存管理、智能指针、虚函数、移动语义这几块这些是服务器开发的核心工具会不会用直接决定你的下限。第二优先级是网络编程TCP状态机、epoll的使用和底层原理、常见网络异常处理这些是服务器开发区别于普通后端开发的特征知识。第三优先级才是操作系统、数据库、并发设计它们重要但不是筛人的第一道门槛。一个特别具体的准备方法不要只刷选择题和看面经一定要自己手写一遍epoll单线程echo服务器、手写一个线程池、手写一个带状态机的房间管理器。笔试里很多题看似在考记忆实际上考的是你写代码时有没有踩过这些坑的肌肉记忆。我这次笔试时epoll那题能在一刻钟里一次性编译通过就是因为半个月前刚在用C重写了一个IM服务的网络层。5.2 时间分配不要在一道题上死磕整张卷子的时间我记得是90分钟题量对大多数人是偏紧的。我的策略是选择题控制在一道题一分钟以内不会的先标记跳过回头再处理两道算法题各留20分钟第一次没思路就先用最笨的办法写一个能跑通的主体再考虑优化综合设计题留25分钟把框架写出来比写满细节更重要因为这道题看的是设计思路完整度。最后5分钟检查代码格式和边界条件。这套节奏下来基本能覆盖全部题目不会出现最后一道大题来不及写的情况。如果时间特别紧优先级应该是两道算法题的暴力解大于一道完美解加一道空白综合设计题的框架大于局部细节选择题的空白等于放弃分数。这个道理看似简单很多人到考场上还是会因为一道题不甘心而陷入死磕结果丢了后面更值钱的大题。5.3 面试复盘笔试之后的思路延续笔试结束不是终点其实畅游的笔试内容可以直接拿来准备面试。比如那道房间管理服务设计题面试环节很可能被追问实现细节房间数据放在Redis里怎么保证Redis和本地内存的数据一致性房主转移权限时如果两个玩家同时点了“退出房间”怎么处理这些都是笔试设计的延伸考前如果没有准备过这些问题的答复思路面试现场很容易卡壳。我的做法是笔试结束后趁热打铁把每道设计题的答案扩展成一篇完整的方案文档包括数据结构定义、接口设计、异常处理流程然后挑核心部分背下来。后面面试时被问到类似问题时直接流利地讲出方案思路这种表现会让面试官觉得你思考有深度、解决问题有系统性。反过来如果笔试做完就丢到一边面试时再被问起同一道题反而更容易露怯。我个人复盘下来最深的体会是畅游这套题没有一道是超纲的所有内容都在服务器开发工程师日常工作的射程范围内。它不是在考你背了多少八股文而是在考你有没有真的写过能扛住玩家流量的服务端代码。如果你现在还在备考阶段与其疯狂刷面经不如静下心来把网络编程、C内存、并发控制这几个核心模块彻底弄明白尤其是把每一块都能对应到一个具体的游戏业务场景里这样不管笔试出什么题目你都能游刃有余。另外再说一个很多人忽略的小技巧笔试前一定要去看看该公司最近上线的游戏类型和它们在技术分享会上的公开内容。畅游这些年一直有在做RPG和卡牌类产品它们对“房间管理”这类多人交互场景的关注度非常高我这次能在设计题上写得这么快很大程度上就是因为提前看了几篇它们技术团队分享过的服务器架构文章对它们关心的技术方向有了预判。这套方法放到任何公司都适用投哪家就看哪家的技术博客把“这家公司最在意什么技术问题”作为自己复习的重点线索比无差别刷题高效得多。