本文主要分析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进行数据读写。