多人网络游戏服务器开发基础学习笔记 I:基本知识 | 游戏设计模式 | 网游服务器层次结构 | 游戏对象序列化 | 游戏 RPC 框架 | 帧同步和状态同步

今天继续开新坑,尽管过了很多 Unix 套接字编程的坑,但是实际还是有很多不同场景和性能的需求,以及最服务器架构的内容也就接触过 preforking 和 master 带 worker 而已。

所以有必要持续阅读一些内容。本文的笔记主干是基于书本的内容的个人理解,然后补充很多网络上的资料作为额外的内容希望能把概念和方法都讲明白,然后会收集一些  realworld 案例加深理解。

主要跟的书本是 Joshua Glazer 的 multiplayer game 网络多人游戏架构与编程,这本书也是有一些好评的。

对于游戏服务器应用,最重要的一点是不可靠丢包和网络延迟,然后涉及安全(外挂?)和继承等东西。

补充一些 gameloop 的基础再看网络(netcode)部分:

游戏开发基础笔记:逻辑帧和物理帧辨析 | Gameloop | 游戏循环_我说我谁呢 --CSDN博客

然后是一些网络编程的基础:

基础服务器 IO 模型 Proactor 模型 Reactor 模型 IO 多路复用 异步 IO 模型 Linux 服务器开发 网络编程服务器模型_我说我谁呢 --CSDN博客这个主要是看一下最后一部分讲 UDP​​​​​​的内容:TCP拥塞控制及其缺点 与 基于UDP应用层自定义可靠有连接协议_我说我谁呢 --CSDN博客基础服务器 IO 模型 Proactor 模型 Reactor 模型 IO 多路复用 异步 IO 模型 Linux 服务器开发 网络编程服务器模型_我说我谁呢 --CSDN博客


额,首先打点基础先,因为单机游戏的开发之前只是跟过 unity 的超级简单的官方教程,这部分参考的资料是游戏编程模式 GPP,中英文链接这里都给出:

GPP 英文版http://gameprogrammingpatterns.com/flyweight.html

GPP 中文版https://gpp.tkchu.me/flyweight.html

游戏开发有关的设计

GoF 设计模式复习

首先是复习 GoF(然而对我而言这本书实在是晦涩难懂,所以直接看的 head first,这里先补充他这里的吧)

  • Command pattern,额其实就是可配置对象(间接寻址,就像虚函数表的实现),配置对象的参数(反射),回调的封装版本。举例是技能槽,手柄按键,可撤销动作(需要记忆曾经的状态)。这个东西就是第一公民函数(我已经很熟悉了属于)。

一个手柄,每个按键都与一个特定的'button_'变量相关联,这个变量再与函数关联。

  • Flyweight pattern,享元(中文翻译老是搞这么多花里胡哨的东西,不过好像的确比轻量级更贴切),就是说共有部分不用继承和 static(因为共有部分也要参数化)而是用 combinaion。GPU 支持 Geometry instancing,先 push 一个共同数据然后再提供参数化的 instance 列表。GoF 叫固有状态,gpp 叫上下文不管。例子是森林一堆参数化树和地理区块(tiles),地理区块的例子有点复杂,就是说通过存储对同一种地形的引用而不是实例,因为上下文无关的东西只有坐标,这个可以引申到对象池的工具管理享受的元

一行树,每个都有自己的参数和位置朝向,指向另一个有网格、纹理、树叶的树模型。

  • Observer, MVC 是观察者模式的好例子。就是事件驱动吧,但是又不能用回调(破坏被观察者的业务代码,强耦合,例子是物理引擎函数回调一个解锁成就函数)。但是这里又不是让观察者去观察他,而是让被观察者发出通知,而不管谁在接受到通知之后要做什么。观察者可以轮询,不过过时了,事件驱动直接监听一个事件就行了。不过具体实现是 subscribe 和 has many,所以实际通知过程还是要让物理引擎做,麻了。然后是慢以及回调阻塞,一种方法是解耦消息和通知的事务进行异步通信,对,那就是我们刚学完的消息队列。对于 vector 扩容减容的问题,可以用链式观察者解决(职责链,流水线),链表又有侵入式和非侵入式,非侵入就是搞一个node然后再has a,浪费啊这是,而链式就必须实现 double linked 脱链,不然不管有无 GC 都有问题。有闭包的话观察者不需要用接口,而是存一系列的闭包函数调用就行了,仍然是面向接口(这个好)。UI 的问题更加常用,血条变化反映到  UI 上需要数据绑定。
  • Prototype,因为工厂模式生产满足接口的子类时每个工厂都要继承一遍麻烦,prototype 说让已经有的 monster 直接自我无性生殖属于,提供 clone 方法返回一个父类接口(什么 fork),这样 spawner(刷怪笼)就能直接 has a prototype which 可以被参数化(即工厂本身是抽象的),javascript 是个原型语言,没有类的概念(类竟然是一个 generized list,然而他没有 clone)。额,比抽象工厂的好处是可以省一点 new ?protoype 支持 runtime 的 clone while af 只能用类层次结构实例化。
  • 单例我不讲了,已经老生常谈了,直接看游戏应用吧,额,只有一个,管理器,manager 子类的,然而这个东西没什么用。然后我重感觉单例模式不是因为没有 static 变量导致的吗,佛了,C 有 static 所以直接用 static 全局就行了,而对于需要控制访问权限的话 C++ 11 有 域内 static,所以单例 workaround 让 Java 去搞吧()
  • State,状态模式这个东西特别有用,因为游戏中的 sprite 的动作本质是状态机,特别提防各种边界问题,只有状态机能解决这一大堆嵌套 if else。而 OOP 实现状态机特别好啊,每个 state 对象只需要完成自己的步骤转换到别的状态就行了。这样包括各种充能也可以用 state 内部状态标识了,到达阈值跳到下一个状态。不过还会涉及一个问题是并发状态,走路同时需要开枪什么的,一种方法是使用多个状态机,一种方法 是用层次状态机,我感觉层次状态机可以类比成 decorator 模式下推状态机是涉及栈的,因为可能需要切换状态,比如射击之前是下蹲。状态机的结构能够助力表示 AI 的动作,从而实现 AI 角色。行为树和规划系统为 AI 所用,我认为行为树就是权值的行为决策树,规划系统是根据目的用 AI系统得到行为树类似的东西。用树也能提供随机性。行为树这个数据结构能替代状态机因为他表示下一步能走的状态是限制的,而且很容易表示。或者说行为树本身就是一个状态机而已,只不过他的状态更加清晰(DAG)。

游戏设计模式

首先是序列模式,这是因为游戏是一个序列,这个就不讲了

  • Double buffer,额,这个太底层了,图形学已经学过了。就是说避免看到的画面撕裂,所以一般做两个缓冲区,一个填满了就 swap 再让显卡显示,不然显卡随时会采样一个没画完的缓冲区。
  • game loop,这个是关键。事件循环是所有 GUI 程序的原理,本质是轮询,而 CPU 底层的 INTR 也不过是硬件轮询。对于游戏而言因为时间在推进,所以 game loop 要保证输出 render 的帧只是游戏时间推移上的采样。所以不能够用什么 sleep 来控制时间,首先 game loop 每轮运行的时间不确定,所以必须在一个 gameloop 里面持续推进游戏进度直到跟上真实时间再进行 render。实际这样就行了,我们只保证更新特定次数(let 60),然后 render 还是让 gpu 能跑就跑,gpu 跑完之后测量一下当前的时间,进入下一轮循环,如果间隔超过刷新间隔(必须大于 update 状态的时间),就更新所有状态(比如 gpu 很卡,就要多刷新补时间),如果时间很短,就不用更新,直接让 gpu 画之前的就行了。但是可能需要插值渲染。手机游戏如果有多余的时间可能要 sleep 一下节约 gpu/cpu 运转时间。(这一段讲不清楚,在讲第一个 demo 分析的时候我会再补充)

逻辑帧和物理帧的问题

while (true) {
  game.update();
  game.paint();
}
  • update 是针对逻辑帧的,一般实现里面,这里面会有一个判断的,就是我们机器猫demo这里也是这样实现的,因为运算全部在 server ,所以只能看 server 的猫,额,这里好像不是通过这个 update 内部实现的,他完全是分离了的,实际的移动还是在客户端,之前讲过了键盘的采样是有一定时间间隔的,没达到时间间隔是不会进行采样的,这个其实就是一个逻辑帧的控制。
  • 而 paint 即 render,这个是每轮都进行的。所以就像 gpp 里面说的那样,必须要进行 插值 render。
  • realworld 的分析是一般游戏可能会采用 16 帧的逻辑帧,赛车游戏用 25帧。性能好一点的话可以做 30 帧。另外一个问题是,如果 CPU 比 GPU 快,那么 GPU 可以采样逻辑帧。如果 CPU 比 GPU 慢,就要插值。
  • 更新方法,实际 update 的时候必须解耦,不能让 gameloop 调整所有对象,只能让 gameloop 调用每个对象的更新一帧的 update 成员,这样就要做迭代器模型的(有栈协程)。然而还有一个问题是更新顺序,每个对象可能和旁边的对象有互动,这样就很麻烦,因为没有办法真正做到同步更新 =>所有游戏本质都是帧回合制属于。这个和 gui 的也异曲同工的,每次遍历html全部节点刷新css。但是实际又没关系,反而减少了对互斥同步问题的考虑,就让他做回合制了(我不明白)。

主要涉及复杂服务器模型的讲解的游戏就几种经典的

MMO massively multiplayer online game,MMORPG role-playing games

FPS first person shooters。还有 RTS Real-Time Strategy Game。

回合制游戏总是最容易实现的,同步问题都没有,到点了才更新状态或者都是持续更新单人状态。

游戏数据类型

一个游戏最有可能给数据进行分类的话:

  • 非保障数据
  • 保障数据,保证可靠以及正确
  • 最近数据,最新的是保障数据,旧的没那么重要
  • 最快保障:移动等决定性状态更新。

服务器层次结构

CS 模型 V.S . P2P 模型,一个是集中式一个是分布式,分布式对带宽的消耗很大(全连接),外加 NAT 原因,大型游戏基本都用 CS 模型。P2P 的本质其实是运算在所有的客户端都演算一遍的。CS 则是在 server 上运行游戏,客户端负责渲染(交互)。

首先讲解的 Starsiege: tribes 游戏(MMOFPS)里

下图是这个游戏的整个架构:

  • 层次结构是 socket api -> connections manager -> stream manager-> 中间层 manager -> game
  • 这里中间层的 manager 用来管理数据包的保障性并且依据情况进行重传等操作(移动、事件等),事件管理器本质上是一个事件触发回调 RPC 的消息队列,然后流管理器会负责把这些 RPC 发出去(比如伤害等于对某个角色回调伤害成员,即 RPC 了)。ghost 管理器是管理玩家的信息的,而对于 128 玩家同屏来说,是有优先级的,如果服务器每次都要同步了 127 个用户的包再发过来肯定是不靠谱的。移动管理器是保证移动数据正确的,不然伤害什么的都会出错。
  • 连接管理器是用来管理 socket 的,而不用每次调用 socket api,相当于 buffer pool?(可能有流控和 ack)
  • 流管理器是为了每次把一堆数据发出去,而不是一个数据发一个包,(这个角度看 UDP 的  datagram 也可以当成流用了)所以他要把上层各种事件管理的数据封装成流,然后传给连接管理器发出去

然后是  RTStrategy 例子 Age of Empires(回合制)

Age of Empires II: Definitive Edition Review - Gamereactor

  • 书本这里比较简略,这里有一个具体的帧锁定的算法描述(知乎用户韦易笑):

帧锁定同步算法 - Skywind Insidehttp://www.skywind.me/blog/archives/131

  • 确定锁同步网络模型,deterministic lockstep,P2P,但是必须处理 NAT 的问题,最后还是要服务器转发(之前计网速通指南讨论过了 nat 打洞并不能保证成功)。lockstep 同步的是每个节点(aka 玩家)的命令,而不是对每个事件(或者物体单元)都发送一次。这个模型的具体这里有个综述:网络同步在游戏历史中的发展变化(二)—— Lockstep与帧同步_Jerish的博客-CSDN博客 有时间可以把参考连接都读了。这个之后书本专门有个章节讲,不知道还要不要整理那部分笔记。
  • 延迟问题,对于 A 要执行命令进行客户端 play 的时候,必须保证接受完来自其他客户端的消息。才能进行模拟。
  • 轮班计时器,命令缓存,每 m 毫秒广播一次清空缓存队列。直观看是增加了延迟,但是全局来看的话,这样能够保证时间窗口内的命令能够被各客户端同步并且确认,之后本地模拟执行。星际争霸也用这种方法。这种方法的还有好处是不用协调各种同步的东西了。所以确定性的意思就是保证这每轮采样是确定的。
  • 覆盖操作不过键盘操作还是要,因为覆盖操作必须是最新的,而且需要保证发送,比如军队移动等,这种数据直接有键盘事件就可以发送,也不用进行同步,直接覆盖就行了。(额,对于进入同一个地方导致 overlap 的话还是要同步的)。
  • 指令模型的另一个好处是可以 replay。(这个其实就是通常说的帧同步 对比 状态同步)
  • 然后对于十分延时的问题,只能把玩家踢出去了。踢出去之前可以进行暂停检测,如果等待超过某个阈值就踢了,没办法了。
  • 同步问题,一个是事务的有序性,这个我感觉可以用数据库的 serializeable 的方法 2PL 什么的来弄吧,也不用 abort,就直接 restart 就行了,这样能保证最后指令执行在一个可串行化的顺序就行了。然后是游戏里必须使用伪随机数生成器保证所有终端的模拟都遵循一致的 “随机性”。
  • p2p 的另一个好处是检测作弊方便,区块链可追溯属于。

帧同步和状态同步(前面这个猫猫游戏的demo例子是状态同步)概念澄清

  • 前面其实讲过一次,但是书本的概念不是很清晰,这里补充一下。
  • 帧同步的实现是定义逻辑帧(锁同步?),服务器的作用是用来同步指令,然后发送,客户端本地按顺序演算,支持战斗回放。就是前面说的确定锁同步网络模型。
  • 状态同步是服务器运算说有点状态,然后计算好下一个的状态再返回给客户端同步全局状态。
  • 但是状态同步和客户端先行不是同等级别的概念,为了防止客户端看到操作和结果之间时间点的割裂,必须再画面表现上优化,客户端先行、平滑插值等在表现上降低对延迟的感受。这里的机器猫完全没有这个实现。RPG游戏中,动画的特效一般做的比较长时间,看着好看同时延长网络响应时间, 攻击的时候给人感觉是击中了。放技能也有一个前摇,同时将攻击请求提交给服务器。等服务器结果返回时,动画也播放完毕了,之后就是更新状态和 HUD 而已。
  • 还有一个问题是反作弊,对于 RTS 和帧同步的方案,只需要定时服务器(或者某个客户的托管服务器,或者大家)校验一下就行了,但是这个只是防止了数据作弊,对于信息泄漏的话可能无解,因为运算和所有数据都在客户端,完全可以全部 dump 出来,包括视野信息,以及透视等。而状态同步不会泄漏运算流程。
  • 然而对于手游,客户端运算才能保证在各种无线网络下的延迟问题,这是和网络因素决定的(就和 TCP 的基础设施反而和网络不相适应导致要定制 UDP 一样)。这种反作弊的确就用校验可以解决,最常用的做法验算,确定客户端随机数种子,还有客户端操作。那么记录正常战斗的数据,放到其他客户端去验算,看看验算能否通过即可。
  • Realword 部分,王者荣耀,皇室战争是帧同步,魔兽用的是帧同步,lol 可能是状态同步(没有相关信息,但是好像本地会有 cd 信息,存疑),dota 用的是帧同步。

简单序列化

怎么序列化一个公共引用对象。

  • 一种方法是 linking,插入一个标识符,然后接收到之后恢复这些关系(类似编译链接里面的地址无关 GOT 表和 PLT 表?符号表之类的?),读者可以用 unordered_map 做一下 leetcode 138 体会一下,不过这道题实际最佳方案的确是原位复制一个。
  • 他这里的代码方法是这样实现的,首先对每个引用对象做一个 map<obj, id> 然后就能产生 id 来序列化了。
  • 反序列的时候,同样先要建好这个 map,然后再恢复引用 。
  • 对于同级引用的来说这个其实比较不靠谱,不过。可以这样,先反序列出一堆 null ref 先(所以还是指针好,能指 null),然后再 iterate 一遍反序列引用。这个再那个复制 random ref linked list 的力扣题(就是上面说的 leetcode 138)的时候学习过很多次了(===忘记很多次)。这个实际又和学 copying GC 的引用(指针) forwarding 的方法好像啊!

压缩也必须考虑

这里又不用 json 字节压缩,所以还是用各种二进制压缩吧。

  • 变长数组的压缩
  • 稀疏矩阵的压缩(这个学DSA和那个讲矩阵的计算机算法的课也学过了,不过基本都忘记了)
  • 针对字节的压缩算法
  • 直接上 protobuf 就行了吧(然而好像这个东西很坑,难顶)

C++ 静态或动态反射

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

代码:Java 中的反射

反射的意思就是说运行时能够得到类的信息,对于 json 序列化等实现很有用。

  • 书本给了一种反射的实现方式,原理是利用 initializer list 来做一个 vector,然后所有的类都有一个 data type,new 的时候就直接用某种编译方法来实现。反正是侵入式的。和之前那个宏的做法差不多不过一个是 C++ 的方法,一个是 C 宏 preprocessing 而已。
  • UE4 的实现是工具生成代码。这种基本都要自己做一套编译工具,额应该可能用 Lexer & yacc 来做。(或者直接用 fscanf 和 fprintf 做,我之前看一个 C 语言侵入式 json 库的实现的确是这样的,不过这个需要简单的语法吧,实际你可以用 python 或者 perl 那些简单的来生成C 代码)。
    • Realworld,UE4 的解析是用 LL 分析(还没学编译原理,看不懂的名词)。
  • 然而生成代码这个和宏也每什么区别,一样是定义一堆 friend,还用上了 name managling(保证代码提示排在最下面),,,
  • UE4为每个类生成了一个专门的反射数据收集类,以友元类的方式来访问类信息。(和宏其实也没什么区别了,都是侵入式)

世界状态的同步

图片:一个 2D 游戏的世界,请想一下怎么再不同的机器(两个猫)上进行同步?

世界状态

这涉及到对象的复制。为了成功复制一个游戏对象,一般有三步:

  • 标记数据包是包含对象状态的数据包。
  • 唯一标识复制对象。
  • 指明被复制对象的类型。

定义数据包

  • 对于一个数据包他可能是包含多种数组的,,,
  • 用一个 enum 来标识他。所有对象都用一个不可重复的标识符->> 单线程自增 uint 就行了。
  • 如果有40亿就用两个uint,这样就有 1600 亿个对象了,哈哈哈哈。

精细反序列化

  • 对于已经存在的对象,就不创建了。如果不存在,才反序列化。
  • 类的类型名字,这个特别耦合
  • 用 if dynamic cast 判断类型是因为他会返回 null 指针(我竟然不知道/没印象)。
  • 不能用 dynamic cast,RTTI 禁用。我差点忘记 RTTI 是什么了,就是虚函数表里面再存一个 typeinfo,这样就能运行时 dynamic cast (记住 decltype 是 compile time 的,rtti 不过 dynamic 和 typeid ,异常而已)到不同的类型,并且能返回 runtime error。游戏里不开启 RTTI。

对象创建注册表 object creation registry

  • 大量的时候用 GUID 做 id
  • Unreal 的 C++ 由于是深度定制的,所以完全支持四字符 int, int a = 'GOBJ'.
  • 对于标准而言这个是 implementation define 的,gcc ,msvc 支持。然后其实要求 I32 才行,比如 ILP32。I32+LP64. 然后可以用 uint32_t . 又没办法手动写转换,,,, 可以试试宏。
  • 代码5.7 ObjectCreationRegistry,用模板直接注册其成员函数。-> 学到的知识点是虽然 C++ 没有能把类型作为对象的机制,但是模板就是啊!学会了!而且用上宏和配置文件,可以自己外面维护一个 map 表格,然后自己编译成宏(用 perl 就行了)再宏展开得到批量注册代码段。

MTU 问题,一个包尽量传多点数据。

整个世界复制过程(TCP?可靠 UDP!)

  • 序列化游戏中的对象
  • 有必要的时候覆写和创建
  • mark and sweep 垃圾清理(server 上总是最新的世界),渲染的话另说。
  • 增量世界复制 -> 对于更新或者覆写传输 command 而不是序列化的对象。对于创建也省去了一次 map 的查询(额,本来也很快,除非他是线性探测哈希)。
  • 鲁棒性,必须考虑不是 create 命令的时候也有找不到对象的情况,由于这种情况必须跳过流/offset,必须还是反序列一个 dummy,或者传的时候添加一个 len hdr。
  • 更加紧密的版本控制涉及 bitmap (然而反序列的时候反而引入一堆 if else,只能说是 S-T trade off 问题,而且 S 太大本身也增加 T 了),不知道 git 那些是怎么 diff 文本的了属于。

RPC I,为什么要用 RPC(这段主要从网络资料学的)

RPC 的概念是网络编程基础,这里不赘述。

这本书还手把手教做 RPC 框架的,也是可以

  • 我有点抽风,没有理解为什么应用层有了 http 还要做 RPC,RPC 本身是包括传输+序列化的。然后 gRPC 他本来就是用 http 来做的。
  • 所以为什么要做 rpc 呢?一个是 http 的头部很垃圾。然后rpc 库主要还要提供高级的特性,而不是简单的传几个包。这些服务包括服务注册和发现,负载均衡和熔断等东西的。
  • 而且历史上是先解决了 RPC (80s)再做网页(90s)的。HTTP 也可以认为是一种 rpc 了,特别是 http2 之后性能好带宽开销少,要么用上 protobuf 这种二进制本质也是为了节约 http 这种文本的。
  • 而且要明确 http 解决的重点问题不一样,如果用的话就等于为没用到的功能付钱,http 在缓存、幂等重试乃至 Cookie 这种浏览器安全相关的方面做了很多功夫。网络环境不同可能有一些不同的协议冒出来,像现在面向移动网络,http2 在减少 RTT、优化长连接(吃我推送拉日活),而 RPC 还是说想专注微服务那边去优化的。
  • 然后说 http 短连接的时候,他可能是乱序的,所以不如直接用 tcp 长字节流来做 rpc 了。
  • 还有安全问题,一个是 ssl 保密,一个是 ddos,对于不通用的协议难背攻击。http 解码也垃圾(字符串行和分隔符分割,unp里面已经体会过要 read line 来 parse 的麻烦了)。http 保活难讲。(额,http2另说)
  • 比如 dubbo 的功能高级用法 | Apache Dubbo
  • 真的有点吓人:启动时检查(服务),序列化安全(安全的序列化而已),集群容错(集群挂了重新调控),负债均衡,线程模型,p2p 直连,只订阅(绕过注册中心测试本地),多注册中心,结果缓存(lru那个 递归 memo)泛化 mock 调用(就是不用知道 api,通过 kv pair 调用),上下文信息 (寄存器快照),异步执行 (直接返回),异步调用(队列等待,定时),参数回调(服务端调用客户端,反向代理),本地存根(让客户端也可以执行部分逻辑,本地 stub 存根,这个这里没解释清楚,下面再讲一遍),tls,路由规则(调整负载均衡的东西吧,和之前配路由器一样),日志,分布式事务,IDL 交互式数据语言…… 我晕!!!

RPC

  • 首先是 RPC over 我们之前做的 replication(复制世界的意思) 流包上,然后让 replication 接收到 RA_RPC (包头的 flag)就路由(我说的路由可能就是 dispatch 的意思,有点概念杂糅了)给 rpc manager 负责解包。然后每个 RPC 都做一个解码器才行。注册的时候就只需要提供接口!即几个 key ,一般做一个结构体定义在客户端就行了。
  • 然后胶水函数必须生成的。客户端需要胶水,这些胶水是把参数写进流里。而服务端需要胶水
  • 用C++实现一个RPC框架 - 知乎 (zhihu.com)
  • 关于这个 stub 是什么我在补充一下,stub 本质上就是胶水,因为本地调用 rpc 的时候实际是通过一个 stub 调用(或者说他叫 proxy?代理人做一层 wrapper 或者说 adapter)的,然后 stub 发给服务器解包再调用。看了一些自己的 rpc 的实现,基本都要宏来实现或者进行一些字符串替换!字符串替换的话可能会比做编译语法分析简单。如果用 json 的话需要自己的 json 库。
  • dubbo 的本地存根的意思是本地再 hook 一次,让 rpc 调用,可以用来做 local 缓存,过滤异常参数。我很好奇为什么要这样做?直接先检查参数再调用 rpc 不好吗?为什么要通过框架来配置?我大概懂了,因为实际调用的时候都是直接通过 dubbo 获取远程的服务类然后进行调用的(getBean)这个相当于不用调用 wrapper 直接 hook,相当于装饰器吧,不用改变原来的代码就能 hook 一个 middleman 过来。(就跟 linux 要提供 LD_PRELOAD 和 gcc 支持 --wrap f 支持库打桩一个道理吧)
  • 但是游戏里面需要不止远程过程调用,需要远程方法调用,基本 Java 那种的,RMI 远程方法 invocation。这样还要涉及 this 指针,对于 C++ 来说由于实现差异,只能用 C++ 11 的 function 来实现第一个参数 this 指针了。RMI 需要作为标识符,这个标识符可以沿用我们之前说的那个 four chars uint32.

第六章开始就是作者做的 demo 不断改进的一些分析了,感觉是很多《 XXX in practice》书籍风格的内容 ,尽管这部分我跟进运行了demo 和阅读了源码也做了笔记,但是由于笔记内容和源码比较紧密,读者没有上下文信息一头雾水,我直接 po 上来没有用,可能需要额外整理一下。

随书配套的源码,支持 vs 和 xcodehttps://github.com/MultiplayerBook/MultiplayerBook

这本书还是比较基础的,但是的确能学到很多内容,在刚刚看完 UNP 两卷基础 API  和游双的高性能服务器前半部分(到目前我只是看了 linux 特色 api 和做了些小型程序编写而已)之后其实对真实网络应用的开发还是有点模糊的,这个的确是个不错的学习材料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值