本文内容是多进程游戏服务器的登陆功能。
登录流程:
第一次登录流程
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(¢erMsg, 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;
}