这篇水文只是我对过去工作中某个问题的复盘和总结,描述了“请求-应答”式协议的另类用法。 尽管本文并不是吹 RPC 实现,但原理与其类似,比如用 {方法标识 + 参数数据} 构造 TCP 字节流。 甚至可以有更多花样,比如用 UID + 上下文标识就能跟踪请求栈,但这超出了本文讨论范围。
本文描述的应用场景仅限于“请求-应答”式的数据交换方式下(如 HTTP)。 例如一个普通 web 应用的运作流程( 不要在意细节 ;b )
普通 web 应用的运作过程就是不断地重复以上步骤。这有点像我们人与人之间对话的过程。 但奈何程序员能力有限,还无法让 Client 与 Server 如人一样自然对话。 双方需要约定 Client 和 Server 之间传递的各项数据的含义。
数据格式种类繁多,二进制如 MsgPack, Protobuf ,文本如 JSON, XML,可另起话题。
如此,项目一开始程序员们分分钟就约定好了数据的含义:“接口 A 返回数据 dataA,接口 B 返回数据 dataB……”。Client 和 Server 轻松快乐地进行着“你问我答”,程序员们便安心回家睡觉觉了,皆大欢喜……
意淫结束。游戏毕竟不同于 web 应用,Client 与 Server 之间存在频繁交互,大量的接口重复交换着有限的几坨数据。来来去去就是些玩家属性、装备、钱包、背包 blahblah…… 用 JSON 举个例子,数据来自商店购买接口:
{
"_code_": 0, # 操作码
"coin": 1000, # 银币
"gold": 1, # 金币
"income": [ # 获得收益
"equip-10001-1",
"equip-10002-1",
"prop-20001-10"
]
}
有着类似数据约定的接口有很多,如副本结算、奖品兑换、PVP奖励、活动奖励等等。 不止如此,说不准哪天策划一拍脑门,还会多几种货币或物品类型。
现在面临的问题很清楚了,有两点:
玩命领加班费很没意思,是时候加点设计。我给每个数据集合加入唯一标识(非绝对唯一,仅用于区分不同数据集合),Server 根据唯一标识提供数据,Client 则根据唯一标 识来解读。如此 Server 可以在一次响应里返回多个数据集合,而 Client 都能正确解读且只需实现一次。至此,这个另类的应用层协议便有了雏形。
仍然用 JSON 描述改良后的数据约定(不代表具体实现):
[
{
"_code_": 0 # 当前接口的返回状态
},
{
"_msgid_": "money", # 玩家钱包状态变化
"coin": 1000,
"gold": 1
},
{
"_msgid_": "bag", # 玩家背包状态变化
"income": [
"equip-10001-1",
"equip-10002-1",
"prop-20001-10"
]
}
]
Client 只需根据 _msgid_ 绑定到不同的方法上即可,细节不表。现在处理服务端的响应就会容易得多,比如这样:
public void buyShopItems(string shopId) {
GameResponse resp = this.gameserver.buyShopItem(shopId);
if (!resp.isOK()){
throw OperateFail(resp.Code);
}
this.saveStates(resp.msgs); // 同步状态
this.showNewItem(resp.msgs.bag)
// do game blahblah……
return;
}
public void saveStates(StateChange states[]) {
for(StateChange s: states) {
MsgHandler h = this.player.getHandler(s.msgid)
h(changes) // 根据 _msgid_ 执行具体的方法
}
}
如此这般,开发起来会顺手不少。
完。