zoukankan      html  css  js  c++  java
  • Photon Server与Unity3D客户端的交互

    Photon Server与Unity3D的交互分为3篇博文实现

      (1)Photon Server的服务器端配置

      (2)Photon Server的Unity3D客户端配置

      (3)Photon Server与Unity3D客户端的交互

    1.客户端向服务器端发送请求

    PhotonEngine.Peer.OpCustom(byte customOpCode, Dictionary<byte, object> customOpParameters, bool sendReliable);

      在客户端任何位置都能调用此方法。

      customOpCode,请求类型,不推荐直接传数字,用枚举类型enum。

      customOpParameters,传递的数据是Dictionary<byte, object>类型,object类是所有类的父类。

      sendReliable,是否使用稳定的连接。

      示例:

      登录的时候客户端向服务器端发送用户名跟密码。本示例创建一个Common类库存放客户端与服务器端都需要的一些枚举类、工具类。每次Common修改都要重新生成dll文件重新添加到客户端跟服务器端。

    namespace Common
    {
        //客户端请求类型。枚举类型默认时是int,-2147483648~2147483647,byte 是 0~255之间的整数
        public enum OperationCode:byte
        {
            Login,
            Register,
            Default,
            SyncPosition,
            SyncPlayer
        }
    }
    namespace Common
    {
        //数据Dictionary<byte, object> customOpParameters里object数据的类型
        public enum ParameterCode:byte
        {
            Username,
            Password,
            Position,
            X,Y,Z,
            UsernameList,
            PlayerDataList
        }
    }
    //客户端向服务器端发送用户名跟密码请求登录
    Dictionary<byte, object> opParameters = new Dictionary<byte, object>(); opParameters.Add((byte)ParameterCode.Username, usename); opParameters.Add((byte)ParameterCode.Password, password);
    PhotonEngine.Peer.OpCustom((byte)OperationCode.Login, opParameters, true);

     2.服务器端接收客户端的请求

    protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)

      在服务器端ClientPeer类里的OnOperationRequest里处理客户端的请求。每一个客户端连接进来都会创建一个ClientPeer,并且Photon Server为我们管理好它。

      operationRequest.OperationCode可以得到客户端传过来的byte customOpCode。

      operationRequest.Parameters可以得到客户端传过来的Dictionary<byte, object> customOpParameters。

      opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationRequest.Parameters里面对应的数据。

      sendParameters是发送请求的一些相关属性。

    SendOperationResponse(OperationResponse operationResponse, SendParameters sendParameters);

      响应客户端需要调用SendOperationResponse方法,只能在ClientPeer类里的OnOperationRequest方法里调用。

      OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode);

      operationResponse.SetParameters(newCustomOpParameters)可以设置Parameters。

      operationResponse.ReturnCode可以设置一个short返回值对应一种请求结果,用枚举类型enum。

      示例:

    namespace Common
    {
        public  enum ReturnCode:short
        {
            Success,
            Failed
        }
    }
    using Common;
    namespace MinecraftServer
    {
        //每一个客户端连接进来都会创建一个ClientPeer
        public class ClientPeer : Photon.SocketServer.ClientPeer
        {
         //处理客户端的请求
            protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
            {
           //不建议直接使用switch,推荐把每一种请求的处理封装成类
           switch (operationRequest.OperationCode)
                {
                    case (byte)OperationCode.Login:
                 //接收客户端请求
                        Dictionary<byte, object> opParameters1= operationRequest.Parameters;
                        opParameters1.TryGetValue((byte)ParameterCode.Username, out object username);
                        opParameters1.TryGetValue((byte)ParameterCode.Password, out object password);
                        MyGameServer.log.Info("得到的参数是" + username.ToString() + "" + password.ToString());
    
                //此处省略判断用户名密码是否正确的操作
    
                        //响应客户端用SendOperationResponse,只能在OnOperationRequest方法里调用
                        OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode);
                operationResponse.ReturnCode = (short)ReturnCode.Success;
                //Dictionary<byte, object> opParameters2 = new Dictionary<byte, object>();
                //opParameters2.Add(,);
                //opParameters2.Add(,);
                //operationResponse.SetParameters(opParameters2);
    SendOperationResponse(operationResponse, sendParameters);           
      break; case (byte)OperationCode.Register: break; default: break; } } } }

     3.客户端接收服务器端的响应

    public void OnOperationResponse(OperationResponse operationResponse)

      在客户端的PhotonEngine类里的OnOperationResponse类里接收服务器端的响应。

      operationResponse.OperationCode可以得到operationResponse构造函数里的operationRequest.OperationCode。

      operationResponse.Parameters可以得到服务器端operationResponse.SetParameters(opParameters2)的opParameters2。

      opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationResponse.Parameters里面对应的数据。

      operationResponse.ReturnCode可以得到服务器端operationResponse.ReturnCode=(short)ReturnCode.Success的(short)ReturnCode.Success。

    4.服务器端向客户端发送事件

      peer.SendEvent向客户端peer发送事件,可以在任何地方调用。

    EventData eventData = new EventData((byte)EventCode.SyncPosition); 
    Dictionary<byte, object> opParameters = new Dictionary<byte, object>();
    opParameters.Add(,);
    opParameters.Add(,);
    eventData.Parameters = opParameters;
    peer.SendEvent(eventData, new SendParameters());

     5.客户端接收服务器端发送的事件 

    public void OnEvent(EventData eventData)

      在PhotonEngine类的OnEvent方法接收服务器端发送的事件。

      eventData.Code可以得到eventData构造函数里的(byte)EventCode.SyncPosition

      eventData.Parameters可以得到服务器端eventData.Parameters

      opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationResponse.Parameters里面对应的数据。

    6.数据的序列化跟反序列化

      序列化

      一般序列化List<T> list。如果有多种数据,新建一个类例如Player,将人物的各种属性字段赋值后存入list。然后序列化list

    List<PlayerData> playDataList = new List<PlayerData>(); StringWriter sw = new StringWriter();
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>));
    xmlSerializer.Serialize(sw, playDataList);
    sw.Close();
    string playDataListString = sw.ToString();

      反序列化

    using (StringReader sr = new StringReader(playerDataListString))
    {
      XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>));
      List<PlayerData> playerDataList = (List<PlayerData>)xmlSerializer.Deserialize(sr);
    } 

     7.将请求、事件封装类

      (1)把客户端的请求封装成类,里面包括请求的OperationCode、发送请求和接收响应的方法。

    public abstract class ARequest:MonoBehaviour{
        public OperationCode OpCode;//设置public自动识别在Inspector里面,并且会识别枚举类型选择
        public abstract void DefaultRequest();
        public abstract void OnOperationResponse(OperationResponse operationResponse);
    
        public virtual void Start()
        {
            PhotonEngine.Instance.AddRequest(this);
        }
    
        public void OnDestroy()
        {
            PhotonEngine.Instance.RemoveRequest(this);
        }
    }
    
    public class LoginRequest : ARequest {
    
        [HideInInspector]
        public string usename;
        [HideInInspector]
        public string password;
    
        private LoginPanel loginPanel;
    
        public override void Start()
        {
            base.Start();
            loginPanel=GetComponent<LoginPanel>();
        }
    
        public override void DefaultRequest()
        {
            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.Username, usename);
            data.Add((byte)ParameterCode.Password, password);
    
            PhotonEngine.Peer.OpCustom((byte)OpCode,data, true);
        }
        public override void OnOperationResponse(OperationResponse operationResponse)
        {
            ReturnCode returnCode= (ReturnCode)operationResponse.ReturnCode;
            loginPanel.OnLoginResponse(returnCode);
        }
    }

      (2)把服务器端处理客户端请求封装成类,里面包括请求的OperationCode和处理请求的方法。

    public abstract class AHandler
    {
        public OperationCode opCode;
        public abstract void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer);
    } 
    class LoginHandler : AHandler
    {
        public LoginHandler()
        {
            opCode = OperationCode.Login;
        }
    
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.Username, out object username);
            operationRequest.Parameters.TryGetValue((byte)ParameterCode.Password, out object password);
    
            UserManager userManager = new UserManager();
            bool isSuccess = userManager.Vertify(username.ToString(), password.ToString());
    
            OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode);
            if (isSuccess)
            {
                operationResponse.ReturnCode = (short)ReturnCode.Success;
                peer.username = username.ToString();
            }
            else
            {
                operationResponse.ReturnCode = (short)ReturnCode.Failed;
            }
    
            peer.SendOperationResponse(operationResponse, sendParameters);
        }
    }

      (3)服务器端发送事件有2种情况,一种是处理客户端请求时发送,二是需要不断同步的数据由线程来发送。

      把客户端接收服务器事件封装成类,里面包括事件的EventCode和处理事件的方法。

    public abstract class AEvent : MonoBehaviour {
    
        public EventCode eventCode;//设置public自动识别在Inspector里面,并且会识别枚举类型选择
        public abstract void OnEvent(EventData eventData);
    
        public virtual void Start()
        {
            PhotonEngine.Instance.AddEvent(this);
        }
    
        public void OnDestroy()
        {
            PhotonEngine.Instance.RemoveEvent(this);
        }
    }
    
    public class NewPlayerEvent :AEvent{
    
        private SyncController player;
    
        public override void Start()
        {
            base.Start();
            player = GetComponent<SyncController>();
        }
    
        public override void OnEvent(EventData eventData)
        {
        object username;
            eventData.Parameters.TryGetValue((byte)ParameterCode.Username, out username);
            player.OnNewPlayerEvent(username.ToString());
        }
    }

      (4)对应的客户端的PhotonEngine、服务器端ClientPeer修改

    public class PhotonEngine : MonoBehaviour, IPhotonPeerListener
    {
        
        //存放所有可能出现的请求和事件
        private Dictionary<OperationCode, ARequest> RequestDict = new Dictionary<OperationCode, ARequest>();
        private Dictionary<EventCode, AEvent> EventDict = new Dictionary<EventCode, AEvent>();
    
    
        //服务器端向客户端发送请求时调用
        public void OnEvent(EventData eventData)
        {
            AEvent tempEvent;
            EventDict.TryGetValue((EventCode)eventData.Code, out tempEvent);
            tempEvent.OnEvent(eventData);
        }
    
        //客户端向服务器端发送请求后,服务器端响应客户端时调用
        public void OnOperationResponse(OperationResponse operationResponse)
        {
            ARequest tempRequest;
            bool temp = RequestDict.TryGetValue((OperationCode)operationResponse.OperationCode, out tempRequest);
    
            if (temp)
            {
                request.OnOperationResponse(operationResponse);
            }
            else
            {
                Debug.Log("没找到对应的响应对象");
            }
        }
    
        public void AddRequest(ARequest request)
        {
            RequestDict.Add(request.OpCode, request);
        }
        public void RemoveRequest(ARequest request)
        {
            RequestDict.Remove(request.OpCode);
        }
        public void AddEvent(AEvent tempEvent)
        {
            EventDict.Add(tempEvent.eventCode, tempEvent);
        }
        public void RemoveEvent(AEvent tempEvent)
        {
            EventDict.Remove(tempEvent.eventCode);
        }
    }
    public class ClientPeer : Photon.SocketServer.ClientPeer
    {
        public float x, y, z;
        public string username;
    
        public ClientPeer(InitRequest initRequest) : base(initRequest)
        {
            MyGameServer.Instance.peerList.Add(this);
        }
    
        //处理客户端断开连接的后续工作
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            MyGameServer.Instance.peerList.Remove(this);
        }
    
        //处理客户端的请求
        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            MyGameServer.Instance.HandleDict.TryGetValue((OperationCode)operationRequest.OperationCode, out AHandler handler);
            if (handler != null)
            {
                handler.OnOperationRequest(operationRequest, sendParameters, this);
            }
            else
            {
                AHandler defaultHandler = DictTool.GetValue<OperationCode, AHandler>(MyGameServer.Instance.HandleDict, OperationCode.Default);
                defaultHandler.OnOperationRequest(operationRequest, sendParameters, this);
            }
        }
    }

    8.线程

       服务器持续向服务器端发送事件时需要用到线程,下面是线程简单的应用。

    public class SyncPositionThread
    {
        private Thread t;
        public void Run()
        {
            t = new Thread(UpdatePosition);
            t.IsBackground = true;
            t.Start();
        }
    
        public void Stop()
        {
            t.Abort();
        }
    
        private void UpdatePosition()
        {
            Thread.Sleep(5000);
            while (true)
            {
                Thread.Sleep(50);
                //执行上传位置
                SendPosition();
            }
        }
    
        private void SendPosition()
        {
            //如果没有客户端连接上则不用传数据
            if (MyGameServer.Instance.peerList.Count == 0)
                return;
    
            //把所有客户端的位置装箱到playerDataList
            List<PlayerData> playDataList = new List<PlayerData>();
            foreach (ClientPeer peer in MyGameServer.Instance.peerList)
            {
                if (string.IsNullOrEmpty(peer.username) == false)
                {
                    PlayerData playerData = new PlayerData();
                    playerData.Username = peer.username;
                    playerData.Position = new Vector3Data() { X = peer.x, Y = peer.y, Z = peer.z };
                    playDataList.Add(playerData);
                }
            }
    
            //如果有客户端登录但是还没连接上,也不用传数据
            if (playDataList.Count == 0)
                return;
    
            //playDataList序列化为playDataListString然后放在data里面
            StringWriter sw = new StringWriter();
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>));
            xmlSerializer.Serialize(sw, playDataList);
            sw.Close();
            string playDataListString = sw.ToString();
            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.PlayerDataList, playDataListString);
    
            //playDataListString发送到各个客户端
            foreach (ClientPeer peer in MyGameServer.Instance.peerList)
            {
                if (string.IsNullOrEmpty(peer.username) == false)
                {
                    EventData eventData = new EventData((byte)EventCode.SyncPosition);
                    eventData.Parameters = data;
                    peer.SendEvent(eventData, new SendParameters());
                }
            }
    
        }
    }
  • 相关阅读:
    C/S WinFORM 快速开发框架 MyRapid快速开发框架更新日志 ---数据权限控制
    C/S WinFORM 快速开发框架 MyRapid快速开发框架更新日志 ---自动生成操作手册
    C/S WinFORM 快速开发框架 MyRapid快速开发框架更新日志 ---添加模块流程图
    代码分享:给窗体添加水印
    版本需更新提醒
    如何做好软件自动更新
    做项目管理踩过的坑
    团队开发的代码管理(VS)
    Java8尽管很香,你想过升级到Java11吗?会踩那些坑?
    这个 Spring 循环依赖的坑,90% 以上的人都不知道
  • 原文地址:https://www.cnblogs.com/DonYao/p/8558466.html
Copyright © 2011-2022 走看看