游戏服务器之登录

本文内容是多进程游戏服务器的登陆功能。


登录流程:

第一次登录流程

client->login->center->gateway->login->client->gateway

重登陆流程

client->login->center->gateway(关闭该连接)->scene(登出保存)->db->center->social(注销社会关系角色,保存社会关系角色数据)->login(返回网关信息)->client->第一次登录流程 ->scene(及时保存)


本文内容:

1、登录服务器

(1)通过登录服务器请求获取网关

(2)处理网关服务器返回网关信息

2、中心服务器

转发登陆消息,添加中心服务器玩家,注销重登陆时的旧玩家数据

3、网关

(1)处理登录请求

(1-1)登录会话添加到会话管理容器

(1-2)重登陆就关闭旧的网关连接

(1-3)新登录的玩家就直接通过登录服务器返回网挂信息到客户端

(2)网关连接之验证连接

(3)、验证账号

(4)、网关处理登录消息

(4-1)检查角色名

(4-2)选择角色

(4-3)请求创建角色


1、登录服务器

(1)通过登录服务器请求获取网关

客户端请求获取网关
  (1-1)缓存找到的网关信息到消息成员,准备返回到客户端
    if (g_server.gateway_info_map.size() > 0 && account_id > 0)
      {
               auto_mutex_lock scope_lock(g_server.mlock);
               gateway_info ginfo;
               g_server.login_id_pool.getUniqueID(responMsg.loginTempID);
               gateid = g_server.get_login_gateway_info(ginfo);
               responMsg.port = ginfo.port;
               responMsg.dwplayerID = account_id;
               this->temp_login_id = responMsg.loginTempID;
               std::string str(this->account);
               g_server.loginID2Account[this->temp_login_id] = str;
    }

(1-2)发送消息给找到的网关

 /* 如果找到了网关,就发送消息到网关,再返回网关信息,再返回客户端。之所以需要先发送消息到网关,

是因为需要把 分配到的临时id发送到网关,该临时id 跟账号相关,等到客户端登陆网关时需要验证该账号和临时id*/
if (gateid > 0)
{
         MSG::FL::t_NewLoginMsg centerMsg;
         centerMsg.session.wdGatewayID = gateid;
         centerMsg.session.loginTempID = responMsg.loginTempID;
        centerMsg.session.wdPort = responMsg.port;
        centerMsg.session.accid = account_id;
      centerMsg.session.isAdult = this->isAdult;
       strncpy(centerMsg.session.account, account, MAX_NAME_LEN);
       if (!g_center_session_manager.broadcast(&centerMsg, sizeof(centerMsg)))//发送消息(通过中心服务器)到网关服务器 ,在中心服务器
      ...
}
...
}

(2)处理网关服务器返回网关信息

通过中心服务器转发来的消息(第一种是网关服务器通过中心服务器发来的;第二种是中心服务器发现有该玩家在中心服务器,就注销场景服务器、网关服务器、社会服务器的玩家,最后社会服务器通过中心服务器发送消息到登陆服务器,登陆服务器再把缓存的网关的信息返回给客户端)

center_session::msgParse

case MSG::FL::PARA_NOTIFY_RETURN_GATEWAY_TO_CLIENT_SS:
{
t_NotifyReturnGatewayInfoToClientMsg* recv = (t_NotifyReturnGatewayInfoToClientMsg*) pMsg;
login_session *session = g_login_session_manager.getTaskByID(recv->accid);
if (session)
{
session->returnSelectedGateFromGateway();//获取账号对应的客户端的登录服务器连接,来返回网关信息
}
}
break;

void login_session::returnSelectedGateFromGateway()
{
if (responMsg.dwplayerID)
    {
        sendmsg(&responMsg, sizeof(responMsg));//返回网关信息
        //每发回网关信息必须清空,避免重复发送
        responMsg.clear();
        logoutServers = 0;
    }
}


2、中心服务器

转发登陆消息,添加中心服务器玩家,注销重登陆时的旧玩家数据。

bool login_client::msgparser_login(const MSG::base_msg *ptrMsg,const unsigned int msglen)
{
switch(ptrMsg->second)
{
using namespace MSG::FL;
case MSG::FL::PARA_NOTIFY_RETURN_GATEWAY_TO_CLIENT_SS:
{
PRINT_MSG_LOG(ptrMsg, 0);
}break;
case MSG::FL::PARA_SESSION_NEWSESSION:

//中心服务器接收登录服务器发来的登录请求,创建中心服务器玩家

//如果已经有中心服务器玩家则强行让场景服务器的玩家下线,并让网关服务器的玩家下线
{
t_NewLoginMsg *rev = (t_NewLoginMsg *)ptrMsg;
t_NewLoginMsg  newsession;
newsession.session = rev->session;

LoginCommand *loginSession = new LoginCommand(rev->session);
 
if(loginSession)
{
bool ok = true;
if(!g_loginsession_mgr.addSession(loginSession))//添加登录会话到中心服务器的登录会话管理器
{
error_log("账号正在网关登陆%s(%u,%u)",rev->session.account,rev->session.accid,rev->session.loginTempID);
ok = false;
}
 center_player *c_player = g_center_player_manager.get_player_by_accid(rev->session.accid);
 if(c_player)
 {
            MSG::Share::t_Force_Save_Player_Data send;
            send.charid = c_player->id;
            MSG::center::stTerminateErrorcenterCmd logoutsend;
             logoutsend.accid = rev->session.accid;
             c_player->terminate_error = false;

   踢掉网关角色
           if(c_player->gateway)
           {
                c_player->gateway->sendmsg(&logoutsend,sizeof(logoutsend));
           }
           uint8 buf[tcp_socket::MAX_DATASIZE];
           MSG::Server::stShareForwardServerCmd *sendmsg = (MSG::Server::stShareForwardServerCmd *)buf;
           constructInPlace(sendmsg);
           sendmsg->num = sizeof(MSG::Share::t_Force_Save_Player_Data);
           bcopy(&send,sendmsg->data,sendmsg->num);

   踢掉场景角色并存档
           g_center_session_manager.broadcastToType(SCENESSERVER,sendmsg,sendmsg->size());
}             

......

//存储账号和对应的网关id到中心角色的登录容器
if(ok && !g_center_player_manager.addAccid(rev->session.accid, rev->session.wdGatewayID))
......

发送消息到网关
if(ok && !g_login_client_manager.sendmsgToGateway(newsession.session.wdGatewayID, &newsession,sizeof(newsession)))
{
error_log("帐号%s(%u,%u)登陆时网关已经关闭",rev->session.account, rev->session.accid,rev->session.loginTempID);
g_loginsession_mgr.removeSession(loginSession);
          SAFE_DELETE(loginSession);
g_center_player_manager.removeAccid(rev->session.accid);
ok = false;
return true;
}
 
}
 
}

3、网关

(1)处理登录请求

先处理完登录请求再验证账号。网关收到登录服务器的消息然后通过登录服务器返回网关信息,玩家再发送请求到网关来,需要验证账号。

case PARA_SESSION_NEWSESSION:
   {
         t_NewLoginMsg *rev = (t_NewLoginMsg *) ptrMsg;
LoginCommand *session = new LoginCommand(rev->session);
          

  (1-1)登录会话添加到会话管理容器
          if (!g_loginsession_mgr.addSession(session))
           {
                g_loginsession_mgr.removeSessionAndDel(session->accid);
                g_log->error("网关添加登陆验证会话失败:(%u,%u,%d)", rev->session.wdGatewayID, rev->session.loginTempID, rev->session.accid);
                g_loginsession_mgr.addSession(session);
            }
 
           (1-2)重登陆就关闭旧的网关连接

               //只要登录就先回收对应的accid的gateway_session
                gateway_session* old_gate_session = g_gateway_session_manager.getTaskByID(rev->session.accid);
                  if (old_gate_session)
                 {
                       debug_log("关闭旧的网关session,accid:%u,loginid:%u",rev->session.accid, rev->session.loginTempID);
                        old_gate_session->TerminateWait();//需要先关闭旧的网关的连接,等到所有的服务器里的角色数据都注销完了之后,最后社会服务器返回网关信息,再返回给玩家
                   }
                   else(1-3)新登录的玩家就直接通过登录服务器返回网挂信息到客户端
                   {
                    debug_log("返回网关信息:%u,%u",rev->session.accid, rev->session.loginTempID);
                                MSG::FL::t_NotifyReturnGatewayInfoToClientMsg resMsg;
                                resMsg.accid = rev->session.accid;
                                return g_server.sendmsgToLoginServer(0, &resMsg, sizeof(resMsg));
                            }
                        }
                        break;
   }
     return true;

(2)、网关连接之验证连接

玩家再发送请求到网关来,验证连接(验证版本、验证账号、验证临时id)

int gateway_session::verifyConn()
{
int retcode = mSocket.recvToBuf_NoPoll();//socket处理接收
 


if(versionVerified)//验证完版本就验证账号
{
if(verifyACCID(ptrMsg))//(2-2)验证账号和临时登录id
}
else if(verifyVersion(ptrMsg))//(2-1)验证版本
{
versionVerified = true;
}


(3)验证账号和临时登录id

bool gateway_session::verifyACCID(const MSG::base_msg *ptrMsg)
{
using namespace MSG;
if(LOGON_USERCMD == ptrMsg->first && PASSWD_LOGON_USERCMD_PARA == ptrMsg->second)
{
stPasswdLogonplayerCmd*ptCmd = (stPasswdLogonplayerCmd *)ptrMsg;
g_log->trace("角色登录(%u,%u,%s,%s),",ptCmd->loginTempID,ptCmd->dwplayerID,ptCmd->pstrName,ptCmd->pstrPassword);
LoginCommand *session = g_loginsession_mgr.getSessionByAccid(ptCmd->dwplayerID);//验证
if(!session)
{
error_log("未找到LoginSession");
return false;
}
t_session = *session;
strncpy(account,session->account,MAX_ACCNAMESIZE);
if(g_loginsession_mgr.verify(ptCmd->loginTempID,ptCmd->dwplayerID))//验证临时id
{
this->id = ptCmd->dwplayerID;
loginTempID = ptCmd->loginTempID;
debug_log("客户端连接同步验证成功,当前时间=%llu",main_logic_thread::currentTime.sec());
 //账号通过验证
return true;
}
 else//验证失败
  {
            using namespace MSG::center;
            stLoginVerifyLogincenterCmd tCmd;
            tCmd.accid = ptCmd->dwplayerID;
            tCmd.loginTempID = ptCmd->loginTempID;
            g_server.sendmsgTocenterServer(&tCmd,sizeof(tCmd));
            g_loginsession_mgr.removeSessionAndDel(tCmd.accid);
            g_log->debug("账号【%u,%s】验证失败..........",ptCmd->dwplayerID, ptCmd->pstrName);
            return false;
        }
}
g_log->error("客户端连接验证账号失败(%d,%d)",ptrMsg->first,ptrMsg->second);
return true;
}

(4)网关处理登录消息

验证客户端通过以后就可以执行登录的功能指令:检查角色名\选择角色\请求创建角色


验证连接->db(请求角色列表)->gate(返回角色列表)->client->gate(检查角色名)->db->gate->client->gate(选择角色/创建角色->广播social建立社会关系->需要再次返回角色列表的流程)->登录social->登录center->登录db-(读档)>登录场景->登录网关(设置网关玩家状态机为进入游戏)登录游戏成功->client


gateway_session::msgParse

(4-1)检查角色名

case CHECK_PLAYER_NAME://检查角色名
{
stCheckPlayerNameMsg *rev = (stCheckPlayerNameMsg*) ptrMsg;
 
if (strlen(rev->name) <= 0)
{
MSG::stRequestErrcodeMsg ret;
ret.err = MSG::INVALID_PLAYER_NAME;
debug_log("名字为空");
return sendmsg(&ret, sizeof(ret));
}

strncpy(createCharCmd.strplayerName, rev->name, MAX_NAME_LEN - 1);
if(gateway_server::checkNameFilter(rev->name) == false)//检查过滤的名字
{
MSG::stRequestErrcodeMsg ret;
ret.err = MSG::INVALID_PLAYER_NAME;
debug_log("角色名含有非法字符:%s", rev->name);
return sendmsg(&ret, sizeof(ret));
}
else if (gateway_server::checkSymbolFilter(rev->name) == false)//检查过滤的符号
{
MSG::stRequestErrcodeMsg ret;
ret.err = MSG::INVALID_PLAYER_NAME;
debug_log("角色名含有非法字符:%s", rev->name);
return sendmsg(&ret, sizeof(ret));
}

if (this->isNameChecked())
{
debug_log("重复检查角色名:%s", rev->name);
this->resetNameChecked();
return false;
}
MSG::DB::stCheckNameSelectRecordCmd test;
test.accid = this->id;
strncpy(test.name, rev->name, sizeof(test.name));
dbClient->sendmsg(&test, sizeof(MSG::DB::stCheckNameSelectRecordCmd));//发送到db检查角色名
 return true;
}
break;

(4-2)选择角色,选择完角色后登录到社会服务器
case CHOOSE_ROLE_C://选择角色
{
if (!testRunState(gateway_session::RunState_Select))
{
warn_log("CHOOSE_ROLE_C 连接状态不对(%u,%s),needstatus:RunState_Select,nowstatus:%s:",this->id, this->name,getRunStateString());
return false;
}
stChooseRoleMsg*rev = (stChooseRoleMsg*) ptrMsg;
 MSG::SelectplayerInfo *userinfo = getSelectplayerInfo(rev->index);//从当前登录的角色列表里获取角色信息
g_log->trace("收到的第(%d)个登录角色指令", rev->index);
if (!userinfo)
{
error_log("收到不存在的角色,可能是恶意攻击!");
return false;
}
if (pplayer->id != 0)
{
error_log("%s已经登录,无视", pplayer->name);
return false;
}
if (userinfo->charid != 0)/*userinfo &&*/
{
pplayer->id = userinfo->charid;
strncpy(pplayer->name, userinfo->name, sizeof(pplayer->name));
if (g_user_mgr.add_player(pplayer) == false)
{
error_log("CHOOSE_ROLE_C 选择(%s,%u)时添加网关角色失败",userinfo->name, pplayer->id);
return true;
}
info_log("-------------%s,%u 登录角色(%u),角色IP:(%s)",userinfo->name, pplayer->id,pplayer->accid ,getIP());


MSG::Share::t_Loginplayer_InServer send;
send.accid = pplayer->accid;
send.charid = pplayer->id;
send.login_ip = this->getAddr();
send.sceneid = userinfo->mapid;
strcpy(send.name, pplayer->name);
send.gate_id = g_server.getServerID();
send.retcode = MSG::Share::LoginplayerInServer_GateOK;
loginSocial(&send, sizeof(send));
g_user_mgr.add_player(pplayer);
userinfo_num = rev->index;
pplayer->set_sock_player();
return true;
}
else
{
error_log("CHOOSE_ROLE_C 选择角色【%s,%u】检查登陆信息失败(%u,%u)", userinfo->name, pplayer->id, pplayer->accid, userinfo->charid);
}
return false;
}
break;

(4-3)请求创建角色
case REQUEST_CREATE_PLAYER_C://请求创建角色
{
stCreatePlayerMsg *rev = (stCreatePlayerMsg*) ptrMsg;

g_log->trace("请求创建角色(%u,%s)", pplayer->accid, rev->strplayerName);
debug_log("runstate now state :%d,(RunState_Create 3)", this->runstate);
if (!this->testRunState(gateway_session::RunState_Create))
{
warn_log("连接状态不对(%u,%s),needstatus:RunState_Select,nowstatus:%s:",pplayer->accid, rev->strplayerName,getRunStateString());
return false;
}
if (this->charInfoFull())
{
stRequestErrcodeMsg responMsg;
responMsg.err = MAX_PLAYER_NUM;
sendmsg(&responMsg, sizeof(responMsg));
error_log("accid:%u,角色信息满,不能再创建角色", pplayer->id);
return true;
}
if(strlen(rev->strplayerName) == 0)
{
stRequestErrcodeMsg responMsg;
responMsg.err = INVALID_PLAYER_NAME;
sendmsg(&responMsg, sizeof(responMsg));
error_log("%u,角色名称为空", pplayer->id);
}
if (strstr(rev->strplayerName, "\\") || strstr(rev->strplayerName, "/"))//检查角色名的合法性
{
stRequestErrcodeMsg responMsg;
responMsg.err = INVALID_PLAYER_NAME;
sendmsg(&responMsg, sizeof(responMsg));
error_log("%u,角色名称含有斜杠反斜杠等非法字符", pplayer->id);
return false;
}
this->createCharCmd.accid = pplayer->accid;
strncpy(this->createCharCmd.strplayerName, rev->strplayerName, MAX_NAME_LEN);
strncpy(this->createCharCmd.account, this->account, MAX_ACCNAMESIZE);
this->createCharCmd.createip = this->getAddr();
this->createCharCmd.job = rev->job;

发送消息到db,创建角色
if (!dbClient->sendmsg(&createCharCmd, sizeof(createCharCmd)))
{
debug_log("发送消息到db server 失败");
return false;
}
debug_log("发送消息到db server 成功");
 
return true;
}
break;
......
};
}
break;
default:
break;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值