[转载归档]2020年最危险的软件缺陷

CVE 和 CWE

了解过信息安全的同学应该对 CVE 有印象,CVE 是通用漏洞披露 Common Vulnerabilities and Exposures 的是缩写,主要为了记录和共享各种计算机系统的安全漏洞,以方便其他用到同样系统的人了解其风险并修复。每个 CVE 都有一个 CVE 编号,比如 2017 年最让人头疼的 Windows 勒索软件就关联到了 CVE-2017-0143 和 CVE-201-0148 两条数据。对于信息安全人员和开发者,关注 CVE 是一种比较有时效性的学习方式,能够帮助我们了解最新的系统风险,避开有风险的底层技术。
CWE 与 CVE 不同,CWE 的全名是通用缺陷列表 Common Weakness Enumeration,CWE 提炼了众多信息安全事件中长常发生漏洞的点,进行分类归纳,供开发者避免在编程中再次出现。举几个 CWE 的例子,CWE-798 Use of Hard-coded Credentials (在代码中硬编码密码留下后门),CWE-476 NULL Pointer Dereference (空指针解引用)。这些条目对与代码审计有着非常好的参考价值,在开发静态检查工具的时候可以对照其中的条目进行开发。

CWE 评选的 2020 年最危险的软件缺陷

2020年8月,CWE 官网发布了 2020 年最危险的软件缺陷的 Top 25。其数据来源为美国政府主导简历的 NVD (National Vulnerbility Database),NVD 的缺陷数据来自 CVE 并且补充了其对应到 CWE 的缺陷类型,以及用来衡量缺陷带来的问题严重性的 CVSS 评分;数据取样时只提取了 2018/ 2019 年的数据,一共有 27,000 条对应的 CVE。讲 2700 条 CVE 数据对应到更少的 CVW 中。然后综合考量严重性(CVSS)和产生的频率得到最终的评分(表格中的score),具体的评分计算细节请访问其官网查看。 统计结果如下表。

RankIDNameScore
[1]CWE-79Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')46.82
[2]CWE-787Out-of-bounds Write46.17
[3]CWE-20Improper Input Validation33.47
[4]CWE-125Out-of-bounds Read26.50
[5]CWE-119Improper Restriction of Operations within the Bounds of a Memory Buffer23.73
[6]CWE-89Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')20.69
[7]CWE-200Exposure of Sensitive Information to an Unauthorized Actor19.16
[8]CWE-416Use After Free18.87
[9]CWE-352Cross-Site Request Forgery (CSRF)17.29
[10]CWE-78Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')16.44
[11]CWE-190Integer Overflow or Wraparound15.81
[12]CWE-22Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')13.67
[13]CWE-476NULL Pointer Dereference8.35
[14]CWE-287Improper Authentication8.17
[15]CWE-434Unrestricted Upload of File with Dangerous Type7.38
[16]CWE-732Incorrect Permission Assignment for Critical Resource6.95
[17]CWE-94Improper Control of Generation of Code ('Code Injection')6.53
[18]CWE-522Insufficiently Protected Credentials5.49
[19]CWE-611Improper Restriction of XML External Entity Reference5.33
[20]CWE-798Use of Hard-coded Credentials5.19
[21]CWE-502Deserialization of Untrusted Data4.93
[22]CWE-269Improper Privilege Management4.87
[23]CWE-400Uncontrolled Resource Consumption4.14
[24]CWE-306Missing Authentication for Critical Function3.85
[25]CWE-862Missing Authorization3.77

Reference

https://www.redhat.com/zh/topics/security/what-is-cve
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0143
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0148
https://cwe.mitre.org/top25/archive/2020/2020_cwe_top25.html

[开发环境]切换到vcpkg

过去看到 laike9m 的博客讲到他喜欢的编程语言,其中共鸣很深的一点是

Python is my favourite language, whose simplicity and powerfulness fascinates me. C++ is great, but writing C++ code is of less fun.

对于没有什么性能指标的个人项目我是很喜欢用 Python 写的,同时在工作中写游戏服务器也大量用到 C++,每次想用 C++ 写一个个人项目时我就开始想 ”啊,又要一个 CMake 搞一下午了“ ,然后放弃。

最近几年微软的开源产品的质量有了很大的提升,然后在了解到 vcpkg 可以兼容 CMake 之后决定把整个 C++ 工作环境迁移到 vcpkg 上。这篇文章用于积累我常用的 vcpkg 命令。

我的 C++ 工作环境

Windows : MSBuild/ CMake

Linux/ MacOS: CMake

所以我选择干脆把三台机器都装上 vcpkg。

简单的使用

场景:我想要写一些简单的 OpenGL 程序,需要用到的库大概有 glfw(动态或者静态库),glew(静态链接)另外我想用一个简单的日志库所以选择 spdlog。

1
2
3
4
5
# 安装
git clone https://github.com/microsoft/vcpkg
# for windows
# bootstrap-vcpkg.bat
sh bootstrap-vcpkg.sh

由于 vcpkg 完全托管在 GitHub 上的,更新的话只需要 pull 一下最新的代码就好了

1
cd vcpkg && git pull

查找以及安装依赖

1
2
3
vcpkg search gl # 可以搜很多很多到 OpenGL 和依赖到 OpenGL 的库
vcpkg install glfw3 spdlog # install with dynamic link
vcpkg install glew:x86-windows-static # install with static lib

如果使用的时 Visual Studio,一个命令就可以直接集成到开发环境中

1
.\vcpkg\vcpkg integrate install

而如果使用 cmake,现在可以放心地在 CMakeLists.txt 里写

1
2
3
4
5
6
7
8
9
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(main PRIVATE glfw)

find_package(spdlog CONFIG REQUIRED)
target_link_libraries(main PRIVATE spdlog::spdlog spdlog::spdlog_header_only)

find_package(GLEW REQUIRED)
target_link_libraries(main PRIVATE GLEW::GLEW)

然后生成时引入 vcpkg 的 toolchain file 就能够正常的找到依赖

1
cmake .. -DCMAKE_TOOLCHAIN_FILE=.../vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x86-windows-static

后记

今天听 CppCast 正好听到了邀请 vcpkg 开发成员 Nicholas 的一期节目,Nicholas 提到他们希望 vcpkg 成为 C++ 生态中的 cargo(顺便黑了一把 Google 的 bazel。总体而言,如果微软能够延续 Visual Studio Code/ Windows Terminal/ winapi-rs 的这种对于开源的热情,未来肯定会从 Mac 生态中吸引来更多的开发者用户。

[编程随记]CPython 中的小整数池的实现

什么是小整数池

在写 Python 的时候小整数池的存在会给我们带来意想不到的结果。比如。

1
2
3
4
5
6
7
8
9
class Foo:
cls_var = 42
def __init__(self):
# 如果这里是整数类型就会出现两个local_var都指向integer pool里的int类型对象了
self.local_var = 42
a = Foo()
b = Foo()
print(a.cls_var is b.cls_var) # Always True
print(a.local_var is b.local_var) # ?

第二个 print 的结果是 True 还是 False 呢?或者说这里的两个 Foo 的实例的 local_var 是不是同一个呢?

答案是True。也就是两个变量在 C 语言这一层指向了同一个 CPython 的 IntergerObject。

另外一个更有趣的现象是发生在在不同的整数段使用 for 循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import timeit

def timer(function):
def new_function():
start_time = timeit.default_timer()
function()
elapsed = timeit.default_timer() - start_time
print('Function "{name}" took {time} seconds to complete.'.format(name=function.__name__, time=elapsed))
return new_function()

shift = 10 ** 5
itertime = 10 ** 6

# small integer
@timer
def small():
for _ in range(0, itertime):
for i in range(0,256):
pass

# big integer
@timer
def big():
for _ in range(0, itertime):
for j in range(0 + shift, 256 + shift):
pass

两个函数 small 和 big的执行时间是否相同呢?

答案是 big 的执行时间是 small 的两倍。是不是觉得很诡异。

这两个种情况出现的原因就是因为在 Python 代码里的 int 变量是在小整数池里的。尽管这看起来不可理喻,但是相信更多人都是小整数池的收益者,比如最常见的 从 0 到 10 的 for 循环,有了小整数池效率要比没有事有提升的。

小整数池的实现

下面的实现来自于 Python 3.9。

小整数池的 Buffer 是 PyThreadState 的一个属性,PyThreadState 是内部类型 _is 的一个别名,小整数池的定义在 pycore_interp.h 文件中。

1
2
3
4
5
typedef 
struct _is {
// ...
PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
};

小整数池的初始化在 Object/longobject.c 中,可以看到它跟随一个 Python 的 thread_state 一起初始化,对于 Python 的线程是一个 thread local 的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int
_PyLong_Init(PyThreadState *tstate)
{
for (Py_ssize_t i=0; i < NSMALLNEGINTS + NSMALLPOSINTS; i++) {
sdigit ival = (sdigit)i - NSMALLNEGINTS;
int size = (ival < 0) ? -1 : ((ival == 0) ? 0 : 1);

PyLongObject *v = _PyLong_New(1);
if (!v) {
return -1;
}

Py_SET_SIZE(v, size);
v->ob_digit[0] = (digit)abs(ival);

tstate->interp->small_ints[i] = v;
}

if (_Py_IsMainInterpreter(tstate)) {
/* initialize int_info */
if (Int_InfoType.tp_name == NULL) {
if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) < 0) {
return 0;
}
}
}

return 1;
}

在 longobject.c 中有从小整数池中获取对象的内部接口

1
2
3
4
5
6
7
8
static PyObject *
get_small_int(sdigit ival)
{
assert(IS_SMALL_INT(ival));
PyObject *v = __PyLong_GetSmallInt_internal(ival);
Py_INCREF(v);
return v;
}

而这个接口在需要创建 int 类型的 PyObject 的时候会进行判断,判断是否可以避免分配出一个新的 PyLongObject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PyObject *
PyLong_FromLong(long ival)
{
PyLongObject *v;
unsigned long abs_ival;
unsigned long t; /* unsigned so >> doesn't propagate sign bit */
int ndigits = 0;
int sign;
// 如果是 small int
if (IS_SMALL_INT(ival)) {
return get_small_int((sdigit)ival);
}
// ...
// 一种需要分配新的对象的情况
v = _PyLong_New(ndigits);
return (PyObject*)v;
// ...
}

可以看到,小整数池的完美的封装到了 Python 的整数类型内,如果需要修改小整数池的大小,或者不使用它,完全不会破坏对外的接口,阅读 CPython 源码的时候也给我了一些启发。

小整数池的意义

对象池是设计一个有动态内存分配的系统的常见优化方法,比如动态管理 TCP 连接的连接池,减少 worker 线程创建销毁开销的线程池。我的理解是,当频繁的创建销毁带来大量开销的时候,使用对象池就能够提升效率。

直觉上,我们写 Python 代码的时候,写这样的 for 循环应该非常常见。

1
2
3
4
5
6
for i in range(10):
pass

for dim in range(3):
# 3d vector
pass

这只是直觉上的一个判断,但是如果一个频繁使用的函数中有这样的一个代码,小整数池就能够提升一定的效率。

我暂时只能自己想到这一个,有空我试着翻一下 CPython 的提交记录看一下看一下其他有优化作用的场景。

[C++]实现std::move和std::forward

尽管平时工作经常写C++,但是大多数对性能也没有敏感到用move和copy的结果是天上地下的情况。最近在工作中要改一个调用频繁的接口,于是用了一次 std::move,结果就写出问题了(我的C++知识在脑子里会发生LRU,稍微不用就容易忘)。在刚学C++的时候我至少看过至少3本不同的书,天天查的cppreference,都说它和内存操作无关。于是去翻一下源码自己实现一下加深其印象。

move

1
2
3
4
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&& //rvalue
move(_Tp&& __t)noexcept // argument is a universal reference
{ return static_cast<typename std::remove_reference<_Tp>::type::type&&>(__t);} // convert to rvalue reference

总结一下就是它把一个对象转成右值 (rvalue) 引用, 不会destruct掉被Move过的对象,所以,在一个复合数据结构中把一个部分给 move 后不要留着已经被 move 的对象,记得析构/释放掉免得后面忘记导致再次使用。

1
2
3
4
5
// 忘记释放再次使用的情况
vector<int> vec{1,2,3,4};
std::move(vec.begin(), vec.begin() + 1);

for(auto && n: vec) std::cout << n; // this is bad

forward

std::forward 是用来实现完美转发的函数, 同样它也是只依靠类型转换实现的。
Effective Modern C++ 有完整的一个篇文章例子来讲什么时候需要用 std::forward ,尤其是函数的参数是一个 universal reference 时, 要正确的解决传入左值引用和传入右值引用两种情况。如果不想手动实现两个版本的话一定要使用 std::forward,如果这时断然地用std::move会导致传入的左值,在没有被察觉的情况下被move掉,如果函数外这个变量再被使用就出问题了。

1
2
3
4
5
6
7
8
9
10
11
12
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept // lvalue reference
{ return static_cast<_Tp&&>(__t);}

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value);
return static_cast<_Tp&&>(__t);
}

[南来北往]京沪卧铺动车乘坐体验

12月份去北京找女朋友体验了一次从上海出发到北京的动车卧铺。买这趟车的最主要的原因是急着去,其一是这个时间点想买到当周周末的飞机票只能买全价票,其二是我真的不像坐接近6个小时从上海到北京。

在上车前,我是觉得动卧路上睡一觉能省下一晚的住宿费的,然而当我开始想睡的时候问题来了:除非是睡眠质量非常非常好的人,就算不考虑噪音也是很难在这种晃晃悠悠的车厢空间内安然入睡的,在去北京的路上我全程戴着降噪耳机,尽管如此也被吵醒了好多次。所以如果你们和我一样对睡眠环境有一丁点的要求,最好还是不要选择买动卧。

如果下次能够买到1400元左右的飞机票我觉得我是不会买动卧的了.最后上两张自己拍的照片吧.
train
cabin

[最近的日常]2020年末装机杂记

先上配置

  • AMD Ryzen 3600
  • ASUS B550 主板
  • 16GB x 2 金士顿 HyerX
  • 1T 希捷SSD
  • NVIDIA 1660s GPU

我的需求

  • 个人编程开发 有Windows环境方便学习游戏引擎,同时有剩余的性能开虚拟机跑点服务 50%
  • 2020前后2年的新游戏能够流畅游玩 50%

总共的成本落在了6500块,加上手残自己没点亮又没时间就找了装机师傅又花了120块人工费.

这样一来我也顺便对手里所有的电子设备做一个功能划分和生命周期估算,毕竟最近突然多了一个十万块的预期支出.

  • iPhone XR 收消息,等编译的时间摸鱼 计划在2021年冬天换成新一代的苹果的budget iPhone
  • MacBook Pro 2018 移动办公/非系统级的应用开发 计划在2022年换成 Apple Silicon的ARM版Air
  • 组装台式机 主力开发,虚拟机,第二游戏平台 根据必要性进行扩展
  • PS4 slim 主要的玩游戏游戏平台。不过暂时还不着急买PS5,因为没有特别想玩的PS5独占,出Pro版或者出其他升级版之后考虑买升级版
  • 潜在的支出:群晖 NAS 我曾经头脑一热在京东上曾经下单过,但是下单不到半天就冷静下来。对我来说它优先级较低,我房间也没有空间能够放 NAS。暂时通过稳定的网络服务比如, iCloud,购买付费的同步服务避免自己维护,有些事情免费的反而时最贵的。

2021年3月2日更新:

现在想想还好12月份组了一台台式机,现在比特币的价格疯涨。同样买一块1660s已经要花接近3000快了。

[我的推荐列表]动画推荐列表

这个POST用来记录我比较推荐的动画列表.
用blog记录喜欢的动画的想法启发自 https://imtwice.cn/twices-anime-list/ .

Tier 1

  • 星际牛仔
  • 回转企鹅罐
  • 红辣椒paprika
  • 天气之子

Tier 2

  • 机动战士高达Unicorn (OVA)
  • 龙的牙医
  • 千年女优
  • 福音战士新剧场版 破
  • 命运石之门
  • JOJO的奇妙冒险 不灭钻石
  • JOJO的奇妙冒险 星尘十字军
  • 你的名字
  • 狂赌之渊/ 狂赌之渊xx
  • Macross Plus OVA
  • 烟草 kemurikusa
  • 吹响!上低音号 第一季/第二季/剧场版/利兹与青鸟
  • 来自新世界 (仅推荐动画作为入门然后去看小说)

Tier 3

  • 福音战士新剧场版 序
  • 福音战士新剧场版
  • 机动战士高达00 TV 第一季/第二季/剧场
  • Macross F (仅剧场版两部 虚空歌姬/恋离飞翼)
  • Aldnoah Zero (第一部)
  • JOJO的奇妙冒险 黄金之风
  • 四月是你的谎言

[文章备份][转载]如何排解政治性抑郁

原文作者: 邓艾艾艾
原文发布于: 微信公众号「雪豹樱桃」

如何排解政治性抑郁,这是我被问过次数最多的一个问题。

我本身也不是一个在情绪上很通坦的人,讲如何排解似乎也不太具有说服力,我会尝试在观测的经验上作一些纸面清谈。不过,人不可能绝对剥离自我存在对感受的支配,就算能做对所有的题目,明白一切的道理,进入那样的状态时,自由抽离的权利终究是临场失踪的。

我不知道这个词是为何而发明的,但我看到它后,无需任何解释,就立刻明白它的体验指向。有人说,这是近年来才有的集体心理现象,在“美好的旧日时光”里不会生发。这也许是因为,那时你只是个唱着“我们的祖国是花园”的小学生,有不关心、不知道所赠予的花园笑脸特权,在那美好时光的不显眼角落里,同样有着苦闷与不解的零余青年。

政治性抑郁伴随政治立场而生,主要来源于自身意识与环境的冲突,由此我以为可以分为两类,相对政治抑郁与绝对政治抑郁。前者是自己的政治立场与周围大部分人有差异所致,后者则是因为这个世界的绝对事实不符合自己政治立场的预期。

相对抑郁在缺乏多元性,主流思潮单调、强大且包容性较低的地方更易发生。譬如,如果大部分人都认为女人一定要生孩子,而同性恋是一种不正常行为,这时候,持不同观念的人在现实中就会变得相对孤立。但这种情况也较容易排解,因为他们其实并不对自己的立场有什么严重的怀疑,只是和应乏声,而生压抑。我以为,此时不必太过自我暗示身边人与自己的价值冲突,保持原则的相信,拒绝行为的干涉,在此之外可对周遭做世俗化处理,而去往更广阔的世界,例如网络上,寻求琴瑟的远方支持,并不是说要钻入信息茧房,而是少数派更要联合起来共振以保护自己不被宏大的波浪所淹没。

不上网,不看新闻,这是人们通常认为的逃避政治抑郁的办法,它可能对解决绝对抑郁有所帮助,但对于相对抑郁,即使封锁了稀星重露的连通,也依然要面对周围言之凿凿的口水,逃离是没有用的,不如互相发出声音与光芒。

至于绝对抑郁,我以为,在大环境不改变的现实里,无法解决,我们也无法苛求一个连自我都疲于照顾的人去改变世界。这时候,切断向世界的联系,逃到自我的深处躲避,或许是最无奈的通道。

世界会变好吗,这是绝对抑郁里最核心的问题,我不敢回答。

高中的时候,在语文课上学《面朝大海,春天花开》,老师讲,这是一首很温暖的、激励人心的诗,那时我并不了解海子,却总隐约觉得,“从明天起,做一个幸福的人”这句话里有一种难以言说的绝望。

我现在想,这不是一首关于温暖与幸福的诗,或许是温暖的,但不是阳光那样的温暖,是让其他绝望的人,晓得阳光照不到的地方,不仅只有他们那一处。

你毫不怀疑,如果你什么都不知道,大概会比现在快乐很多,但,你要为了这快乐而跳进深渊吗。

[工作笔记]分布式UID常见方案

游戏服务器在经历了长期的单体单进程之后,现代的MMO服务器为了伸缩性都设计成了分布式的设计。撇开较为新的不怎么 OOP 的 ECS 设计,大部分游戏程序都要把游戏中的道具、NPC、敌人、召唤物设计成对象,而这些对象在生成的时候也必须要有一个 UID,因此在拆分单体游戏服务器到分布式设计时,如何保证不同的 server 上的对象 UID 不会重复就是一个能够影响游戏玩法正确性的问题。同时,创建对象作为非常底层的 API 其效率也是非常需要考虑的一个优化点。

去年在工作中完整做了这一部分的内容,所以在这里写一下学到的东西和自己的思路用于记录。

成为标准的设计:UUID

UUID 许多不同的版本的,基于时间、硬件地址、命名空间、随机数的都有,但是由于标准是128bit 整数构成的 32个十六进制数据字符串,所以除非项目达到这种规模不如拆解其中的设计方法定制自己的 UID 方案。

直觉上的设计:MySQL auto_increament 一把梭

  • 依赖于 counter

MySQL 已经有了 auto_increment 这种设计,不如就直接建一张新表用来当作分布式 UID,然后所有的 node 甚至都不需要额外的添加网络通信,直接服用 MySQL 的连接就好了。大致写一下用 SQL 实现的的方式。

1
2
3
4
5
6
7
8
9
CREATE DATABASE `gen_id`;
USE `gen_id`;

DROPTABLE IF EXIST `id_list`;

CREATE TABLE `id_list` (
`id` int() unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42;

这样的设计的问题是,我们无法忍受每次都要进行一次 SQL 查询,同时如果这时候 MySQL 发生了慢查询,UID 的赋值就会被卡住。

成熟的设计1:Snowflake

  • 依赖时钟、counter 和机器编号

Snowflake 是 Twitter 开源的一种分布式 UID 设计方案。它的 UID 的表现形式是一个 64-bit 的无符号整数,其设计大致如图。

Snowflake

  • 需要说明的是 10 bit 的节点id 编码实现时需要自己配置一下
  • 12bit 的counter 保证了每个毫秒对于单台机器可以产生 2^12 个不重复的id,这个数量级对于一般的游戏服务端项目应该是够用了

但是这种设计的问题就是,由于时间戳由于依赖 UID 生成器的时间,所以如果回拨时钟就会有发生 UID 重复的危险,不过这种情况可以在获取 UID 的更上层检测然后重新给予对象新的 UID来避免。

成熟的设计2:MongoDB ObjectID

  • 依赖时钟,随机数和 counter

MongoDB 的 ObjectID 是一个 96bit 的整数,虽然 C++ 并没有自带的 128 位整数的实现,但是像 EABASE 这种库都是有现成的 128bit 整数库可以直接用的。

mongodb_objectid

值得注意的是,MongoDB 所谓的 UID 只是把 UID 冲突缩小到了一个比较小的概率,因为 MongoDB 的 UID 的一部分是一个随机数,只不过这个随机数有 40bit 只有极少数情况下会发生碰撞,但理论上仍存在可能(详情可以去 Google 关键词:鸽巢原理,狄利克雷抽屉原理)。

尽管有理论上的缺陷,这种方案依然有它的明显的优势,就是完全不依赖与任何的一个中心结点,完全依靠 node 自己生成。这种方式也比较类似 MAC 抵制,只要一个路由器内不存在两个 MAC 地址相同的设备,就不用担心组网冲突;类比就是,只要一组游戏服务器内不发生 UID 冲突,我们就没必要考虑 node 数量级特别大的情况。

结语

由于合规性所以最后我们的设计不方便直接在这里展开讲,不过大致上就是基于这几种方案做一个缝合怪。比较重要的就是 UID 必须要由 node 自己生成,而不是每次去访问某个中心服务,UID 是要关连到游戏核心游玩的系统,设计的时候一定要考虑到效率。

Reference

[1] Generating Globally Unique Identifiers for use with MongoDB, MongoDB Blog

[2] 发号器设计漫谈, 三月沙

[3] RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace (ietf.org)

[最近的日常]成为了游戏服务器程序员

最近在干什么

距离上一篇博客的发布日期已经过去好久了,中间我也想过要不要写点东西发出来。工作之后学到一些东西,但是又没时间梳理语言。还是尽可能的写点东西锻炼一下自己的表达能力。

今年4月初正式去了一家规模还算可以的游戏公司入职,入职之后看了三套网络游戏的服务器代码,其中一套是现在自己仍然在维护的一个线上项目,也就是我现在上班的日常工作。

入职以来在做什么

首先我还再在继续学C++,因为 C++ 真的是包罗万象。

要驾驭好 C++ 并不简单,在掌握基本语法之后至少要学不少 idiom , 比如至少要看过著名的 Effective C++, 才能避免各种让人头大的未定义行为,将 C++ 开发的性能优势尽可能的发挥出来。

其次就是一些熟悉一些网络游戏服务器的设计架构了,相对而言并不是很难。

工作的内容是怎样的

写游戏逻辑是游戏公司服务器程序员做的最多的一项工作,这种工作就像互联网公司一样是无穷无尽的 CRUD,时间久了,写多了就自然而然会对着感到无趣。尤其是面对历史包袱导致的屎山代码, 就仿佛未来的我对着我这篇不成熟的文章, 恨不得穿越到屏幕的另一侧锤原来的作者。

游戏的开发流程可以用版本作为一个标准单位。版本初期,首先策划头脑风暴想好策划案,然后交给程序来实现,开发的过程中程序也会不断找策划确认需求的细节。完工后验收项目的基本玩法,再交给QA去测试一些边界情况和线上的兼容性。QA 反馈完所有的 Bug 并且尽数修复后,一个版本也就完工准备发布了。

游戏服务器和Web服务器有什么区别

非要从本质上讲的话没区别,就像云风前辈的 skynet 游戏服务器框架也可以用在其他的服务上一样。因为游戏是模拟现实生活的,游戏的商城也是简易版的现实的电商网站。至于不同的游戏类型,MMORPG ,放置类游戏,卡牌游戏,其架构也是百花齐放,不存在所谓的「最好的游戏服务器架构」。

游戏服务器里大量使用数据驱动的开发方式,大量的功能都抽象成游戏组件供策划完成下一步的内容产出。比如一个副本可以通过excel配置里面的出现的怪物的id/ 数量/ 等级, 然后在另一份怪物的配置中通过配置 lua 脚本/ 行为树/ 状态机配置怪物的行动,而怪物又对应到另一张表里配置可能掉落的道具。因此,修改这些配置的时候,并不需要彻底的停服,只需要让服务器重新加载一遍配置文件就可以完成更新。

关于自己技术的发展规划

其实之前这个部分不是「发展规划」,现在看之前写的那个部分有点无病呻吟,于是果断删掉写一个更加实际的话题。

坦白讲,中国游戏行业的换皮现象很严重,现在的一些服务器也是祖传且换皮多次的,技术上的求稳也使整体的技术较为落后。这一点主要在服务器上尤为明显,而客户端的技术迭代欣欣向荣,每年都有更有趣的技术出现,每年 GDC 都能看到很多有趣的 talk 。所以尽管工作中主要做服务器,但是我也会抽出时间复习一下在城大学的图形学然后去学习一下游戏引擎的使用和设计。毕竟,谁没有梦想自己做一款大家认可好玩的游戏呢?