Lite Lobby Chat Demo Lite大厅的聊天演示
This demo shows some of the things you can do with Photon’s “Lite Lobby” Application, purely from the client side. No server programming is involved here. The client is written in C# and uses the Unity Engine (which also has a free edition) as framework and GUI.
这个演示展示了一些你能用Photon的“Lite Lobby” 应用程序做的东西,纯粹从客户端的角度来说。这里没有涉及服务器编程。客户端是用c#编写的,并使用统一的引擎 (这也有一个免费版)作为框架和GUI。
Lite Lobby 聊天室演示部分,统一客户端使用SDK v6 2 0(和 )。
Content 内容
- Quick Walkthrough 快速演练
- Development Toolkit 开发工具包
- The Lobby 大厅
- The Chatroom 聊天室
- Using Properties for Usernames 使用户名作为属性
- Sending and Receiving Chat Messages 发送和接收聊天信息
- Lite Lobby Chat Demo Live Lite Lobby聊天室演示
Quick Walkthrough 快速演练
First off, let’s go through the screens. When you start, the demo will automatically connect to Photon on the local machine. If this fails (and whenever connection is lost), it will display an error message.
首先,让我们浏览一下屏幕。当你开始,演示将自动连接到本地机器上的Photon。 如果失败了,它将显示一条错误消息。
You will be asked to enter a name and “Proceed to Lobby”. A short text explains “debug mode” keys to control multiple clients. This will come in handy in a minute. Enter the lobby. As no one else is on your server, you should create a room. Enter a name and click “Enter”. The next screen is a simple chat: Messages are entered at the bottom, users listed on the right side and there is a button to leave. Now, more users would be nice.
你将被要求输入一个名称和“Proceed to Lobby”。 一个短文本提供“调试模式”的key来控制多个客户端。这将在一分钟内派上用场。进入大厅。因为没有人是在你的服务器上, 您应该创建一个房间。输入一个名称,然后单击“进入”。接下来的屏幕是一个简单的聊天:在底部输入消息,用户列表在右侧,还有一个离开按钮。现在,用户越多越好。
Of course, you could start the demo several times to get more people in the chat. To make things easier, you can instantiate a chat client by pressing insert and switch between the simulated clients with Page Up and page down key.
当然,你可以开始演示几次,让更多的人在聊天。为了让事情简单,你可以实例化一个聊天客户端,通过插入和切换模拟客户页面。
Press: insert, then page down. You will see the name input screen once more. Keep in mind: now the input controls another client. Choose another name and press the button and now you will see a button. The lobby will create a button for each room that’s in use. You can join that room or create another.
按:插入,然后下一页。您将看到名字输入屏幕越来越多。记住:现在输入控制其他的客户端。选择另一个名称和按下按钮,现在你会看到一个按钮。大厅将为每个房间创建一个按钮。你可以加入这个房间或创建另一个。
Development Toolkit 开发工具包
In this demo (and in general) things get easier to develop if you don’t have to run a game many times to test multiplayer. For that reason, we will create clients that maintain their connection and state but not their input. Input is something we do in a central place and apply to an active client.
在这个演示里(一般来说)事情变得更容易发展,如果你不要运行游戏的多人多次测试。出于这个原因,我们将创建客户端,让他们保持连接和状态但不是他们输入的。输入是我们在一个中心位置和适用于一个活跃的客户端。
The PhotonClient is the lowest level we can automate a client. This can be re-used in other applications and will simply keep a state and connection. Extending it is the ChatPhotonClient, which adds chat-related states, a few methods and most of all, it handles our custom chat events.
这个PhotonClient是最低级的可以自动化的客户端。这可以被重用在其他应用程序和将简单地保持状态和连接。ChatPhotonClient扩展了它,从而增加了聊天相关的状态,一些方法和最重要的是,它处理我们定制的聊天事件。
To make this run in Unity, we need to attach the ChatGUI script and an initial ChatPhotonClient to any game object. We used the empty object “PhotonScripts”. The public values of the initial ChatPhotonClient let us edit the server before we start the demo. While running, we can check each client’s state, name, room and control if we want it’s debug out in the console. This setup makes things transparent and we might even save a few debugging sessions.
为了让这个运行在Unity,我们需要将ChatGUI脚本和一个最初的ChatPhotonClient放入任何的游戏对象。我们使用了空对象“PhotonScripts”。在我们演示开始前初始化ChatPhotonClient公共的值让我们编辑这服务器。在运行时,我们可以检查每个客户端的状态、名称、房间和控制,如果我们希望在控制台上调试它。这种设置使得事情更透明,我们甚至可能节省几个调试会话。
Missing in the client library is a special peer class for Lite Lobby, which is more or less treated like any game you would develop. To fill the gap, we implement a LiteLobbyPeer class, which is used instead of the LitePeer. Once you create your own operations for your game, you should also create a fitting “myGamePeer” class.
对于大厅来说在客户端库中缺少的是一个特殊的peer类,这是或多或少被对待像任何你会开发的游戏。填补这种差距,我们实现了一个LiteLobbyPeer类,它是用来代替 LitePeer的。一旦你为你自己的游戏创建你自己的操作,你还应该创建一个恰当的“myGamePeer”类。
The Lobby 大厅
The Lite Lobby Application defines two types of rooms: games and lobbies. But there is only one operation to join a room and there is no parameter to choose. The secret is that Lite Lobby creates a lobby when the roomname to join ends on “_lobby” and a game room in any other case. An optional parameter can be used to tell a game room to update a certain lobby. Read about the server side on page:
Lite Lobby Concepts .
Lite Lobby 应用程序定义了两种类型的房间:游戏和游戏大厅。但只有一个操作来加入一个房间,没有参数选择。这个是因为Lite大厅创建一个大厅同时roomname加入“_lobby”的最后,和创建一个游戏房间在任何其他情况下。一个可选的参数可用于告诉一个游戏房间更新一个确定的大厅。阅读服务器端页面: Lite大厅概念 。
The additional features of Lite Lobby are currently not implemented by the client library. This where our LiteLobbyPeer class comes to use. It defined the needed operation- and event-codes and the codes for parameters and event-content already.
通过客户端库,Lite大厅的额外特性是目前尚未实现的。这里我们的LiteLobbyPeer类将拿来使用。它已经定义了所需的操作、事件代码、代码参数、事件内容。
As all rooms are created on the fly, it’s up to the client to create lobbies, too. This demo uses a single lobby called “chat_lobby” but you could also add a selection-screen for a list of lobbies. As Lite Lobby does not support listing of lobbies out of the box, you could either add a hard-coded list to select or change the server code to support it.
因为所有的房间都是动态创建的,促使它的客户端也去创建大厅。该示例使用单个大厅称为“聊天大厅”,但是你还可以添加一个选择屏幕列表的大厅。因为Lite Lobby不支持大厅清单,你可以补充一个硬编码的列表,选择或更改服务器代码来支持它。
The Chatroom 聊天室
We already noticed that the operation Join creates lobbies and games alike. When we create a room, we might set a lobby to report to. LiteLobbyPeer implements this version of operation Join as OpJoinFromLobby. It wraps up the required parameters and even allows us to set actor properties (but not room properties, which we don’t use in this demo anyways).
我们已经注意到这个Join操作创建大厅和游戏。当我们创建一个房间,我们可能会设置一个大厅报告。LiteLobbyPeer实现了这个版本的Join操作作为OpJoinFromLobby。它包括了必需的参数,甚至允许我们设置actor的属性。
public
virtual
short
OpJoinFromLobby(
string
gameName,
string
lobbyName, Hashtable actorProperties,
bool
broadcastActorProperties)
{
if
(
this
.DebugOut >= DebugLevel.ALL)
{
this
.Listener.DebugReturn(DebugLevel.ALL, String.Format(
"OpJoin({0}/{1})"
, gameName, lobbyName));
}
// All operations get their parameters as key-value set (a Hashtable)
Hashtable opParameters =
new
Hashtable();
opParameters[(
byte
)LiteLobbyOpKey.RoomName] = gameName;
opParameters[(
byte
)LiteLobbyOpKey.LobbyName] = lobbyName;
if
(actorProperties !=
null
)
{
opParameters[(
byte
)LiteOpKey.ActorProperties] = actorProperties;
if
(broadcastActorProperties)
{
opParameters[(
byte
)LiteOpKey.Broadcast] = broadcastActorProperties;
}
}
return
OpCustom((
byte
)LiteLobbyOpCode.Join, opParameters,
true
);
}
Inside the chatroom, we want to know who is there and we want to send messages to everyone in the room.
在聊天室,我们想知道有谁和我们想要发送信息给房间里的每一个人。
Using Properties for Usernames 使用户名作为属性
In the Lite Lobby application, properties are key-value sets you can use to describe rooms and actors. This demo uses actor properties to store usernames. Each client sets its name in the Join operation and uses broadcast to send the new name to the others.
在Lite大厅应用程序中,属性是一个键-值集,你可以使用它来描述房间和玩家。该示例使用玩家属性来存储用户名。每个客户端设置它的名称在加入操作和使用广播发送新名称到其他人那。
public
void
JoinRoomFromLobby(
string
roomName)
{
this
.RoomName = roomName;
this
.ChatState = ChatStateOption.JoiningChatRoom;
this
.ActorProperties =
new
Hashtable();
Hashtable props =
new
Hashtable() { { ChatActorProperties.Name,
this
.UserName } };
this
.Peer.OpJoinFromLobby(
this
.RoomName,
this
.LobbyName, props,
true
);
}
A new user won’t get a join event for players who were in the room before, so we use GetProperties to get everyone’s name. It’s result is the complete property set at the time being. From now on, we just add properties when someone joins or remove them on leave.
一个新用户不会得到房间里其他玩家之前的加入操作信息,所以我们使用GetProperties获得每个人的名字。它的结果是在任何时候都有完整的属性集。从现在起,我们只是添加属性在有人加入或离开时移除它们。
switch
(eventCode)
{
// this client or any other joined the room (lobbies will not send this event but chat rooms do)
case
(
byte
)LiteEventCode.Join:
this
.DebugReturn(SupportClass.HashtableToString(photonEvent));
// update the list of actor numbers in room
this
.ActorNumbersInRoom = (
int
[])photonEvent[(
byte
)LiteOpKey.ActorList];
// update the list of actorProperties if any were set on join
Hashtable actorProps = photonEvent[(
byte
)LiteOpKey.ActorProperties]
as
Hashtable;
if
(actorProps !=
null
)
{
this
.ActorProperties[originatingActorNr] = actorProps;
}
break
;
// some other user left this room - remove his data
case
(
byte
)LiteEventCode.Leave:
// update the list of actor numbers in room
this
.ActorNumbersInRoom = (
int
[])photonEvent[(
byte
)LiteOpKey.ActorList];
// update the list of actorProperties we cache
if
(
this
.ActorProperties.ContainsKey(originatingActorNr))
{
this
.ActorProperties.Remove(originatingActorNr);
}
break
;
Sending and Receiving Chat Messages 发送和接收聊天信息
The Lite Lobby Application gives us rooms and events and a way to raise them. In our simple chat demo we don’t need the server to understand what the clients are talking about.
Lite应用提供给我们房间、事件和一个方法去触发他们。在我们的简单聊天演示中我们不需要服务器来理解客户正在谈论什么。
Custom events are defined by the client and handled by the clients. The server just passes them on when a client calls RaiseEvent. In our case, we select a free event code and add in our text line.
通过客户端定义和处理自定义事件。服务器只是传递他们当客户端调用RaiseEvent的时候。在我们的例子中,我们选择一个事件代码并添加在我们的文本行中。
public
void
SendChatMessage(
string
line)
{
Hashtable chatEvent =
new
Hashtable();
// the custom event's data. content we want to send
chatEvent.Add((
byte
)ChatEventKey.TextLine, line);
// add some content
this
.Peer.OpRaiseEvent((
byte
)ChatEventCode.Message, chatEvent,
true
);
// call raiseEvent with our content and a event code
// because Photon won't send this event back to this client, we will add the chat line locally
this
.ChatLines.AppendLine(String.Format(
"{0}: {1}"
,
this
.UserName, line));
}
Lite Lobby will create an event from our data and adds the origin to it. Other users in a room will receive the event and know who sent it. To keep things lean, this is done by the actorNumbers.
Lite大厅将创建一个事件来自我们的数据并添加它。其他用户在一个房间里将接收到事件和知道是谁发送来的。保持精简,这是通过actorNumbers可以做的。
Our sent text line is the data of our custom event. We fetch the name and put the line to the chat buffer.
我们发送的文本行是我们自定义事件的数据。我们取这个名字并把它放入聊天缓冲区。
Lite Lobby Chat Demo Live Lite Lobby聊天演示
This is a live version of the Lite Lobby Chat Demo from the Unity Client SDK (v6.2.0 and up).If this page is loaded more than once, you will see a list of rooms in use and chat users inside those.
这是一个可用的Lite大厅聊天演示来自Unity客户端SDK(v6 2 0)。如果这个页面被加载不止一次,您将看到一列个房间列表被使用和聊天的用户在那里面。
An App From Scratch 一个应用程序从头开始
This tutorial will try to help you understand how to build an application from scratch.(aka “Blank Server Tutorial”)
本教程将试图帮助您理解如何从头构建应用程序(又名空白服务器教程)
Build a Simple Chat Server From the Scratch In 10 Minutes
10分钟从头开始构建一个简单的聊天服务器
Hint: This tutorial is thought as a first step in understanding the basics of the main concepts in Photon Application and Peer. For most developers that at some point will use rooms we recommend to start with with an application that inherits from Lite.Application.
提示:本教程是作为理解Photon应用和Peer中的主要概念的第一步。对于大多数开发人员来说,当使用房间时,我们建议应用程序继承自Lite应用程序。
- Download and unzip the SDK
- Create a new class library project ‘ChatServer’
- Add references to ExitGamesLibs.dll, Photon.SocketServer.dll and PhotonHostRuntimeInterfaces.dll
- Create a new class ‘ChatServer’ and inherit from ‘Photon.SocketServer.ApplicationBase’:
- 下载并解压缩SDK
- 创建一个新的类库项目”ChatServer”
- 添加引用ExitGamesLibs.dll, Photon.SocketServer.dll and PhotonHostRuntimeInterfaces.dll
- 创建一个新类的ChatServer和继承 Photon.SocketServer.ApplicationBase
using
Photon.SocketServer;
public
class
ChatServer : ApplicationBase
{
protected
override
PeerBase CreatePeer(InitRequest initRequest)
{
}
protected
override
void
Setup()
{
}
protected
override
void
TearDown()
{
}
}
- Create a new class ‘ChatPeer’ and inherit from ‘Photon.SocketServer.PeerBase’:
- 创建一个新的类ChatPeer并继承自Photon.SocketServer.PeerBase
using
Photon.SocketServer;
using
PhotonHostRuntimeInterfaces;
public
class
ChatPeer : PeerBase
{
public
ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer)
:
base
(protocol, unmanagedPeer)
{
}
protected
override
void
OnDisconnect()
{
}
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
}
}
- Return a new instance of ChatPeer at ChatServer.CreatePeer:
- 在ChatServer.CreatePeer返回一个新的ChatPeer实例
protected
override
PeerBase CreatePeer(InitRequest initRequest)
{
return
new
ChatPeer(initRequest.Protocol, initRequest.PhotonPeer);
}
- The server configuration looks like this:
- 服务器配置文件如下:
<
Applications
Default
=
"ChatServer"
>
<
Application
Name
=
"ChatServer"
BaseDirectory
=
"ChatServer"
Assembly
=
"ChatServer"
Type
=
"ChatServer"
/>
</
Applications
>
This config requires that the server binaries are located under ‘deploy/ChatServer/bin’ and that class ChatServer does not belong to a namespace.
这种配置要求服务器二进制文件位于下 deploy/ChatServer/bin,这类ChatServer不属于一个名称空间。
- Create a new console project for a chat client
- Add reference to PhotoDotNet.dll to the new project
- Client code:
- 创建一个新的控制台项目作为聊天客户端
- 添加引用PhotoDotNet.dll
- 客户端代码:
using
System;
using
System.Collections.Generic;
using
System.Text;
using
ExitGames.Client.Photon;
public
class
ChatClient : IPhotonPeerListener
{
private
bool
connected;
public
static
void
Main()
{
var
client =
new
ChatClient();
var
peer =
new
PhotonPeer(client,
true
);
// connect
client.connected =
false
;
peer.Connect(
"127.0.0.1:4530"
,
"ChatServer"
);
while
(!client.connected)
{
peer.Service();
}
var
buffer =
new
StringBuilder();
while
(
true
)
{
peer.Service();
// read input
if
(Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey();
if
(key.Key != ConsoleKey.Enter)
{
// store input
buffer.Append(key.KeyChar);
}
else
{
// send to server
var
parameters =
new
Dictionary<
byte
,
object
> { { 1, buffer.ToString() } };
peer.OpCustom(1, parameters,
true
);
buffer.Length = 0;
}
}
}
}
public
void
DebugReturn(DebugLevel level,
string
message)
{
Console.WriteLine(level +
": "
+ message);
}
public
void
OnEvent(EventData eventData)
{
Console.WriteLine(
"Event: "
+ eventData.Code);
if
(eventData.Code == 1)
{
Console.WriteLine(
"Chat: "
+ eventData.Parameters[1]);
}
}
public
void
OnOperationResponse(OperationResponse operationResponse)
{
Console.WriteLine(
"Response: "
+ operationResponse.OperationCode);
}
public
void
OnStatusChanged(StatusCode statusCode)
{
if
(statusCode == StatusCode.Connect)
{
this
.connected =
true
;
}
else
{
Console.WriteLine(
"Status: "
+ statusCode);
}
}
}
- If we now start the server the client will be able to connect and to send text messages, but the server logic to process these text messages is still missing. To verify that the message was received we answer with an OperationResponse at ChatPeer.OnOperationRequest:
- 如果我们开始这服务器,客户端将可能去连接并发送文本信息,但是服务器处理这些消息仍然会有丢失。为了验证这些信息是被接收到了,我们会在ChatPeer.OnOperationRequest响应一个OperationResponse:
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
var
response =
new
OperationResponse(operationRequest.OperationCode);
this
.SendOperationResponse(response, sendParameters);
}
The chat client should now print the event code and the chat message.
这聊天客户端会打印事件代码和聊天信息
- Next thing we want to do is to receive the chat messages on other clients. We implement a publish/subscribe pattern:
- 接下来我们要做的就是在其他客户端接收聊天信息。我们实现了一个发布/订阅模式:
using
System;
using
Photon.SocketServer;
using
PhotonHostRuntimeInterfaces;
public
class
ChatPeer : PeerBase
{
private
static
readonly
object
syncRoot =
new
object
();
public
ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer)
:
base
(protocol, unmanagedPeer)
{
lock
(syncRoot)
{
BroadcastMessage +=
this
.OnBroadcastMessage;
}
}
private
static
event
Action<ChatPeer, EventData, SendParameters> BroadcastMessage;
protected
override
void
OnDisconnect()
{
lock
(syncRoot)
{
BroadcastMessage -=
this
.OnBroadcastMessage;
}
}
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
var
@
event
=
new
EventData(1) { Parameters = operationRequest.Parameters };
lock
(syncRoot)
{
BroadcastMessage(
this
, @
event
, sendParameters);
}
var
response =
new
OperationResponse(operationRequest.OperationCode);
this
.SendOperationResponse(response, sendParameters);
}
private
void
OnBroadcastMessage(ChatPeer peer, EventData @
event
, SendParameters sendParameters)
{
if
(peer !=
this
)
{
this
.SendEvent(@
event
, sendParameters);
}
}
}
If you now start two clients they should be able to exchange messages.
如果你现在开始运行2个客户端,他们可以交换信息。
Adding Operations 添加操作
In many cases, just sending events is not enough for a game. If you want to provide authorization, persistency or game-specific Operations, it’s time to extend Lite (or LiteLobby).This page shows two ways to implement new Operations and how to use them. Our sample Operation expects a message from the client, checks it and provides return values.
在许多情况下,只是发送事件是不够的,对于一个游戏。如果你想要提供授权、持续性或游戏操作,它是需要扩展Lite(或LiteLobby)。这个页面显示了两个方法来实现新业务和如何使用他们。我们的示例操作预计消息来自客户端,检查它并提供返回值。
Server-Side, Simple Variant 服务端,简单的变体
Let’s start on the server side, in the Lite Application. This variant is not very elegant but simple.
让我们在服务端开始,在Lite应用中,这变体不是很优雅但是很简单
|
//Server SDK, LitePeer.cs
public void OnOperationRequest(OperationRequest request)
{
// handle operation here (check request.OperationCode)
switch (request.OperationCode)
{
case 1:
{
var message = ( string )request.Params[100];
if (message == "Hello World" )
{
// received hello world, send an answer!
var response = new OperationResponse(request, 0, "OK" , new Dictionary< short , object > { { 100, "Hello yourself!" } } );
this .PhotonPeer.SendOperationResponse(response);
}
else
{
// received something else, send an error
var response = new OperationResponse(request, 1, "Don't understand, what are you saying?" );
this .PhotonPeer.SendOperationResponse(response);
}
break ;
}
}
}
|
The above code first checks the called OperationCode. In this case, it’s 1. In Photon, the OperationCode is a shortcut for the name/type of an Operation. We’ll see how the client call this below.
上面的代码首先检查OperationCode的调用。在这种情况下,它是1。在Photon,OperationCode是一个操作的名称或类型的快捷方式。下面我们将看到客户端是如何调用这个。
If Operation 1 is called, we check the request parameters. Here, parameter 100 is expected to be a string. Again, Photon only uses byte-typed parameters, to keep things lean during transfer.
如果操作1被调用,我们检查请求参数。在这里, 参数100预计是一个字符串。再一次,Photon只使用字节输入参数,为了保持精简传递。
The code then checks the message content and prepares a response. Both responses have a returnCode and a debug message. The returnCode 0 is a positive return, any other number (here: 1) could mean an error and needs to be handled by the client.
然后该代码检查消息内容和准备一个响应。两端的响应有一个returnCode和调试消息。这个returnCode 0是一个积极的返回值,任何其他号码(这里是:1)可能意味着一个错误和需要由客户端处理。
Our positive response also includes a return value. You could add more key-value pairs, but here we stick to 100, which is a string.
我们积极响应还包括一个返回值。你可以添加更多的键-值对,但是这里我们坚持到100,这是一个字符串。
This already is a complete implementation of an Operation. This is our convention for a new Operation, which must be carried over to the client to be used.
这已经是一个完整的操作实现。这是我们约定的新操作,必须被传递到客户端使用。
Client Side 客户端
Knowing the definition of above’s Operation, we can call it from the client side. This client code calls it on connect:
知道了上面操作的定义,我们可以从客户端调用。客户端代码调用它在连接时候:
public void PeerStatusCallback( int returnCode)
{
// handle peer status callback
switch ((ReturnCode)returnCode)
{
case ReturnCode.Connect:
// send hello world when connected
var parameter = new Hashtable { { ( byte )100, "Hello World" } };
this .Peer.OpCustom(1, parameter, true );
break ;
//[...]
|
|
As defined above, we always expect a message as parameter 100. This is provided as parameter Hashtable. We make sure that the parameter key is not mistaken for anything but a byte, or else it won’t send. And we put in: “Hello World”.
如上所述,我们总是期待一个消息作为参数100。 这是提供的参数散列表。我们确保参数key没有误认为是任何别的东西,否则它不会发送。我们输出:“Hello World”。
The PhotonPeer (and LitePeer) method OpCustom expects the OperationCode as first parameter. This is 1. The parameters follow and we want to make sure the operation arrives (it’s essential after all).
这个PhotonPeer(和LitePeer)的方法OpCustom预计OperationCode作为第一个参数。这里是1。这个参数遵循并是我们想要的来确保操作到达。
Aside from this, the client just has to do the usual work, which means it has to call PhotonPeer.Service in intervals.
除了这个,客户端只需要做平时的工作,这意味着它必须调用PhotonPeer.Service在时间间隔的时候。
Once the result is available, it can be handled like this:
一旦结果是可用的,它可以处理像这样:
|
public void OperationResponse(OperationResponse operationResponse)
{
// handle response by code (action we called)
switch (operationResponse.OperationCode)
{
// out custom "hello world" operation's code is 1
case 1:
// OK
if (operationResponse.ReturnCode == 0)
{
// show the complete content of the response
Console.WriteLine(operationResponse.ToStringFull());
}
else
{
// show the error message
Console.WriteLine(operationResponse.DebugMessage);
}
break ;
}
}
|
Server Side, Advanced Version 服务端,高级版本
The preferred way to handle Operations would be to create a class for it. This way it becomes strongly typed and issues due to missing parameters are handled by the framework.
首选的办法是为处理操作创建一个类。这种方式是强类型的和失踪的参数由framework来处理。
This is the definition of the Operation, its parameters, their types and of the return values:
这是操作的定义,它的参数,它的类型和返回值:
|
//new Operation Class
namespace MyPhotonServer
{
using Photon.SocketServer;
using Photon.SocketServer.Rpc;
public class MyCustomOperation : Operation
{
public MyCustomOperation(OperationRequest request) : base (request)
{
}
[RequestParameter(Code = 100, IsOptional = false )]
[ResponseParameter(Code = 100, IsOptional = false )]
public string Message { get ; set ; }
}
}
|
With the Operation class defined above, we can map the request to it and also provide the response in a strongly typed manner.
上面是操作类的定义,我们可以请求映射到它和在一个强类型的方式中提供响应
public
void
OnOperationRequest(OperationRequest request)
{
switch
(request.OperationCode)
{
case
1:
{
var
operation =
new
MyCustomOperation(request);
if
(peer.ValidateOperation(operation) ==
false
)
{
// received garbage, send an error
var
response =
new
OperationResponse(request, 1,
"That's garbage!"
);
this
.PhotonPeer.SendOperationResponse(response);
return
;
}
if
(operation.Message ==
"Hello World"
)
{
// received hello world, send an answer!
operation.Message =
"Hello yourself!"
;
OperationResponse response = operation.GetOperationResponse(0,
"OK"
);
this
.PhotonPeer.SendOperationResponse(response);
}
else
{
// received something else, send an error
var
response =
new
OperationResponse(request, 1,
"Don't understand, what are you saying?"
);
this
.PhotonPeer.SendOperationResponse(response);
}
break
;
}
}
}
What's in Photon 3 Photon3是什么
1. High Performance S2S API (Native/C++) 高性能的S2S API
A while back we introduced the TCPClient a managed class better suited for server to server communication than the standard client library. The TCPClient had two disadvantages: First the programming model on both ends of the connection was different (TCPClient on one side, peer on the other), second it was designed for low-bandwidth communication. With this release Photon can setup a connection to other Photon instances leveraging the networking power of the Photon Core while allowing to use the same programming model using peers on both ends of the connection.
不久前我们介绍了托管类TCPClient比标准客户端类库更适合于服务器到服务器的通信。这个TCPClient有两个缺点:第一,编程模型连接的两端都是不同的(TCPClient在一侧,peer在另一侧),第二,是专为低带宽通信。这个版本的Photon可以设置一个连接其它Photon实例,利用Photon Core,同时允许peer在连接的两端使用相似的编程模型。
2. Load Balancing 负载均衡
With Photon Load Balancing, developers are enabled to create scalable multi-node solutions. It includes out of the box a master server hosting the lobby and game servers to host the active games. The Lite Lobby Application is delivered with a set of matchmaking methods and a load balancing component that distributes the load (where to create new games).
Photon负载均衡,开发人员能够创建可扩展的多节点的解决方案。它包括了一个主服务来托管大厅和多个游戏服务来托管游戏。这个Lite Lobby应用程序是通过设置配对方法和使用负载均衡组件分配负载来进行交付工作的。
3. Event Caching 事件缓存
So far, Lite had “Properties” for rooms and actors to store values for players who join later on. They are a nice idea but a bit clumsy to use: Whenever something has to be stored, you had to set properties. In all other cases, you send events.The new Lite Application combines both and gives you caching for events. Per player and event, you could cache the values in it. If anyone joins, the events are sent immediately, like they were just raised.
到目前为止,Lite用房间和玩家的属性为那些加入游戏之后的玩家来存储值。这是一个好主意,但是使用起来有点笨拙:无论什么东西被存储,您必须设置属性。在其他情况下,您发送事件。新的Lite应用程序结合了给予你的高速缓存事件。每个玩家和事件,您都可以缓存值在这里面。如果某人加入,立即发送事件,他们仅仅会被触发。
4. Extended Data Type Support 支持数据类型的扩展
Photon now supports more data-types than ever: Null values, dictionaries, arrays of objects (each with its own type) and even custom classes can be integrated and used in events.
Photon现在比以往任何时候都更加支持数据类型,例如:Null值、字典、数组对象(每个都有其自己的类型)和自定义被集成并用于事件里的类。
5. The Peer has new properties Peer的新属性
NetworkProtocol, LocalIP, LocalPort.
6. Up to now you configured a global default application 为你配置一个全局的默认应用程序
Now it is also possible to define a default application per listener.
现在还可以定义一个默认的应用程序监听器。
Changed 改变
1. Lite and Lite Lobby Code-Cleanup Lite和Lite Lobby代码清理
Event-, Operation- and Parameter-Codes are cleaned up. Everything was reassigned and the predefined codes should be out of your way now. There is an extra page which lists each old and new value.
事件、操作和参数编码被清理干净。 现在是一切重新分配和预定义的代码应该出现了。有一个额外的页面,其中列出了每个旧的和新的值。
2. Type of Codes 代码的类型
We decided to keep using bytes as type for codes. It’s just effective and lean. If needed, any operation or event could get sub-codes (not supported out of the box).The ReturnCode for operation responses was of type int. This is now short-typed. Not extremely lean. But short.
我们决定继续使用bytes作为代码的类型。这是有效的和精益的。如果需要,可以得到任何操作或事件的子码。这个ReturnCode对于操作响应是int类型的。这是短类型。不是非常精简,但是短。
3. Protocol Cleanup 协议清理
We cleaned up the protocol for Photon 3. This is next to invisible for you, but has some benefits. Most of all, the new protocol does no longer hard-code any codes. Any code for operations, results, parameters or events can be defined by your code.
我们为Photon3清理了协议。这对于你来说几乎是看不见的,但有一些好处。最重要的是用新的协议来做,不再需要硬编码任何代码。任何代码的操作、结果、 参数或事件可以根据你的代码来定义。
4. Error Handling 错误处理
The error handling was re-designed to
错误处理是被重新设计为
- always call the unhandled exception event handler
- it is no longer required to initialize the logging facade
- introduced three policies for unhandled exceptions: ignore, restart application, end process
- consistent behavior: before unhandled exceptions in threads controlled by Photon where logged and then ignored, those in other threads usually terminated the process.
- 总是调用未处理的异常事件处理
- 不再需要初始化 logging facade
- 对于未处理的异常有以下三个策略:忽略、 重启应用程序、终止进程
- 一致的行为:在线程通过Photon登录和忽略来控制未处理的异常之前,其他线程通常会终止这个进程。
Note: due to a bug in 2.6 sometimes exceptions where “transformed” into ThreadAbortException or the application was unloaded without being loaded again (this is also fixed with 2.6.28).
注意:由于一个错误(在2.6)有时有例外,“transformed”到 ThreadAbortException 或应用程序被卸载不会再次被加载(这也是固定与2.6.28)。
5. Server SDK Changes (breaking) 服务器SDK改变(破坏)
- Peer classes are now required to inherit from class ‘PeerBase’. The former interface IPeer and class PhotonPeer were removed. Take a thorough look at the PeerBase class definition as it is a central piece of your application.
- Applications are now required to inherit from class ‘ApplicationBase’. Like PeerBase this is one of the most important classes.
- Classes ResponseParameterAttribute, RequestParameterAttribute and EventParameterAttribute have been substituted with class DataMemberAttribute. As a consequence, request and response parameters can not be part of the same container class anymore.
- The Params property of classes EventData, OperationRequest and OperatioResponse has been substituted wth the new property Parameters. This dictionary’s keys are now of type byte.
- The property EventData.EventCode has been renamed to ‘Code’ and its type was changed to byte.
- The type of the properties OperationRequest.OperationCode and OperationResponse.OperationCode has been changed to byte.
- The new class DataContract substitutes classes Operation and Event. While class Operation remains part of the framework it is not mandatory to use it in order to convert the parameters to class properties anymore.
- Methods Event.GetEventData and Operation.GetOperationResponse have been removed. Instead use the new OperationResponse and EventData constructors.
- PhotonPeer.SendBufferFull was removed. Override PeerBase.OnSendBufferFull to respond to a full send buffer. The counterpart PeerBase.OnSendBufferEmpty is called when Photon is ready to send more data.
- Peer类现在需要继承类自PeerBase。接口IPeer和类PhotonPeer IPeer被移除。把一个完整的PeerBase类的定义作为你应用程序的核心部分。
- 应用程序现在必须继承自类 ApplicationBase。像PeerBase这是其中一个最重要的类。
- 类 ResponseParameterAttribute 、 RequestParameterAttribute 和 EventParameterAttribute 被类DataMemberAttribute 代替。因此,请求和响应的参数不能属于同一个容器类了。
- 类 EventData , OperationRequest 和 OperatioResponse 的属性参数被新的属性参数代替。这本字典的键的类型是byte。
- 属性EventData.EventCode 已经改名为 “Code” 和它的类型改为byte。
- OperationRequest.OperationCode 和 OperationResponse.OperationCode 属性的类型已经变成了byte。
- 这个新类DataContract 替代了类 Operation 和 Event。虽然类的操作是framework的一部分,为了转换参数为类的属性它是不强制被使用的。
- 方法 Event.GetEventData 和 Operation.GetOperationResponse 已被移除。而使用新的 OperationResponse 和 EventData 构造函数。
- PhotonPeer.SendBufferFull 已被移除。覆盖 PeerBase.OnSendBufferFull 来响应一个完整的发送缓冲区。当Photon是准备好并发送更多的数据时这个 PeerBase.OnSendBufferEmpty 会被调用。
6. Client SDK Changes (breaking) 客户端SDK改变(破坏)
- renamed IPhotonListener interface member OperationResult to OnOperationResponse, EventAcion to OnEvent and PeerStatusCallback to OnStatusChanged.
- The different parameters of the old OperationResult (OperationCode, Parameters, …) are passed in one object OperationResponse. Same applies to OnEvent now has one parameter EventData.
- Support for invocation ID was removed.
- 更名IPhotonListener接口成员 OperationResult 为 OnOperationResponse 、 EventAcion 为 OnEvent 和 PeerStatusCallback 为 OnStatusChanged 。
- 旧的 OperationResult ( OperationCode , 参数 ,…)的不同参数被传递在一个 OperationResponse 对象里。同样适用于 OnEvent 有一个参数 EventData。
- 支持调用ID已被移除。
- PhotonHostRuntime version attribute was removed from the config file and is no longer required - we recommend to remove it to ease feature updates.
- The application NetSyncObjects is no longer part of the SDK.
- Changed default for dump files from mini to full
- Added version display to log file during start up.
- PhotonHostRuntime version属性已从配置文件中移除,和不再是必需的,我们建议删除它以便于功能更新。
- 应用程序NetSyncObjects不再是SDK的一部分。
- 改变转储文件的默认选项从迷你到全部。
- 添加版本显示到日志文件在启动的时候。
Bug Fixes Bug修复
- Fixed a memory leak when canceling actions scheduled on a fiber.
- S2S.TCPClient did not close the socket at disconnect which caused an error when calling Connect again.
- Flash policy file is claimed malformed with a top-level XML tag that is not, see http://www.adobe.com/devnet/flashplayer/articles/fplayer9_security.html#_Malformed_Policy_Files
- Fixed CLR startup to ensure GC Server is used (issue affected .net 4.0 only).
- Fixed ENet fragmented buffering flow control bug.
- Fixed TCP magic byte issues - fragmented pings where not parsed correctly and an issue in the internal handling of the size of packets could lead to parsing errors (bigger packets and high load).
- Fixed Photon hang on shutdown trying to log on a dead logging instance.
- Fixed the method Peer GetRemoteAddress - due to a caching issue the result was wrong in some cases.
- Fixed mini dumps - now obey configured limits.
- Changed PhotonPeer::GetRemotePort so that it doesn’t rely on a cached port.
- 修正了当取消线程池的活动时内存的泄漏。
- S2S。 在断开的时候TCPClient没有关闭套接字,然后再次连接造成的一个错误。
- Flash策略文件被声称为畸形的最高等级的XML标记,其实它不是,看到 http://www.adobe.com/devnet/flashplayer/articles/fplayer9_security.html #畸形的策略文件
- 修正了CLR启动时以确保GC服务器是可用的。
- 修正了eNet分散缓冲流控制的错误。
- 修正了TCP魔法字节问题——不正确的解析ping 和一个内部处理的问题,数据包的大小可能导致解析错误(更大的数据包和高负载)。
- 修正了Photon在关机时试图在一个关闭的日志实例进行记录。
- 修正了方法peer GetRemoteAddress ——由于缓存问题,在某些情况下结果错了。
- 修正迷你转储——现在服从配置的限制。
- 修改了 PhotonPeer::GetRemotePort ,因此,它不依赖于一个缓存的端口。
Lite Concepts Lite概念
Here we will give you a quick overview of the basic concepts for the Lite application.
这我们将给出一个Lite应用的基本概念
Peers 参与者
Lite is based on the Application framework for Photon and also uses “Peer” as reference to a connected player. This is wrapped up and extended in the class LitePeer.When a client connects to the Lite Application, a new LitePeer is created in LiteApplication.CreatePeer. Further requests from that client (e.g. Operations) will now be handled by the corresponding LitePeer.In Lite, every LitePeer has a single RoomReference as State. Players can be only in a single room. The State is set by operation Join.The OnDisconnect method in LitePeer is called when a peer disconnects. If the peer is still in a room, this gets a message to remove the peer.
Lite是基于Photon应用框架,也是使用Peer作为玩家的连接引用。这在LitePeer中被扩展的。当一个客户端连接到Lite应用,一个新的LitePeer被LiteApplication.CreatePeer创建。进一步的客户端请求将通过LitePeer被处理。在Lite,每一个LitePeer有一个独立的RoomReference状态。玩家们可以在一个独立的房间。当操作为加入时这状态将被设置。当peer断开时,LitePeer中的OnDisconnect方法会被调用,如果这个玩家人仍在一个房间内,将会获取一个移除peer的消息。
Rooms 房间
In Lite, peers get into rooms to play together, which is why they are also called games. Rooms are responsible for everything. Everything aside from join, that’s it.Every game has a unique name and is treated as completely separated from any other. Also, every request that comes from a player is handled in sequence. Because many rooms exist in parallel and each request is handled in mere ticks, this setup scales well.Rooms have lists of players which they update on join, leave or disconnect. Between these players, events can be sent.Within a room, a player is also known as Actor and each Actor has an ActorNumber. That number is used to identify the origin of an event.
在Lite中,参与者需要进入房间去和别人一起进行游戏。房间是要为所有东西负责的,除了加入。每一个游戏都有一个唯一的名字,并且是完全独立于其他游戏的。而且每个玩家的请求都会排队进行处理。因为许多的房间存在并行,且在短短的时间内处理请求,这样的设置能很好的进行扩展。房间有参与者的列表,当加入、离开或者断开时列表将被更新。在这些玩家中,事件可能会被发送。在一个房间内,每个玩家都是被当做Actor并且都有一个ActorNumber。这个编号是用于识别事件的源头。
Operations and Events 操作和事件
Lite defines these Operations
- Join: Enter any room by name. It gets created on the fly if it does not exist. Join will implicitly leave the previous room of a peer (if any). Returns the assigned ActorNumber.
- Leave: Leaves the room (but keeps the connection).
- RaiseEvent: Tells the room to send an event to the other peers in it. Events have an EventCode and can carry data provided by the client. Lite does not store events.
- GetProperties: In Lite, properties can be attached to rooms and players. With this operation, they are fetched.
- SetProperties: Attaches arbitrary key-value pairs ro a room or a single player. Lite does not use the data, so the client can send arbitrary data along. Code by convention.
Lite定义了一下的操作
- 加入: 输入任何房间的名字,如果它不存在的话它将被创建,加入时将隐式地离开先前的房间(如果有的话),返回指定的ActorNumber。
- 离开: 离开房间(但保持连接)。
- 触发事件: 告诉房间发送一个事件到其他Peer。事件有一个EventCode,可以携带客户端提供的数据。Lite并不存储事件。
- 获取属性: 在Lite中,属性可以被附加到房间和玩家上,伴随着这个操作,他们都将被获取。
- 设置属性: 高度的任意的键-值对,一个房间或一个单一的玩家,Lite不使用数据,所以客户端可以发送任意的数据,代码按照惯例。
Lite defines these Events
- Join: Joining a room (by Operation) updates the players list inside. This is sent in an event to all players (including the new one).
- Leave: If a player leaves a room, the others are updated again.
- Properties update: When properties are changed, there is the option to broadcast the change. This event takes care that every client is up to date.
Lite定义了以下的事件
- 连接: 加入一个房间(通过操作)更新房间内的玩家名单,这是发送一个事件给所有的玩家(包括新的那个)。
- 离开: 如果一个玩家离开房间,其余的玩家将被再次更新。
- 更新属性: 当属性被更改了,将广播这个更改。这个事件负责更新每个客户端为最新的。
RaiseEvent / Custom Events 触发事件与自定义事件
With Lite, players can send events with arbitrary content to the other players in a room. The operation RaiseEvent takes care of this and forwards the event content accordingly.As an example, this client code from the DotNet Realtime Demo sends a player’s position:
Lite中,玩家可以发送任意内容的事件到其他同一房间里的玩家。操作RaiseEvent负责这相应的事件内容。作为一个示例,该客户端代码与DotNet实时演示发送玩家的位置:
|
// Raises an event with the position data of this player.
internal void SendEvMove(LitePeer peer)
{
if (peer == null )
{
return ;
}
Hashtable evInfo = new Hashtable();
evInfo.Add(( byte )STATUS_PLAYER_POS_X, ( byte ) this .posX);
evInfo.Add(( byte )STATUS_PLAYER_POS_Y, ( byte ) this .posY);
//OpRaiseEvent(byte eventCode, Hashtable evData, bool sendReliable, byte channelId, bool encrypt)
peer.OpRaiseEvent(EV_MOVE, evInfo, isSendReliable, ( byte )0, Game.RaiseEncrypted);
}
|
The Lite Application does not try to interpret your events server-side, so you can send just about anything you want to. The EV_MOVE above is nothing Lite knows, it’s only defined and used client-side.The move event that’s raised in the sample above causes a EventAction call on the receiving clients. They handle the event and data like this:
Lite应用程序并不会试图解释你的服务端事件,所以你可以发送任何你想要的。EV_MOVE是Lite不知道的,它仅仅在客户端被定义和使用。在一个EventAction调用客户端接收的情况下这个移动事件被触发。他们处理事件和数据如下:
//in Game.cs:
public
void
EventAction(
byte
eventCode, Hashtable photonEvent)
{
int
actorNr = 0;
if
(photonEvent.ContainsKey((
byte
)LiteEventKey.ActorNr))
{
actorNr = (
int
) photonEvent[(
byte
) LiteEventKey.ActorNr];
}
// get the player that raised this event
Player p;
this
.Players.TryGetValue(actorNr,
out
p);
switch
(eventCode)
{
case
Player.EV_MOVE:
p.SetPosition((Hashtable)photonEvent[(
byte
)LiteEventKey.Data]);
break
;
//[...]
}
}
//in Player.cs:
internal
void
SetPosition(Hashtable evData)
{
this
.posX = (
byte
)evData[(
byte
)STATUS_PLAYER_POS_X];
this
.posY = (
byte
)evData[(
byte
)STATUS_PLAYER_POS_Y];
}
Lite Lobby Concepts Lite Lobby概念
Lite Lobby is an application that (literally) extends the Lite Application to offer a listing of currently used rooms. To achieve this, it implements two different types of rooms: the LiteLobbyRoom and a LiteLobbyGame.By convention, join will put you into a lobby when the given room name ends on “_lobby” and into a game room in all other cases. The operation join got an optional parameter to name a lobby where the game is listed. This puts the responsibility on the client side again and you can create lobbies on the fly.
Lite Lobby 是一个应用程序,它扩展了Lite应用程序提供了一个房间的清单。为了达到这个目标, 它实现了两种不同类型的房间:LiteLobbyRoom和一个 LiteLobbyGame。按照惯例,当给定的房间的名称在“_lobby”的结尾时,加入操作会使你进入一个大厅,并进入这个游戏房间。加入操作获得一个可选的参数去命名一个游戏列表的大厅。这由客户端负责,你可以创建大厅。
Lobby 大厅
The lobby rooms (LiteLobbyRoom.cs) are special as they are not used to play a match with opponents. They just provide a list of available rooms (games) instead, so player can choose and join those.Despite being a room, too, a lobby won’t send events on join and leave of actors. As a result, players inside a lobby don’t notice each other which prevents a flood of join/leave events. There are no operations aside from leave. As a player leaves rooms when joining another, leave is not even required.On join, the complete games list is sent once to get a client up to date. After that, each player gets the same update event, which is sent every few seconds. It lists each game by name its current number of players. The clients keep and update their initial list. Games that are empty or full, both simply report 0 players as they should no longer be in the room list.To send the room list in intervals, the lobby schedules a message for itself, once the list is sent. This enqueues the event sending in the regular message passing mechanism, avoiding threading issues.
大厅的房间(LiteLobbyRoom.cs)是特别的,因为他们不是用于玩家游戏的。他们只是提供了一个可用房间的列表,因此玩家可以选择和加入。尽管是一个房间,一个大厅不会发送事件在玩家加入和离开的时候。因此,玩家在一个大厅不会通知其他的玩家,这可以防止大量的加入/离开事件被发送。除了离开没有别的操作。作为一个玩家离开房间加入另一个时,离开请求甚至是不需要的。在加入时,完整的游戏列表发送一次去获得一个客户端。在那之后,每个玩家获得同样的更新事件,它每隔几秒钟就被发送一次。它列出每个游戏的名字和目前的玩家数量。客户端保持和更新他们的列表。游戏是空的或是是满的,都只是报告0个玩家,他们不再是在房间列表里。一定时间间隔内发送房间列表,大厅安排一个消息在每次列表发送的时候。这种事件队列发送在常规消息传递机制中,可避免线程问题。
Rooms for Games 游戏房间
In Lite Lobby, regular games can now be connected to a lobby. The lobby of a room is set when gets created. If the lobby does not exist, it will be created as well.Once a room is connected to a lobby it is responsible to update the lobby with its info. The lobby itself does not mind what’s forwarded as info to the clients. By default, each room only sends its actor count and will be “full” when 4 players are in a room.
在LiteLobby中,常规的游戏现在可以被连接到一个大厅。一个房间的大厅是设置在被创建的时候。如果大厅不存在,它会被创建。一旦一个房间是连接到一个大厅,它将负责更新其信息在这个大厅里。大厅本身并不明白信息到客户端转发的是什么。默认情况下,每个房间只有发送它自己的玩家计数,并且当4个玩家在一个房间里时为full。
LiteLobby Config LiteLobby配置
To avoid incompatibilities, Lite Lobby uses its application config file to define the event types, update interval and the lobby suffix.Some values can’t be edited this way yet: count of users to make a room “full”, event-values and more.Lite Lobby can be running parallel to Lite but usually a game would use just one of those. The SDKs Photon Config assigns the name Lite Lobby (used by the clients on connect).
为了避免不兼容,用Lite Lobby配置文件来定义事件类型,更新时间间隔和大厅后缀。一些值不能被这样的方式编辑:用户计数使得一个房间 “full”,事件值还有更多。Lite大厅可以平行运行,Lite通常会使用其中的一个游戏。这个SDKs Photon配置指定Lite大厅的名字 (客户端使用的连接)。
posted on 2015-11-25 11:29
思月行云 阅读(1391)
评论(0) 编辑 收藏 引用 所属分类:
Photon