棋牌svn服务端端口gameserver端口设置问题

只需一步,快速开始
棋牌类网络游戏服务端的架构设计
查看: 748|
评论: 0|原作者: lingmin
摘要: 网络游戏目前在国内相当热门,棋牌类游戏在网络游戏中占有比较重要的地位。越来越多的开发商加入到了网络游戏的开发中来,但有关网络游戏开发技术介绍的资料却比较少。该文总结了一款通用的棋牌类网 ...
摘要:网络游戏目前在国内相当热门,棋牌类游戏在网络游戏中占有比较重要的地位。越来越多的开发商加入到了网络游戏的开发中来,但有关网络游戏开发技术介绍的资料却比较少。该文总结了一款通用的棋牌类网络游戏服务端的架构设计,介绍了基本架构、通信协议、多线程模型和第3方接I:1的相关内容,并对整个架构作了一定分析。
关健词:网络游戏;服务器;服务端架构;通信协议
& && &&&棋牌类休闲游戏是网络游戏中非常重要的一类。从早期的联众到现在的QQ游戏,棋牌类休闲游戏得到了长足的发展。由于这类游戏是将传统的民间游戏网络化,玩家很容易上手,因此这类游戏成了受众群最大的网络游戏之一。
& && &&&作为研究开发人员,比较关心如何去开发这样的游戏。然而,由于商业竞争的原因,目前网络上、期刊上很难找到相关的技术文档。本文结合工程开发实践提出了一种通用的棋牌类网络游戏的服务端架构,希望对读者能有一定的参考价值。
<font color="#、问题
在设计这个通用棋牌类网络游戏的服务端架构时,必须考虑以下4个问题:
(1)高效性:网络游戏服务端的首要目标就是能为大量的客户端服务,同时还应该具备相当的游戏逻辑处理速度&#65377;
(2)安全性:服务端要求能够对一些恶意攻击有一定的防护能力&#65377;
(3)扩展性:一个好的网络游戏服务端架构,要求在一定程度上,能动态地增加或者减少当前的服务端规模&#65377;
(4)通用性:对于一个通用的棋牌类游戏平台来讲,其通用性就显得相当重要&#65377;平台要求能兼容第3方开发的游戏&#65377;
下面就以上面提出的4个问题为目标,给出一种通用的棋牌类网络游戏的服务端架构的设计&#65377;
<font color="#、设计
<font color="#.1&&架构
& && &&&棋牌类游戏与其.他网游最大的不同点在于,其游戏逻辑比较简单,通常一台服务器就可以承担一种甚至多种游戏的逻辑处理,而其.他游戏却不一定是这样&#65377;比如典型的MMORPG游戏,一般一台服务器只负责一块地图的逻辑处理,还可能有NPC服务器&#65380;物品掉落服务器等专门服务器&#65377;
另外,棋牌类游戏的结构相对厮言也比较固定和单一,一般由大厅和游戏房间组成,大厅是选择游戏的平台,玩家在房间里进行游戏&#65377;针对这些特点,给出了如下一种架构:
一种通用的棋牌类网络游戏服务端的架构如图1所示,主要分为5种软服务器:
& && &&&登录服务器(109in server,LS),
& && &&&大厅服务器(hall server,HS),
& && &&&中央服务器(main server,MS),
& && &&&游戏服务器(game server,GS),
& && &&&数据库服务器(DB)&#65377;
& && &&&所谓“软服务器”是指逻辑意义上的服务器,并不等于现实中的一台具体的物理服务器&#65377;一台物理服务器上可以运行多个软服务器,一个软服务器也可能是由多台物理服务器所组成&#65377;
图1服务器架构示意图
& && &&&Ls用于对玩家进行登录验证&#65377;玩家通过验证后,就与Ls断开连接,以腾出Socket资源&#65377;根据玩家数量的多少,可以设置多个Ls,这时可采用一定的负载均衡策略,如最简单的基于DNS的负载均衡&#65377;
& && &&&玩家通过Ls验证之后,就会与HS发起连接&#65377;一旦客户端和HS建立起合法连接,玩家就进入了游戏的大厅&#65377;此后玩家一直与HS保持连接,这样可以很方便地检测玩家的意外断线等情况&#65377;HS也可以根据负载设置多个,负载均衡算法下面给出例子说明&#65377;
& && &&&MS可以说是整个服务端的核心,它用于存储一些全局信息,如厅中玩家信息&#65380;游戏服务器的信息等等&#65377;MS还负责一些合法性检验和消息的转发等功能&#65377;为保证系统的安全性,整个过程中玩家不会直接与MS进行连接和交互&#65377;
& && &&&GS在客户端看来,就是提供游戏的一个房间&#65377;玩家在客户端点击进入某个房间,就会与相应的GS建立连接,玩家离开GS时便与之断开连接&#65377;
& && &&&DB用于存储玩家的游戏信息,包括账号信息和游戏中的数据等等&#65377;为保证系统安全,玩家不会直接与DB进行连接和交互&#65377;
& && &&&以上介绍了几个软服务器的功能,下面给出一个具体的玩家从登录到退出的例子,说明服务端如何实现相应功能&#65377;玩家首先通过某种负载均衡算法选择一个Ls并发出登录消息&#65377;Ls收到消息后,到DB查询账号的合法性,如果合法,则向MS发送验证重复登录的消息&#65377;若MS上没有此玩家已登录的信息,则在MS上的玩家列表中加入该玩家,同时MS给Ls返回一个HS的连接信息&#65377;HS的负载信息在MS上有记录,这样MS就可以选择一个负载最轻的HS告知客户端&#65377;客户端收到后便向HS发出连接请求,HS向MS发送该客户端的合法性验证请求&#65377;如果通过验证,MS将通过HS将当前各游戏服务器的信息传送给客户端,方便玩家选择进入哪个游戏房间&#65377;经过上述步骤,玩家便进入了大厅&#65377;接下来,玩家会选择进入某个游戏房间&#65377;同样,玩家必须通过MS的合法性验证才能与GS建立连接&#65377;建立连接后,房间中的所有逻辑都由GS处理&#65377;GS最重要的功能就是负责具体游戏的逻辑处理,为保证平台的通用性,设计了一套第3方接I:I,这样游戏逻辑就可由第3方根据接I:1来开发,将在2.4节具体讨论&#65377;玩家退出房间时,GS进行相应的数据保存后与客户端断开连接即可&#65377;玩家退出整个游戏时,与HS断开连接,HS告知MS,MS删除玩家信息,玩家即成功退出游戏&#65377;
<font color="#.2&&通信协议
& && &&&网络游戏一个重要的特点就是存在网络通信,如客户端和服务端的通信,还有服务端之间的通信&#65377;为保证系统的可靠性,所有的通信都采取了TCP协议&#65377;由于网络游戏的用户量一般都非常大,因此在设计客户端和服务端之间的通信协议时,采用了完成端口(Completion Port)的技术,大家可参考相关资料。
& && &&&下面重点介绍一下软服务器之间的通信协议。我们没有采用现成的网络中间件,如DCOM、CORBA等。一方面是为了节约开发成本,另一方面这些网络中间件的诸多功能也用不上。根据自己的需求设计了一套通信协议,称为星形分布式通信协议(Star Distributed Communication Protocol,SDCP)。由图1可以看出,整个服务端(不包括DB)的架构是一个星型结构,MS是中心节点(CenterNode,CN),而其.他服务器是叶节点(Leaf Node,LN)。这里没把DB考虑在内,是因为一般DB本身都会提供相应的通信功能。实际上,SDCP仍然是一种C/S模式的通信协议,CN作为Server,而LN作为Client。为CN设计了一个CenterNetbase类,为LN设计了一个LeafNetbase类,片段代码如下:
class CenterNetbase
& && &&&public:
& && &&&//开始监听
& && &&&bool StartListen(int listenPORT);
& && &&&//获取LN的消息
& && &&&Message* GetMsg();
& && &&&//向LN发送消息
& && &&&void SendMsg(Message* outData);
& && &&&…
class LeafNetbase
& && &&&public:
& && &&&//向CN发起连接
& && &&&bool Connect(ConnectInfo *target);
& && &&&//与CN断开连接
& && &&&bool DisConnect();
& && &&&//获取CN的消息
& && &&&Message* GetMsg();
& && &&&//向CN发送消息
& && &&&void SendMsg(Message* outData);
& && &&&…
};复制代码
& && &&&在CN首先启动好的前提下,SDCP允许LN随时向CN发起连接请求,这样就实现了整个系统的可扩展性&#65377;当觉得LS&#65380;HS或者GS的负载过重时,完全可以动态地增加相应的服务器&#65377;各服务器之间的TCP连接,也保证了服务器之间通信的高可靠性要求&#65377;
& && &&&上面曾提到,几个软服务器可以设置在同一台物理服务器上&#65377;SDCP中的节点是以socket来区分的,因而,对同一台物理服务器上的软服务器,只需要为它们分配不同的端El就能满足SDCP的要求了&#65377;服务器之间的通信都是通过消息传递来实现的,对消息的格式定义如下:
class Message
& && &&&public:
& && &&& //消息的字节数
& && &&&int senderlD;//消息发送方的标识
& && &&& //消息类型
& && &&&char* pM //指向消息的首字节
& && &&&…
};复制代码
& && &&&消息的结构如图2所示&#65377;Message类中还提供了一系列用于构造消息和解析消息的方法,主要用于将需要发送的信息拼装成一条完整的消息,接收方收到后再根据消息类型解析出所包含的信息&#65377;这里就不一一介绍了&#65377;
图2&&消息结构
<font color="#.3&&多线程模型
& && &&&对玩家的操作响应越快,服务器的性能就越好&#65377;为了达到这个要求,最好能为每个玩家在服务端启动单独的线程来处理消息&#65377;但这种做法是不现实的&#65377;一方面,操作系统的最大并发线程数限制了最大用户量;另一方面,如果采用多线程处理,那么玩家的数据同步问题就会显得相当复杂&#65377;经权衡,设计了这样一个多线程模型&#65377;
& && &&&以MS为例,MS的CenterNetbase包含两个缓冲队列,inQueue和outQueue&#65377;inQueue用于存放接收到的消息,outQueue用于存放待发送的消息&#65377;CenterNetbase中将启动两个线程,接收线程和发送线程&#65377;接收线程不停地将网络上接收到的消息存放到inQueue中,而发送线程不停地将outQueue中的消息发送给相应接收者&#65377;MS还将启动唯一的一个主线程,用于处理inQueue中的消息&#65377;在处理过程中,当需要往外发送消息时,则将消息插入outQueue&#65377;整个多线程模型如图3所示&#65377;
图3&&MS的多线程模型
& && &&&Ls&#65380;Gs和Hs的多线程模型和MS有点不一样&#65377;这3种服务器除了含有类似于CenterNetbase的LeafNetbase之外,还有一个ServerNetbase类用于处理和客户端的通信&#65377;同样,ServerNetbase中也包含两个线程和两个队列&#65377;为了保证数据同步处理的简单,在主线程中仍然只启动唯一的一个线程,只不过这时需要处理两个inQueue中的消息&#65377;扩展一下,当Netbase类不止是两个时,就得到了一个可复用的多线程模型&#65377;处理线程的片段代码如下:
& && &&&//对inQueueCount个inQueue依次处理
& && &&&for(int i=O;i&inQueueCi++)
& && && && && & msgCount=inQueue.size();
& && && && && & //将此inQueue中的当前消息处理完
& && && && && & for(int j=0;j&msgCj++)
& && && && && & {
& && && && && && && && &//获取消息
& && && && && && && && &msg=netbase一&GetMsg();
& && && && && && && && &if(msg!=NULL)
& && && && && && && && &{
& && && && && && && && && && &&&HandleMessage(msg);//处理消息
& && && && && && && && &}
& && && && && & }
& && &&&Sleep(interval);
<font color="#.4&&第3方接日
& && &&&由于不同游戏的逻辑不一样,如果将游戏逻辑与GS绑定的话,那么不同的游戏将具有不同的GS,这样大大影响了平台的通用性&#65377;我们希望在这个平台上,能比较方便地运行多种游戏,因而设计了一套第3方接口,来兼容第3方开发的游戏逻辑处理程序&#65377;
& && &&&采取了动态链接库(did的形式来实现第3方接口&#65377;对于GS,如果它们承担不同的游戏,只需要动态加载不同的游戏dll就可以了&#65377;为尽量筒化第3方接口,我们通过消息机制来完成GS和第3方dll之间的通信&#65377;接口类定义如下:
class ThirdInterface
& && &&&public:
& && &&&bool Initialize();
& && &&&bool Stop();
& && &&&void HandleThirdMsg(ThirdMsg* msg);
& && &&&ThirdMsg* GetGameMsg();
& && &&&…
};复制代码
& && &&&接口类存在于dll中,并可动态导出,以供GS使用&#65377;Initialize和Stop分别用于初始化和关闭第3方程序&#65377;在客户端,也相应地提供了一套第3方接El&#65377;第3方服务端会和第3方客户端进行消息通信,这些由平台实现&#65377;将这些消息定义成一类特殊的消息,当服务端接收到这样的消息时,就调用第3方接IZl HandleThirdMsg,让第3方来对这些消息进行处理&#65377;当第3方服务端要求发送消息时,就将消息放到一个缓冲队列中去,GS的主线程会不断地调用接口函数GetGameMsg来获取缓冲队列中的消息,发现是需要往客户端发送的消息时,就将其插入到outQueue中&#65377;GS和第3方服务端之间的其.他信息传递也通过以上方式进行&#65377;
<font color="#.5&&其.他
& && &&&在设计开发过程中还有很多细节性的东西需要注意,这里简单列举一二&#65377;比如,一般情况下LS&#65380;HS和GS都是几个运行在同一台物理服务器上,而MS则需要由一台甚至多台物理服务器来实现&#65377;当MS成为系统瓶颈时,就需要将MS设计成一个单接IZl的应用集群服务器&#65377;还比如,为了进一步减轻服务端的负载,可以将非关键逻辑消息直接在客户端之间传送,而不通过服务端&#65377;但这是在不影响游戏安全性的前提下的改进,这就要求进一步去区分关键逻辑和非关键逻辑&#65377;再比如,可以为所有的消息设定一定的优先级,进一步提高系统的性能&#65377;
<font color="#、分析
& && &&&以上就是给出的设计方案,现在来回头看一下第1节中所提出的4个问题&#65377;
& && &&&(1)高效性:在设计系统结构时提出了软服务器的概念,多个软服务器可以运行在同一台物理服务器上,这样可以灵活地根据负载来搭配服务器,能更好地利用服务器资源&#65377;Ls和HS都有一定的负载均衡算法,不会出现某个服务器负载过重导致崩溃的情况&#65377;多线程模型也保证了数据操作的便捷性&#65377;另外,开发时使用的是c++这种效率比较高的编程语言,在开发过程中也充分利用了它的高效性&#65377;比如接收消息线程从网络上接收到消息以后,通过c++强大的内存管理能力,就能直接对消息所在的内存进行一系列操作,而不用再将消息中的信息再次复制出来使用,尽量减小内存拷贝的次数,也极大地提高了系统的性能&#65377;
& && &&&(2)安全性:将最核心的MS没有暴露给玩家,这大大提高了系统的安全性&#65377;另外,在设计开发过程中,充分考虑了玩家的合法性验证,比如在玩家登录LS&#65380;HS和GS时,都做了相应的合法性检验,这也提高了系统的安全性&#65377;当然,还可以对某些重要的通信消息进行加密,进一步提高系统的安全性&#65377;
& && &&&(3)扩展性:把架构设计成星型结构,并提出了SDCP通信协议&#65377;在CN已经启动的前提下,通过SDCP,能非常方便地对LN进行动态扩展&#65377;这使得在系统中LS&#65380;HS和GS都有非常大的扩展空间,能承受更多的用户量&#65377;
& && &&&(4)通用性:为了实现平台的通用性,为客户端和服务端分别定义了一套第3方接I:1(由于篇幅原因,只介绍了服务端的接口)&#65377;如果第3方开发商能按照这套接171去开发游戏逻辑,那么这些游戏就能很好地集成到平台中来&#65377;在实际开发过程中,已经开发了两款不同的游戏,这两种游戏按照第3方接El的标准开发,通过DLL形式很好地跟平台集成了起来&#65377;
<font color="#、小结
& && &&&本文提出的服务端的系统架构,在一定程度上比较好地解决了高效性&#65380;安全性&#65380;扩展性和通用性的问题&#65377;当然,本系统架构仍然存在很多不完善的地方,希望与读者进行交流,能够提出更好的解决方案来与大家分享,让网络游戏开发者都得到进一步的提高&#65377;
参考文献:
1&&Ohlund J.Windows网络编程[M].杨合庆,译.北京:清华大学出版社,2002.
2&&苏羽,王媛媛.Visual c++网络游戏建模与实现[M].北京:北京科海电子出版社,2003.
3&&李文正,郭巧,王 利.Intemet服务器负载均衡的研究与实现【J].计算机工程,):98—99.
4&&唐磊,金连甫.一种新的大型通用分布式服务器架构[J]计算机工程与设计,):.
上一篇:下一篇:
Powered by3644人阅读
使用PhotonServer和Unity建立一个棋牌类游戏实例(三)Unity客户端的注册请求和服务器的注册请求响应
上一章我们完成了服务器与数据库的交互设计,下面我们开始第一个客户端与服务器的交互:注册请求
1、客户端的建立,建立unity工程-ILovePaoHuZi
建立2D工程ILovePaoHuZi,新建LoginScene场景。自行建立UI,UI部分这里就不讲解了,你们自己来做,我这里只讲解与服务器交互的部分.
1.1引入photon的DLL 文件
在unity工程中建立一个Plugins文件夹,之下再建立一个PhotonClient文件夹,将你的photon安装目录下的lib文件夹下的Photon3Unity3D.dll文件放进去,这样就可以在脚本中引用它了。
1.2 建立PhotonClientManager
& & &我们的游戏需要有一个脚本专门负责处理与服务器之间的通讯与交互。在场景中建立一个叫做PhotonClientManager的空物体,再其上建立一个叫做PhotonClientManager脚本。脚本引用上面导入的Photon3Unity3D.dll文件(using ExitGames.Client.P),然后继承它里面的IPhotonPeerListener接口并实现接口,这样客户端的管理脚本就建立好了。
& & 然后在Awake方法中将此脚本单列化,并设置后台运行、切换场景不删除等必须配置。
& & 再建立一个与服务器交互的photon的peer,并定义你的服务器地址以及端口号,以及服务器的名字,这些定义的变量需要与你服务器设置相同。
& & 最后在update中添加peer发送请求的命令if (peer != null) peer.Service();,代表每帧都会检查一次peer是否有请求要发送,如果有就会发送给服务器,完成后代码如下:
&span style=&font-size:10&&using UnityE
using System.C
using ExitGames.Client.P
public class PhotonClientManager : MonoBehaviour, IPhotonPeerListener
#region 变量定义
#region 定义photon客户端设置需要的变量
/// &summary&
/// PhotonClientManager的单列模式
/// &/summary&
public static PhotonClientManager s_PhotonClientM
/// &summary&
/// 连接PHTON服务器用的 连接工具
/// &/summary&
private PhotonP
/// &summary&
/// 连接工具PhotonPeer 使用的协议
/// &/summary&
public ConnectionProtocol 连接协议 = ConnectionProtocol.T
/// &summary&
/// 连接工具PhotonPeer 要连接的IP地址和端口号
/// &/summary&
public string SeverIpAdress = &127.0.0.1:4530&;
/// &summary&
/// 连接工具PhotonPeer 要连接的服务器名字
/// &/summary&
public string SeverName = &ILovePaoHuZi&;
#endregion
#endregion
#region MonoBehaviour脚本原生方法
void Awake()
//创建单列模式
//如果单列模式还没有设置,则让这个脚本成为单列模式,如果存在,则删除这个脚本以及物体
if (s_PhotonClientManager == null)
s_PhotonClientManager =
Destroy(this.transform);
//设置脚本属性,换场景不删除、后台运行等
DontDestroyOnLoad(this.transform);
Application.runInBackground =
//建立PhotonPeer实例(第一个参数为带有IPhotonPeerListener的脚本(通讯监听类的实例),第2个为选用的通讯协议);
peer = new PhotonPeer(this, 连接协议);
//连接器 将要连接的服务器IP端口加上服务器名字
peer.Connect(SeverIpAdress, SeverName);
//连接器 开始发送队列中拥有的 通讯信息,所有固话请求以及通讯请求都以队列的方式存储在连接器中,当执行peer.Service()方法时才对服务器发送他们
void Start()
void Update()
//每帧检查peer中是否有命令需要发送,当peer有命令时,开始发送请求
if (peer != null) peer.Service();
#endregion
#region photon接口方法
//当photonclient要输出debug时,会调用此方法
public void DebugReturn(DebugLevel level, string message)
//当发送event事件时会调用此方法
public void OnEvent(EventData eventData)
//当收到服务器的响应时,会调用此方法
public void OnOperationResponse(OperationResponse operationResponse)
//当客户端状态改变时会调用此方法
public void OnStatusChanged(StatusCode statusCode)
#endregion
1.3建立请求类型处理模型
我们的游戏的通讯系统需要设计一个处理模型来规范化处理流程:
这是我准备使用的结构,里面有个需要我们去建立,
第一:是各种请求的处理脚本,我准备一种请求一个脚本,这样方便管理。例如:登录,注册、开游戏房、开始游戏、新加入玩家、游戏结束等等。
第二:是类型判断,我准备采用字典的形式将类型与处理脚本联系起来,当收到信息后,直接可以在字典内查到要使用的处理脚本,然后直接由其处理就好了。
那么,现在我们需要建立的就是类型,而且要求在服务器和客户端上的类型都是一样的,我们可以采用枚举类型进行划分。而photon的通讯信息的类型是由byte&#26684;式来区分的,那么我们建立一个通讯类型枚举(byte型)在ILovePaoHuZiCommon工程中,还记得这个工程吗?他在我们photon服务器解决方案中,他是用来存储服务器与客户端共用的一些类的。
1.4建立服务器与客户端共用的通讯类型枚举
使用VS打开我们的photon服务器解决方案-ILovePaoHuZi.在ILovePaoHuZiCommon工程中添加一个叫做通讯类型枚举的类文件。将其改成枚举byte类型,并添加2个项目,注册请求_通讯类型和登录请求_通讯类型。代码如下:
namespace ILovePaoHuZiCommon
public enum 通讯类型枚举:byte
注册请求_通讯类型,
登录请求_通讯类型
}然后对整个解决方案进行生成,注意:因为unity的mono最高只支持3.5版本的.NET,所以要修改ILovePaoHuZiCommon的目标框架为.net3.5,然后在重新生成,否则unity引用ILovePaoHuZiCommon时候会报错。
然后到你生成的Photon安装路径\deploy\ILovePaoHuZi文件夹内找到ILovePaoHuZiCommon.dll文件,将他复制放入unity工程的Plugins\PhotonClient目录下,以后我们可以引用它;
1.5、在PhotonClientManager&上建立发送注册请求方法:
发送注册请求方法可以建立在任何脚本上,只是为了方便管理才放入这里;但是,将通讯信息是必须通过PhotonClientManager脚本中的peer来发送的,所以我们必须在PhotonClientManager脚本中建立一个发送通讯请求的方法,方便所有的处理器对其调用,并发送请求:
A:PhotonClientManager上建立发送通讯请求方法:
public void 发送通讯请求(通讯类型枚举 _通讯类型枚举,Dictionary&byte,object& 要发送的内容,bool 是否需要保证传输到位)
peer.OpCustom((byte)_通讯类型枚举, 要发送的内容, 是否需要保证传输到位);
}这里解释下,bool&是否需要保证传输到位,是代表photon是不是要保证这个信息被对方接收,如果为ture的话,服务器会确认收到,如果信息在途中丢失,客户端会再次发送这个信息,如果为false的话,客户端只管发出去,不管服务器是否接收的到,对于很多信息例如:死亡、开房等等主要信息,必须保证服务器收到,而位置信息,聊天信息可以不确认。当然你也可以全部为ture,但是如果某个信息卡住了,会导致一些游戏表现上的问题,例如位置信息,因为有时间序列的要求,如果1分钟前的信息被卡住了,然后再次发送后,你会突然发现你的人物突然又回到1分钟前的位置。
peer.OpCustom就是photon构建通讯信息的方法,他发送的内容我们叫做请求参数,这个参数必须是一个字典,而且必须是byte为key的字典,而字典的&#20540;可以是任何东西,类、数字、数组、甚至是一个list.所以我们需要把 参数这个字典的byte也要做一个枚举,好到了服务器或者客户端能够让他知道字典的&#20540;到底是啥,然后通过正确的转换就可以的到传输的东西。
B:建立 通讯内容参数类型枚举
因为这个枚举是服务器与客户端共用的,所以又要回到服务器端的ILovePaoHuZiCommon工程中添加这个枚举,并重新生成它,然后unity重新导入最新的ILovePaoHuZiCommon.dll文件:
namespace ILovePaoHuZiCommon
public enum 通讯内容_参数枚举:byte
UserInfo_Model类,
Image_Model类
}C:在PhotonClientManager脚本上建立注册方法
我们在注册的时候需要将一个完整的UserInfo_Model类传输给服务器,但是photon并不支持直接传送自定义类,那么我们可以想办法将这个类转换为string的形式传送过去,这里我们就需要一个 数据交换&#26684;式 来转换它为string,这里我们使用LitJson,我们可以在Json的官网获得它,地址是http://www.json.org,然后找到C#里面的LitJson,下载它并且将其放在unity工程中的Plugins-PhotonClient文件夹下,并且引用它:
以后要注册新用户的话可以直接调用它来发送注册申请
public void 注册用户(string _用户名, string _密码, string _昵称, string _省名, string _市, string _县区)
UserInfo_Model 欲注册的用户信息 = new UserInfo_Model();
欲注册的用户信息.用户名 = _用户名;
欲注册的用户信息.密码 = _密码;
欲注册的用户信息.昵称 = _昵称;
欲注册的用户信息.省名 = _省名;
欲注册的用户信息.市 = _市;
欲注册的用户信息.县区 = _县区;
//将 UserInfo_Model 类格式 通过 LitJson数据交换格式 转换为string
string 内容信息 = JsonMapper.ToJson(欲注册的用户信息);
//生成要附带的数据字典
Dictionary&byte, object& 要传送的内容 = new Dictionary&byte, object&();
要传送的内容.Add((byte)通讯内容_参数枚举.UserInfo_Model类, 内容信息);
//将字典和通讯类型代码使用PhotonClientManager发送出去
PhotonClientManager.s_PhotonClientManager.发送通讯请求(通讯类型枚举.注册请求_通讯类型, 要传送的内容, true);
2、服务器上建立注册响应方法
2.1、在服务器上建立handler_base和handler_注册请求;
要使用字典进行类型判断并直接使用字典类储存的处理脚本对通讯进行处理,那么我们为了将处理脚本装入字典,就需要一个定义一个处理脚本基类。然后我们以它为父类继承它建立各种类型的处理脚本,这样就可以装入字典了。
这2个类文件代码如下,我们在ILovePaoHuZi工程下新建立一个文件夹Handler专门用来放通讯处理脚本;handler_base代码如下,它是作为通讯处理器的基类:
using Photon.SocketS
namespace ILovePaoHuZi.Handler
public abstract
class Handler_base
public abstract OperationResponse 收到需要处理的信息(OperationRequest 客户端发来的请求Request);
}而建立handler_注册请求之前我们还需要在ILovePaoHuZiCommon工程中建立一个新的枚举,它表示了服务器返回给客户端的简单响应,例如,成功,失败,错误等结果。在ILovePaoHuZiCommon中新建一个类文件&服务器返回码枚举 。代码如下:
namespace ILovePaoHuZiCommon
public enum 服务器返回码枚举 :byte
服务器错误
}有了这个返回码,我们的一些简单请求就直接在响应信息中带上就可以了。
然后建立&handler_注册请求 类文件,放入ILovePaoHuZi工程下Handler文件夹代码如下(注意:要在服务器ILovePaoHuZi工程中引用LitJson作为数据交换&#26684;式,将信息中的string还原为UserInfo_Model类)
using ILovePaoHuZiCommon.
using ILovePaoHuZiC
using Photon.SocketS
using ExitGames.L
using ILovePaoHuZi.DB.DBM
using LitJ
namespace ILovePaoHuZi.Handler
public class Handler_注册请求 : Handler_base
/// &summary&
/// LOG接口
/// &/summary&
private static readonly ILogger log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
/// &summary&
/// 数据库操作脚本
/// &/summary&
UserInfo_Manager UserInfo类_数据库管理器;
//编写一个构造函数,在new这个脚本的时候执行一些指令
public Handler_注册请求(){
//在这个通讯处理器被实例化时,建立一个数据库管理器,专门为这个 通讯处理器 服务
UserInfo类_数据库管理器 = new UserInfo_Manager();
public override OperationResponse 收到需要处理的信息(OperationRequest 客户端发来的请求Request)
//获取传输过来的内容中的UserInfo_Model
object 传来的内容;
客户端发来的请求Request.Parameters.TryGetValue((byte)通讯内容_参数枚举.UserInfo_Model类,out 传来的内容);
//通过 数据交换格式 LitJson将客户端传递过来的string内容转换为UserInfo_Model
UserInfo_Model 传来的注册信息 = JsonMapper.ToObject&UserInfo_Model&(传来的内容.ToString());
//调用数据库操作脚本的新建用户方法
int 数据库操作结果=
UserInfo类_数据库管理器.用户注册(传来的注册信息);
//根据调用数据库返回来的代码选择服务器响应的内容和代码
if (数据库操作结果 == -1)
{//返回为-1时,代表用户名有相同的用户,注册失败
OperationResponse 服务器的响应 = new OperationResponse();
服务器的响应.OperationCode = (byte)通讯类型枚举.注册请求_通讯类型;
服务器的响应.ReturnCode =(byte) 服务器返回码枚举.失败;
服务器的响应.DebugMessage = &用户名被占用&;
return 服务器的响应;
if (数据库操作结果 == -2)
{//返回为-2时,代表有错误发生
OperationResponse 服务器的响应 = new OperationResponse();
服务器的响应.OperationCode = (byte)通讯类型枚举.注册请求_通讯类型;
服务器的响应.ReturnCode = (byte)服务器返回码枚举.服务器错误;
服务器的响应.DebugMessage = &服务器未知错误&;
return 服务器的响应;
if (数据库操作结果 &= 0)
{//返回为&=0时,代表注册成功,新用户ID为返回码
OperationResponse 服务器的响应 = new OperationResponse();
服务器的响应.OperationCode = (byte)通讯类型枚举.注册请求_通讯类型;
服务器的响应.ReturnCode = (byte)服务器返回码枚举.成功;
//注册成功,将注册产生的用户ID返回给客户端
服务器的响应.DebugMessage = 数据库操作结果.ToString();
return 服务器的响应;
2.1、在服务器上MainClass上建立通讯处理器字典以及每个通讯处理器注册的方法。
2.1.A、先将MainClass定义为单列模式,方便其他脚本的访问:
public static MainClass s_MainC并在Setup()方法中添加赋&#20540;语句:
//服务器启动成功的时候会调用这个方法
protected override void Setup()
s_MainClass =
InitLogging();
log.Debug(&服务器启动&);
}2.1.B、定义 通讯处理器字典,好让其他脚本根据 信息内容中的 通讯类型枚举 调用它选择 通讯处理脚本(Handler脚本)
public Dictionary&byte, Handler_base& 通讯处理器字典 = new Dictionary&byte, Handler_base&();2.1.C、添加通讯处理器(Handler脚本)的注册方法,将其添加进字典,记住,每编写一个通讯处理器就需要在这个方法中将其添加进字典(这里和客户端不一样,因为客户端是挂载在场景上,会自动运行,所以就自动添加了,而在服务器上只能手动实例化它并手动添加进字典)。
//每编写一个通讯类型处理器,必须将其添加进字典进行注册。
public void 通讯处理器注册方法()
通讯处理器字典.Add((byte)通讯类型枚举.注册请求_通讯类型, new Handler_注册请求());
}2.1.d、完成后,MainClass的代码:
using ExitGames.L
using ExitGames.Logging.Log4N
using ILovePaoHuZi.H
using log4
using log4net.C
using Photon.SocketS
using System.Collections.G
using System.IO;
using ILovePaoHuZiC
namespace ILovePaoHuZi
public class MainClass : ApplicationBase
#region 定义变量
public static MainClass s_MainC
public Dictionary&byte, Handler_base& 通讯处理器字典 = new Dictionary&byte, Handler_base&();
private static readonly ILogger log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
#endregion
#region 其他方法
//每编写一个通讯类型处理器,必须将其添加进字典进行注册。
public void 通讯处理器注册方法()
通讯处理器字典.Add((byte)通讯类型枚举.注册请求_通讯类型, new Handler_注册请求());
#endregion
#region photon接口方法
//当有客户端连接上服务器时,将引用这个方法,建立一个客户端处理类,我们叫它ClientPeer,他是一个继承自PeerBase的自定义类
protected override PeerBase CreatePeer(InitRequest initRequest)
客户端Peer temp = new 客户端Peer(initRequest);
//服务器启动成功的时候会调用这个方法
protected override void Setup()
s_MainClass =
InitLogging();
log.Debug(&服务器启动&);
//服务器停止的时候会调用这个方法
protected override void TearDown()
log.Debug(&服务器停止&);
//设置Log文件
protected virtual void InitLogging()
ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
//这里是设置Log文件储存的路径,一般不用改了
GlobalContext.Properties[&Photon:ApplicationLogPath&] = bine(this.ApplicationRootPath, &log&);
//将这里的&MS&改成&ILovePaoHuZi&
GlobalContext.Properties[&LogFileName&] = &ILovePaoHuZi& + this.ApplicationN
//注意,这里有一个log4net.config配置文件需要拷贝到工程里面来,并且要设置其输出时为拷贝
//这个文件位于Photon安装路径\src-server\Loadbalancing\Loadbalancing文件下,
//复制进工程后,点击它选择属性-高级-复制到输出目录这一项,原来是不复制,改成始终复制
XmlConfigurator.ConfigureAndWatch(new bine(this.BinaryPath, &log4net.config&)));
#endregion
2.3、服务器接收信息并响应信息。
& & 还记得&客户端Peer 这个类文件吗?一个服务器会有很多个这个脚本在运行,它是服务器上专门用来处理客户端信息的类文件,每一个连接上的客户端都会有一个对应的&客户端Peer
&类,它拥有几个接口,所以当客户端的信息传到服务器的时候,第一时间是传到它的OnOperationRequest()方法中,我们需要在这里对信息进行解析,以决定采用的响应。
代码如下,解释在代码中:
using Photon.SocketS
using PhotonHostRuntimeI
using ExitGames.L
using ILovePaoHuZi.H
namespace ILovePaoHuZi
class 客户端Peer : ClientPeer
private static readonly ILogger log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
//这个自定义类的构造方法,因为他的父类同样需要参数构造,所以加上base(initRequest)
public 客户端Peer(InitRequest initRequest):base(initRequest){
//当客户端断线的时候调用的方法
protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
//当服务器收到来自这个客户端的请求的时候的方法
protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
Handler_base 选定的通讯处理器;
//先检查字典,看看这个通讯类型是否有处理器进行处理
if( ! MainClass.s_MainClass.通讯处理器字典.TryGetValue(operationRequest.OperationCode, out 选定的通讯处理器))
//没有这个通讯类型,代表通讯信息代码错误或者通讯处理器没有注册
log.Debug(&未知的通讯类型:&+ operationRequest.OperationCode.ToString());
//根据字典选用 通讯处理器 进行处理,并获得返回的 响应
OperationResponse 服务器的响应= 选定的通讯处理器.收到需要处理的信息(operationRequest);
if (服务器的响应 != null)
{//代表 正确的从 通讯处理器 获得了服务器响应,那么我们返回它给客户端
SendOperationResponse(服务器的响应, sendParameters);
3、客户端收到服务器响应后的处理。
好了,上面的工作完成了客户端发送注册请求,服务器收到注册请求并操作数据库进行工作,然后返回一个响应信息给客户端,下面我们就需要编写在客户端接收服务器响应后的处理方法了;前面我们说过,我们的客户端接收到服务器的信息是在PhotonClientManager类文件的OnOperationResponse()方法中,在这个方法中我们根据服务器返回的信息代码按照字典去选择通讯处理器完成动作;
3.1、建立客户端通讯处理字典
要使用字典进行类型判断并直接使用字典类储存的处理脚本对通讯进行处理,那么我们为了将处理脚本装入字典,就需要一个定义一个处理脚本类型。然后我们以它为父类继承它建立各种类型的处理脚本,这样就可以装入字典了。当然,你可以可以使用if(通讯类型==XX)使用XX处理请求脚本的方式来实现。不过,当今后通讯类型多了的话,你不要忘记每个类型都要增加一个判断语句哦!
3.1.A、我们建立一个处理脚本的基础类-Handler_Base,将他放置到Scripts-PhotonClient-Handles文件夹下,以后的通讯处理器全放这里,Handler_Base要设置成抽象类,并且继承MonoBehaviour,要定义一个抽象函数用来定义子类的通讯类型,还要定义一个virtual虚拟start方法,让他自动向PhotonClientManager的单列模式的字典进行注册;这样我们今后建立的新处理脚本会自动将自己和自己的类型在PhotonClientManager的字典中注册好;然后建立一个抽象方法-进行信息处理(OperationResponse
服务器的响应信息),用来接收由PhotonClientManager传入的来自服务器的通讯信息;代码如下:
using UnityE
using ILovePaoHuZiC
using System.C
using ExitGames.Client.P
public abstract class Handler_Base:MonoBehaviour
public abstract 通讯类型枚举 本脚本通讯类型 { }
public virtual void
//PhotonClientManager中的字典还未建立,所以暂时空着把
public abstract void 进行信息处理(OperationResponse 服务器的响应信息);
3.1.B、在PhotonClientManager中建立 通讯处理字典 并编写注册方法;
下面我们回到PhotonClientManager中,定义一个字典名字叫做&通讯类型处理器字典,
#region 其他定义
/// &summary&
/// 储存有通讯类型和应该采用的处理器的字典。通过他来自动选择不同的处理器处理不同类型的通讯类型信息
/// &/summary&
public Dictionary&byte, Handler_Base& 通讯类型处理器字典 = new Dictionary&byte, Handler_Base&();
#endregion并添加注册方法
#region 其他方法
public void 注册通讯类型处理器(通讯类型枚举 _通讯类型, Handler_Base _具体的通讯处理器脚本)
通讯类型处理器字典.Add((byte)_通讯类型, _具体的通讯处理器脚本);
#endregion
3.1.C、在Handler_Base中添加注册方法
下面我们再回到Handler_Base中,在start方法中添加注册的命令;
using UnityE
using ILovePaoHuZiC
using System.C
using ExitGames.Client.P
public abstract class Handler_Base:MonoBehaviour
public abstract 通讯类型枚举 本脚本通讯类型 { }
public virtual void
PhotonClientManager.s_PhotonClientManager.注册通讯类型处理器(本脚本通讯类型,this);
public abstract void 进行信息处理(OperationResponse 服务器的响应信息);
3.2、建立客户端上的第一个通讯处理器-注册请求处理器
先解释一下,因为我们的Handler_Base中继承MonoBehaviour需要用到start()方法,所有我们的具体的通讯处理器都需要挂载在场景中的物体上,他才会工作并自动注册。所以,我们在场景中的PhotonClientManager空物体下再建立一个空物体-handleList,专门用来挂载各种通讯处理器;
3.2.1、建立注册请求处理器-Handler_注册请求
在场景的PhotonClientManager-handleList上新建一个叫做&Handler_注册请求 的脚本,让其继承自Handler_Base,实现抽象方法,重写start()方法,记住start方法中的base.Start()不要删除。代码如下:
using ExitGames.Client.P
using ILovePaoHuZiC
public class Handler_注册请求 : Handler_Base
public override 通讯类型枚举 本脚本通讯类型
//这个脚本是处理哪种通讯类型的就return 哪种通讯类型
return 通讯类型枚举.注册请求_通讯类型;
public override void Start()
base.Start();
//这后面还可以添加你需要的代码,但是不能删除base.Start();
//这个方法是用来接收由PhotonClientManager传递的来自服务器的通讯信息的方法
public override void 进行信息处理(OperationResponse 服务器的响应信息)
3.2.2、刚刚我们在服务器上又建立了一个返回码的枚举,所以我们要重新生成ILovePaoHuZiCommon.dll并导入最新版本的文件到unity客户端上
3.3、在PhotonClientManager的OnOperationResponse中分拣服务器响应信息,并指派 通讯处理器字典中相对应的处理器类进行处理
public void OnOperationResponse(OperationResponse operationResponse)
Handler_Base 选择的处理器脚本;
if( !通讯类型处理器字典.TryGetValue(operationResponse.OperationCode, out 选择的处理器脚本))
Debug.Log(&未知的服务器响应通讯类型:& + operationResponse.OperationCode.ToString());
选择的处理器脚本.进行信息处理(operationResponse);
好了,这里已经完成分拣工作,如果是注册的通讯类型,那么就会选择我们刚刚建立的Handler_注册请求 脚本来处理。
3.4、Handler_注册请求的信息处理;
好了,现在服务器的响应信息已经传到了&Handler_注册请求 脚本的&进行信息处理(OperationResponse 服务器的响应信息) 方法中了,下面我们就编写根据服务器反馈采用的行为,UI_注册.s_UI_注册.注册成功()这些方法是我的处理方法,你要怎么做你自己写就好了;
public override void 进行信息处理(OperationResponse 服务器的响应信息)
switch (服务器的响应信息.ReturnCode)
case (byte)服务器返回码枚举.成功:
{//如果服务器返回为成功
UI_注册.s_UI_注册.注册成功();
case (byte)服务器返回码枚举.失败:
{//如果服务器返回为失败
UI_注册.s_UI_注册.注册失败(服务器的响应信息.DebugMessage);
case (byte)服务器返回码枚举.服务器错误:
{//如果服务器返回为错误
UI_注册.s_UI_注册.注册失败(服务器的响应信息.DebugMessage);
好了,最后在电脑-管理中打开数据库的服务,然后启动photonserver,然后运行unity,注册一个用户试试。OK,反正我的测试正常了,你的呢?
如果有问题请联系我微信:hmokhmok
后面的登录什么什么的请求都可以自己按照结构写了;客户端的结构和服务器的数据结构我会做个图发上来。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:11184次
排名:千里之外
原创:10篇
(3)(1)(5)(3)(3)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'}

我要回帖

更多关于 服务端 端口 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信