[小明带你玩儿Photon]2.从零开始一个程序
一.写在前面
上一篇对Photon进行了基本的介绍,并进行了下载与安装,本篇将会尝试让你快速的理解如何创建一个Photon应用程序.
二.创建一个简单的聊天服务端程序
1.创建程序
首先,用Visual Studio创建一个新的class library 程序,命名为ChatServer.然后在解决方案管理器中,右键引用编辑引用,通过Browse为项目添加在服务器lib文件夹下的ExitGamesLibs.dll,Photon.SocketServer.dll和PhotonHostRuntimeInterfaces.dll
之后我们创建一个新类ChatServer,并且继承自ApplicationBase,然后根据提示,要引入命名空间Photon.SocketServer.然后重写需要重写的抽象方法.
using Photon.SocketServer; public class ChatServer : ApplicationBase { protected override PeerBase CreatePeer(InitRequest initRequest) { } protected override void Setup() { } protected override void TearDown() { } }
接着我们要创建另外一个继承自PeerBase的类ChatPeer,并为其创建和其基类有一样参数的构造方法以完成对象的创建.同样,需要使用Photon.SocketServer的命名空间.
using Photon.SocketServer; using PhotonHostRuntimeInterfaces; public class ChatPeer : PeerBase { public ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer) : base(protocol, unmanagedPeer) { } protected override void OnDisconnect(DisconnectReason disconnectCode, string reasonDetail) { } protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) { } }
创建完ChatPeer之后,我们回到ChatServer,更改CreatePeer函数,让他返回一个ChatPeer实例:
protected override PeerBase CreatePeer(InitRequest initRequest) { return new ChatPeer(initRequest.Protocol, initRequest.PhotonPeer); }
这样,我们的服务器示例已经完成了.
2.部署
在Photon的deploy文件夹下,新建一个文件夹命名为ChatServer,并在里面再创建一个bin文件夹.然后在Visual Studio中设置项目属性,将生成位置设置为刚刚新建的bin文件夹,Build当前项目,即在文件夹中生成当前项目.
在完成了项目的Build之后,需要通过配置文件来让服务器来加载运行我们新生成的程序.配置时,我们打开bin_Win64里面的PhotonServer.config文件,在Default节点的Applications节点下内容替换为如下所示内容:
<Default [...]> <!-- other elements --> <Applications Default="ChatServer"> <Application Name="ChatServer" BaseDirectory="ChatServer" Assembly="ChatServer" Type="ChatServer"> </Application> <!-- any other applications --> </Applications> <!-- other elements --> </Default>
保存,然后运行PhotonControl.exe,点击右下角的图标,选择图中菜单位置,即可运行此ChatServer
三.客户端程序的创建
我们的服务器已经创建好了,接下来需要创建一个客户端程序来访问这个服务器了.
我们通过Visual Studio再创建一个Console工程,创建完工程后,需要添加引用Photon3DotNet.dll.之后,在项目中添加一个继承自IPhotonPeerListener的类ChatClient,并实现基类的抽象方法,此时还需要使用命名空间ExitGames.Client.Photon.起代码如下:
using System; using System.Collections.Generic; using System.Text; using ExitGames.Client.Photon; public class ChatClient : IPhotonPeerListener { private bool connected;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); } } }
完成了ChatClient类的创建之后,我们在主函数中生成实例调用它,代码如下:
class Program { public static void Main() { var client = new ChatClient(); var peer = new PhotonPeer(client, ConnectionProtocol.Tcp); // 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; } } } } }
值得注意的是,PhotonPeer的Request并不是声明之后就发送给服务器的,peer会先将请求存储在队列中,当调用Service()方法时,才会将队列中的请求发送出去.当前主方法中连接之后的模块会记录当前控制台输入的字符,并再摁下Enter键后将其发送给服务器,并清空当前记录.
至此,我们也已经完成了我们聊天程序的客户端创建于开发.
四,服务端程序的完善.
但是很明显,我们在之前的服务端只实现了客户端与服务端的连接,并没有实现响应客户端的方法,那么接下来我们就来修改服务端让其可以响应客户端发送过来的数据.
首先,我们在ChatPeer的OnOperationRequest中发送Response确认服务器收到了信息.代码如下:
protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) { var response = new OperationResponse(operationRequest.OperationCode); this.SendOperationResponse(response, sendParameters); }
现在,客户端能够收到响应并打印出来了.
接着,我们需要让其它的客户端也能够收到刚刚发送给服务器的信息,我们用一个发布/订阅模式来实现接收器.代码如下:
using System; using Photon.SocketServer; using PhotonHostRuntimeInterfaces; namespace ChatServer { class ChatPeer : PeerBase { //用来锁住线程 private static readonly object SyncRoot = new object(); //用来执行广播操作 public static event Action<ChatPeer, EventData, SendParameters> BroadcastMessage; /// <summary> /// 构造函数 /// </summary> /// <param name="protocol"></param> /// <param name="unmanagedPeer"></param> public ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer) : base(protocol, unmanagedPeer) { //连接后就订阅 lock (SyncRoot) { BroadcastMessage += OnBroadcastMessage; } } /// <summary> /// 接收到请求时调用 /// </summary> /// <param name="operationRequest"></param> /// <param name="sendParameters"></param> protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) { //发送响应表示收到 var response = new OperationResponse(operationRequest.OperationCode); SendOperationResponse(response, sendParameters); //触发广播 var @event = new EventData(1) { Parameters = operationRequest.Parameters }; lock (SyncRoot) { if (BroadcastMessage != null) BroadcastMessage(this, @event, sendParameters); } } /// <summary> /// 断开连接时调用 /// </summary> /// <param name="reasonCode"></param> /// <param name="reasonDetail"></param> protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail) { //断开连接后取消订阅 lock (SyncRoot) { BroadcastMessage -= OnBroadcastMessage; } } /// <summary> /// 将事件数据发送给所连客户端 /// </summary> /// <param name="peer"></param> /// <param name="event"></param> /// <param name="sendParameters"></param> private void OnBroadcastMessage(ChatPeer peer, EventData @event, SendParameters sendParameters) { SendEvent(@event, sendParameters); } } }
在代码中,我们使用一个发布/订阅,定义了一个静态的发布者,每次新建一个peer即新连接一个客户端的时候都为其增加一个订阅者,当连接断开时取消该其广播方法的订阅,然后通过多播委托的方式,调用每个客户端的OnBroadcastMessage方法发送一个事件,让每个客户端都可以响应.
五.运行结果
带Response的消息表示服务器收到请求,然后进行了广播.运行结果如下图所示: