跳到主要内容
左猫娘右猫娘

网易游戏服务端一面

· 阅读需 19 分钟
Heng_Xin
ここから先は一方通行だ!

游戏研发工程师实习生(服务器方向)一面, 官网写只招 1 人, 我都没有想到能进面qwq...

Tip

写的是我个人的回答, 不一定是对的. 并且是凭借记忆写的. 不一定准确. 仅个人复盘使用.

一共面试了 40 来分钟吧(面试官大概提前了30s进入会议室)~, 感觉被拷打了很多, 还得练! (至少回答了 20 个问题了qwq, 而且包括自我介绍、反问、以及面试官思考(真的, 面试官每次切换问题, 他都要思考 5s+ 来看看问我什么qwq 我真的是接受审判 \o/))

1. 自我介绍

2. 问题

被问的有点多了, 顺序可能有点记不住~

  1. 说说C++多态

分为静态多态和动态多态, 动态多态是基于虚函数实现的, 比如对象有一个虚函数表指针指向静态存储区的虚函数 (以实现动态绑定)

静态多态主要 函数重载 以及 模板 CRTP

  1. 幻读, 脏读是什么

!@#.?, emm 让我组织一下 (面试官微微一笑) qwq...

emm... 不会.. (不好意思, 我脑子一片空白, 不敢耽误太久qwq), 因为我项目中使用数据库主要是做持久化方面, 就是全部加载到内存, 然后希望做搜索, 这样更快. 这样...

  • 八股:

    1. 脏读 (Dirty Read)

      • 事务A读取了事务B尚未提交的数据。
      • 如果事务B随后回滚, 事务A就读到了无效数据。
    2. 幻读 (Phantom Read)

      • 事务A在两次执行同一查询条件时, 得到的结果行数不同。
      • 原因是事务B在A执行期间插入或删除了满足条件的新行。

区别:

  • 脏读: 读到了未提交数据。
  • 幻读: 读到了已提交但新增/删除的行。
  • 不可重复读: 读到了已提交但被修改的数据。
  1. 你看看下面的结果:
struct node {
virtual func(){}
char a;
int b;
};

// 64位操作系统, sizeof(node)=______。
cpp

一开始太紧张了, 看错了(char*)qwq 答了 24, 被面试官提醒不对哦

马上反应是 char + int = 8 (内存对齐), 外加 虚函数表指针 8 = 16

  1. 你了解 rpc 吗?

了解, 主要就是 远程的函数调用可以像调用本地函数一样调用. 它内部自己实现传输协议, 但是不暴露给用户, 用户使用时候是无感的.

  1. tcp和udp的区别是什么

tcp 主要是实现了 可靠性传输, 超时重传, 拥塞控制, 这块

upd 主要是 无连接的, 就没有 像 tcp 的三次握手, 四次挥手这些; 然后更快

八股:

TCP 提供了可靠、面向连接的传输, 适用于需要数据完整性和顺序的场景。

UDP 则提供了更轻量、面向报文的传输, 适用于实时性要求高的场景。

  1. 让你实现一个可靠性 udp 怎么实现

我回答是, 在应用层实现一个 收到就回复机制. 比如 服务端发送给客户端, 客户端必须要回复一个. 如果没有收到就重发.

ps: 明明我有复习的qwq, 但是也只能现场答出一点, 而且现在看来好像不是很对啊qwq

八股:

实现可靠UDP的核心是在应用层补TCP的机制。

关键点:

  • 包序号(seq): 每个包唯一编号。
  • 确认(ACK): 收到包后回ACK。
  • 超时重传: 未收到ACK则重发。
  • 去重: 收到重复包丢弃。
  • 按序交付: 缓存乱序包, 交付有序流。
  • 可选滑动窗口: 提升吞吐。

即: "可靠UDP = UDP + 序号 + ACK + 重传 + 去重 + 定时器"。

  1. 我看你项目有写 红黑树定时器, 具体是怎么实现的?

这块我结合项目讲解了qwq... 说实话, 我现场都能感觉到我说的比较乱(因为面试官怎么可能熟悉我的项目呢? 对吧? 我至少举例一下, 以后!). 不过好在面试官一直引导, 以及配合举例...

我把大概的意思传递到了qwq...

还问了, 如果是 定时到 xx 时间, 你是一直循环判断吗?, 我回复是: 不是, 一次计算到时间差, 就可以阻塞 (实际上应该回答挂起睡眠) 这个时间差的时间.

对了, 还基于这个问我你知道时间片算法吗? (emmm, 好像是时间轮转算法, 我忘记问的什么了)

我回答说好像有了解, 就是 时间取模然后做事这样 (实际上完全不对. 面试官看我好像不行, 就问下一个问题了qwq)

  1. 我看你项目写了 Json序列化与反序列化, 说说是怎么实现的?

哥们是基于反射的, 所以说了 基于编译期for循环 + 函数重载直接匹配, 总之简单的很. 不过提到了反序列化时候使用了编译期完美哈希表进行 name -> idx, 然后 idx 映射到 类的基地址偏移 然后 转为该类型指针, 然后 *(T*) 左值传入, 然后函数重载匹配, 然后再进行对应解析即可. (不知道面试官有没有听懂, 不过我稍微边写边讲解了:)

    json -> struct Data {
int a; // 0
std::string str; // 1
};

编译期 for (idx, std::string_view, auto&& v) {
}

std::string -> idx
*(int*)
""
cpp
  1. 来写点代码题吧~
/*
先输入2个值 n m (n < 1000, m < 50)
然后n个数字
每个数字只能用一次, 求出和是m的组合, 输出下标。每次尽量取前面的
输入:
7 6
1 2 2 4 5 5 3
输出:
1 2 7
3 4

ps: ds 说我这算法基本正确; 因为是 每次尽量取前面的 => 贪心 => 我这样 ok
*/
#include <iostream>
#include <vector>
#include <string>
#include <tuple>

using namespace std;

struct A {
int n, m;
vector<int> arr;
vector<int> res;
vector<char> vis;
A() {
// 网易的控制台不知道怎么输入, 面试官让我直接写死
// cin >> n >> m;
// arr = vector<int>(n);
// for (int& v : arr)
// cin >> v;
n = 7;
m = 6;
arr = {1, 2, 2, 4, 5, 5, 3};
vis = vector<char>(n);

for (int i = 0; i < n; ++i) {
dfs(i, m);
res.clear();
}
}
bool dfs(int i, int s) {
if (i >= n || s <= 0) {
if (s == 0) {
for (int v : res) {
vis[v - 1] = 1;
cout << v << ' ';
}
cout << '\n';
return true;
}
return false;
}
if (!vis[i]) {
res.push_back(i + 1);
if (dfs(i + 1, s - arr[i]))
return true;
res.pop_back();
}
if (dfs(i + 1, s))
return true;
return false;
}
};

int main() {
A {};
}
cpp
  1. 如何排查代码的性能问题?

我回答有使用到 火焰图, 可以看那个函数的执行时长, 然后就定位到时间比较长的, 就可以具体看看代码了.

以及靠直觉感知代码, 因为代码是我自己写的, 所以我知道哪里性能有问题 (这回答不行, 给面试官整笑了qwq)

还有就是比如 ui 这种, 可以很明显体验到的, 比如按下后会卡顿. 你就可以直接看看 对应的逻辑 使用 打印、调试 定位问题.

  1. 说说你工作中, 定位并修复客⼾端断线重连引起的段错误问题 是怎么做的

我先说架构是 (客户端 + 后台服务), 然后说实际上这很简单, 主要原因就是原本代码是仅考虑了一次断线重连情况, 我修改了一些状态, 就修好了. (还提到了我是不小心触发, 导致我发现这个问题的qwq, 不过不知道有没有传达到意思)

  1. 问一个比较奇怪的问题: tcp开了keepalive, 连接建立后, 断电和进程崩溃的区别

我懵逼了.. 说可能 进程崩溃 的话, 操作系统可能还有收尾工作, 会断开. 而 断电 就什么都没了.

(说实话, tcp开了keepalive 是什么意思, 不是http1.1+才有keepalive这个吗qwq?)

八股:

TCP Keepalive 机制说明

TCP keepalive由操作系统内核维护, 原理如下:

  • 空闲一定时间后(如默认7200秒, 可调节tcp_keepalive_time) 内核开始发送keepalive探测包(一个空ACK)给对方。
  • 若对方回应ACK, 则连接仍存活。
  • 若对方无回应, 内核会重复发送若干次(tcp_keepalive_probes次, 每次间隔tcp_keepalive_intvl)。
  • 若多次无响应, 内核认为连接断开, 通知应用层ETIMEDOUTEPIPE等错误。

所以:

  • 进程崩溃 => 内核仍运行, TCP栈活着。操作系统会检测到socket关闭(close()或进程退出释放文件描述符)。内核会发FIN或RST给对端。 => 对方立刻收到FIN或RST, 连接关闭。 => 立刻感知

  • 断电 / 主机硬崩溃 => 整个内核停止工作, 没人发FIN或RST。连接在对端看来仍是ESTABLISHED。 => 对方收不到任何包, 要靠TCP keepalive或应用层心跳超时后才知道。 => 要等超时

要点总结:

  • TCP keepalive是内核级探测, 与HTTP keep-alive无关。
  • 进程崩溃 → 内核发送RST/FIN。
  • 断电 → 内核死掉, 对方靠超时才发现。
  1. 已知最小堆为8,15,10,28,35,16,12, 删除元素8后需重建堆, 在此过程中会发生____次比较

我记不得了qwq...

然后我这样写:

/*
首先
8,
15,10,
28,35,16,12

会和末尾元素对换
12,
15,10,
28,35,16,8

并且末尾元素被标记为无效空位置
12,
15,10,
28,35,16,_

然后 头部会和下层比较, 然后交换, 以维护 上层元素永远小于下层元素 (小根堆)
10,
15,12,
28,35,16,_

我没有回答次数, 面试官说过程是对的. 也没有让我回答次数. (哥们一开始猜 logN..)
*/
cpp
  1. 问你知道拓扑排序吗? 说说~

我说主要是用来判断是否有环的. 然后面试官让我讲一下算法. 这个算法我最熟了哈哈

首先记录一个入度数组, 和一个节点队列, 首先把入度为 0 的节点入队.

然后循环出队, 然后把这个节点的出度的节点的入度数组减一, 如果为 0, 就继续加入队列.

这样就可以得到拓扑序. 然后如果有剩下的节点, 那就是成环了. (面试官: 哈哈然后反正成环的就是有问题的~)

  1. 说说C++有哪几种智能指针 (这个好像是很前面问的, 不记得顺序了)

我说 C++11 之后有三种: U_ptr / S_ptr / W_ptr, 而 C++11 之前有一个 auto_ptr, 他在 C++11 被废弃, C++17 被删除

  1. 你说说如何防止智能指针的循环引用

我先说明, 在 双向链表 / 图上, 使用 S_ptr 容易循环引用

比如:

A -> B // s
A <- B // s

// 这时候只需要, 把其中一个变为 w_ptr 即可:
A -> B // s
A <- B // w
cpp
  1. 在图中, 如何快速判断两个节点是否属于同一个强联通分量?

一开始我忘记 强联通分量 是什么了, 和一个 求双连通分量的算法搞混了, 一直在想算法名字 (哥们不知道英文怎么念) -> Tarjan 算法

后面面试官看我听不懂思密达, 就改描述说, 简单点, 如何判断两个节点是否联通 (路径可达)

我就秒回 并查集了~

  1. 说说死锁产生的条件吧~ (这个好像是很前面问的, 不记得顺序了)

我说有4个条件, 资源竞争、资源顺序 ... 记不清楚了qwq...

八股:

  • 互斥条件: 每个资源在任意时刻只能被一个进程所占用, 或者资源处于可用状态等待被分配。这意味着资源不是共享的, 而是互斥的。

  • 保持和等待条件: 已经持有至少一个资源的进程, 还可以请求获取新的资源, 并且在新资源被分配给它之前, 它不会释放已经持有的资源。

  • 不可抢占条件: 资源只能由持有它的进程显式释放, 而不能被其他进程强制抢占。

  • 循环等待条件: 在发生死锁时, 系统中必然存在一个或多个进程组成的循环链, 其中每个进程都在等待下一个进程持有的资源, 而最后一个进程又在等待第一个进程持有的资源。

  1. 说一下 B+ 树. (这个好像是很前面问的, 不记得顺序了)

我主要描述了, B+树他是一颗 N 叉搜索树, 这里 N 是一个很大的数字, 比如 100、1000; (主要目的就是窄, 以减小搜索次数, 从而减小磁盘IO (这块没有回答)), 并且(它和 B 树不同的是) 它的数据都存储在叶子节点上, 然后我们可以在叶子上进行范围搜索 (因为叶子上有一个双向链表, 支持范围顺序访问)

  1. 我看你项目自己实现了 JWT, 你知道 它的 Payload 吗? (我不知道面试官是不是说这个, 只记得是 P 开头的发音吧? 不懂英语) (这个好像是很前面问的, 不记得顺序了)

我说我这块主要是实现了一个 类 JWT 的协议, 并没有完整的按照原协议 ba la ba la... (我以为面试官说的是加密算法和JWT的直观可视化这块, 我可没有实现) 我只是为了可以复用我的Json反射模块, 以及调研了2个高start的库, 他们都使用的是那种先解析到中间态的共用体中存储的json库. 和我的完全不一样. 所以我干脆自己实现了~

  1. (我感觉还有一些问题, 但是我不记得了qwq?!)

3. 反问

  1. 具体进去是做什么的?

服务器策划开发(?我不知道有没有记错), 实习生应该被安排做 一些服务端工具类, 也可能会有业务任务.

  1. 公司 C++ 最高支持多少版本? C++17 / C++20 ?

答: 好像没有那么高, C++11 ? 不过一般不写C++, 一般是看引擎代码. 进去应该写 lua, 没事的, 一般都是进去才学~

  1. 公司的培训制度?

有导师带~

完~~

请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...