Extending Lite 扩展Lite
Persistency is currently not covered in the SDKs we provide. None of our applications saves any data. Every game and application is different and on a high performance server solution, you probably want to control this aspect yourself.
持久性是目前没有包含在我们提供的sdk里。我们没有应用程序要保存任何数据。在高性能服务器解决方案中每个游戏和应用程序都是不同的,你可能想要控制这个方面通过你自己。
You are free to use any of the many professional solutions developed in C#. As example, take a look at:
- NHibernate : Mature, open source object-relational mapper for the .NET framework
- Membase : Distributed key-value database management system
- CSharp-SQLite : SQL database in a local file
- Lightspeed : High performance .NET domain modeling and O/R mapping framework
你可以自由的使用任何专业的基于C#的开发解决方案。例如:
- NHibernate:成熟的,开放源的对象关系映射器的
- Membase:分布式数据库键值管理系统
- CSharp-SQLite:SQL数据库在一个本地文件
- Lightspeed:高性能建模和O/R映射框架
Trigger Game-Logic In Intervals 在时间间隔触发游戏逻辑
If you want your server application to execute some logic in intervals, add this as method into LiteGame and schedule a message for the room. By using a message, the method call will be in sequence with any operations (avoiding threading issues).In the Lite Lobby Application’s LiteLobbyRoom.cs we used this code:
如果你想让你的服务器应用程序在时间间隔内执行某些逻辑,添加这个方法到LiteGame和安排房间的消息。通过使用一个消息,方法调用将带着任何操作入队 (避免线程问题)。在Lite Lobby应用程序的LiteLobbyRoom.cs中我们使用这些代码:
/// <summary>Schedules a broadcast of all changes.</summary>
private
void
SchedulePublishChanges()
{
var
message =
new
RoomMessage((
byte
)LobbyMessageCode.PublishChangeList);
this
.schedule =
this
.ScheduleMessage(message, LobbySettings.Default.LobbyUpdateIntervalMs);
}
/// <summary>Initializes a new instance of the LiteLobbyRoom class.</summary>
public
LiteLobbyRoom(
string
lobbyName)
:
base
(lobbyName)
{
this
.roomList =
new
Hashtable();
this
.changedRoomList =
new
Hashtable();
// schedule sending the change list
this
.SchedulePublishChanges();
}
/// <summary>Sends the change list to all users in the lobby and then clears it.</summary>
private
void
PublishChangeList()
{
//do something in intervals...
//schedule the next call!
this
.SchedulePublishChanges();
}
As you can see, at the end of PublishChangeList the next call is scheduled explicitly.
我们可以看到,在PublishChangeList 的最后安排了下一个调用。
Client Connection Handling 客户端连接处理
Client side, Photon is built to give you as much control as possible. This makes sure it integrates well with your game loop.It also requires some attention to avoid inexplicable disconnects (see also “Connections and Timeouts” in
Basic Concepts ). The following info should enable you to avoid them.
客户端,Photon给予你更多可能的控制。这确保了它很好的集成你的游戏循环。它还需要关注避免一些异常的断开。下面的信息可以帮助你避免他们。
Keep Calling Service 保持调用服务
To include Photon in your client’s game loop, you basically just have to call Service regularly. Regularly means between 10 to 50 times per second (depending on your game / network usage).
在你的游戏客户端中包含Photon,你只需要定期调用服务。大概是每秒10到50次。
Service covers two tasks:
- Received events and data are executed. This is done when you can handle the updates. As a sequence order is always kept intact, everything that the client receives is queued and ordered. Service calls DispatchIncomingCommands for this task.
- Outgoing data of your client is sent to the server. This includes acknowledgements (created in the background) which are important to keep connection to the server. Service internally calls SendOutgoingCommands to do this task. Controlling the frequency of SendOutgoingCommands calls controls the number of packages you use to send a client’s produced data.
服务涵盖2个任务:
- 接收被执行的事件和数据,当你处理更新的时候进行。作为一个顺序序列总是保持完好。客户端接收到的所有东西是被排序和队列化的。服务调用DispatchIncomingCommands完成这个任务
- 你的客户端发送传出数据给服务端。这包括确认保持着服务端的连接。服务在内部调用SendOutgoingCommands 完成这个任务。控制调用SendOutgoingCommands控制发送客户端产生数据包数量的频率
Profiling Service Calls 剖析服务调用
To detect if you called Service regularly, we added a statistics module to the client APIs.
如果你定期调用服务去检测,我们添加了一个模块到客户端API中。
If you encounter disconnects, check these values first:The PhotonPeer.TrafficStatsGameLevel track how often you call DispatchIncomingCommands and SendOutgoingCommands when you turn the stats on (PhotonPeer.TrafficStatsEnabled = true). Check the values of LongestDeltaBetweenSending and LongestDeltaBetweenDispatching.
如果你发生了断开,首先检查这些值:当你打开统计时,TrafficStatsGameLevel 跟踪DispatchIncomingCommands和SendOutgoingCommands的调用,检查LongestDeltaBetweenSending和LongestDeltaBetweenDispatching的值。
This feature is implemented in C# based libraries and Android currently. More platforms will get this soon. PUN has a GUI component for this: PhotonStatsGui.
这个特性是用c#的基础库和Android实现的。更多的平台将很快也能实现这个。PUN有一个GUI组件:PhotonStatsGui。
Platform Specific Info 平台特定的信息
Unity3d and PUN
The Photon Unity Networking package implements the Service calls for you: PhotonHandler.Update calls Service in intervals.However, Unity won’t call Update while it’s loading scenes (or maybe even any assets).
Photon Unity 网络通信包为你实现服务的调用:PhotonHandler.Update定期调用服务。然而,Unity不会调用更新在加载场景的时候。
To keep the connection while loading scenes, you should set PhotonNetwork.IsMessageQueueRunning = false.
在加载场景的时候要保持连接,你必须设置PhotonNetwork.IsMessageQueueRunning为false
Pausing the message queue has two effects:
- A background Thread will be used to call SendOutgoingCommands while Update is not called. This keeps the connection alive, sending acknowledgements only but no events or operations (RPCs or sync updates). Incoming data is not executed by this Thread.
- All incoming updates are queued. Neither RPCs are called, nor are observed objects updated. While you change the level, this avoids calling RPCs in the previous one.
暂停消息队列有2个效果:
- 后台线程被用于调用SendOutgoingCommands时更新未被调用。这个保证了连接,发送确认但没有事件或操作。在这个线程里传入数据不被执行。
- 所有的传入更新被排队。RPCs被调用。也不是查看对象的更新,当你改变了级别时,这避免了调用RPCs在前面一个。
Unity3d
If you use the “plain” from our Unity Client SDKs, you implemented Service most likely in some MonoBehaviour Update method.To make sure Photon’s SendOutgoingCommands is called while you load scenes, implement a background Thread. This Thread should pause 100 or 200 ms between each loop, so it does not take away all performance.
如果你使用了Unity客户端SDK包中的plain,在一些MonoBehaviour Update 方法中你实现服务。去确保在你加载场景的时候Photon的SendOutgoingCommands被调用。现实一个后台线程。这个线程在循环间暂停100或200毫秒,所以它不会影响性能。
Application - LoadBalancing 负载均衡应用
This article explains the server-side implementation of the LoadBalancing application.
本文解释服务端是如何实现LoadBalancing应用程序的。
Content 目录
- Concept 概念
- Basic Workflow 基本工作流
- Master Server 主服务器
- Master: Handling Client Peers 处理客户端用户
- Master: Handling Game Server Peers 处理游戏服务端用户
- Game Server 游戏服务器
- Game Server: Handling Client Peers 处理客户端用户
- Game Server: Reporting Game States to the Master 报告游戏状态给主服务
- LoadBalancing implementation 负载均衡的实现
- Game Servers: Determine Workload 游戏服务:确定工作负荷
- Implementation Details 实现细节
- Master Server: LoadBalancing Algorithm 主服务:负载均衡算法
- Configuration and Deployment 配置和部署
- Deploying a Game Server 部署游戏服务
- Deploying a Master Server 部署主服务
Concept 概念
The LoadBalancing Application (literally) extends the
Lite Application. It provides all the well-known Lite functions - like Rooms, Events, Properties and so on, and adds a layer of scalability, that enables you to run the application on multiple servers.
LoadBalancing应用扩展了Lite应用。它提供了所有的Lite的功能。例如,房间、事件、属性等等,另外添加了一个可扩展的层,使得你可以运行这个应用在多个服务器上。
The basic setup is simple: There is always 1 Master Server and 1..N Game Servers.
这基本配置是这样的:总有一个主服务和1到N个游戏服务
The Master Server has two roles:
- keep track of the games that are currently open on the Game Servers.
- keep track of the workload of the connected Game Servers and assign peers to the appropriate Game Servers.
主服务有2个角色:
- 保持跟踪当前打开着的游戏服务
- 保持跟踪连接着的游戏服务器的工作负荷和分配用户到对应的游戏服务器
The Game Servers have two tasks as well:
- host the game rooms. They run a slightly modified version of the Lite Application for this purpose.
- regularly report their current work load and the list of their games to the Master Server.
游戏服务器有2个任务:
- 承载游戏房间。为此运行一个略做修改版本的Lite
- 定期报告他们当前的工作负荷和游戏列表到主服务器上
Basic Workflow 基本工作流
The workflow from a client side perspective is quite simple as well:
这个工作流从客户端角度来看非常的简单:
Clients connect to the Master Server, where they can join the lobby and retrieve a list of open games.
客户端连接到主服务器,他们可以加入大厅和获取一个游戏列表
When they call a CreateGame operation on the Master, the game is not actually created - the Master Server only determines the Game Server with the least workload and returns its IP to the client.
这时他们在主服务器上调用了CreateGame操作,这个游戏不是实际创建的,主服务器仅仅定义了游戏服务器的最少工作负荷和返回IP地址给客户端。
When clients call a JoinGame or JoinRandomGame operation on the Master, the Master looks up the Game Server on which the game is running, and returns its IP to the client.
当客户端在主服务器上调用JoinGame或JoinRandomGame操作时,主服务器查找运行着的游戏服务器,并返回IP地址给客户端
The client disconnects from the Master Server, connects to the Game Server with the IP it just received, and calls the CreateGame or JoinGame operation again.
客户端从主服务器断开,连接到获得的IP地址对应的游戏服务器,并再次调用CreateGame或JoinGame操作
From then on, everything works the same way as with a
Lite application.
从这以后,工作方式和Lite应用一样。
Master Server 主服务器
This section explains the Master Server implementation - see the LoadBalancing.MasterServer namespace in the \src-server\Loadbalancing\Loadbalancing.sln solution.
本节将解释主服务器的实现
The MasterApplication decides if incoming connections are originated by game clients (on the “client port”) or by game servers (on the “game server port”).
这主应用程序定义传入的连接是来自客户端还是来自游戏服务端。
Master: Handling Client Peers 处理客户端用户
The MasterClientPeer represents a client connection to the Master Server. The following operations are available for a MasterClientPeer:
这MasterClientPeer代表一个客户端连接到主服务器。以下操作可用于MasterClientPeer
The
Authenticate operation has only a dummy implementation. The developer should use it as a starting point to implement his own authentication mechanism:
验证操作只有一个伪实现。开发者需要使用它为开始点实现自己的身份验证机制
// MasterClientPeer.cs:
private
OperationResponse HandleAuthenticate(OperationRequest operationRequest)
{
OperationResponse response;
var
request =
new
AuthenticateRequest(
this
.Protocol, operationRequest);
if
(!OperationHelper.ValidateOperation(request, log,
out
response))
{
return
response;
}
this
.UserId = request.UserId;
// publish operation response
var
responseObject =
new
AuthenticateResponse { QueuePosition = 0 };
return
new
OperationResponse(operationRequest.OperationCode, responseObject);
}
The JoinLobby operation is used to add the MasterClientPeer to the AppLobby, which contains a GameList - the list of all open games on any Game Server. The peer receives an initial GameListEvent, which contains the current list of games in the GameList (filtered by the optional Properties of the JoinLobby operation):
加入大厅操作添加MasterClientPeer到大厅里,包含一个开放游戏的列表。用户接收到一个初始化的GameListEvent,包含一个当前的游戏列表:
Afterwards, a GameListUpdateEvent is send to the client at regular intervals, which contains the list of changed games (also filtered by the optional Properties of the JoinLobby operation). The client will receive the update events as long as it is connected.
之后,一个GameListUpdateEvent定期发送到客户端,它包含改变的游戏列表。客户端将接收更新事件只要它连接着。
// AppLobby.cs:
protected
virtual
OperationResponse HandleJoinLobby(MasterClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters)
{
// validate operation
var
operation =
new
JoinLobbyRequest(peer.Protocol, operationRequest);
OperationResponse response;
if
(OperationHelper.ValidateOperation(operation, log,
out
response) ==
false
)
{
return
response;
}
peer.GameChannelSubscription =
null
;
var
subscription =
this
.GameList.AddSubscription(peer, operation.GameProperties, operation.GameListCount);
peer.GameChannelSubscription = subscription;
peer.SendOperationResponse(
new
OperationResponse(operationRequest.OperationCode), sendParameters);
// publish game list to peer after the response has been sent
var
gameList = subscription.GetGameList();
var
e =
new
GameListEvent { Data = gameList };
var
eventData =
new
EventData((
byte
)EventCode.GameList, e);
peer.SendEvent(eventData,
new
SendParameters());
return
null
;
}
- JoinGame / JoinRandomGame 加入游戏/加入随机游戏
The JoinGame operation is called when a client wants to join an existing game that is listed in the AppLobby’s GameList, specified by a unique GameId. If the game exists and the peer is allowed to join, the Master Server returns the IP of the Game Server, on which the Game is actually running, to the client.
当客户端想加入到游戏列表中一个指定游戏ID的已存在的游戏中时JoinGame操作将被调用。如果游戏存在并允许用户加入,这主服务会返回游戏服务器的IP地址到客户端,在这个游戏服务器上游戏是真实运行的。
The Master Server also updates the GameState and adds the peer to its list of “joining peers”. It will be removed once it has joined the game on the Game Server (or after a certain timeout). This way, the Master Server can keep track of peers that are in transition between the Master and the Game Server.
主服务器也更新游戏状态和添加用户到 “joining peers”列表。当它加入到游戏服务器时将被移除。这样,主服务器可以保持跟踪那些传输在主服务器和游戏服务器两边的用户。
JoinRandomGame works in a similar way, except that the game is chosen by random by the Master Server and the GameId is returned to the client.
JoinRandomGame工作方式类似,除了,游戏的选择是通过主服务器随机得到的并要返回游戏ID到客户端。
The CreateGame operation is called when a client wants to create a new game. The Master Server determines a Game Server on which the new game will be created and returns the Game Server’s IP to the client. See the “LoadBalancing Algorithm” section for more details.
当客户端想要创建一个新的游戏时,CreateGame操作将被调用。这主服务器决定在一个游戏服务器上创建一个新的游戏并返回这个游戏服务器的IP地址到客户端。
In addition, a GameState object is created and added to the GameList, and the peer is stored as a “joining peer”.
此外,一个游戏状态对象被创建并添加到游戏列表,用户被储存为 “joining peer”。
Note that this GameState is only used to keep track of the games - the game itself only exists on a Game Server.
注意:游戏状态是仅仅被用于保持跟踪游戏服务器上存在的游戏。
Master: Handling Game Server Peers 处理游戏服务端用户
The Master Server always knows which Game Servers are available, how many games they host and how the current workload is.
主服务器总是知道哪些游戏服务器是可用的,多少游戏被托管,当前的工作负荷是多少。
To achieve this, each Game Server connects to the Master Server on startup. The MasterApplication maintains a GameServerCollection, in which IncomingGameServerPeers are stored.
为此,每个游戏服务器在启动时就要连接到主服务器。主应用程序包含了一个游戏集合,储存着传入游戏服务器用户。
The Game Server can only call one operation:
这游戏服务器能够调用的操作是:
- RegisterGameServer 注册游戏服务器
The Game Servers call the RegisterGameServer operation once after they are connected to the Master Server. The Game Server is added to the Master’s GameServerCollection and to its LoadBalancer (see the “LoadBalancing Algorithm” seection below). It will be removed from the GameServerCollection on disconnect.
游戏服务器调用RegisterGameServer操作在它连接到主服务器时。这游戏服务器添加到主服务的游戏服务器集合和负载均衡器中。当断开时它将被移除。
Check the “Game Server” section to see how the Game Server sends further updates about its games and its workload to the Master.
检查 “Game Server”,看看游戏服务器是如何发送进一步的游戏更新和它的工作负载到主服务器
Game Server 游戏服务器
This section describes the Game Server implementation. See the LoadBalancing.GameServer namespace in the \src-server\Loadbalancing\Loadbalancing.sln solution.
这节描述了游戏服务器的实现。
Game Server: Handling Client Peers 处理客户端用户
The Game Server is derived from the Lite application. As soon as a client has received a Game Server address from the Master, the client can call any operation on the Game Server that is available in Lite. The only difference is that we have separate operation codes for JoinGame and CreateGame on the Game Server, while Lite handles both with JoinGame.
游戏服务器来源于Lite应用。当客户端从主服务器接收到游戏服务器的地址时,客户端可以在游戏服务器上调用任意可用的操作。唯一不同的是在游戏服务器上我们有单独的操作码JoinGame和CreateGame,而Lite的处理都是JoinGame。
Game Server: Reporting Game States to the Master 报告游戏状态到主服务
The connection to the Master server is represented as an OutgoingMasterServerPeer in the Game Server. Once the connection is established, the Game Server calls a Register operation on the Master Server. Afterwards, the Game Server publishes all existing game states to the master server:
连接到主服务器将在游戏服务器上被表示为一个OutgoingMasterServerPeer。一旦连接被建立,游戏服务器将在主服务器上调用Register操作。然后,游戏服务器将公布所有的游戏状态到主服务器上。
// OutgoingMasterServerPeer.cs:
protected
virtual
void
HandleRegisterGameServerResponse(OperationResponse operationResponse)
{
// [...]
switch
(operationResponse.ReturnCode)
{
case
(
short
)ErrorCode.Ok:
{
log.InfoFormat(
"Successfully registered at master server: serverId={0}"
, GameApplication.ServerId);
this
.IsRegistered =
true
;
this
.UpdateAllGameStates();
this
.StartUpdateLoop();
break
;
}
}
}
This is done by sending a message to each Game that tells it to send it’s game state to the master:
这是发送一个信息到每个游戏,告知他们发送游戏状态到主服务器
// OutgoingMasterServerPeer.cs:
public
virtual
void
UpdateAllGameStates()
{
// [...]
foreach
(
var
gameId
in
GameCache.Instance.GetRoomNames())
{
Room room;
if
(GameCache.Instance.TryGetRoomWithoutReference(gameId,
out
room))
{
room.EnqueueMessage(
new
RoomMessage((
byte
)GameMessageCodes.ReinitializeGameStateOnMaster));
}
}
}
The Game handles this in the ProcessMessage method and calls the UpdateGameStateOnMaster method to send an UpdateGameEvent to the master:
游戏处理ProcessMessage方法和调用UpdateGameStateOnMaster方法去发送UpdateGameEvent到主服务器
protected
virtual
void
UpdateGameStateOnMaster(
byte
? newMaxPlayer =
null
,
bool
? newIsOpen =
null
,
bool
? newIsVisble =
null
,
object
[] lobbyPropertyFilter =
null
,
Hashtable gameProperties =
null
,
string
newPeerId =
null
,
string
removedPeerId =
null
,
bool
reinitialize =
false
)
{
// [...]
var
e =
this
.CreateUpdateGameEvent();
e.Reinitialize = reinitialize;
e.MaxPlayers = newMaxPlayer;
// [ ... more event data is set here ... ]
var
eventData =
new
EventData((
byte
)ServerEventCode.UpdateGameState, e);
GameApplication.Instance.MasterPeer.SendEvent(eventData,
new
SendParameters());
}
}
The game state is also updated on the master whenever a game is created, joined or left by a client or its properties are changed.
在游戏被用户创建、加入、离开或者属性被更改时,游戏状态就会被更新到主服务器上。
LoadBalancing implementation 负载均衡的实现
The next section describes how the game servers report their current work load to the Master Server, and how the Master Server determines the Game Server that is best suited to handle new CreateGame requests - the actual LoadBalancing algorithm.
下一节描述的是游戏服务器如何报告他们当前的工作负荷到主服务器上,主服务器是如何决定哪个游戏服务器处理新的CreateGame请求(实际的负载均衡算法)。
Game Servers: Determine Workload 确定工作负荷
See the LoadBalancing.LoadShedding namespace in the \src-server\Loadbalancing\Loadbalancing.sln solution for implementation details.
The Game Servers regularly report their current work load to the master server. The work load includes, for example: - CPU usage - Bandwidth usage - some Photon-specific values, like ENet + Business Queue Length, the average time the server spends on each request, etc. - Latency (when sending requests to itself)
游戏服务器定期报告他们当前的工作负荷到主服务器。这工作负荷包括:CPU的使用、带宽的使用、Photon的一些特殊值。
The most important (and easiest to understand) factor is the CPU load, so we will focus on the CPU load in this documentation.
最重要的也是最容易理解的是CPU的负载,所以在此我们聚焦于CPU
All these factors are summarized in a single value - the “Load Level” of a Game Server, which is reported to the Master.
所有的因素被概括在一个独立的值里,游戏的负载等级,这将被报告到主服务器上。
The lower the load level, the better is the Game Server suited to host new games.
较低的负载水平,是更适合于承载新游戏的游戏服务器。
Implementation Details 实现细节
The Game Server collects “Feedback” about the above-mentioned factors. There is one FeedbackController object for each factor - it consists of a FeedbackName and a FeedbackLevel:
游戏服务器收集上述因素的反馈“Feedback”,这为每个因素提供一个FeedbackController对象,它由一个FeedbackName和一个FeedbackLevel组成。
internal
enum
FeedbackName
{
CpuUsage,
Bandwidth,
TimeSpentInServer
}
public
enum
FeedbackLevel
{
Highest = 4,
High = 3,
Normal = 2,
Low = 1,
Lowest = 0
}
The DefaultConfiguration class defines the thresholds for each value - e.g., a server has the “lowest” FeedbackLevel up to 20% CPU usage, it reaches the “highest” FeedbackLevel at 90% CPU and so on.
DefaultConfiguration类定义了每个值的阀值,一个服务器有 “lowest” 级别的FeedbackLevel 提升20%的CUP使用,它达到“highest” 级别的FeedbackLevel 时CPU已经使用了90%
// DefaultConfiguration.cs:
internal
static
List<FeedbackController> GetDefaultControllers()
{
internal
static
List<FeedbackController> GetDefaultControllers()
{
var
cpuController =
new
FeedbackController(
FeedbackName.CpuUsage,
new
Dictionary<FeedbackLevel,
int
>
{
{ FeedbackLevel.Lowest, 20 },
{ FeedbackLevel.Low, 35 },
{ FeedbackLevel.Normal, 50 },
{ FeedbackLevel.High, 70 },
{ FeedbackLevel.Highest, 90 }
},
0,
FeedbackLevel.Lowest);
// [...]
}
These values can also be configured in a workload.config file. The LoadBalancing.LoadShedding.Configuration namespaces takes care of reading values from a config file, or applies the DefaultConfiguration if no config exists.
这些值可以被配置在workload.config文件中。 LoadBalancing.LoadShedding.Configuration命名空间关注于读取配置文件,或当配置文件不存在的时候应用默认值。
At regular intervals, the Game Server checks some Windows Performance Counters, sets the current values for all its FeedbackControllers and calculates a new “overall feedback”.
在一定的时间间隔内,游戏服务器会检查一些Windows性能计数器,为所有的FeedbackControllers设置当前值并计算一个新的“overall feedback”
This is done in the WorkloadController class:
WorkloadController类是这样的:
private
void
Update()
{
FeedbackLevel oldValue =
this
.feedbackControlSystem.Output;
if
(
this
.cpuCounter.InstanceExists)
{
var
cpuUsage = (
int
)
this
.cpuCounter.GetNextAverage();
Counter.CpuAvg.RawValue = cpuUsage;
this
.feedbackControlSystem.SetCpuUsage(cpuUsage);
}
// [...]
if
(
this
.timeSpentInServerInCounter.InstanceExists &&
this
.timeSpentInServerOutCounter.InstanceExists)
{
var
timeSpentInServer = (
int
)
this
.timeSpentInServerInCounter.GetNextAverage() + (
int
)
this
.timeSpentInServerOutCounter.GetNextAverage();
Counter.TimeInServerInAndOutAvg.RawValue = timeSpentInServer;
this
.feedbackControlSystem.SetTimeSpentInServer(timeSpentInServer);
}
this
.FeedbackLevel =
this
.feedbackControlSystem.Output;
Counter.LoadLevel.RawValue = (
byte
)
this
.FeedbackLevel;
if
(oldValue !=
this
.FeedbackLevel)
{
if
(log.IsInfoEnabled)
{
log.InfoFormat(
"FeedbackLevel changed: old={0}, new={1}"
, oldValue,
this
.FeedbackLevel);
}
this
.RaiseFeedbacklevelChanged();
}
}
If the overall feedback level changes, the OutgoingMasterServerPeer will report the new server state to the Master:
如果overall feedback等级改变,OutgoingMasterServerPeer 将报告新的服务器状态到主服务器:
public
void
UpdateServerState()
{
// [...]
this
.UpdateServerState(
GameApplication.Instance.WorkloadController.FeedbackLevel,
GameApplication.Instance.PeerCount,
GameApplication.Instance.WorkloadController.ServerState);
}
Master Server: LoadBalancing Algorithm 主服务:负载均衡算法
See the LoadBalancing.LoadBalancer class in the \src-server\Loadbalancing\Loadbalancing.sln solution for implementation details.
The Master Server stores the LoadLevel for each Game Server in the LoadBalancer class. It also holds an additional list of all the servers that currently have the lowest load level.
主服务器在LoadBalancer类中储存了每个游戏服务器的负载水平。它还附加处理一个包含所有目前最低负荷水平的服务器的列表。
Whenever a client calls the CreateGame operation, the Master Server fetches the address of a server with the lowest load level from the LoadBalancer and returns it to the client, which then connects to that server.
任何时候一个客户端调用CreateGame操作时,主服务器会从LoadBalancer中获取一个最低负荷的服务器地址返回给客户端并连接到服务器。
Configuration and Deployment 配置和部署
For demonstration purposes, the SDK contains a setup of 1 Master and 2 Game Servers in its deploy directory:
出于演示的目的,SDK中包含1个主服务器与2个游戏服务器在deploy目录下
-
- /deploy/LoadBalancing/Master
- /deploy/LoadBalancing/GameServer1
- /deploy/LoadBalancing/GameServer2
This setup is only intended for local development.
这个设置仅仅适用于本地开发
Deploying a Game Server 部署游戏服务
When you deploy your LoadBalancing project to a production server, you should not host 2 Game Server applications on one server. Remove all settings for “GameServer2” from the PhotonServer.config, and delete the /deploy/LoadBalancing/GameServer2 directory.
当你部署你的负载均衡项目到一个服务器上时,你不要承载2个游戏服务在一个服务器上,从PhotonServer.config中移除“GameServer2”的所有设置。并删除/deploy/LoadBalancing/GameServer2目录
You need to make sure that the Game Servers can register at the Master Server. Set the MasterIPAddress in the Photon.LoadBalancing.dll.config to the Master’s public IP.
你需要确保游戏服务器可以注册到主服务器上。在Photon.LoadBalancing.dll.config设置MasterIPAddress为主服务器的公共IP地址。
You also need to make sure that the game clients can reach the Game Servers. On each Game Server, you need to set the Public IP address of that Game Server. If you leave the value empty, the Public IP Address will be auto detected.
你还需要确保游戏客户端可以到达游戏服务器。在每个游戏服务器上,你需要设置公共的IP地址。如果你离开的值为空,这公共的IP地址将自动被检测到。
<
Photon.LoadBalancing.GameServer.GameServerSettings
>
<
setting
name
=
"MasterIPAddress"
serializeAs
=
"String"
>
<
value
>127.0.0.1</
value
>
<
setting
>
<
setting
name
=
"PublicIPAddress"
serializeAs="String”>
<
value
>127.0.0.1</
value
>
<!-- use this to auto-detect the PublicIPAddress: -->
<!-- <value> </value> -->
</
setting
>
<!-- [...] -->
</
Photon.LoadBalancing.GameServer.GameServerSettings
>
You can also use the Photon Control to set a Public IP.
你也可以使用Photon控制器来设置一个公共的IP。
Deploying a Master Server 部署主服务
You need to make sure that you only have 1 Master server: either remove all settings for the “Master” application from the PhotonServer.config on your Game Servers, or at least make sure that your game servers and clients all use the same IP to connect to the same, single Master server.
你需要确保你至少有一个主服务器,在你的游戏服务器上的PhotonServer.config中移除所有的 “Master”设置,或者至少确保你的游戏服务器和客户端使用的IP可以连接到独立的主服务器。
Otherwise, no special configuration is required on the master server.
不然,主服务器需要其他特殊的配置。
Matchmaking & Room Properties 匹配和房间属性
Getting into a room to play with (or against!) someone else is very easy with Photon. The workflow described here gets players into rooms without asking them to pick one (randomly) from a long list of rooms.
在Photon上加入一个多人的房间是非常容易的。下面的工作流描述了玩家随机的进入房间,而不是在房间的长列表中选择。
Random Matchmaking 随机匹配
If you just want to get some players into a room quickly, do the following:
如果你仅仅想快速加入一个多人房间:
- Try “Join Random”. This is an operation (named OpJoinRandom or JoinRandomRoom, depending on the API / platform).
-
- In best case, that’s it. Your client will join a room successfully.
-
- In worst case, no room is existing or no space is left in any room.
-
- If this doesn’t find a room instantly, create one!
-
- If you never show room names (and why should you), don’t make up a name. Let the server do this! Set null or “” as “room name” when calling OpCreateRoom. The room gets a guid which is unique.
-
- 如果你从不显示房间的名称,不要生成一个名字,让服务器去做这个,当调用OpCreateRoom时设置null或者“”作为你房间的名字,只有房间的guid是唯一的。
- Apply a value for “max players”. This way, the server eventually stops adding players.
-
- 应用一个值为 “max players”。这样,服务器将停止添加玩家。
- If your client is alone in the room (players == 1): Wait. Show a screen you’re waiting for opponents.
-
- 如果你的客户端是房间里唯一的玩家,请等待,屏幕上显示等待。
- When enough players are in the room, you might “start” the game. To keep new players out, “close” the room. The server stops filling up the room, even if it’s not full yet.
-
- 当有足够的玩家时,你可以开始游戏。保持房间关闭,新玩家无法再加入,服务器停止添加玩家到房间,即使房间是未满的。
- Note: When you close the room, there is a short time where players maybe are already on the way in. Don’t be surprised if someone joins even after closing.
-
- 注意:当你关闭房间时,有一个短暂的时间,玩家也许已经加入了。不要惊讶,即使是有人在关闭后加入。
Not so random matchmaking 非随机配对
Totally random matchmaking is not always something players will enjoy. Sometimes you just want to play a certain map or mode (two versus two, etc.).In Photon Cloud and Loadbalancing, you can set arbitrary room properties and filter for those in JoinRandom.
完全随机配对并不总是一些玩家想要的。有时候你只是想玩一个特定的地图或模式(两个与两个,等等)。在Photon云和Loadbalancing中,你可以为JoinRandom设置任意的房间属性和过滤器。
Room Properties and the Lobby 房间属性和大厅
Room properties are synced to all players in the room and can be useful to keep track of the current map, round, starttime, etc. They are handled as Hashtable with string keys. Preferably short keys.You can forward selected properties to the lobby, too. This makes them available for listing them and for random matchmaking, too. Not all room properties are interesting in the lobby, so you define the set of properties for the lobby on room creation.
房间属性是同步到所有房间中的玩家,可能是有用的去跟踪当前的地图、圆形、starttime等等。他们正在处理字符串键值的散列表,最好是短键。你可以为大厅提供属性。这使它们可提供房间清单和随机配对。并不是所有的房间属性都适合于大厅,所以你需要为大厅定义属性集合。
string
[] roomPropsInLobby = {
"map"
,
"ai"
};
Hashtable customRoomProperties =
new
Hashtable() { {
"map"
, 1 } };
CreateRoom(roomName,
true
,
true
, 4, customRoomProperties, roomPropsInLobby);
Note that “ai” has no value yet. It won’t show up in the lobby until it’s set in the game via Room.SetCustomProperties. When you change the values for “map” or “ai”, they will be updated in the lobby with a short delay, too.Keep the list short to make sure your clients performance doesn’t suffer from loading the list.
注意:“AI”还没有价值。它不会出现在大厅,直到它的设置在游戏大厅中通过 Room.SetCustomProperties 。当你改变值为“map”或“AI”,他们将被更新在大厅,略有短延迟。保持短列表来确保你的客户端性能不受来自加载列表的影响。
Filtering Room Properties in Join Random 在随机加入时过滤房间的属性
In JoinRandom, you could pass a Hashtable with expected room properties and max player value. These work as filters when the server selects a “fitting” room for you.
在 JoinRandom 时,你可以传递一个Hashtable,包括了已知的房间属性和最大玩家的值。当服务器选择一个“装配”房间给你时这些工作即为过滤器。
Hashtable expectedCustomRoomProperties =
new
Hashtable() { {
"map"
, 1 } };
JoinRandomRoom(expectedCustomRoomProperties, 4);
If you pass more filter properties, chances are lower that a room matches them. Better limit the options.Make sure you never filter for properties that are not known to the lobby (see above).
如果你传递更多的过滤器属性,房间匹配的选择更少。更好的限制这个选项,可确保你永远不会通过未知大厅的过滤器。
Application - Policy 策略应用
The Policy Application runs on Photon to send the crossdomain.xml. Web Player platforms like Unity Web Player, Flash and Silverlight request authorization before they contact a server.
Policy应用运行在Photon上发送crossdomain.xml。Web Player平台请求授权之前他们连接的服务器。
The actual file sent in response to a policy request is loaded from: deploy\Policy\Policy.Application\assets. There is a special file for Silverlight and one for Unity and Flash.
这文件发送在一个policy请求的响应中。这有一个特殊的文件对于Silverlight and one for Unity and Flash
Configuration 配置
The default setup from the SDK will start the Policy Application. The assemblies, which are in the deploy\Policy folder, and the configuration to load are:
SDK的默认设置是启动Policy应用的。这个程序集在deploy\Policy 文件夹中,配置如下:
<
Applications
>
<!-- [other Application nodes] -->
<!-- Flash & Silverlight Policy Server -->
<
Application
Name
=
"Policy"
BaseDirectory
=
"Policy\Policy.Application"
Assembly
=
"Policy.Application"
Type
=
"Exitgames.Realtime.Policy.Application.Policy"
>
</
Application
>
</
Applications
>
Ports 端口
Policy requests are usually done behind the scenes on TCP Port 843 and 943 (Silverlight), so these two have to be open as well. This includes Windows Security settings, other Firewalls in software and hardware. If you host Photon in a Cloud, check the Security settings of that as well. Amazon’s EC2 has Security Groups to restrict access to ports.
Policy 请求通常是在后台TCP端口843和 943(Silverlight),所以这两个端口必须也开启的。这包括 Windows安全设置,其他防火墙软件和硬件。如果你托管在一个Photon云,检查安全设置。亚马逊的EC2有安全组限制端口的访问。
Performance Tips 性能技巧
Performance is a vital part for providing a fluid and seamless integration of multiplayer components into your application. So we assembled a list of tips you should keep in mind when developing with Photon.
性能是多层组件与你的应用继承的至关重要的部分,以下是我们的建议。
Call Service Regularly 定期调用服务
The client libraries rely on regular calls to LitePeer.Service, to keep in touch with the server. Bigger pauses between the service calls could lead to a timeout disconnect, as the client can’t keep up the connection.
客户端类库是依赖于调用 LitePeer.Service,来保持与服务端的连接。更大的服务调用可能导致超时断开连接,因为客户端不能保持着连接。
Loading data is a common situation where less updates per second are done in the main loop. Make sure that service is called despite loading or the connection might suffer and be closed. If overlooked, this problem is hard to identify and reproduce.
加载数据是一个常见的情况,在主程序循环中每秒执行几次更新。确保服务是被调用加载或是连接受阻和断开连接。如果被忽略,这个问题是很难被识别的。
Updates vs. Bandwidth 更新与带宽
Ramping up the number of updates per second makes a game more fluid and up-to-date. On the other hand, used bandwidth might increase dramatically. Keep in mind that possibly each operation you call will create events for other players.
增加每秒更新的数量使得游戏是最新的和更加流畅,另一方面,使得带宽可能会显著的增长。记住可能每个操作你都可能会创建事件给其他玩家。
On a mobile client 4 to 6 operations per second are fine. In some cases even 3G devices use pretty slow networking implementations. In fact, it might be faster to send fewer updates per second.
在移动客户端,每秒4到6次更新是比较好的。在某些情况下3G设备使用的是相对慢的网络。事实上,他可能是每秒发送更少的更新。
PC based clients can go a lot higher. The target frame rate should be the limit for these clients.
基于PC的客户端可以高一些,对于这些客户端目标是限制帧率。
Producing and Consuming Data 产生与使用数据
Related to the bandwidth-topic is the problem of producing only the amount of data that can be consumed on the receiving end. If performance or frame rate don’t keep up with incoming events, they are outdated before they are executed.
相关的问题是数据传输的带宽。如果性能与帧率不被限制在发送事件中,他们在执行前就过时了。
In the worst case one side produces so much data that it breaks the receiving end. Keep an eye on the queue length of your clients while developing.
最坏的情况是数据破坏了接收端。注意客户端的队列长度。
Datagram Size 数据报大小
The content size of datagrams is limited to 1200bytes to run on all devices.
数据报在所有的设备上都被限制为1200字节。
Operations and events that are bigger than 1200bytes get fragmented and are sent in multiple commands. These become reliable automatically, so the receiving side can reassemble and dispatch those bigger data chunks when completed.
操作和事件的数据大于1200字节则分发在多个命令中。这样是可靠的,客户端可以重组数据报。
Bigger data “streams” can considerably affect latency as they need to be reassembled from many packages before they are dispatched. They can be sent in a separate channel, so they don’t affect the “throw away” position updates of a (lower) channel number.
更大的数据流可能造成延迟,造成他们需要重发,他们可以发送在独立的频道,这样他们不会影响一个较低频道的“throw away”的位置更新
Licenses 许可证
When starting Photon without a License, it will run with a 20 CCU limitation. You can also get a free License for 100 CCU in our download section. Find more information regarding Licenses below.
当启动Photon没有许可证时,它将运行20CCU。你也可以获得一个免费的许可证有100CCU,下面有更多的关于许可证的信息。
Free License 免费许可证
Photon can be used free of charge for up to 100 concurrent connections (players that are connected at the same time). You can use the free license even in commercial games and you can have any number of monthly active users.Get your free license here:
http://www.exitgames.com/download/photon .
Photon可以使用免费许可证,有100 CCU。您可以使用免费的许可证在商业游戏上,并且你可以有任意数量的每月活跃用户。
License Options 许可证操作
Once your project is growing past the 100 CCU limit of the free license, you can
buy Photon licenses in our online shop . Smaller indie developers get a discount.Existing licenses can be upgraded for the cost difference to the new one. Please contact us for that!
当你需要的CCU超过100个时。你可以购买Photon许可证。小型的开发团队可以获得一个discount.Existing许可证,许可证可以升级到新的,请联系我们。
Using the License 使用许可证
Photon uses .license files, which are provided by Exit Games. New license files are simply copied into the folder where the Photon executables are. Other .license files should be removed.PhotonControl and the Photon Server will read the files on start, so a new license currently requires a restart.
将Photon使用许可证文件简单的拷贝到Photon 执行文件所在的文件夹中。另外,许可证文件被移除时,Photon控制器和Photon服务器将读取新的许可证文件并重启服务。
Floating Licenses 动态许可证
Photon’s “Floating Licenses” are not bound to hardware or IP. They will run on any machine but need to contact our Floating License Monitor. On start, this HTTPs call must succeed. The following hourly contact has to succeed once every 24 hours. If the Monitor is not available for that long, Photon will shut down.
Photon的“Floating Licenses” 是不绑定硬件和IP的。他们可以运行在任意机器上,但是需要连接到我们的动态许可证监视器。在启动的时候。这HTTPs调用必须是成功的。之后每24小时需要成功连接一次。如果监视器长时间不可用,Photon将停止运行。
The License Monitor 许可证监视器
A Photon does not start without contacting the License Monitor, so we made sure that our License Monitor is highly available. To achieve this, we use the Google App Engine service, which is available 24/7.
一个Photon不连接许可证监视器不能启动,所以我们要确保我们的许可证监视器是高度可用的。实际上,我们使用的是谷歌的App引擎服务,可靠性24/7。
posted on 2015-11-25 11:37
思月行云 阅读(2518)
评论(0) 编辑 收藏 引用 所属分类:
Photon