TeamTalk 服务器代码分析
转自https://blog.csdn.net/hailong0715/article/details/52804165
本文主要分析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");
从配置文件中读取消息服务器的监听IP,监听IP可能有多个,因此分别在每个IP上监听客户端的连接请求,设置当前的SOCKET状态为listenning,并设置客户端发起连接时的的回调函数msg_serv_callback,
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;
}
在主事件循环中,当客户端发起连接请求时,select或者epoll调用返回,发现与数据可读,调用Onread函数,读取对应的数据,(其实这个数据是客户端发送的SYN标识)
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);
}
}
调用前面注册的客户端发起连接回调msg_server_callbakc。
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);
}
}
在回调中记录连接上msg_server,,并且设置创建一个LoginConn对象,在已连接的socket map中插入一条记录,并且修改该socket对象的回调为imconn_callback
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进行数据读写。
设置时间超时回调
在Msg_server的主事件循环中,每次循环都会检查定时器时间是否到了,每次定时时间都为1S,在main函数中注册定时器回调。
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<TimerItem*>::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);
}
}
}
定时器回调函数为msg_conn_timer_callback
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);
}
定时器回调中会遍历当前已经连接的socket描述符,分别对对应的连接调用OnTimer函数,处理定时器时间到
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<msg_ack_t>::iterator it_old;
for (list<msg_ack_t>::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;
}
}
}
在上述OnTimer函数中,对分别将当前的TICK和客户的连接的last_recv_tick进行比较,判断每个连接是否超时,如果连接超时了,则关闭该socket描述符,回收系统资源,因为服务器的socket描述符时有限的因此,在连接不可用的时候应当立即回收资源。而last_recv_tick是通过心跳机制来更新的,详情可参考我的另一篇博文《 TeamTalk心跳保活机制分析》。
连接服务器FileServer,LoginServer,DBServer,PushServer,RouteServer
文档服务器在TeamServer中用于存储文档或者在线离线传送文件用,因此Msg_Server需要个文档服务器保持一个长连接。
main函数中,通过init_file_serv_conn向文档服务器发起连接。
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<CFileServConn>(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 <class T>
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消息,报告当前消息服务器的状态。
具体实现为,当消息服务器向文档服务器发起的TCP连接建立以后,时间分发器中的select或者epoll会发现当前缓冲区可写,因此准备向该连接的缓冲区写数据,具体为调用该Socket的OnWrite函数。
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);
}
}
由于之前在向File_Server发起连接的时候,该socket的state设置为STATE_SOCKET_CONNECTING,因此该函数走第一个if分支,调用前面socket注册的回调imconn_callback,消息类型为NETLIB_MSG_CONFIRM。
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消息。
连接登录服务里,RouteServer,routeServer(路由服务器),pushServer(推送服务器),DBServer(DB服务器)的过程于上述的连接文档服务器的过程基本相同。有区别的是一两点:
1、在模板函数ServerInit中文档服务器实例化的是CFileServerConn,而在其他服务器中需要实例化其自身对应的类,创建对应的对象。
2、消息服务器于不同服务器发起的连接需要向其发送的消息不同,即OnConfirm函数不同,分别为:
登录服务器
消息服务器与登录服务器建立连接后需要向登录服务器上报自身的信息,如消息服务器监听IP,监听端口,消息服务器的当前连接数,最大连接数等信息,登录服务器会根据从消息服务器获取的负载状态分配一个合适的消息服务器给客户端。
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_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);
}
路由服务器RouteServer
消息服务器需要向路由服务器上报当前的在线用户信息。
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<user_stat_t> online_user_list;
CImUserManager::GetInstance()->GetOnlineUserInfo(&online_user_list);
IM::Server::IMOnlineUserInfo msg;
for (list<user_stat_t>::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);
}
DBServer 数据库服务器
仅作一些初始化的操作,不向其发送消息
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;
}
PushServer推送服务器
仅作一些初始化的操作,不向其发送消息
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;
}
消息循环(事件分发器)
Msg_Server中最重要的部分就是消息循环,在main函数中通过netlib_eventloop进入事件分发器,开始主事件循环,
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();
}
}
在主事件循环中主要做两件事,1、判断当前连接状态是否可读可写,2、定时器轮询,判断定时时间是否到了。
TeamTalk的MSG_Server采用了select/epoll的机制来进行socket操作,select/epoll会不断的阻塞当前的所有操作,直到当前的socket字符集合中有数据可读可写。其中可写的部分比较简单,在回调函数imconn_callback中调用对应的类进行数据的写入操作。
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());
}
当socket集中有数据可读时,在回调函数中,调用对应的basesocket类的Onread函数,实现数据的都操作,在数据的读部分,最核心的部分在于处理各个服务器或者客户端发送给消息的服务器的消息函数HandPdu(TeamTalk的消息单元成为PDU 协议数据单元,是TeamTalk自己实现的一种协议,其中的数据部分用Google的Protobuf格式,方便进行序列化和反序列化)。
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);
}
所有消息服务器和其他服务器和客户端的通信都通过该函数完成,TeamTalk定义了每一种数据的CmdId,ServiceId,根据对应的消息ID进行相应的消息处理。
自此,MSG_Server的启动流程已经简单介绍完,水平有限,可能有些地方理解有误,还请看官指正,谢谢
TeamTalk 服务器代码分析相关推荐
- 录音函数网络对讲机C#服务器 Android客户端(二) C#服务器代码分析 附加
时光紧张,先记一笔,后续优化与完善. 整完的工程下载址地(源码) :http://download.csdn.net/detail/zhujinghao09/5313666 C# 录音功能现实 引用 ...
- BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象) -- 转贴自 wolfenstein (NeverSayNever)
BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象) author: wolfenstein (NeverSayNever) 上次我们分析了Tracker类初始化的 ...
- BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化) -- 转贴自 wolfenstein (NeverSayNever)
BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化) author:wolfenstein Tracker在BT中是一个很重要的部分.这个名词我注意到以前的文章中都是直接引用,没 ...
- BT源代码学习心得(八):跟踪服务器(Tracker)的代码分析(用户请求的实际处理) - 转贴自 wolfenstein (NeverSayNever)
BT源代码学习心得(八):跟踪服务器(Tracker)的代码分析(用户请求的实际处理) author: wolfenstein 通过上一次的分析,我们已经知道了Tracker采用http协议和客户端通 ...
- BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化)
BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化) 发信人: wolfenstein (NeverSayNever), 个人文集 标 题: BT源代码学习心得(六):跟踪服务 ...
- 完整的IOCP模型 Echo服务器及代码分析
首先,先感谢http://www.cnblogs.com/talenth/p/7068392.html 这篇博文,作者写的通俗易懂,语言幽默,偶然一次在公交车上见到这篇博文相见恨晚,一口气读下来很长一 ...
- TeamTalk源码分析之login_server
login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是80 ...
- TeamTalk源码分析(二) —— 服务器端的程序的编译与部署
写在前面的话,如果您在部署teamtalk过程中遇到困难,可以关注我的微信公众号『easyserverdev』,在微信公众号中回复『teamtalk部署求助』,我将与你取得联系并协助您解决.或者您对高 ...
- TeamTalk源码分析(十一) —— pc客户端源码分析
--写在前面的话 在要不要写这篇文章的纠结中挣扎了好久,就我个人而已,我接触windows编程,已经六七个年头了,尤其是在我读研的三年内,基本心思都是花在学习和研究windows程序上 ...
- 使用Hadoop和ELK进行业务代码分析!分分钟捉到Bug!
大数据是计算领域的新高地,它有望提供一种方法来应对二十一世纪不断增长的数据生成.越来越多的大数据爱好者正在涌现,越来越多的公司正在采用各种大数据平台,并希望提出以客户为中心的解决方案,帮助他们在竞争激 ...
最新文章
- C语言实现将彩色bmp图像转化为灰图、灰度图像反色
- 满屋研选获1亿元B轮融资,华创资本领投,五岳资本、金地集团、治平资本等跟投...
- python多态_Python 简明教程 21,Python 继承与多态
- hmailserver php,hmailserver关于afterlogic webmail php版本下的用户密码修改功能
- acfun html5 转换,AcFun剧场模式插件
- zookeeper教程,docker 安装,命令,python操作zookeeper,分布式队列,分布式锁
- Android项目实战(三十四):蓝牙4.0 BLE 多设备连接
- 新品“鸿鹄”获2020世界VR产业大会创新金奖!
- SpringBoot统一返回result结果集
- 约瑟夫环问题:有n个人围成一圈,顺序编号。从第1个人开始报数(从1-3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位?
- odoo16外部api接口
- iOS 视频录制流程解析
- OS及Mac开源项目和学习资料【超级全面】
- 2种方式获取StreamingAssets下音频
- Git是什么?如何用?
- 短线看盘比较有效的方法
- 大数据建设意义_从宜信中台构架看企业大数据建设的意义
- 卷毛崽|Linux自学|Vim 编辑器与 Shell 命令脚本
- 史密斯圆图串并联口诀_串联并联口诀
- android 电池
热门文章
- 8个免费和最佳开源视频流服务器软件
- C++ Concurrency in Action, 2nd Edition 免积分下载
- Python Excel操作人口普查
- B2B电子邮件营销:下一个突破口在哪?
- 声卡可以利用计算机进行,您决定听到什么!使用软件实现声卡分流
- 百度地图开发入门(4):散点图示例
- php李炎恢笔记,李炎恢PHP笔记2
- 英汉汉英词典,牛津高级词典,电子词典,离线英汉,汉英词典的使用方法
- 厦门理工学院计算机毕业要求,计算机教学中心-厦门理工学院教务处.PDF
- php电影模板下载,【PHP源码】团啊VIP电影系统V3.7.6源码下载 带自动采集+手机版模板...