MoleServer游戏服务器框架使用教程(三)

MoleServer游戏服务器框架使用教程(三)


 

在这篇文章中,我们将以我们提供的游戏为例,详细讲解下整个游戏的开发过程,首先我们的客户端是基于cocos2dx-js 3.6版本开发而成,当然你也可以使用其它游戏引擎来做这件事,我们这里主要讲讲整个游戏客户端和服务器是如何交互的。

用IDE打开websocket_demo,在这里,我们的游戏一启动就开始去连接账号服务器了:

socket = new WebSocket(host);
socket.onopen = function(){
    socket.send('100');
 last_health = new Date();
 clearInterval(keepalivetimer);
 keepalivetimer = setInterval( function(){keepalive(socket)},1000);
socket.onopen = function(){
    socket.send('100');
 last_health = new Date();
 clearInterval(keepalivetimer);
 keepalivetimer = setInterval( function(){keepalive(socket)},1000);

连接成功后开启了一个定时器,这个定时器每秒执行一次,干嘛呢,代码如下:

function keepalive(ws,type=0) {
    var time = new Date();
 var curtime = last_health.getTime();
 if(type == 1)
        curtime = last_health_game.getTime();
 if(time.getTime() - curtime > health_timeout)
    {
        if(type == 0)
            clearInterval(keepalivetimer);
 else
 clearInterval(keepalivetimer_game);
 }
    else
 {
        if (ws.bufferedAmount == 0) {
            ws.send('100');
 last_health = time;
 last_health_game = time;
 }
    }
};
    var time = new Date();
 var curtime = last_health.getTime();
 if(type == 1)
        curtime = last_health_game.getTime();
 if(time.getTime() - curtime > health_timeout)
    {
        if(type == 0)
            clearInterval(keepalivetimer);
 else
 clearInterval(keepalivetimer_game);
 }
    else
 {
        if (ws.bufferedAmount == 0) {
            ws.send('100');
 last_health = time;
 last_health_game = time;
 }
    }
};

主要看ws_send(‘100’),这是一个心跳消息,基于TCP/IP协议的连接都要靠心跳来维持长连接,但我们游戏框架中已经在内部处理了这个消息,消息号就是100,客户端启动之后,需要每秒发送一次,如果服务器在5秒后没有收到任何心跳消息,那就断定这个客户端已经离开了。关于框架内部如何处理这个心跳消息,可以具体看看molenet网络库。

到这里,我们的游戏客户端的连接就稳定的建立了,然后显示登录框,可以登录,注册,接微信什么的。

输入账号密码后发送下面的消息到账号服务器注册:

var row1 = {};
row1.MsgId = 700;
row1.UserName = this._boxusername.getString();
row1.UserPW = hex_md5(this._boxuserpassword.getString());
socket.send(JSON.stringify(row1));
row1.MsgId = 700;
row1.UserName = this._boxusername.getString();
row1.UserPW = hex_md5(this._boxuserpassword.getString());
socket.send(JSON.stringify(row1));

关于框架所有用到的消息定义都在moleserver/include/Common/defines.h里面,我们具体来看看700有那些消息:

#define IDD_MESSAGE_USER_REGISTER 700 // 用户注册

#define IDD_MESSAGE_USER_REGISTER_SUCCESS 701 // 用户注册成功

#define IDD_MESSAGE_USER_REGISTER_FAIL 702 // 用户注册失败

#define IDD_MESSAGE_SUPER_BIG_MSG 703 // 广播消息


 

游戏客户端是如何处理这些消息的呢,代码如下:

case 700:
{
    switch(obj.MsgSubId)
    {
        case 701:
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("注册成功!");
 m_registerLayer.setVisible(false);
 MyMainLoginlayer.setVisible(true);
 }
            break;
 case 702:
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("注册失败,请检查您的用户名和密码!");
 }
            break;
 default:
            break;
 }
}
    break;
{
    switch(obj.MsgSubId)
    {
        case 701:
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("注册成功!");
 m_registerLayer.setVisible(false);
 MyMainLoginlayer.setVisible(true);
 }
            break;
 case 702:
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("注册失败,请检查您的用户名和密码!");
 }
            break;
 default:
            break;
 }
}
    break;

用户注册成功以后,就可以开始进行登录验证了,代码如下:

var row1 = {};
row1.MsgId = 400;
row1.username = m_userloginname;
row1.userpwd = m_userloginpassword;
row1.machinecode = 'html5';
socket.send(JSON.stringify(row1));
row1.MsgId = 400;
row1.username = m_userloginname;
row1.userpwd = m_userloginpassword;
row1.machinecode = 'html5';
socket.send(JSON.stringify(row1));

我们来看看关于400,有哪些消息:

#define IDD_MESSAGE_CENTER_LOGIN 400 // 用户登录消息

#define IDD_MESSAGE_CENTER_LOGIN_SUCESS 401 // 用户登录成功

#define IDD_MESSAGE_CENTER_LOGIN_FAIL 402 // 用户登录失败

400的返回消息很多,我们这里不一一展示,我们来看看游戏客户端是如何处理的,代码较多,我们只截取验证成功后的代码:

case 401:
{
    isLoginSuccuss = true;
 myselfUserId = obj.UserId;
 m_myselfusermoney = obj.money;
 m_myselftempusermoney = m_myselfusermoney;
 self.MyUserName.setString(m_userloginname);
 self.MyUserMoney.setString(m_myselfusermoney);
 //MyMainLoginlayer.removeAllChildrenWithCleanup(true);
 MyMainLoginlayer.removeFromParent();
 self.MyUserName.setVisible(true);
 self.MyUserMoney.setVisible(true);
 //获取服务器列表
 var row1 = {};
 row1.MsgId = 800;
 socket.send(JSON.stringify(row1));
 // if(m_isplaymusic) {
   //     cc.audioEngine.playMusic(soud_01, true);
   // }
}
    break;
{
    isLoginSuccuss = true;
 myselfUserId = obj.UserId;
 m_myselfusermoney = obj.money;
 m_myselftempusermoney = m_myselfusermoney;
 self.MyUserName.setString(m_userloginname);
 self.MyUserMoney.setString(m_myselfusermoney);
 //MyMainLoginlayer.removeAllChildrenWithCleanup(true);
 MyMainLoginlayer.removeFromParent();
 self.MyUserName.setVisible(true);
 self.MyUserMoney.setVisible(true);
 //获取服务器列表
 var row1 = {};
 row1.MsgId = 800;
 socket.send(JSON.stringify(row1));
 // if(m_isplaymusic) {
   //     cc.audioEngine.playMusic(soud_01, true);
   // }
}
    break;

验证成功后,我们开始获取所有的游戏服务器信息,关于800的消息如下:

#define IDD_MESSAGE_GET_GAMESERVER 800 // 得到游戏服务器列表

#define IDD_MESSAGE_GET_GAMEINFO 801 // 得到游戏信息列表

#define IDD_MESSAGE_GET_GAMEINFO_SUCCESS 802 // 得到游戏信息列表成功

#define IDD_MESSAGE_GET_GAMEINFO_FAIL 803 // 得到游戏信息列表失败

游戏客户端处理如下:

case 800:
    {
        var MsgSubId = obj.MsgSubId;
 if(MsgSubId == 803)
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("获取服务器失败,请稍后再试!");
 }
        else
 {
            var RoomCount = obj.RoomCount;
 console.info("start."+RoomCount);
 m_isLoginSuccess = true;
 m_gameserver = "ws://"+obj.Room[0].serverip+":"+obj.Room[0].serverport;
 //socket.onclose();
 console.info("start.");
 gameserversocket = new WebSocket(m_gameserver);
 gameserversocket.onopen = function(){
                gameserversocket.send('100');
 last_health_game = new Date();
 clearInterval(keepalivetimer_game);
 keepalivetimer_game = setInterval( function(){keepalive(gameserversocket,1)},1000);
 console.info("connned.");
 }
    {
        var MsgSubId = obj.MsgSubId;
 if(MsgSubId == 803)
        {
            var loginlayer = new MyMessageBoxLayer();
 self.addChild(loginlayer,8);
 loginlayer.init("获取服务器失败,请稍后再试!");
 }
        else
 {
            var RoomCount = obj.RoomCount;
 console.info("start."+RoomCount);
 m_isLoginSuccess = true;
 m_gameserver = "ws://"+obj.Room[0].serverip+":"+obj.Room[0].serverport;
 //socket.onclose();
 console.info("start.");
 gameserversocket = new WebSocket(m_gameserver);
 gameserversocket.onopen = function(){
                gameserversocket.send('100');
 last_health_game = new Date();
 clearInterval(keepalivetimer_game);
 keepalivetimer_game = setInterval( function(){keepalive(gameserversocket,1)},1000);
 console.info("connned.");
 }

这里可以获取到你所配置的所有游戏服务器,我们这里只连接一台。游戏服务器连接成功后,和账号服务器一样,开启定时器,每隔一秒发送一条心跳信息,以维持和游戏服务器的长连接。

游戏服务器连接建立成功之后,就用开始用于账号服务器验证的账号和密码进行验证登录。

case 300:
{
    if(objgame.MsgSubId == 301)
    {
        var row1 = {};
 row1.MsgId = 500;
 row1.UserName = m_userloginname;
 row1.UserPW = m_userloginpassword;
 row1.DeviceType=1;
 gameserversocket.send(JSON.stringify(row1));
 }
}
break;
{
    if(objgame.MsgSubId == 301)
    {
        var row1 = {};
 row1.MsgId = 500;
 row1.UserName = m_userloginname;
 row1.UserPW = m_userloginpassword;
 row1.DeviceType=1;
 gameserversocket.send(JSON.stringify(row1));
 }
}
break;

游戏服务器账号验证的返回消息:

#define IDD_MESSAGE_GAME_LOGIN 500 // 用户登录消息

#define IDD_MESSAGE_GAME_LOGIN_SUCESS 501 // 用户登录成功

#define IDD_MESSAGE_GAME_LOGIN_FAIL 502 // 用户登录失败

#define IDD_MESSAGE_GAME_LOGIN_BUSY 503 // 系统忙,用户登录过于频繁

#define IDD_MESSAGE_GAME_LOGIN_EXIST 504 // 用户已经在系统中

#define IDD_MESSAGE_GAME_LOGIN_FULL 505 // 服务器满

#define IDD_MESSAGE_GAME_LOGIN_CLOSE_SERVER 506 // 关闭当前服务器

#define IDD_MESSAGE_GAME_LOGIN_MATCHING_NOSTART 507 // 比赛未开始

#define IDD_MESSAGE_GAME_LOGIN_MATCHING_NOSCROE 508 // 没有达到比赛场所要求的积分

#define IDD_MESSAGE_GAME_LOGIN_MATCHING_NOLEVEL 509 // 没有达到比赛场所要求的等级

#define IDD_MESSAGE_GAME_LOGIN_BANLOGIN 510 // 服务器被封

#define IDD_MESSAGE_GAME_LOGIN_USERBANLOGIN 511 // 玩家账号被封

游戏客户端在收到游戏服务器验证通过的消息后,就发送进入房间消息:

case 501:
{
    if(objgame.ID == myselfUserId){
        var row1 = {};
 row1.MsgId = 900;
 row1.MsgSubId=901;
 row1.RoomIndex = -1;
 row1.ChairIndex = -1;
 row1.EnterPWd = "";
 row1.Enterfirst=0;
 row1.Entersecond=0;
 gameserversocket.send(JSON.stringify(row1));
 }
}
    break;
{
    if(objgame.ID == myselfUserId){
        var row1 = {};
 row1.MsgId = 900;
 row1.MsgSubId=901;
 row1.RoomIndex = -1;
 row1.ChairIndex = -1;
 row1.EnterPWd = "";
 row1.Enterfirst=0;
 row1.Entersecond=0;
 gameserversocket.send(JSON.stringify(row1));
 }
}
    break;

当你发送这个消息后,如果成功后,就会触发moleserver/games/example/CServerServiceManager.cpp文件下的OnProcessEnterRoomMsg接口。游戏逻辑的接口如下:

/// 用于处理用户开始游戏开始消息

virtual void OnProcessPlayerGameStartMes();

/// 用于处理用户进入游戏房间后的消息

virtual void OnProcessPlayerRoomMes(int playerId,Json::Value &mes);

/// 处理用户进入房间消息

virtual void OnProcessEnterRoomMsg(int playerId);

/// 处理用户离开房间消息

virtual void OnProcessLeaveRoomMsg(int playerId);

/// 处理用户断线重连消息

virtual void OnProcessReEnterRoomMes(int playerId);

/// 处理用户断线消息

virtual void OnProcessOfflineRoomMes(int playerId);

/// 处理用户定时器消息

virtual void OnProcessTimerMsg(int timerId,int curTimer);


 

我们在来看看 OnProcessEnterRoomMsg接口中具体干了些什么:

if(m_gamisrunning == false)

{

LoadGameConfig();

m_cardrecord.push_back(m_CGameLogic.GetCardByColor(rand()%5));


 

m_g_GameRoom->StartTimer(IDD_TIMER_GAME_STARTING,3);

m_gamisrunning=true;

}


 

Player *pPlayer = m_g_GameRoom->GetPlayer(playerId);


 

std::map<uint32,tagJettons>::iterator iter = m_userjettonresult.find(pPlayer->GetChairIndex());

if(iter == m_userjettonresult.end())

m_userjettonresult[pPlayer->GetChairIndex()].clear();


 

Json::Value root;

root["MsgId"] = IDD_MESSAGE_ROOM;

root["MsgSubId"] = IDD_MESSAGE_ROOM_ENTERGAME;

root["gamestate"] = m_GameState;

root["gamepielement"] = (int32)m_GamePielement;

root["jvindex"] = m_gamejvcount;

Json::Value arrayObj;

for(int i=0;i<5;i++)

{

arrayObj[i] = (int)(m_jettonTrad[i]*10.0f);

}

root["GamePielement"] = arrayObj;

Json::Value arrayObj2;

for(int i=0;i<5;i++)

{

arrayObj2[i] = m_colorrecordcount[i];

}

root["colorrecordcount"] = arrayObj2;

Json::Value arrayObj3;

for(int i=0;i<(int)m_cardrecord.size();i++)

{

arrayObj3[i] = m_cardrecord[i];

}

root["cardrecourdcount"] = arrayObj3;


 

root["timexiazhu"] = m_timexiazhu;

root["timekaipai"] = m_timekaipai;

root["timejiesuan"] = m_timejiesuan;


 

//root["unitmoney"] = m_unitmoney;

m_g_GameRoom→SendTableMsg(playerId,root);


 

看代码,首先开启了游戏定时器,然后将游戏中的一些参数发送给了游戏客户端,游戏中的消息定义都存放在cdefines.h文件中,如下代码:

#define IDD_MESSAGE_ROOM_ENTERGAME IDD_MESSAGE_ROOM+1 // 进入房间消息

#define IDD_MESSAGE_ROOM_STARTJETTON IDD_MESSAGE_ROOM+2 // 开始下注消息

#define IDD_MESSAGE_ROOM_OPENCARD IDD_MESSAGE_ROOM+3 // 开始开牌消息

#define IDD_MESSAGE_ROOM_GAMEOVER IDD_MESSAGE_ROOM+4 // 游戏结束消息

#define IDD_MESSAGE_ROOM_JETTON IDD_MESSAGE_ROOM+5 // 游戏下注消息

#define IDD_MESSAGE_ROOM_CLEARJETTON IDD_MESSAGE_ROOM+6 // 清除下注消息

#define IDD_MESSAGE_ROOM_REENTERGAME IDD_MESSAGE_ROOM+7 // 重回房间消息

游戏消息都是以 IDD_MESSAGE_ROOM开始的, IDD_MESSAGE_ROOM为1000.

关于房间的接口,主要有以下几个:

/// 游戏结束时调用

virtual void GameEnd(bool isupdateuserdata=true) = 0;

/// 游戏开始是调用

virtual void GameStart(void) = 0;

/// 向指定的玩家发送旁观数据

virtual void SendLookOnMes(int index,Json::Value &msg) = 0;


 

/// 开始一个定时器

virtual bool StartTimer(int timerId,int space) = 0;

/// 关闭一个定时器

virtual void StopTimer(int id) = 0;

/// 关闭所有的定时器

virtual void StopAllTimer(void) = 0;

/// 写入用户积分

virtual bool WriteUserScore(int wChairID, int64 lScore, int64 lRevenue, enScoreKind ScoreKind,int64 pAgentmoney=0,bool isCumulativeResult=true,int64 pcurJetton=0,const char* pgametip="") = 0;


 

我们这个例子中主要的逻辑就在定时器接口中,一个玩家进入房间后,游戏定时器就打开了,然后整个游戏就是在游戏定时器中进行的。

if(timerId == IDD_TIMER_GAME_STARTING && curTimer <= 0)

{

m_g_GameRoom->StopTimer(IDD_TIMER_GAME_STARTING);


 

ClearJettonRecord();

m_resultCard = 0;

m_GameState = GAMESTATE_XIAZHU;


 

m_g_GameRoom->GameStart();


 

Json::Value root;

root["MsgId"] = IDD_MESSAGE_ROOM;

root["MsgSubId"] = IDD_MESSAGE_ROOM_STARTJETTON;

root["gamestate"] = m_GameState;

root["jvindex"] = m_gamejvcount;


 

m_g_GameRoom->SendTableMsg(INVALID_CHAIR,root);


 

m_g_GameRoom->StartTimer(IDD_TIMER_GAME_XIAZHU, m_timexiazhu);

}

else if(timerId == IDD_TIMER_GAME_XIAZHU && curTimer <= 0)

{

m_g_GameRoom->StopTimer(IDD_TIMER_GAME_XIAZHU);


 

m_resultCard = GetResultCard();


 

m_colorrecordcount[m_CGameLogic.GetCardColor(m_resultCard)] += 1;

m_GameState = GAMESTATE_KAIPAI;


 

Json::Value root;

root["MsgId"] = IDD_MESSAGE_ROOM;

root["MsgSubId"] = IDD_MESSAGE_ROOM_OPENCARD;

root["gamestate"] = m_GameState;

root["resultcard"] = m_resultCard;


 

m_g_GameRoom->SendTableMsg(INVALID_CHAIR,root);


 

m_g_GameRoom->StartTimer(IDD_TIMER_GAME_KAIPAI, m_timekaipai);

}

else if(timerId == IDD_TIMER_GAME_KAIPAI && curTimer <= 0)

{

m_g_GameRoom->StopTimer(IDD_TIMER_GAME_KAIPAI);


 

m_GameState = GAMESTATE_KONGXIAN;


 

TradGame();


 

m_cardrecord.push_back(m_resultCard);


 

if((int)m_cardrecord.size() >= 65)

{

m_cardrecord.clear();

m_gamejvcount+=1;

std::map<uint8,int>::iterator iter = m_colorrecordcount.begin();

for(;iter != m_colorrecordcount.end();++iter) (*iter).second = 0;


 

m_cardrecord.push_back(m_resultCard);

}


 

m_g_GameRoom->GameEnd();


 

m_g_GameRoom->StartTimer(IDD_TIMER_GAME_STARTING, m_timejiesuan);

}


 

这里代码有几个地方要说明一下:

1. SendTableMsg函数,第一个参数如果为具体某个玩家的椅子号,就是单独发送给某个玩家,如果为 INVALID_CHAIR就是发送给所有玩家;

2. StartTimer开始定时器,第一个参数是定时器ID,第二个参数是时间间隔,以秒为单位,但这个定时器接口是每秒都会执行的,这样做,是因为某些游戏需要检查每秒的东西。所以如果要走完你设置的时间间隔,就需要判断 curTimer为0,这样你所设置的才表示走完。

if(timerId == IDD_TIMER_GAME_KAIPAI && curTimer <= 0)

3.GameStart(),GameEnd()这两个函数必须成对使用,当你调用 GameStart后,整个游戏所有玩家的状态和房间的状态就被锁住了,只有调用 GameEnd才会解开所有玩家和房间的状态。比如当你因为异常或者非正常强制关闭服务器的时候,就会导致有些玩家被锁住在房间里,这时你就需要到网站后台:“玩家”-》“玩家管理”-》“玩家列表”-》“玩家解锁”来解锁所有玩家的状态。


 

我们在这个例子中处理的游戏逻辑非常简单,而且并没有处理机器人的逻辑,我们将在后面的章节中详细讲解机器人和代理方面的操作。

接下来我们看看游戏客户端大游戏逻辑处理代码:

case 1000:
{
    console.info(objgame.MsgSubId);
 switch(objgame.MsgSubId)
    {
        case 1001:
        case 1007:
        {
            gamestate = objgame.gamestate;
 gamepielement = objgame.gamepielement;
 m_GameItemBeiLv = objgame.GamePielement;
 var pjvhao = objgame.jvindex + 1;
 m_colorrecordcount = objgame.colorrecordcount;
 m_cardrecourdcount = objgame.cardrecourdcount;
 m_timexiazhu = objgame.timexiazhu;
 m_timekaipai = objgame.timekaipai;
 m_timejiesuan = objgame.timejiesuan;
 // m_myselfgamegonggaostr = objgame.gamegonggao;
{
    console.info(objgame.MsgSubId);
 switch(objgame.MsgSubId)
    {
        case 1001:
        case 1007:
        {
            gamestate = objgame.gamestate;
 gamepielement = objgame.gamepielement;
 m_GameItemBeiLv = objgame.GamePielement;
 var pjvhao = objgame.jvindex + 1;
 m_colorrecordcount = objgame.colorrecordcount;
 m_cardrecourdcount = objgame.cardrecourdcount;
 m_timexiazhu = objgame.timexiazhu;
 m_timekaipai = objgame.timekaipai;
 m_timejiesuan = objgame.timejiesuan;
 // m_myselfgamegonggaostr = objgame.gamegonggao;

我们这里由于篇幅所限,只贴部分代码,详细代码请具体看看代码。

当然,这个例子中只展示了整个游戏服务器框架的一些基础功能,框架还提供更多有趣并好玩的其它功能,需要你自己去发现了。

在下一篇教程中,我们将详细讲解游戏机器人和代理分销相关的东西。

欢迎加入QQ群交流:131296225

email:akinggw@126.com

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值