基于消息的面向对象双人对弈游戏服务程序

发表于:2007-07-04来源:作者:点击数: 标签:
浙江大学计算机系 胡朝晖 宁波海峰塑化有限公司 王海瑛 设计通用的可扩展的双人对弈 游戏 服务程序需要考虑很多细节性问题,如何把设计过程简单明确化是一个需要研究的问题。本文提出基于消息响应机制和 面向对象 的服务程序的设计方法,使服务端的程序设计简
浙江大学计算机系 胡朝晖
宁波海峰塑化有限公司 王海瑛

  设计通用的可扩展的双人对弈游戏服务程序需要考虑很多细节性问题,如何把设计过程简单明确化是一个需要研究的问题。本文提出基于消息响应机制和面向对象的服务程序的设计方法,使服务端的程序设计简单明确,并且把各个实现的细节封装在对象里面去实现,各种对象实现自己特定的功能。整个体系结构简单明了,是实现服务端程序的一种比较通用的模式。

  本文设计和实现了一个基于Internet的双人对弈游戏服务程序,用户可以通过与TCP/IP协议和服务器进行连接,选择与其他的在线用户进行厮杀。我们的系统是在Windows NT平台下构建的,使用了VC的开发工具,但是其设计思想可以扩展到所有其他的操作系统上。

  在客户/服务器和浏览器/服务器体系结构中,服务器一般都是处于被动的状态,等待客户的各种请求,然后服务器通过消息触发和消息响应机制来完成它的工作。所以如何合理地设计服务器端程序的体系结构来满足应用程序的需求就显得尤为重要。 一.体系结构

  在我们设计的服务程序的结构中,着重考虑服务器响应客户端消息,并把响应的请求提交给特定的对象进行操作,使系统体系结构比较清楚而且容易扩充。

  系统采用了多线程的技术,主线程可以对服务器进行动态的配置,并可以动态对用户进行管理,可以查看系统的日志情况,关闭和重新开放服务器程序。调度线程用来对服务器所有的Socket连接进行动态的管理和维护。侦听线程用来处理所有用户的连接请求,并对每一个新的连接建立一个新的用户连接的子线程,而和用户连接的子线程则处理用户的消息并返回给用户相关的消息命令。具体框架见图1。

二 用户请求信息分类和协议格式

  用户的请求信息可以分为以下几类:

  1.用户管理信息

  ● 用户注册服务器:当一个新的用户需要登入到服务器之前,必须在服务器中注册自己,注册信息包括用户的名称、密码、E-Mail地址等信息,然后系统会分配给用户一个唯一的ID号来唯一标识这个用户。其命令格式为: /Register:username:userpassword:usermail。其中/表示命令的开始,Register表示命令的类型,username表示用户的注册名称,userpassword表示用户的密码,usermail表示用户的E-Mail。

  ● 用户请求登入服务器:正式注册用户通过服务器传送它的ID和密码请求申请登入服务器。其命令格式为: /Login:userID:userpassword。

  ● 用户取消自己的注册:当用户不想在该服务器上使用时,取消自己的注册。其命令格式为:

  /Unregister:userID: userpassword。

  ● 用户修改自己的密码:其命令格式为:

  /ModifyPassword:userID:
useroldpassword:usernewpassword。

  ● 用户查询其他用户信息:用户可以根据用户名称查询在这个服务器上注册的该用户的信息。其命令格式为: /QueryUser:userID。

  ● 用户列出当前所有在线用户的名单:用户可以列出当前所有的在线用户的信息。其命令格式为: /ListAllUser。

  ● 用户和所有在线上的人聊天:用户可以和在线的所有的用户聊天,也可以和特定的一个人说悄悄话。其命令格式为: /TalkOnLine: (public|private):
talkway:talkObject:talkContent。

  ● 用户要求列出当前所有在线棋局:可以列出所有在线棋局的ID、名称和状态等其他信息。其命令格式为: /ListAllBoard。

  ● 用户退出该游戏服务器:表示用户要离开该游戏服务器。其命令格式为:/Logout

  2.用户下棋过程发布信息

  ● 用户要求开新的棋局

  /CreateNewBoard:boardName:
(boardParameter|boardParameterValue)

  ● 用户要加入现开的棋局,成为该棋局的一个弈方

  /AddInBoard:boardID

  ● 开棋局的用户对应战用户的响应:

  /Agree:boardID:(Yes|No)

  ● 用户要求观战一个棋局

  /VisitBoard:boardID

  ● 用户对某一棋局发表评论

  /CommentBoard:boardID:Rumor:CommentContent

  /CommentBoard:boardID:
PrvTalk:userID:CommentContent

  ● 用户发送走子信息

  /SendInputPos:boardID:
X_Pos:Y_Pos:PeerSpendingTime

  ● 用户发出认输信息

  /GiveUp:boardID

  ● 用户发出赢棋或者是输棋信息

  /Win:boardID:(Yes|No)

  ● 用户退出对弈的棋局

  /Quit:boardID

  ● 用户发出封局要求

  /Pause:boardID

  ● 棋局另一方对封局要求的响应

  /Pause:boardID:(Yes|No)

  ● 用户要求悔棋

  /Repent:boardID

  ● 棋局另一方对悔棋要求的响应

  /Repent:boardID:(Yes|No)

  3.服务器端对消息的处理

  ● 当服务器收到用户注册服务器命令

  /Register:username:userpassword:usermail的时候,查找相应的用户数据库,如果不存在该用户名称,就把该用户信息写入到数据库中并分配一个唯一的ID号给该用户,使用ID 号的原因是为了加快以后数据库搜索的速度,同时返回注册成功的信息给该用户。命令格式为:/Register:Yes:username:userID。UserID表示服务器分配给该用户的ID号。如果该用户名称已经存在或者发生其他错误的话,返回如下的命令格式: /Register:No:username:FailReason,表示该注册未成功。FailReason表示失败的原因,实际上是一个描述字符串。

  ● 当服务器收到用户请求登入服务器命令 /Login:userID:userpassword的时候,服务器查找用户数据库发现是否存在该用户,并检查该用户是否已经在当前在线用户名单中,如果通过检查,返回如下的命令格式: /Login:Yes:userID:YourPosition,注意该条消息不仅发送给登入用户,同时也发送给所有在线用户,通知他们一个用户的登入。这里的YourPosition表示该用户是当前在线的第几个用户。当没有通过检查时,返回如下的命令格式给请求登入的用户:/Login:No:UserID: FailReason。

  ● 当服务器收到用户取消自己的注册 /Unregister:userID:userpassword的时候,服务器查找用户数据库并删除相应的该条记录。如果成功的话:返回如下的命令格式:/Unregister:Yes:UserID。如果不成功的话,返回如下的命令格式:/Unregister:No:UserID:FailReason。

  ● 当服务器收到用户修改自己的密码命令 /ModifyPassword:userID:
useroldpassword:usernewpassword的时候,如果成功,返回如下的命令格式:/ModifyPassword:Yes:UserID。如果不成功,返回如下的命令格式: /ModifyPassword:No:UserID:FailReason。

  ● 当服务器收到用户查询其他用户信息 /QueryUser:userid命令的时候,如果成功,返回如下的命令格式: /QueryUser:Yes:UserID:
{Parameter|ParameterValue}。这里的Parameter表示该参数的名称(比如用户的等级分),ParameterValue表示该参数的值。如果不成功,返回如下的命令格式:/QueryUser:No:UserID:FailReason。

  ● 当服务器收到用户列出当前所有在线用户名单的命令/ListAllUser的时候,如果成功,返回如下的命令格式: /ListAllUser:Yes:{UserID|username}。如果不成功,返回如下命令格式: /ListAllUser:No:FailReason。

  ● 当服务器收到用户和所有在线上的人聊天的命令/TalkOnLine:(public|private):
talkway:talkObject:talkContent的时候,根据talkway 的类型向不同的用户发送命令,如果是公共的消息,就向所有的用户发送命令 /TalkOnLine:public:userID:talkway:
talkObject:talkContent,如果是私人的消息,就向 talkObject发送命令/TalkOnLine:private:userID:
talkway:talkObject:talkContent。这里的userID是指消息发送者的ID号。

  ● 当服务器收到用户要求列出当前所有在线棋局命令/ListAllBoard的时候,发送如下的命令格式给请求的用户: /ListAllBoard:{(Yes|No)
|boardID|boardName|OpenuserID|Openusername
< |ResponseUserI D|ResponseUserName >},这里Openusername表示开这个棋局的人,如果该棋局已经在下的话,ResponseUserName表示应战的那个人。

  ● 当服务器收到用户退出该游戏服务器命令 /Logout的时候,如果成功,返回如下的命令格式:/Logout:Yes:UserID:username,注意该条消息不仅发送给该退出用户,同时也发送给所有在线用户,通知他们一个用户的退出。如果不成功,返回如下的命令格式给发出该命令的用户:/Logout:No:UserID:FailReason。

  ● 当服务器收到用户要求开新的棋局命令 /CreateNewBoard:boardName:
(boardParameter|boardParameterValue)的时候,如果成功,返回如下的命令格式:/CreateNewBoard:Yes:
userID:boardID:boardName: (boardParameter|boardParameterValue), 注意该条消息不仅发送给该要求开新棋局的用户,同时也发送给所有在线的用户,用来通知他们现在有一个新开的棋局。这里的userID 表示开棋局人的ID号,boardID是服务器分配的给该棋局的ID号。boardParamter和 boardParameterValue分别表示关于棋局参数和相应的参数值。如果失败的话,返回如下的命令给发出该命令的用户:/CreateNewBoard:No:userID:
boardName:FailReason。

  ● 当服务器收到用户要加入现开的棋局,成为该棋局的一个弈方命令/AddInBoard: boardID的时候,判断该棋局是否已经有两个人在下,该请求人是否满足条件,如果满足的话,发送如下的命令格式给开该棋局的用户 /AddInBoard:Yes:userID:boardID,这里的userID是指应战人的ID号。如果已经有两个人在下或者条件不满足的时候,发送如下的命令格式给该要求加入现开棋局的用户: /AddInBoard:No:userID:boardID,这里的userID是指应战人的ID号。

  ● 当服务器收到开棋局的用户对应战用户的响应命令/Agree:boardID:Yes的时候,向请求加入该棋局的用户和所有其他在线用户发送命令格式:/Agree:userID:boardID:Yes,这里userID表示应战人的ID号。如果收到开棋局的用户对应战用户的响应命令/Agree:boardID:No的时候, 向请求加入该棋局的用户发送命令格式:/Agree:userID:boardID:No,这里userID表示应战人的ID号。

  ● 当服务器收到用户要求观战一个棋局命令 /VisitBoard:boardID的时候,根据该棋局的设置向发送该命令的用户发送如下的命令之一:(1)/VistiBoard:Yes:boardID:
CurrentSit,这里CurrentSit实际上表示和当前已经下过的子的位置和顺序信息相关的一个字符串。(2) /VistiBoard:No:boardID:FailReason表示拒绝该用户的观战。

  ● 当服务器收到用户对某一棋局发表评论命令 /CommentBoard:boardID:
Rumor:CommentContent的时候,向和所有该棋局相关的用户(即下子方和观战方)发送命令/CommentBoard:boardID:
Rumor:CommentContent。这些消息不显示消息发出者的名字,只显示消息本身。当收到 /CommentBoard:boardID:PrvTalk:userID:
CommentContent的时候,向和该条消息相关的用户发送消息/CommentBoard:boardID:
PrvTalk:userID:CommentContent。这里的userID是指消息接收者的ID号。

  ● 当服务器收到用户发送走子信息 /SendInputPos:boardID:X_Pos:Y_Pos:
PeerSpendingTime的时候, 向和所有该棋局相关的用户发送命令/SendInputPos:boardID:
X_Pos:Y_Pos:PeerSpendingTime。X_Pos和Y_Pos分别表示下子的位置。PeerSpendingTime表示该走子方所花费的总时间。

  ● 当服务器收到用户发出认输信息命令 /GiveUp:boardID的时候,向和所有该棋局相关的用户发送此命令。

  ● 当服务器收到用户发出赢棋或者输棋信息 /Win:boardID:(Yes|No)的时候,向和所有该棋局相关的用户发送此命令。该信息实际上是客户端根据下子主动判断输赢情况。

  ● 当服务器收到用户退出对弈的棋局 /Quit:boardID的时候,向和所有该棋局相关的用户发送此命令。

  ● 当服务器收到用户发出封局要求命令 /Pause:boardID的时候, 向和该棋局相关的另一个下子方用户发送此命令。

  ● 当服务器收到棋局另一方对封局要求的响应 /Pause:boardID:(Yes|No)的时候,向和该棋局相关另一个下子方用户发送命令 /Pause:boardID:(Yes|No),如果是/Pause:boardID:Yes的话,还需要向所有观战的用户发出该消息。

  ● 当服务器收到用户要求悔棋命令 /Repent:boardID的时候,向和该棋局相关的另一个下子方用户发送此命令。

  ● 当服务器收到棋局另一方对悔棋要求的响应命令/Repent:boardID:(Yes|No)的时候,如果是/Repent:boardID:Yes的话,向和所有该棋局相关的用户发送命令/Repent:boardID:Yes,如果是/Repent:boardID:No的话,向和该棋局相关的另一个下子方用户发送命令/Repent:boardID:No。 三 系统对象结构描述

  在体系结构中,我们建立了如下的对象,分别用来处理用户的请求。首先,我们有一个基本的通信对象类用来处理和用户和服务器之间的数据通信。其他的对象有:在线用户对象集合类,当前棋局对象集合类,在线用户类,在线棋局对象类,棋局状态对象,棋子对象,规则判断类,记分对象类,用户聊天信息对象集合类、用户聊天信息对象。

  在线用户对象集合类(CClientConnetionList)是所有在线用户对象的集合,通过该类可以查找、增加和删除具体在线的用户。当前棋局对象集合类(CBoardList)包含了所有的在线的、在等待的和已经在下的棋局。通过该类可以查找、增加和删除具体在线的每一盘棋局。用户聊天信息对象集合类(CMessageList)包含所有用户发送的各种聊天信息。通过该类可以查找、增加和删除具体的每一条聊天消息对象。

  具体类对象的属性和方法如下:

  1.通信对象类(CBaseCommunication)

  封装了所有和SOCKET相关的API函数,只要通过调用CBaseCommunication中的方法和成员变量就能方便实现和用户及游戏服务器的通信。主要方法有Create、Bind、Connect、Aclearcase/" target="_blank" >ccept、Send、Receive、GetPeerName等等。在这里所有的SOCKADDR都被封装在类CBaseCommunication里面,对类的使用者而言,只要知道机器的IP地址和端口就可以使用,不需要了解SOCKADDR的结构,方便了程序的开发。实际上, CClientConection类派生自CBaseCommunication,用来处理和用户的数据和信息的传输。

  2.在线用户对象集合类(CClientConnectionList)

  是当前在线用户的集合,主要属性有:当前在线用户的链表。主要方法有:增加一个在线用户对象到链表中去、从链表中删除一个在线用户对象、得到在线用户的总数目、查找某一用户是否在线。

  3.在线用户类(CClientConnection)

  表示所有当前在线用户的相关信息。该类继承了通信对象类,每一个用户对应于一个SOCKET连接。主要属性有:用户的ID号、用户的游戏服务器中的名称、用户的密码、用户的等级分、用户下的总盘数、用户赢棋总盘数、用户输棋总盘数、用户在观战的棋局链表、用户在下的棋局链表。主要方法有:对用户请求信息的分析、发送相应的命令给用户、注册新用户、判断该用户是否是注册用户、从文件或数据库中装载用户信息、从用户的观战的棋局链表中删除一个棋局、从用户在下的棋局链表中删除一个棋局、返回用户的记录信息、更新用户的记录信息(包括等级分、下的总盘数等)、用户退出服务器、把用户最新信息写入文件或数据库。

  4.在线棋局对象集合类(CBoardList)

  主要的属性有:当前棋局对象的链表,主要的方法有:增加一个棋局对象到链表中去、从链表中删除一个棋局对象、得到当前在下的棋局的总数目、根据棋局ID号查找某一棋局。

  5.棋局对象类(CBoardSet)

  该对象是用来表示和每一盘棋局相关的信息和方法。主要的属性有:棋局ID号、A棋手(表示先下的棋手)、B棋手(表示后下的棋手)、当前棋局状态(无人、单人、双人)、棋局类型(人-机、人-人)、可否悔棋、后进入者等级分要求、拒绝允许进入的用户的链表、是否允许观战、是否允许下子双方互相发送信息、是否允许观战者评论、棋局当前进行状态(如正在互相攻防、平局、A胜、B胜)、当前谁可下子、观看棋局者链表、当前棋局情况的结构描述,即记录棋局的整个当前情况,它本身是棋局状态对象的实例。主要的方法有:判断当前棋局状态、改变当前棋局状态标志、是否允许某一用户成为B棋手、是否允许发表评论、棋局初始状态的设定(如是否允许悔棋、是否允许观战等等),由棋局创立者设定下子是否合理、该状态是否可以终盘、设定B棋手、设定下一个行子方、某一用户是否是观战方、某一用户是否是弈子方、返回本棋局相关用户集合。

  6.棋局状态对象(CStepList)

  这实际上是棋子对象的集合。主要的属性有:每一个棋子对象的集合,主要的方法有:增加一个棋子对象、删除一个棋子对象、总下过的步数、回退到以前的某一个状态。

  7.棋子对象(CStep)

  主要属性有:棋子的位置,棋子的颜色,棋子处于第几步。

  8.规则判断类(CruleSet)

  主要的属性有:游戏的名称,某一棋局对象。主要方法有:根据游戏规则判断当前局面所处的情况,是正常对攻阶段还是已可分胜负或平局,需要根据当前游戏的名称(如中国象棋、国际象棋、围棋、五子棋等等)和棋局的ID号。

  9.记分对象类(CrecordScore)

  主要的属性有一般输赢的正常加减分,记分相关的棋局对象,主要的方法有:根据对弈双方的等级分折算比例乘上一般输赢的正常加减分,给每个棋手算本局应得到的分数。

  10.用户聊天信息对象集合类(CMessageList)

  是对所有聊天信息对象的组织、管理和维护。能够根据消息对象的特点,动态维护信息池中所有消息对象,包括增加、删除和选取相关的消息对象。

  11.用户聊天信息对象(CMessage)

  消息对象封装了所有和消息相关的属性:比如消息发送者、消息接收者、消息发送的时间、消息的类型、消息的内容、消息是发送到公共的聊天室还是相关的某一个棋局等等。 四 系统设计实现描述

  主线程中主要有三个属性对象,一个为 m_clientConnectionList(CClientConnectionList),它表示当前和服务器相连接的在线用户对象的的集合。第二个为m_pMessageList(CMessageList),它表示现有的服务器的信息池中信息对象的集合。第三个为m_BoardList(CBoardList),它表示当前棋局对象的集合。

  侦听线程工作的描述:等待用户的连接请求,每当有新的用户请求连接的时候,创建一个新的线程和该用户进行数据和信息的传送,同时把当前的用户连接对象加入到m_clientConnectionList中去。

  调度线程工作的描述:更新用户的连接情况。基本的实现方法是这样的,当用户登入的时候判断每一个连接所处的状态,如果连接在很长时间(可以设定非活动状态的最大时间)内处于非活动状态的话,就断掉它,因为实际上和每一个用户的连接都会占用一个线程,通过不停的扫描,断掉不活动的连接,释放相关的线程,同时保证了其他用户的连接登入。

  和用户连接的子线程的工作描述:填写和该用户相关的一些静态的参数,如用户的ID号、用户在游戏服务器中的名称、用户的密码、用户的等级分、用户下的总盘数、用户赢棋总盘数、用户输棋总盘数等等。同时接收用户的数据包,对数据包进行分析,根据不同的消息进行不同的处理,比如增加棋局对象、生成用户聊天信息对象并加入到用户聊天信息对象集合类等等, 同时发送相应的消息给该连接的用户。 五 小结

  根据以上的思路,我们在NT Server上实现了基于C/S 结构的五子棋的服务器,效果良好。如果要实现其他双人对弈的服务器程序,只要在规则判断类中对当前局面分析函数进行修改就可以完成相应的功能,体现了面向对象程序设计的灵活性。    

原文转自:http://www.ltesting.net