Photon Server

来源:互联网 发布:重庆网站推广优化外包 编辑:程序博客网 时间:2024/06/06 03:24
Photon 是什么
Photon原意是指光量子。在这里是指exitgames开发的MMO服务器端引擎。
Photon包含两个部分。一部分是一个socket服务器,另一部分是其针对各个平台编写的sdk。这些平台包括了:android, Flash, iPhone, iPad, .NET., Unity 3D, Windows,甚至还有silverlight。这也意味着Photon的发布也包括两个部分,Client SDK Release和Server SDK Update。 目前Server SDK的版本是v2.4.5,而Client SDK的版本是v6.2.0。
Photon 和其他MMO服务器端引擎有什么不同
这里有一幅图可以供大家参考。
当然还有几处是值得一提的:
  1. Photon的API已经不限于C#了。
  2. Photon可以支持HTTP,则意味着它或许还可以作为其他用途,比方说专用的Ajax服务器。
  3. Photon的socket server是用c++实现的,效率自然不是java可以比拟的。
  4. Photon已经通过私有协议完成对象的二进制序列化。大家也不必操心去笨手笨脚的自己去实现了。
  5. Photon支持UDP。对于某些需求来说,这就具备了技术上的可行性了。
Photon 的实际应用

事实上Photon和Unity3d之间的合作是有战略意义的。了解Unity3d的朋友一定知道island这个sample。这里有一段视频就是展示如何在Unity3d中使用Photon Client SDK。不得不说,Photon和Unity3d的风格很接近:支持广泛,效率良好,易于上手。


Photon概述
Photon是一个开发框架,用来构建实时MMO游戏,并支持多个平台。如同我们在Photon Socket 简介里介绍的一样,它包含一个Server SDK和一个Client SDK。
Photon提供一个低延时的通讯层,会根据需求选择TCP或UDP,来分别通过"可靠传输"和"非可靠传输"的方式发送"命令"。这些术语在本文都会有详细的解释。但在开始的时候,我希望大家明白,Photon是一个基于"操作"和"事件"的框架。这么做的目的是为了简化开发游戏系统的代价。这个系列的后续文章里我们会通过一系列例子来说明这点。
在服务器端,每一个独立的程序都被称作"线应用"(Lite Application)。它们内置了一些基础的"操作",以使开发人员更关注他们的业务逻辑而非底层实现。
下面我们就开始介绍这些在开发中经常会使用术语或类。
Class LitePeer
我们从下面这个客户端的工作流开始,介绍一个最常见的Photon的运行步骤。大家可以大概理解一个服务器端程序是如何建立"房间"(Room)并且让用户"加入"(Join)进去。在"房间"中每一个用户都是一个"活动者"(Actor),并有一个房间内唯一的标记。
  • 创建一个LitePeer类的实例对象,并准备在下面步骤中调用该对象的方法
  • 调用Connect()方法来尝试连接服务器,并通过实现IPhotonPeerListener.PeerStatusCallback来得到连接状态的通知,成功的状态是StatusCode.Connect。
  • 周期性的调用Service()方法来获取事件,同时发送"命令"(比如每秒十次)
  • 调用OpJoin()来加入游戏,通过实现OperationResult()来得到连接状态的通知,成功的状态是LiteOpCode.Join
  • 调用OpRaiseEvent()向服务器发送数据
  • 通过实现IPhotonPeerListener.EventAction()来获取服务器的数据更新
  • 调用LitePeer.OpLeave()离开游戏,若OperationResult()返回LiteOpCode.Leave则表示服务器端已经确认成功离开。
  • 调用Disconnect()以断开连接,通过PeerStatusCallback()得到连接状态的通知,StatusCode.Disconnect为成功断开连接。
通过上述流程,我们可以发现,这些已经介绍到的方法可以分为以下三类:
  • 异步操作:比如Connect(),OpJoin(),OpRaiseEvent(),OpLeave()
  • 异步回调:比如OperationResult(),用来返回异步主动操作的结果。值得注意的是,这种返回一般和异步操作是以一问一答的形式出现的。
  • 事件:不确定合适会被触发,比如PeerStatusCallback()。
通讯层(Level)
PhotonServer SDK和Client SDK之间的通讯可以分为三个通讯层。
  • 通讯层:Service, Connect, Disconnect 还有 StatusCallback这些都是直接对应 连接状态的。它们和UDP/TCP协议都一一对应。
  • 逻辑层:内置的"操作","状态"和"事件"都算作是逻辑层的一部分,我们可以通过方法的名称把它们分辨出来,比如OpJoin,OperationResult,PeerStatusCallback。
  • 应用层:我们自己游戏中的业务逻辑一般被列在这个层中。比如我们可以通过实现OpCustom(),来发送自定义的"操作"。
大部分情况下,我们不必控制通讯层。然而了解客户端和服务器端的通讯机制依然有意义。
Interface IPhotonPeerListener
IPhotonPeerListener被定义来接受服务器端事件,下列方法需要被实现。
  • PeerStatusCallback:用来得到通讯层的连接状态变化的通知,比如connect, disconnect, errors, compare,这些状态都被定义在StatusCode这个枚举里。
  • OperationResult:用来得到逻辑层操作的回调,比如(join, leave)
  • EventAction:用来得到应用层的事件回调
  • DebugReturn:只在调试中使用
同时下列属性也值得关注
  • TimePingInterval:设置客户端对服务器的Ping间隔
  • RoundTripTime:用来指定"可靠信道"的往返间隔。可以简单理解为,在"可靠信道"里发送数据包的时候,依靠RoundTripTime和RoundTripTimeVariance来判断单位时间内响应是否超时。
  • RoundTripTimeVariance:RoundTripTime允许的方差。
  • ServerTimeInMilliSeconds:服务器端的计时器,以微秒为单位。
操作(Operation)
操作是我们用来描述对Photon服务器远程方法调用(RPC)的术语。也就是说,操作是在服务器端被实现,然而在客户端被调用。就像所有的方法,他们有参数和返回值。Photon负责处理操作的调用机制,而我们只需要定义和调用即可。
在服务器端,操作运行于Photon内核之上。默认由Exit Games(Photon开发公司)提供的服务器端程序叫做线应用(Lite Application)。LitePeer类继承自PhotonPeer并实现了所有被继承的基础操作的方法。
关于这些基础操作,最典型的例子就是join和raise event。在客户端,这两个操作对应的就是LitePeer类的OpJoin和OpRaiseEvent这两个方法。由于他们已经被LitePeer实现了,所以在应用层你可以放心来调用。
自定义操作(Custom Operation)
Photon是可拓展的。比如游戏程序中,你可能需要在服务器端持久化游戏世界的某些状态,也需要在客户端确认这些信息是否同步。所以你需要自己实现一些业务逻辑,所有这些没有被内置实现的操作都叫做自定义操作。实现这些操作一般都是服务器端程序员的工作,而调用这些远程方法及更新客户端则是客户端程序员的工作。
在定义自定义操作的时候,名字和参数数目是不受限制的,但为了节省带宽,这些操作名和参数是会被二进制化成字节码。事实上,Photon会用一个唯一的code标识每一个操作,这个code就被称为操作码(opCode)。
在调用自定义操作就是在客户端调用OpCustom(),Photon发送一个哈希表的数据结构来调用远程方法。
事件(Event)
不像操作是主动调用的方法,事件是通过暴露回调方法来得到通知。事件可能是由服务器端或者其他客户端发出的。
一般来说,得到事件的通知是由于受到操作的影响,比如你进入一个游戏房间,这个房间里的其他客户端应该得到通知。当然有自定义操作就有自定义事件。大部分事件带有关于这个事件的描述数据,这也是用哈希表来作为数据结构的。
段(Fragmentation)
超过1KB的数据都会被拆成以1KB为单位的消息包,并且会在传输到对点之后自动重组。这就意味着有些包含很大数据量的命令会导致其后的命令延时。所以经常调用Service() 或 SendOutgoingCommands()是绝对必要的。你可以检查PhotonPeer.QueuedOutgoingCommands这个属性是否归零来了解是否队列里的命令是否都发出去了。你也可以检查debug输出里的是否有"UDP package is full",这有时候是会发生的。
非可信数据(Unreliable data)
有些数据需要保证其实时,然而对其准确性和丢失率并无很高要求,这类数据被称为非可信数据。有些数据要求务必准确,并且需要保证发送的顺序即接收的顺序,这类数据被称为可信数据。事实上,可信数据一般基于TCP协议,而非可信数据基于UDP协议。当然,TCP的通讯效率明显低于UDP,而且是单播的,UDP却可以组播。
在游戏程序中,我们可以根据需求来使用这两种传输方式。比如在FPS游戏中,移动信息可以作为非可信数据,因为其要频繁更新,并且可以接受一定的误差因为可以很快被之后的消息更正,而对话信息则必须是可信数据,因为顺序错误或者消息丢失是不能被接受的。
信道(Channels)
.net的客户端已经支持多信道。这个特性允许客户端和服务器之间可以并行传输消息。这就意味,如果我们把某些特别容易造成延时的命令或事件放在一个独立的信道里,它就不会导致那些有实时性要求的数据等待太久。当然你不可以跨信道发送事件。
默认情况下,每一个PhotonPeer对象实例有两条信道,并且0号信道是默认用来发送操作的信道。而如加入房间或离开房间则是通过2号信道发送的,这么做是为了让业务更清楚。在这两条以外其实还有一条后台信道,即255号信道,用来发送网络连接和断开的消息,这样我们就可以把主动断开网络和意外性的断开网络封装成一种情况。
信道也是分优先级的,低号码的信道会被放在UDP协议来优先发送。举例来说,对话消息可能会被放在1号信道,而移动消息则被放到0号信道。那么当聊天信息发送延时的时候,不会导致实时性要求更高的移动消息被阻塞。
TCP
如Flash或Silverlight的客户端是不支持的UDP协议的,这就意味着我们只能把TCP协议作为最底层协议。这虽然不是使用Photon的最佳实践,但也只能如此。
而在TCP协议下,我们无法使用非可信数据的传输效率高和组播这样的特性。当然,我们的业务代码也可以简化很多。
可序列化数据类型(Serializable Datatypes)
从服务器SDK 1.8.0和.net客户端5.6.0开始,Photon支持的可序列化数据类型有了一次大的变化。ArrayList(弱类型数组)被放弃,而Arrays(强类型数组)和Hashtable被支持。具体可以参考以下列表:
  • String / string
  • Boolean / bool
  • Byte / byte (unsigned)
  • Int16 / short (signed)
  • Int32 / int (signed)
  • Int64 / long
  • Single / float
  • Double / double
  • Array (支持以上类型的强类型数组, 但长度限于 Short.MaxValue,而且二位数组是不被支持的。 )
  • Hashtable (需注意的是Hashtable[] 是不被支持的。对一个Hashtable对象,虽然可以同时使用不同类型的数值作为它的键,但从传输性能上考虑,最好不要出现这种情况。因为一旦出现一个Hashtable的键有多个类型的情况,该对象的读取会被拆成多个子命令,每一个键都会需要一个子命令。)
组播属性(broadcasting Property)
线应用可以用来在服务器端为客户端维护数据一致性,在同一个房间的玩家需要将彼此的数据同步,而对客户端而言,整个过程也许就像是读写对象的属性一样。
通过Photon内置的组播选项可以轻松的实现组播属性的功能。在服务器端的属性发生改变时,响应的事件会被触发以通知所有需要被通知的玩家更新数据,除了属性的修改者(因为这是不需要的)。
下列方法就是用来将属性写回服务器端:
  • LitePeer.OpSetPropertiesOfActor
  • LitePeer.OpSetPropertiesOfGame
  • LitePeer.OpJoin(值得一提的是,玩家可以加入一个还没有被创建出来的游戏房价,也就是说,服务器端不会对加入的房间做检查,你需要自己来完成)
而下列方法用来从服务器端读取属性并缓存在客户端本地:
  • OpGetPropertiesOfActor
  • OpGetPropertiesOfGame
需要注意的是,线应用目前不支持删除属性,也不支持用含宽字符的字符串作为组播属性的键值。而属性方法(.net中可以把方法包装成属性)也暂时不被支持。

0 0