1、客户端逻辑
UI按钮响应
获取用户名、密码,封装成DoLoginServerHttpOperation,放到HTTP 处理线程池,交给工作线程处理。
cpp
void LoginDialog::_DoLogin()
{
LOG__(APP,_T("User Clicked LoginBtn"));
m_ptxtTip->SetText(_T(""));
CDuiString userName = m_pedtUserName->GetText();
CDuiString password = m_pedtPassword->GetText();
if (userName.IsEmpty())
{
CString csTip = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_USERNAME_EMPTY"));
m_ptxtTip->SetText(csTip);
return;
}
if (password.IsEmpty())
{
CString csTip = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_PASSWORD_EMPTY"));
m_ptxtTip->SetText(csTip);
return;
}
module::TTConfig* pCfg = module::getSysConfigModule()->getSystemConfig();
pCfg->userName = userName;
if (m_bPassChanged)
{
std::string sPass = util::cStringToString(CString(password));
char* pOutData = 0;
uint32_t nOutLen = 0;
int retCode = EncryptPass(sPass.c_str(), sPass.length(), &pOutData, nOutLen);
if (retCode == 0 && nOutLen > 0 && pOutData != 0)
{
pCfg->password = std::string(pOutData, nOutLen);
Free(pOutData);
}
else
{
LOG__(ERR, _T("EncryptPass Failed!"));
CString csTip = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_LOGIN_ENCRYPT_PASE_FAIL"));
m_ptxtTip->SetText(csTip);
return;
}
}
pCfg->isRememberPWD = m_pChkRememberPWD->GetCheck();
module::getSysConfigModule()->saveData();
CString csTxt = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_BTN_DOLOGIN"));
m_pBtnLogin->SetText(csTxt);
m_pBtnLogin->SetEnabled(false);
//连接登陆服务器
DoLoginServerParam param;
DoLoginServerHttpOperation* pOper = new DoLoginServerHttpOperation(
BIND_CALLBACK_1(LoginDialog::OnHttpCallbackOperation), param);
module::getHttpPoolModule()->pushHttpOperation(pOper);
}
HTTP 处理线程池,是生产者-消费者模式,pushHttpOperation 是生产者,向队列插入任务,高优先级插入队列头,其次插入队尾,_launchThread创建线程(如果已经达到了最大线程数,不会创建新的线程),发出信号,让工作线程执行任务。TTHttpThread::process是消费者,从队列头部获取任务执行。最终会调用到子类中的方法,登录流程对应DoLoginServerHttpOperation::processOpertion
cpp
void HttpPoolModule_Impl::pushHttpOperation(module::IHttpOperation* pOperaion, BOOL bHighPriority /*= FALSE*/)
{
if (NULL == pOperaion)
{
return;
}
CAutoLock lock(&m_mtxLock);
if (bHighPriority)
m_lstHttpOpers.push_front(pOperaion);
else
m_lstHttpOpers.push_back(pOperaion);
_launchThread();
::ReleaseSemaphore(m_hSemaphore, 1, NULL);
return;
}
cpp
UInt32 TTHttpThread::process()
{
module::IHttpOperation * pHttpOper = NULL;
HttpPoolModule_Impl *pPool = m_pInstance;
while (m_bContinue)
{
if (WAIT_OBJECT_0 != ::WaitForSingleObject(pPool->m_hSemaphore, INFINITE))
{
break;
}
if (!m_bContinue)
{
break;
}
{
CAutoLock lock(&(pPool->m_mtxLock));
if (pPool->m_lstHttpOpers.empty())
pHttpOper = NULL;
else
{
pHttpOper = pPool->m_lstHttpOpers.front();
pPool->m_lstHttpOpers.pop_front();
}
}
try
{
if (m_bContinue && pHttpOper)
{
pHttpOper->process();
pHttpOper->release();
}
}
catch (...)
{
LOG__(ERR, _T("TTHttpThread: Failed to execute opertaion(0x%p)"), pHttpOper);
}
}
return 0;
}
DoLoginServerHttpOperation::processOpertion 逻辑
1、从 SysConfigModule 获取配置好的登录服务器地址
2、向该地址(login_server)发起一个 GET HTTP 请求(Http::HttpRequest request("get", url))
3、解析响应体中的json数据
4、如果成功,JSON中的code==0,则从JSON中提取消息服务器、文件服务器的ip、端口信息
5、调用asyncCallback将结果异步发回给ui线程
cpp
void DoLoginServerHttpOperation::processOpertion()
{
module::TTConfig* pCfg = module::getSysConfigModule()->getSystemConfig();
LOG__(APP, _T("loginAddr = %s"), pCfg->loginServIP);
std::string& loginAddr = util::cStringToString(pCfg->loginServIP);
std::string url = loginAddr;
DoLoginServerParam* pPamram = new DoLoginServerParam();
pPamram->resMsg = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_LOGIN_HTTP_DEFERROR"));
Http::HttpResponse response;
Http::HttpClient client;
//对于登录:url=http://192.168.226.128:8080/msg_server
Http::HttpRequest request("get", url);
if (!client.execute(&request, &response))
{
CString csTemp = util::stringToCString(url);
pPamram->result = DOLOGIN_FAIL;
LOG__(ERR,_T("failed %s"), csTemp);
asyncCallback(std::shared_ptr<void>(pPamram));
client.killSelf();
return;
}
/**
{
"backupIP" : "localhost",
"code" : 0,
"discovery" : "http://127.0.0.1/api/discovery",
"msfsBackup" : "http://127.0.0.1:8700/",
"msfsPrior" : "http://127.0.0.1:8700/",
"msg" : "",
"port" : "8000",
"priorIP" : "localhost"
}
*/
std::string body = response.getBody();
client.killSelf();
//json解析
try
{
Json::Reader reader;
Json::Value root;
if (!reader.parse(body, root))
{
CString csTemp = util::stringToCString(body);
LOG__(ERR, _T("parse data failed,%s"), csTemp);
pPamram->result = DOLOGIN_FAIL;
pPamram->resMsg = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_LOGIN_HTTP_JSONERROR"));
goto End;
}
int nCode = root.get("code", "").asInt();
if (0 == nCode)//登陆成功
{
LOG__(APP, _T("get msgSvr IP succeed!"));
pCfg->msgSevPriorIP = root.get("priorIP", "").asString();
pCfg->msgSevBackupIP = root.get("backupIP", "").asString();
std::string strPort = root.get("port", "").asString();
pCfg->msgServPort = util::stringToInt32(strPort);
pCfg->fileSysAddr = util::stringToCString(root.get("msfsPrior", "").asString());
pCfg->fileSysBackUpAddr = util::stringToCString(root.get("msfsBackup", "").asString());
pPamram->result = DOLOGIN_SUCC;
}
else
{
LOG__(ERR, _T("get msgSvr IP failed! Code = %d"),nCode);
pPamram->result = DOLOGIN_FAIL;
CString csRetMsgTemp = util::stringToCString(root.get("msg", "").asString());
if (!csRetMsgTemp.IsEmpty())
pPamram->resMsg = csRetMsgTemp;
}
}
catch (...)
{
CString csTemp = util::stringToCString(body);
LOG__(ERR,_T("parse json execption,%s"), csTemp);
pPamram->result = DOLOGIN_FAIL;
pPamram->resMsg = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_LOGIN_HTTP_JSONERROR"));
}
End:
asyncCallback(std::shared_ptr<void>(pPamram));
}
调用asyncCallback将结果异步发回给ui线程
1、将回调函数callback、回调数据封装成CallbackOperationEvent
2、PostMessage将一个自定义消息UI_EVENT_MSG、UIEventManager 实例指针 (this) 和 CallbackOperationEvent 指针一起打包,放入了这个隐藏窗口所属的消息队列中。
Teamtalk启动时会创建一个隐藏的代理窗口,专门用于接收消息处理,从工作线程来的消息,无论都会发到代理窗口。
cpp
void asyncCallback(std::shared_ptr<void> param)
{
CallbackOperationEvent* pEvent = new CallbackOperationEvent(m_callback, param);
module::getEventManager()->asynFireUIEvent(pEvent);
}
cpp
module::IMCoreErrorCode UIEventManager::asynFireUIEvent(IN const IEvent* const pEvent)
{
assert(m_hWnd);
assert(pEvent);
if (0 == m_hWnd || 0 == pEvent)
return IMCORE_ARGUMENT_ERROR;
if (FALSE == ::PostMessage(m_hWnd, UI_EVENT_MSG, reinterpret_cast<WPARAM>(this), reinterpret_cast<WPARAM>(pEvent)))
return IMCORE_WORK_POSTMESSAGE_ERROR;
return IMCORE_OK;
}
Teamtalk创建隐藏的代理窗口代码
cpp
imcore::IMCoreErrorCode UIEventManager::startup()
{
IMCoreErrorCode errCode = IMCORE_OK;
if (0 != m_hWnd)
return IMCORE_OK;
else
{
if (!_registerClass())
return IMCORE_INVALID_HWND_ERROR;
m_hWnd = ::CreateWindowW(uiEventWndClass, _T("uiEvnetManager_window"),
0, 0, 0, 0, 0, HWND_MESSAGE, 0, GetModuleHandle(0), 0);
if (m_hWnd)
{
::SetTimer(m_hWnd, reinterpret_cast<UINT_PTR>(this), 1000, NULL);
}
}
if (FALSE == ::IsWindow(m_hWnd))
errCode = IMCORE_INVALID_HWND_ERROR;
return errCode;
}
代理窗口处理消息UI_EVENT_MSG
代理窗口收到自定义消息,最终会调用回调函数,这个回调是登录时绑好的回调函数,也就是LoginDialog::OnHttpCallbackOperation
cpp
class CallbackOperationEvent : public module::IEvent
{
public:
CallbackOperationEvent(IOperationDelegate& callback, std::shared_ptr<void> param)
:m_callback(callback)
, m_param(param)
{
}
virtual ~CallbackOperationEvent()
{}
virtual void process()
{
m_callback(m_param);
}
virtual void release(){ delete this; }
private:
IOperationDelegate m_callback;
std::shared_ptr<void> m_param;
};

LoginDialog::OnHttpCallbackOperation
封装成一个LoginOperation任务,放到任务队列中
Teamtalk初始化时,会启动任务队列处理线程,用于处理相关任务,比如:登录任务
cpp
void LoginDialog::OnHttpCallbackOperation(std::shared_ptr<void> param)
{
DoLoginServerParam* pParam = (DoLoginServerParam*)param.get();
if (DOLOGIN_SUCC == pParam->result)
{
module::TTConfig* pCfg = module::getSysConfigModule()->getSystemConfig();
PTR_VOID(pCfg);
LoginParam loginparam;
loginparam.csUserName = pCfg->userName;
loginparam.password = pCfg->password;
loginparam.csUserName.Trim();
LoginOperation* pOperation = new LoginOperation(
BIND_CALLBACK_1(LoginDialog::OnOperationCallback), loginparam);
imcore::IMLibCoreStartOperation(pOperation);
}
else
{
m_ptxtTip->SetText(pParam->resMsg);
module::TTConfig* pCfg = module::getSysConfigModule()->getSystemConfig();
LOG__(ERR, _T("get MsgServer config faild,login server addres:%s:%d"), pCfg->loginServIP,pCfg->loginServPort);
CString csTxt = util::getMultilingual()->getStringById(_T("STRID_LOGINDIALOG_BTN_LOGIN"));
m_pBtnLogin->SetText(csTxt);
m_pBtnLogin->SetEnabled(true);
}
}
任务队列处理线程
OperationManager::startOperation:是生产者,负责将Operation任务放入队列。
OperationManager::startup是消费者,负责从任务队列中取任务执行。
cpp
IMCoreErrorCode OperationManager::startup()
{
m_operationThread = std::thread([&]
{
std::unique_lock <std::mutex> lck(m_cvMutex);
Operation* pOperation = nullptr;
while (m_bContinue)
{
if (!m_bContinue)
break;
if (m_vecRealtimeOperations.empty())
m_CV.wait(lck);
if (!m_bContinue)
break;
{
std::lock_guard<std::mutex> lock(m_mutexOperation);
if (m_vecRealtimeOperations.empty())
continue;
pOperation = m_vecRealtimeOperations.front();
m_vecRealtimeOperations.pop_front();
}
if (!m_bContinue)
break;
if (pOperation)
{
pOperation->process();
pOperation->release();
}
}
});
return IMCORE_OK;
}
cpp
IMCoreErrorCode OperationManager::startOperation(IN Operation* pOperation, Int32 delay)
{
assert(pOperation);
if (0 == pOperation)
{
return IMCORE_ARGUMENT_ERROR;
}
//todo kuaidao...delay operntion process
if (delay > 0)
{
}
else
{
std::lock_guard<std::mutex> locker(m_mutexOperation);
m_vecRealtimeOperations.push_back(pOperation);
m_CV.notify_one();
}
return IMCORE_OK;
}
LoginOperation::processOpertion
登录场景:任务队列处理,最终会调用到子类的实现LoginOperation::processOpertion
1、从 SysConfigModule 获取之前通过 HTTP 请求得到的消息服务器地址
2、发起 TCP 登录: 调用 module::getTcpClientModule()->doLogin(...) 方法
3、处理登录结果:
4、调用 asyncCallback 将成功结果(包括用户 ID、状态等)传递回调用方(UI 线程)
cpp
void LoginOperation::processOpertion()
{
LOG__(APP,_T("login start,uname:%s,status:%d"), m_loginParam.csUserName
, m_loginParam.mySelectedStatus);
LoginParam* pParam = new LoginParam;
pParam->csUserName = m_loginParam.csUserName;
pParam->mySelectedStatus = m_loginParam.mySelectedStatus;
//连接消息服务器
module::TTConfig* pCfg = module::getSysConfigModule()->getSystemConfig();
CString server = util::stringToCString(pCfg->msgSevPriorIP);
LOG__(APP, _T("MsgServeIp:%s,Port:%d"), server, pCfg->msgServPort);
//8000端口
IM::Login::IMLoginRes* pImLoginResp = (IM::Login::IMLoginRes*)module::getTcpClientModule()
->doLogin(server, pCfg->msgServPort,m_loginParam.csUserName,m_loginParam.password);
if (0 == pImLoginResp || pImLoginResp->result_code() != IM::BaseDefine::REFUSE_REASON_NONE
|| !pImLoginResp->has_user_info())
{
//TODO,若失败,尝试备用IP
LOG__(ERR,_T("add:%s:%d,uname:%s,login for msg server failed"),server,pCfg->msgServPort, m_loginParam.csUserName);
if (pImLoginResp)
{
CString errInfo = util::stringToCString(pImLoginResp->result_string());
pParam->errInfo = errInfo;
pParam->result = LOGIN_FAIL;
pParam->server_result = pImLoginResp->result_code();
LOG__(ERR, _T("error code :%d,error info:%s"), pImLoginResp->result_code(), errInfo);
}
else
{
pParam->result = IM::BaseDefine::REFUSE_REASON_NO_MSG_SERVER;
LOG__(ERR, _T("login msg server faild!"));
}
asyncCallback(std::shared_ptr<void>(pParam));
return;
}
pParam->result = LOGIN_OK;
pParam->serverTime = pImLoginResp->server_time();
pParam->mySelectedStatus = pImLoginResp->online_status();
//存储服务器端返回的userId
IM::BaseDefine::UserInfo userInfo = pImLoginResp->user_info();
pCfg->userId = util::uint32ToString(userInfo.user_id());
pCfg->csUserId = util::stringToCString(pCfg->userId);
//登陆成功,创建自己的信息
module::UserInfoEntity myInfo;
myInfo.sId = pCfg->userId;
myInfo.csName = m_loginParam.csUserName;
myInfo.onlineState = IM::BaseDefine::USER_STATUS_ONLINE;
myInfo.csNickName = util::stringToCString(userInfo.user_nick_name());
myInfo.avatarUrl = userInfo.avatar_url();
myInfo.dId = util::uint32ToString(userInfo.department_id());
myInfo.department = myInfo.dId;
myInfo.email = userInfo.email();
myInfo.gender = userInfo.user_gender();
myInfo.user_domain = userInfo.user_domain();
myInfo.telephone = userInfo.user_tel();
myInfo.status = userInfo.status();
myInfo.signature = userInfo.sign_info();
module::getUserListModule()->createUserInfo(myInfo);
asyncCallback(std::shared_ptr<void>(pParam));
LOG__(APP, _T("login succeed! Name = %s Nickname = %s sId = %s status = %d")
, m_loginParam.csUserName
, util::stringToCString(userInfo.user_nick_name())
, module::getSysConfigModule()->UserID()
, m_loginParam.mySelectedStatus);
//开始发送心跳包
module::getTcpClientModule()->startHeartbeat();
}
LoginDialog::OnOperationCallback
登录场景:代理窗口回调,会执行LoginDialog::OnOperationCallback
判断登录结果,如果登录成功
1、关闭登录窗口
2、创建用户目录
3、开启同步消息时间timer
4、获取组织架构信息、群列表
显示主界面,至此客户端登录流程完毕
2、服务端逻辑
客户端点击登录,先给login_server,发送http请求
login_server查找合适(负载最小)的msg_server,把相关地址(ip、端口号)发送给客户端。
cpp
void CHttpConn::_HandleMsgServRequest(string& url, string& post_data)
{
msg_serv_info_t* pMsgServInfo;
uint32_t min_user_cnt = (uint32_t)-1;
map<uint32_t, msg_serv_info_t*>::iterator it_min_conn = g_msg_serv_info.end();
map<uint32_t, msg_serv_info_t*>::iterator it;
if(g_msg_serv_info.size() <= 0)
{
Json::Value value;
value["code"] = 1;
value["msg"] = "没有msg_server";
string strContent = value.toStyledString();
char* szContent = new char[HTTP_RESPONSE_HTML_MAX];
snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, strContent.length(), strContent.c_str());
Send((void*)szContent, strlen(szContent));
delete [] szContent;
return ;
}
for (it = g_msg_serv_info.begin() ; it != g_msg_serv_info.end(); it++) {
pMsgServInfo = it->second;
if ( (pMsgServInfo->cur_conn_cnt < pMsgServInfo->max_conn_cnt) &&
(pMsgServInfo->cur_conn_cnt < min_user_cnt)) {
it_min_conn = it;
min_user_cnt = pMsgServInfo->cur_conn_cnt;
}
}
if (it_min_conn == g_msg_serv_info.end()) {
log("All TCP MsgServer are full ");
Json::Value value;
value["code"] = 2;
value["msg"] = "负载过高";
string strContent = value.toStyledString();
char* szContent = new char[HTTP_RESPONSE_HTML_MAX];
snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, strContent.length(), strContent.c_str());
Send((void*)szContent, strlen(szContent));
delete [] szContent;
return;
} else {
Json::Value value;
value["code"] = 0;
value["msg"] = "";
if(pIpParser->isTelcome(GetPeerIP()))
{
value["priorIP"] = string(it_min_conn->second->ip_addr1);
value["backupIP"] = string(it_min_conn->second->ip_addr2);
value["msfsPrior"] = strMsfsUrl;
value["msfsBackup"] = strMsfsUrl;
}
else
{
value["priorIP"] = string(it_min_conn->second->ip_addr2);
value["backupIP"] = string(it_min_conn->second->ip_addr1);
value["msfsPrior"] = strMsfsUrl;
value["msfsBackup"] = strMsfsUrl;
}
value["discovery"] = strDiscovery;
value["port"] = int2string(it_min_conn->second->port);
string strContent = value.toStyledString();
char* szContent = new char[HTTP_RESPONSE_HTML_MAX];
uint32_t nLen = strContent.length();
snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, nLen, strContent.c_str());
Send((void*)szContent, strlen(szContent));
delete [] szContent;
return;
}
}
客户端和msg_server通信,发送登录请求
msg_server的业务处理函数是HandlePdu,根据客户端发送包的命令id为CID_LOGIN_REQ_USERLOGIN,可以知道登录处理函数是CMsgConn::_HandleLoginRequest。
cpp
void CMsgConn::HandlePdu(CImPdu* pPdu)
{
// request authorization check
if (pPdu->GetCommandId() != CID_LOGIN_REQ_USERLOGIN && !IsOpen() && IsKickOff()) {
log("HandlePdu, wrong msg. ");
throw CPduException(pPdu->GetServiceId(), pPdu->GetCommandId(), ERROR_CODE_WRONG_SERVICE_ID, "HandlePdu error, user not login. ");
return;
}
switch (pPdu->GetCommandId()) {
case CID_OTHER_HEARTBEAT:
_HandleHeartBeat(pPdu);
break;
case CID_LOGIN_REQ_USERLOGIN:
_HandleLoginRequest(pPdu );
break;
case CID_LOGIN_REQ_LOGINOUT:
_HandleLoginOutRequest(pPdu);
break;
case CID_LOGIN_REQ_DEVICETOKEN:
_HandleClientDeviceToken(pPdu);
break;
case CID_LOGIN_REQ_KICKPCCLIENT:
_HandleKickPCClient(pPdu);
break;
// 此处省略
// ........
// ........
}
// 此处省略
// .......
// .......
}
CMsgConn::_HandleLoginRequest
1、检查数据库服务器、路由服务器是否可用,如果不可用,回消息给客户端指定错误码、描述符
2、将用户名、密码信息发给db_proxy_server(数据库代理服务器),由db_proxy_server验证用户名、密码是否有效,包的命令id是CID_LOGIN_RES_MSGSERVER
cpp
void CMsgConn::_HandleLoginRequest(CImPdu* pPdu)
{
// refuse second validate request
if (m_login_name.length() != 0) {
log("duplicate LoginRequest in the same conn ");
return;
}
// check if all server connection are OK
uint32_t result = 0;
string result_string = "";
CDBServConn* pDbConn = get_db_serv_conn_for_login();
if (!pDbConn) {
result = IM::BaseDefine::REFUSE_REASON_NO_DB_SERVER;
result_string = "服务端异常";
}
else if (!is_login_server_available()) {
result = IM::BaseDefine::REFUSE_REASON_NO_LOGIN_SERVER;
result_string = "服务端异常";
}
else if (!is_route_server_available()) {
result = IM::BaseDefine::REFUSE_REASON_NO_ROUTE_SERVER;
result_string = "服务端异常";
}
if (result) {
IM::Login::IMLoginRes msg;
msg.set_server_time(time(NULL));
msg.set_result_code((IM::BaseDefine::ResultType)result);
msg.set_result_string(result_string);
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_LOGIN);
pdu.SetCommandId(CID_LOGIN_RES_USERLOGIN);
pdu.SetSeqNum(pPdu->GetSeqNum());
SendPdu(&pdu);
Close();
return;
}
IM::Login::IMLoginReq msg;
CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
//假如是汉字,则转成拼音
m_login_name = msg.user_name();
string password = msg.password();
uint32_t online_status = msg.online_status();
if (online_status < IM::BaseDefine::USER_STATUS_ONLINE || online_status > IM::BaseDefine::USER_STATUS_LEAVE) {
log("HandleLoginReq, online status wrong: %u ", online_status);
online_status = IM::BaseDefine::USER_STATUS_ONLINE;
}
m_client_version = msg.client_version();
m_client_type = msg.client_type();
m_online_status = online_status;
log("HandleLoginReq, user_name=%s, status=%u, client_type=%u, client=%s, ",
m_login_name.c_str(), online_status, m_client_type, m_client_version.c_str());
CImUser* pImUser = CImUserManager::GetInstance()->GetImUserByLoginName(GetLoginName());
if (!pImUser) {
pImUser = new CImUser(GetLoginName());
CImUserManager::GetInstance()->AddImUserByLoginName(GetLoginName(), pImUser);
}
pImUser->AddUnValidateMsgConn(this);
CDbAttachData attach_data(ATTACH_TYPE_HANDLE, m_handle, 0);
// continue to validate if the user is OK
IM::Server::IMValidateReq msg2;
msg2.set_user_name(msg.user_name());
msg2.set_password(password);
msg2.set_attach_data(attach_data.GetBuffer(), attach_data.GetLength());
CImPdu pdu;
pdu.SetPBMsg(&msg2);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_VALIDATE_REQ);
pdu.SetSeqNum(pPdu->GetSeqNum());
pDbConn->SendPdu(&pdu);
}
DB_PROXY::doLogin
根据消息包id,数据库代理服务器的响应函数是DB_PROXY::doLogin,
1、解析请求
2、登录限制检查
3、执行身份验证
4、处理验证结果
cpp
void doLogin(CImPdu* pPdu, uint32_t conn_uuid)
{
CImPdu* pPduResp = new CImPdu;
IM::Server::IMValidateReq msg;
IM::Server::IMValidateRsp msgResp;
if(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()))
{
string strDomain = msg.user_name();
string strPass = msg.password();
msgResp.set_user_name(strDomain);
msgResp.set_attach_data(msg.attach_data());
do
{
CAutoLock cAutoLock(&g_cLimitLock);
list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];
uint32_t tmNow = time(NULL);
//清理超过30分钟的错误时间点记录
auto itTime=lsErrorTime.begin();
for(; itTime!=lsErrorTime.end();++itTime)
{
if(tmNow - *itTime > 30*60)
{
break;
}
}
if(itTime != lsErrorTime.end())
{
lsErrorTime.erase(itTime, lsErrorTime.end());
}
// 判断30分钟内密码错误次数是否大于10
if(lsErrorTime.size() > 10)
{
itTime = lsErrorTime.begin();
if(tmNow - *itTime <= 30*60)
{
msgResp.set_result_code(6);
msgResp.set_result_string("用户名/密码错误次数太多");
pPduResp->SetPBMsg(&msgResp);
pPduResp->SetSeqNum(pPdu->GetSeqNum());
pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
CProxyConn::AddResponsePdu(conn_uuid, pPduResp);
return ;
}
}
} while(false);
log("%s request login.", strDomain.c_str());
IM::BaseDefine::UserInfo cUser;
if(g_loginStrategy.doLogin(strDomain, strPass, cUser))
{
IM::BaseDefine::UserInfo* pUser = msgResp.mutable_user_info();
pUser->set_user_id(cUser.user_id());
pUser->set_user_gender(cUser.user_gender());
pUser->set_department_id(cUser.department_id());
pUser->set_user_nick_name(cUser.user_nick_name());
pUser->set_user_domain(cUser.user_domain());
pUser->set_avatar_url(cUser.avatar_url());
pUser->set_email(cUser.email());
pUser->set_user_tel(cUser.user_tel());
pUser->set_user_real_name(cUser.user_real_name());
pUser->set_status(0);
pUser->set_sign_info(cUser.sign_info());
msgResp.set_result_code(0);
msgResp.set_result_string("成功");
//如果登陆成功,则清除错误尝试限制
CAutoLock cAutoLock(&g_cLimitLock);
list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];
lsErrorTime.clear();
}
else
{
//密码错误,记录一次登陆失败
uint32_t tmCurrent = time(NULL);
CAutoLock cAutoLock(&g_cLimitLock);
list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];
lsErrorTime.push_front(tmCurrent);
log("get result false");
msgResp.set_result_code(1);
msgResp.set_result_string("用户名/密码错误");
}
}
else
{
msgResp.set_result_code(2);
msgResp.set_result_string("服务端内部错误");
}
pPduResp->SetPBMsg(&msgResp);
pPduResp->SetSeqNum(pPdu->GetSeqNum());
pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
CProxyConn::AddResponsePdu(conn_uuid, pPduResp);
}
db_proxy_server:执行身份验证
1、获取数据库管理器单例,获取teamtalk_slave数据库的连接
2、构造SQL查询语句,在表IMUser查询用户相关信息
3、密码校验
客户端传来的明文密码与数据库中存储的盐值(strSalt)拼接
使用MD5算法计算拼接后字符串的MD5值szMd5
比较szMd5值和数据库存的密码比较
cpp
bool CInterLoginStrategy::doLogin(const std::string &strName, const std::string &strPass, IM::BaseDefine::UserInfo& user)
{
bool bRet = false;
CDBManager* pDBManger = CDBManager::getInstance();
CDBConn* pDBConn = pDBManger->GetDBConn("teamtalk_slave");
if (pDBConn) {
string strSql = "select * from IMUser where name='" + strName + "' and status=0";
CResultSet* pResultSet = pDBConn->ExecuteQuery(strSql.c_str());
if(pResultSet)
{
string strResult, strSalt;
uint32_t nId, nGender, nDeptId, nStatus;
string strNick, strAvatar, strEmail, strRealName, strTel, strDomain,strSignInfo;
while (pResultSet->Next()) {
nId = pResultSet->GetInt("id");
strResult = pResultSet->GetString("password");
strSalt = pResultSet->GetString("salt");
strNick = pResultSet->GetString("nick");
nGender = pResultSet->GetInt("sex");
strRealName = pResultSet->GetString("name");
strDomain = pResultSet->GetString("domain");
strTel = pResultSet->GetString("phone");
strEmail = pResultSet->GetString("email");
strAvatar = pResultSet->GetString("avatar");
nDeptId = pResultSet->GetInt("departId");
nStatus = pResultSet->GetInt("status");
strSignInfo = pResultSet->GetString("sign_info");
}
string strInPass = strPass + strSalt;
char szMd5[33];
CMd5::MD5_Calculate(strInPass.c_str(), strInPass.length(), szMd5);
string strOutPass(szMd5);
if(strOutPass == strResult)
{
bRet = true;
user.set_user_id(nId);
user.set_user_nick_name(strNick);
user.set_user_gender(nGender);
user.set_user_real_name(strRealName);
user.set_user_domain(strDomain);
user.set_user_tel(strTel);
user.set_email(strEmail);
user.set_avatar_url(strAvatar);
user.set_department_id(nDeptId);
user.set_status(nStatus);
user.set_sign_info(strSignInfo);
}
delete pResultSet;
}
pDBManger->RelDBConn(pDBConn);
}
return bRet;
}
数据库验证通过
给msg_server发送消息包,消息id为CID_OTHER_VALIDATE_RSP
CDBServConn::_HandleValidateResponse
接收响应: msg_server 接收来自 db_server 的验证结果。
处理结果: 根据 db_server 返回的成功或失败,执行不同的逻辑(更新用户状态、踢人、或关闭连接)。
回复客户端: 向客户端发送最终的登录响应 IM::Login::IMLoginRes。
到此服务端登录验证流程结束。
cpp
void CDBServConn::_HandleValidateResponse(CImPdu* pPdu)
{
IM::Server::IMValidateRsp msg;
CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
string login_name = msg.user_name();
uint32_t result = msg.result_code();
string result_string = msg.result_string();
CDbAttachData attach_data((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());
log("HandleValidateResp, user_name=%s, result=%d", login_name.c_str(), result);
CImUser* pImUser = CImUserManager::GetInstance()->GetImUserByLoginName(login_name);
CMsgConn* pMsgConn = NULL;
if (!pImUser) {
log("ImUser for user_name=%s not exist", login_name.c_str());
return;
} else {
pMsgConn = pImUser->GetUnValidateMsgConn(attach_data.GetHandle());
if (!pMsgConn || pMsgConn->IsOpen()) {
log("no such conn is validated, user_name=%s", login_name.c_str());
return;
}
}
if (result != 0) {
result = IM::BaseDefine::REFUSE_REASON_DB_VALIDATE_FAILED;
}
if (result == 0)
{
IM::BaseDefine::UserInfo user_info = msg.user_info();
uint32_t user_id = user_info.user_id();
CImUser* pUser = CImUserManager::GetInstance()->GetImUserById(user_id);
if (pUser)
{
pUser->AddUnValidateMsgConn(pMsgConn);
pImUser->DelUnValidateMsgConn(pMsgConn);
if (pImUser->IsMsgConnEmpty())
{
CImUserManager::GetInstance()->RemoveImUserByLoginName(login_name);
delete pImUser;
}
}
else
{
pUser = pImUser;
}
pUser->SetUserId(user_id);
pUser->SetNickName(user_info.user_nick_name());
pUser->SetValidated();
CImUserManager::GetInstance()->AddImUserById(user_id, pUser);
pUser->KickOutSameClientType(pMsgConn->GetClientType(), IM::BaseDefine::KICK_REASON_DUPLICATE_USER, pMsgConn);
CRouteServConn* pRouteConn = get_route_serv_conn();
if (pRouteConn) {
IM::Server::IMServerKickUser msg2;
msg2.set_user_id(user_id);
msg2.set_client_type((::IM::BaseDefine::ClientType)pMsgConn->GetClientType());
msg2.set_reason(1);
CImPdu pdu;
pdu.SetPBMsg(&msg2);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_SERVER_KICK_USER);
pRouteConn->SendPdu(&pdu);
}
log("user_name: %s, uid: %d", login_name.c_str(), user_id);
pMsgConn->SetUserId(user_id);
pMsgConn->SetOpen();
pMsgConn->SendUserStatusUpdate(IM::BaseDefine::USER_STATUS_ONLINE);
pUser->ValidateMsgConn(pMsgConn->GetHandle(), pMsgConn);
IM::Login::IMLoginRes msg3;
msg3.set_server_time(time(NULL));
msg3.set_result_code(IM::BaseDefine::REFUSE_REASON_NONE);
msg3.set_result_string(result_string);
msg3.set_online_status((IM::BaseDefine::UserStatType)pMsgConn->GetOnlineStatus());
IM::BaseDefine::UserInfo* user_info_tmp = msg3.mutable_user_info();
user_info_tmp->set_user_id(user_info.user_id());
user_info_tmp->set_user_gender(user_info.user_gender());
user_info_tmp->set_user_nick_name(user_info.user_nick_name());
user_info_tmp->set_avatar_url(user_info.avatar_url());
user_info_tmp->set_sign_info(user_info.sign_info());
user_info_tmp->set_department_id(user_info.department_id());
user_info_tmp->set_email(user_info.email());
user_info_tmp->set_user_real_name(user_info.user_real_name());
user_info_tmp->set_user_tel(user_info.user_tel());
user_info_tmp->set_user_domain(user_info.user_domain());
user_info_tmp->set_status(user_info.status());
CImPdu pdu2;
pdu2.SetPBMsg(&msg3);
pdu2.SetServiceId(SID_LOGIN);
pdu2.SetCommandId(CID_LOGIN_RES_USERLOGIN);
pdu2.SetSeqNum(pPdu->GetSeqNum());
pMsgConn->SendPdu(&pdu2);
}
else
{
IM::Login::IMLoginRes msg4;
msg4.set_server_time(time(NULL));
msg4.set_result_code((IM::BaseDefine::ResultType)result);
msg4.set_result_string(result_string);
CImPdu pdu3;
pdu3.SetPBMsg(&msg4);
pdu3.SetServiceId(SID_LOGIN);
pdu3.SetCommandId(CID_LOGIN_RES_USERLOGIN);
pdu3.SetSeqNum(pPdu->GetSeqNum());
pMsgConn->SendPdu(&pdu3);
pMsgConn->Close();
}
}