本文主要分析TeamTalk的服务器架构中MsgServer的启动流程,在TeamTalk的各个服务器中,消息服务器Msg-Server是最复杂。本文剖析了其启动流程。
1、在Msg_server.cpp的main函数是消息服务器启动的入口函数,其主要的流程包含一下几个部分:
(1)、读取配置文件中设置的各个服务器的监听IP和端口。
(2)、初始化网络库
(3)、消息服务器在监听端口监听
(4)、设置时间超时回调
(5)、连接文档服务器FileServer
(6)、连接DBServe,DB服务器
(7)、连接登录服务器loginServer
(8)、连接路由服务器RouteServer
(9)、连接推送服务器PushServer
(10)、进入主事件循环。
其中读取配置文件和初始化网络库比较简单,没有什么理解难点。下面对其他几点逐条分析。
由于消息服务器在TeamTalk的业务流程中的占比很大,主要负责进行客户端的消息递送等服务,因此消息服务器需要监听客户端到消息服务器的连接,在消息服务器中保存每个连接的客户端类型用户ID等值,主要流程如下:
char* listen_ip = config_file.GetConfigName("ListenIP");
for (uint32_t i = 0; i < listen_ip_list.GetItemCnt(); i++) {
ret = netlib_listen(listen_ip_list.GetItem(i), listen_port, msg_serv_callback, NULL);
if (ret == NETLIB_ERROR)
return ret;
}
void CBaseSocket::OnRead()
{
if (m_state == SOCKET_STATE_LISTENING)
{
_AcceptNewSocket();
}
else
{
u_long avail = 0;
if ( (ioctlsocket(m_socket, FIONREAD, &avail) == SOCKET_ERROR) || (avail == 0) )
{
m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
}
else
{
m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL);
}
}
}
由于当前的状态时LISTENNING状态,因此接受新的Sokcet请求,并将客户端连接的socket描述符增加到select或者epoll的可读事件里, 在全局的socket map中插入一条新记录.
void CBaseSocket::_AcceptNewSocket()
{
SOCKET fd = 0;
sockaddr_in peer_addr;
socklen_t addr_len = sizeof(sockaddr_in);
char ip_str[64];
while ( (fd = accept(m_socket, (sockaddr*)&peer_addr, &addr_len)) != INVALID_SOCKET )
{
CBaseSocket* pSocket = new CBaseSocket();
uint32_t ip = ntohl(peer_addr.sin_addr.s_addr);
uint16_t port = ntohs(peer_addr.sin_port);
snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", ip >> 24, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
log("AcceptNewSocket, socket=%d from %s:%d\n", fd, ip_str, port);
pSocket->SetSocket(fd);
pSocket->SetCallback(m_callback);
pSocket->SetCallbackData(m_callback_data);
pSocket->SetState(SOCKET_STATE_CONNECTED);
pSocket->SetRemoteIP(ip_str);
pSocket->SetRemotePort(port);
_SetNoDelay(fd);
_SetNonblock(fd);
AddBaseSocket(pSocket);
CEventDispatch::Instance()->AddEvent(fd, SOCKET_READ | SOCKET_EXCEP);
m_callback(m_callback_data, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL);
}
}
void msg_serv_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
log("msg_server come in");
if (msg == NETLIB_MSG_CONNECT)
{
CLoginConn* pConn = new CLoginConn();
pConn->OnConnect2(handle, LOGIN_CONN_TYPE_MSG_SERV);
}
else
{
log("!!!error msg: %d ", msg);
}
}
void CLoginConn::OnConnect2(net_handle_t handle, int conn_type)
{
m_handle = handle;
m_conn_type = conn_type;
ConnMap_t* conn_map = &g_msg_serv_conn_map;
if (conn_type == LOGIN_CONN_TYPE_CLIENT) {
conn_map = &g_client_conn_map;
}else
conn_map->insert(make_pair(handle, this));
netlib_option(handle, NETLIB_OPT_SET_CALLBACK, (void*)imconn_callback);
netlib_option(handle, NETLIB_OPT_SET_CALLBACK_DATA, (void*)conn_map);
}
至此,Msg_Server的监听已经客户端发起连接已经处理完成,主事件循环中如果有数据可读或者可写分别调用该socket最后注册的imconn_callback进行数据读写。
void init_msg_conn()
{
g_last_stat_tick = get_tick_count();
signal(SIGUSR1, signal_handler_usr1);
signal(SIGUSR2, signal_handler_usr2);
signal(SIGHUP, signal_handler_hup);
netlib_register_timer(msg_conn_timer_callback, NULL, 1000);
s_file_handler = CFileHandler::getInstance();
s_group_chat = CGroupChat::GetInstance();
}
主事件分发器中每次轮询都会检查当前的TICK,如果当前Tick大于上次的Tick,则调用定时器回调函数。
void CEventDispatch::_CheckTimer()
{
uint64_t curr_tick = get_tick_count();
list::iterator it;
for (it = m_timer_list.begin(); it != m_timer_list.end(); )
{
TimerItem* pItem = *it;
it++; // iterator maybe deleted in the callback, so we should increment it before callback
if (curr_tick >= pItem->next_tick)
{
pItem->next_tick += pItem->interval;
pItem->callback(pItem->user_data, NETLIB_MSG_TIMER, 0, NULL);
}
}
}
void msg_conn_timer_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
ConnMap_t::iterator it_old;
CMsgConn* pConn = NULL;
uint64_t cur_time = get_tick_count();
for (ConnMap_t::iterator it = g_msg_conn_map.begin(); it != g_msg_conn_map.end(); ) {
it_old = it;
it++;
pConn = (CMsgConn*)it_old->second;
pConn->OnTimer(cur_time);
}
if (cur_time > g_last_stat_tick + LOG_MSG_STAT_INTERVAL) {
g_last_stat_tick = cur_time;
log("up_msg_cnt=%u, up_msg_miss_cnt=%u, down_msg_cnt=%u, down_msg_miss_cnt=%u ",
g_up_msg_total_cnt, g_up_msg_miss_cnt, g_down_msg_total_cnt, g_down_msg_miss_cnt);
}
void CMsgConn::OnTimer(uint64_t curr_tick)
{
m_msg_cnt_per_sec = 0;
if (CHECK_CLIENT_TYPE_MOBILE(GetClientType()))
{
if (curr_tick > m_last_recv_tick + MOBILE_CLIENT_TIMEOUT) {
log("mobile client timeout, handle=%d, uid=%u ", m_handle, GetUserId());
Close();
return;
}
}
else
{
if (curr_tick > m_last_recv_tick + CLIENT_TIMEOUT) {
log("client timeout, handle=%d, uid=%u ", m_handle, GetUserId());
Close();
return;
}
}
if (!IsOpen()) {
if (curr_tick > m_login_time + TIMEOUT_WATI_LOGIN_RESPONSE) {
log("login timeout, handle=%d, uid=%u ", m_handle, GetUserId());
Close();
return;
}
}
list::iterator it_old;
for (list::iterator it = m_send_msg_list.begin(); it != m_send_msg_list.end(); ) {
msg_ack_t msg = *it;
it_old = it;
it++;
if (curr_tick >= msg.timestamp + TIMEOUT_WAITING_MSG_DATA_ACK) {
log("!!!a msg missed, msg_id=%u, %u->%u ", msg.msg_id, msg.from_id, GetUserId());
g_down_msg_miss_cnt++;
m_send_msg_list.erase(it_old);
} else {
break;
}
}
}
void init_file_serv_conn(serv_info_t* server_list, uint32_t server_count)
{
g_file_server_list = server_list;
g_file_server_count = server_count;
serv_init(g_file_server_list, g_file_server_count);
netlib_register_timer(file_server_conn_timer_callback, NULL, 1000);
s_file_handler = CFileHandler::getInstance();
}
server_list 是server_count分别是文档服务器的地址结构和文档服务器个数(可配置多个文档服务器)。serv_init是一个模板函数,根据具体类型实例化,调用相应实例的Connect函数创建TCP连接。
template
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;
}
}
调用CFileServConn的connec函数,发起TCP连接,并将连接的socket描述符添加到g_file_server_conn_map中,该map保存了所有消息服务器和文档服务器之间的连接描述符。
void CFileServConn::Connect(const char* server_ip, uint16_t server_port, uint32_t idx)
{
log("Connecting to FileServer %s:%d ", server_ip, server_port);
m_serv_idx = idx;
m_handle = netlib_connect(server_ip, server_port, imconn_callback, (void*)&g_file_server_conn_map);
if (m_handle != NETLIB_INVALID_HANDLE) {
g_file_server_conn_map.insert(make_pair(m_handle, this));
}
}
后面调用netlib库具体实现网络请求就不详细列出了,都是通用的协议。需要注意的是,在连接上我文档服务器后,消息服务器需要向文档服务器发送Confirm消息,报告当前消息服务器的状态。
void CBaseSocket::OnWrite()
{
#if ((defined _WIN32) || (defined __APPLE__))
CEventDispatch::Instance()->RemoveEvent(m_socket, SOCKET_WRITE);
#endif
if (m_state == SOCKET_STATE_CONNECTING)
{
int error = 0;
socklen_t len = sizeof(error);
#ifdef _WIN32
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
#else
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
#endif
if (error) {
m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
} else {
m_state = SOCKET_STATE_CONNECTED;
m_callback(m_callback_data, NETLIB_MSG_CONFIRM, (net_handle_t)m_socket, NULL);
}
}
else
{
m_callback(m_callback_data, NETLIB_MSG_WRITE, (net_handle_t)m_socket, NULL);
}
}
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();
}
在该函数中call_back_data指的是保存的各个类型的socket描述符,在此处是g_file_server_conn_map,查找对应的connl类,注意的是,pConn是父类指针指向子类对象,在调用OnConfirm调用的是具体子类对象的OnConfirm函数,父类的OnConfirm函数是空的。此处调用CFileServConn的OnConfirm函数:
void CFileServConn::OnConfirm()
{
log("connect to file server success ");
m_bOpen = true;
m_connect_time = get_tick_count();
g_file_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
IM::Server::IMFileServerIPReq msg;
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_FILE_SERVER_IP_REQ);
SendPdu(&pdu);
}
在该处向FileServer发送了一条CID为CID_OTHER_FILE_SERVER_IP_REQ消息。
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;
list 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);
}
void CRouteServConn::OnConfirm()
{
log("connect to route server success ");
m_bOpen = true;
m_connect_time = get_tick_count();
g_route_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
if (g_master_rs_conn == NULL) {
update_master_route_serv_conn();
}
list online_user_list;
CImUserManager::GetInstance()->GetOnlineUserInfo(&online_user_list);
IM::Server::IMOnlineUserInfo msg;
for (list::iterator it = online_user_list.begin(); it != online_user_list.end(); it++) {
user_stat_t user_stat = *it;
IM::BaseDefine::ServerUserStat* server_user_stat = msg.add_user_stat_list();
server_user_stat->set_user_id(user_stat.user_id);
server_user_stat->set_status((::IM::BaseDefine::UserStatType)user_stat.status);
server_user_stat->set_client_type((::IM::BaseDefine::ClientType)user_stat.client_type);
}
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_ONLINE_USER_INFO);
SendPdu(&pdu);
}
void CDBServConn::OnConfirm()
{
log("connect to db server success");
m_bOpen = true;
g_db_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
}
void CPushServConn::OnConfirm()
{
log("connect to push server success ");
m_bOpen = true;
g_push_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
g_master_push_conn = this;
}
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();
}
}
void CImConn::OnWrite()
{
if (!m_busy)
return;
while (m_out_buf.GetWriteOffset() > 0) {
int send_size = m_out_buf.GetWriteOffset();
if (send_size > NETLIB_MAX_SOCKET_BUF_SIZE) {
send_size = NETLIB_MAX_SOCKET_BUF_SIZE;
}
int ret = netlib_send(m_handle, m_out_buf.GetBuffer(), send_size);
if (ret <= 0) {
ret = 0;
break;
}
m_out_buf.Read(NULL, ret);
}
if (m_out_buf.GetWriteOffset() == 0) {
m_busy = false;
}
log("onWrite, remain=%d ", m_out_buf.GetWriteOffset());
}
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;
}
}
void CMsgConn::_HandleHeartBeat(CImPdu *pPdu)
{
//响应
SendPdu(pPdu);
}