Overview
网络连接
建立网络连接的步骤:
如果全部用Connect、Send、Receive,则是一个同步socket程序。这样的程序简单且容易实现,但是服务端一次只能处理一个客户端消息,客户端也会时常卡顿。解决方案是异步和多路复用。
异步模式(案例待补充)
异步模式的时候客户端可以使用BeginConnect和EndConnect完成同步程序中的Send和Receive功能。
状态检测
异步调用虽然能够解决线程阻塞的问题,但是引入了新的多线程的问题。微软提供了Poll方法来对线程进行判断,就是在执行接收发送任务之前加上一个对于线程的判断。
这种方式的缺陷在于在没有收到消息的时候也一直在循环检测,对于CPU的性能造成浪费。
同步理论
同步的过程:客户端1向服务端发送一条消息,服务端稍作处理之后广播给所需的客户端。传递的消息包括位置角度等状态值(状态同步)和向前走这样的指令(指令同步)。
同步的难题:消息的传播会有延时;消息到达的时间并不稳定。这些难题并不能完全解决,只能尽量优化,例如:表现优化等。
状态同步
指同步的是状态信息。游戏中客户端把坦克的位置坐标、旋转发给服务端,服务端再做广播。
- 直接状态同步:客户端定时向服务端报告位置,其他客户端根据收到的消息将对方坦克移动到指定的地方。
- 跟随算法:解决直接状态同步的瞬移问题。收到协议之后,客户端不直接把坦克拉到目的地,而是让坦克以一定的速度移动。这种方式会有一定的延迟。
- 预测算法:让物体提前走到预测的位置上去。缺点是会出现拉扯和抖动。
帧同步
帧同步是指令同步的一种,同步的是操作信息。只传输玩家的操作指令,使得数据量减少了,网络延迟也会得到缓解。
这种情况很容易产生误差的累积,因此引入了“同步帧”的概念。
同步帧:在发送命令的时候附带时间信息,客户端根据指令的时间信息去修正路程的计算方式,使得所有的客户端表现一致。所保证的效果就是,各个客户端在执行到一个同步帧的时候,表现效果完全一样。
代码实现:
在指令同步中,客户端向服务端发送的指令包含了具体的指令和时间信息,表示在哪一帧做了哪个操作。
指令的执行中为了保证客户端一致的表现,往往需要做一些妥协:(1)延迟执行:让速度快的客户端等待速度慢的客户端,这对于速度快的客户端较为不利;(2)乐观帧同步:对于速度慢的客户端发送的过时指令,进行丢弃,直到速度慢的客户端赶上来;这对于速度慢的客户端比较不利。所以帧同步会让某些客户端做出妥协,来保证多个客户端保持一致的一种方案。
项目实例
网络连接
以下是客户端和服务端收发消息的示意图:
移动同步
主要可以分为:进入游戏同步、移动同步、离开游戏同步。
这里整理了角色离开同步的流程图和角色移动的流程图,在Debug的时候对照着流程图来还是挺有用的。
移动同步的大致步骤:
- 客户端发送
- PlayerInputController: 角色控制脚本中触发移动同步,然后找到实体控制器切换动画
- MapService: 从地图协议中发送同步信息(Id = entity.Id, Event = entityEvent,Entity = entity)
- 服务端处理
- MapService: 收到消息之后根据sender的mapid找到对应的地图,让MapManager做UpdateEntity(对于游戏中的角色做个遍历,entityid和发送者一致的就进行移动,其他的entity就添加到待同步对象的list中,等待做移动同步)
- MapManager: 发送同步消息,主要包含的就是需要同步的entity对象
- 客户端接收
- MapService:遍历传过来的待同步对象,调用EntityManager进行同步
- EntityManager:通过id拿到对应的entity,将传送过来的数据赋值给这个entity;如果有订阅了这个变动的对象进行触发(例如,触发对应的动画等)
大图在此:
http://assets.processon.com/chart_image/635ffedfe0b34d77dbc3cd4e.png
后处理机制
参考文档
《Unity3D网络游戏实战》