1、概述
消息服务器:提供客户端大部分信令处理,直接处理或转发给其他服务(比如:聊天、传输文件等)
可以在部署在不同机器上,通过RouteServer(路由服务器),为登录在不同MsgServer的用户提供消息转发功能。
2、代码学习
2.1、主函数代码
1、读取配置文件
2、8000端口上监听客户端连接
3、连接其他服务器
连接file_server,每个连接对应FileServConn实例,代码文件FileServConn.cpp
连接db_server,每个连接对应DBServConn实例,代码文件DBServConn.cpp
连接login_server,每个连接对应LoginServConn实例,代码文件LoginServConn.cpp
连接route_server,每个连接对应RouteServConn实例, 代码文件RouteServConn.cpp
连接push_server,每个连接对应PushServConn实例, 代码文件PushServConn.cpp
4、启动事件循环
2.2、init_login_serv_conn
每个连接服务器流程都差不多一样的,比如:init_login_serv_conn
serv_init模版函数最终会执行CLoginServConn::Connect函数,会连接login_server
cpp
void init_login_serv_conn(serv_info_t* server_list, uint32_t server_count, const char* msg_server_ip_addr1,
const char* msg_server_ip_addr2, uint16_t msg_server_port, uint32_t max_conn_cnt)
{
g_login_server_list = server_list;
g_login_server_count = server_count;
serv_init<CLoginServConn>(g_login_server_list, g_login_server_count);
g_msg_server_ip_addr1 = msg_server_ip_addr1;
g_msg_server_ip_addr2 = msg_server_ip_addr2;
g_msg_server_port = msg_server_port;
g_max_conn_cnt = max_conn_cnt;
netlib_register_timer(login_server_conn_timer_callback, NULL, 1000);
}
cpp
void serv_init(serv_info_t* server_list, uint32_t server_count)
{
for (uint32_t i = 0; i < server_count; i++) {
T* pConn = new T();
pConn->Connect(server_list[i].server_ip.c_str(), server_list[i].server_port, i);
server_list[i].serv_conn = pConn;
server_list[i].idle_cnt = 0;
server_list[i].reconnect_cnt = MIN_RECONNECT_CNT / 2;
}
}
cpp
void CLoginServConn::Connect(const char* server_ip, uint16_t server_port, uint32_t serv_idx)
{
log("Connecting to LoginServer %s:%d ", server_ip, server_port);
m_serv_idx = serv_idx;
m_handle = netlib_connect(server_ip, server_port, imconn_callback, (void*)&g_login_server_conn_map);
if (m_handle != NETLIB_INVALID_HANDLE) {
g_login_server_conn_map.insert(make_pair(m_handle, this));
}
}
连接login_server成功后,会执行imconn_callback,CLoginServConn继承CImConn,根据多态会执行 CLoginServConn::OnConfirm
cpp
void imconn_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
NOTUSED_ARG(handle);
NOTUSED_ARG(pParam);
if (!callback_data)
return;
ConnMap_t* conn_map = (ConnMap_t*)callback_data;
CImConn* pConn = FindImConn(conn_map, handle);
if (!pConn)
return;
//log("msg=%d, handle=%d ", msg, handle);
switch (msg)
{
case NETLIB_MSG_CONFIRM:
pConn->OnConfirm();
break;
case NETLIB_MSG_READ:
pConn->OnRead();
break;
case NETLIB_MSG_WRITE:
pConn->OnWrite();
break;
case NETLIB_MSG_CLOSE:
pConn->OnClose();
break;
default:
log("!!!imconn_callback error msg: %d ", msg);
break;
}
pConn->ReleaseRef();
}
查看CLoginServConn::OnConfirm代码,连接login_server成功后,会立即向login_server发一个包,告诉login_server自己的ip、端口号、当前登录的用户数量和可容纳的最大用户数量。
这样login_server将来对于一个需要登录的用户,会根据不同的msg_server的负载状态来决定用户到底登录哪个msg_server。
cpp
void CLoginServConn::OnConfirm()
{
log("connect to login server success ");
m_bOpen = true;
g_login_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
uint32_t cur_conn_cnt = 0;
uint32_t shop_user_cnt = 0;
//连接login_server成功以后,告诉login_server自己的ip地址、端口号
//和当前登录的用户数量和可容纳的最大用户数量
list<user_conn_t> user_conn_list;
CImUserManager::GetInstance()->GetUserConnCnt(&user_conn_list, cur_conn_cnt);
char hostname[256] = {0};
gethostname(hostname, 256);
IM::Server::IMMsgServInfo msg;
msg.set_ip1(g_msg_server_ip_addr1);
msg.set_ip2(g_msg_server_ip_addr2);
msg.set_port(g_msg_server_port);
msg.set_max_conn_cnt(g_max_conn_cnt);
msg.set_cur_conn_cnt(cur_conn_cnt);
msg.set_host_name(hostname);
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_MSG_SERV_INFO);
SendPdu(&pdu);
}
在连接每个服务器时,都会设置一个定时器,代码都差不多一样的。如下LoginServConn定时器中发送心跳包、检查心跳超时关闭连接。

cpp
void login_server_conn_timer_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
ConnMap_t::iterator it_old;
CLoginServConn* pConn = NULL;
uint64_t cur_time = get_tick_count();
for (ConnMap_t::iterator it = g_login_server_conn_map.begin(); it != g_login_server_conn_map.end(); ) {
it_old = it;
it++;
pConn = (CLoginServConn*)it_old->second;
pConn->OnTimer(cur_time);
}
// reconnect LoginServer
serv_check_reconnect<CLoginServConn>(g_login_server_list, g_login_server_count);
}
cpp
void CLoginServConn::OnTimer(uint64_t curr_tick)
{
if (curr_tick > m_last_send_tick + SERVER_HEARTBEAT_INTERVAL) {
IM::Other::IMHeartBeat msg;
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_HEARTBEAT);
SendPdu(&pdu);
}
if (curr_tick > m_last_recv_tick + SERVER_TIMEOUT) {
log("conn to login server timeout ");
Close();
}
}
定时器在一个事件循环中被触发,每个服务都有这样一个循环,也是经典实现模式,广泛应用在网络编程中。俗称 one thread one loop:每个线程运行一个无限循环(Event Loop),负责处理该线程绑定的所有I/O事件、定时任务和其他回调。
while(条件)
{
1、IO复用函数检测socket事件
2、处理socket可读事件
3、处理socket可写事件
4、处理socket异常事件
5、遍历定时器队列,检查是否有定时器事件到期,有则执行定时器的回调函数
6、遍历其他任务队列,检测是否有其他任务需要执行,有执行
}
cpp
void CEventDispatch::StartDispatch(uint32_t wait_timeout)
{
struct epoll_event events[1024];
int nfds = 0;
if(running)
return;
running = true;
while (running)
{
nfds = epoll_wait(m_epfd, events, 1024, wait_timeout);
for (int i = 0; i < nfds; i++)
{
int ev_fd = events[i].data.fd;
CBaseSocket* pSocket = FindBaseSocket(ev_fd);
if (!pSocket)
continue;
//Commit by zhfu @2015-02-28
#ifdef EPOLLRDHUP
if (events[i].events & EPOLLRDHUP)
{
//log("On Peer Close, socket=%d, ev_fd);
pSocket->OnClose();
}
#endif
// Commit End
if (events[i].events & EPOLLIN)
{
//log("OnRead, socket=%d\n", ev_fd);
pSocket->OnRead();
}
if (events[i].events & EPOLLOUT)
{
//log("OnWrite, socket=%d\n", ev_fd);
pSocket->OnWrite();
}
if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP))
{
//log("OnClose, socket=%d\n", ev_fd);
pSocket->OnClose();
}
pSocket->ReleaseRef();
}
_CheckTimer();
_CheckLoop();
}
}
每个实例(比如:FileServConn、DBServConn、LoginServConn等)的业务处理在HandlePdu函数中,比如CMsgConn::HandlePdu如下,当socket可读时,会一层一层回调,调到CImConn::OnRead,OnRead中又调用HandlePdu(pPdu),根据多态会调用到具体业务子类的 HandlePdu函数,详细业务需要调试HandlePdu理解。
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;
case CID_LOGIN_REQ_PUSH_SHIELD:
_HandlePushShieldRequest(pPdu);
break;
case CID_LOGIN_REQ_QUERY_PUSH_SHIELD:
_HandleQueryPushShieldRequest(pPdu);
break;
case CID_MSG_DATA:
_HandleClientMsgData(pPdu);
break;
case CID_MSG_DATA_ACK:
_HandleClientMsgDataAck(pPdu);
break;
case CID_MSG_TIME_REQUEST:
_HandleClientTimeRequest(pPdu);
break;
case CID_MSG_LIST_REQUEST:
_HandleClientGetMsgListRequest(pPdu);
break;
case CID_MSG_GET_BY_MSG_ID_REQ:
_HandleClientGetMsgByMsgIdRequest(pPdu);
break;
case CID_MSG_UNREAD_CNT_REQUEST:
_HandleClientUnreadMsgCntRequest(pPdu );
break;
case CID_MSG_READ_ACK:
_HandleClientMsgReadAck(pPdu);
break;
case CID_MSG_GET_LATEST_MSG_ID_REQ:
_HandleClientGetLatestMsgIDReq(pPdu);
break;
case CID_SWITCH_P2P_CMD:
_HandleClientP2PCmdMsg(pPdu );
break;
case CID_BUDDY_LIST_RECENT_CONTACT_SESSION_REQUEST:
_HandleClientRecentContactSessionRequest(pPdu);
break;
case CID_BUDDY_LIST_USER_INFO_REQUEST:
_HandleClientUserInfoRequest( pPdu );
break;
case CID_BUDDY_LIST_REMOVE_SESSION_REQ:
_HandleClientRemoveSessionRequest( pPdu );
break;
case CID_BUDDY_LIST_ALL_USER_REQUEST:
_HandleClientAllUserRequest(pPdu );
break;
case CID_BUDDY_LIST_CHANGE_AVATAR_REQUEST:
_HandleChangeAvatarRequest(pPdu);
break;
case CID_BUDDY_LIST_CHANGE_SIGN_INFO_REQUEST:
_HandleChangeSignInfoRequest(pPdu);
break;
case CID_BUDDY_LIST_USERS_STATUS_REQUEST:
_HandleClientUsersStatusRequest(pPdu);
break;
case CID_BUDDY_LIST_DEPARTMENT_REQUEST:
_HandleClientDepartmentRequest(pPdu);
break;
// for group process
case CID_GROUP_NORMAL_LIST_REQUEST:
s_group_chat->HandleClientGroupNormalRequest(pPdu, this);
break;
case CID_GROUP_INFO_REQUEST:
s_group_chat->HandleClientGroupInfoRequest(pPdu, this);
break;
case CID_GROUP_CREATE_REQUEST:
s_group_chat->HandleClientGroupCreateRequest(pPdu, this);
break;
case CID_GROUP_CHANGE_MEMBER_REQUEST:
s_group_chat->HandleClientGroupChangeMemberRequest(pPdu, this);
break;
case CID_GROUP_SHIELD_GROUP_REQUEST:
s_group_chat->HandleClientGroupShieldGroupRequest(pPdu, this);
break;
case CID_FILE_REQUEST:
s_file_handler->HandleClientFileRequest(this, pPdu);
break;
case CID_FILE_HAS_OFFLINE_REQ:
s_file_handler->HandleClientFileHasOfflineReq(this, pPdu);
break;
case CID_FILE_ADD_OFFLINE_REQ:
s_file_handler->HandleClientFileAddOfflineReq(this, pPdu);
break;
case CID_FILE_DEL_OFFLINE_REQ:
s_file_handler->HandleClientFileDelOfflineReq(this, pPdu);
break;
default:
log("wrong msg, cmd id=%d, user id=%u. ", pPdu->GetCommandId(), GetUserId());
break;
}
}
