login_server是TeamTalk的登錄服務器,負責分配一個負載較小的MsgServer給客戶端使用,按照新版TeamTalk完整部署教程來配置的話,login_server的服務端口就是8080,客戶端登錄服務器地址配置如下(這里是win版本客戶端):

1、login_server啟動流程
login_server的啟動是從login_server.cpp中的main函數開始的,login_server.cpp所在工程路徑為server\src\login_server。下表是login_server啟動的大致流程匯總。
| signal(SIGPIPE, SIG_IGN) |
|
|
忽略SIGPIPE信號,向一端關閉的socket寫數據會觸發該信號 |
| CconfigFileReader:: CconfigFileReader() |
|
|
讀取login_server.conf配置文件 |
| netlib_init() |
|
|
初始化網絡連接,Linux下無操作 |
| netlib_listen(client_listen_ip和port) |
|
|
貌似是讓msg_server建立連接用的,不過不是很清楚 |
| netlib_listen(msg_server_listen_ip和port) |
|
|
監聽msg_server連接,msg_server會主動與login_server建立連接 |
| netlib_listen(http_listen_ip和port) |
|
|
監聽客戶端連接,客戶端首先會與login_server建立連接。listen端口時,會設置回調函數,並把相應事件添加到事件監聽器中 |
|
|
CBaseSocket::Listen() |
|
底層listen函數 |
| init_login_conn(); init_http_conn(); |
|
|
一些初始化操作,比如會添加定時事件 |
| netlib_eventloop |
|
|
進入事件循環(接收請求/發送響應) |
2、客戶端連接login_server流程
login_server在啟動之后就進入了事件循環中,即netlib_eventloop(),而netlib_eventloop()實際調用的是CEventDispatch::Instance()->StartDispatch(wait_timeout)(我們簡稱為事件分發器),事件分發器在Linux下使用epoll,會處理讀事件、寫事件、異常等事件,當客戶端建立連接時,相當於是讀事件。客戶端發送請求格式如下(ps:下圖為wireshark抓包結果,運行TeamTalk的主機IP是192.168.1.150):

在事件分發器中會處理讀事件,其他事件處理流程也大致類似,相應代碼如下(注意:以下代碼只是截取能夠說明流程那部分代碼,並不完整,...為省略部分):
1 void CEventDispatch::StartDispatch(uint32_t wait_timeout) 2 { 3 ... 4 while (running) 5 { 6 nfds = epoll_wait(m_epfd, events, 1024, wait_timeout); 7 for (int i = 0; i < nfds; i++) { 8 ... 9 if (events[i].events & EPOLLIN) { 10 pSocket->OnRead(); 11 } 12 _CheckTimer(); 13 _CheckLoop(); 14 } 15 ...
到達OnRead()時,流程如下:
1 void CBaseSocket::OnRead() 2 { 3 if (m_state == SOCKET_STATE_LISTENING) { 4 _AcceptNewSocket(); 5 } 6 else { 7 u_long avail = 0; 8 // 得到緩沖區中有多少個字節要被讀取,然后將字節數放入b里面。 9 if ( (ioctlsocket(m_socket, FIONREAD, &avail) == SOCKET_ERROR) || (avail == 0) ) { 10 m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL); 11 } 12 else { 13 m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL); 14 } 15 } 16 }
1 // 接收一個新連接 2 void CBaseSocket::_AcceptNewSocket() 3 { 4 ... 5 // accept為非阻塞的,所以這里可以用while()循環 6 while ( (fd = accept(m_socket, (sockaddr*)&peer_addr, &addr_len)) != INVALID_SOCKET ) { 7 CBaseSocket* pSocket = new CBaseSocket(); 8 ... 9 pSocket->SetSocket(fd); 10 pSocket->SetCallback(m_callback); 11 pSocket->SetCallbackData(m_callback_data); 12 pSocket->SetState(SOCKET_STATE_CONNECTED); // 設置m_state狀態為建立連接 13 pSocket->SetRemoteIP(ip_str); 14 pSocket->SetRemotePort(port); 15 16 _SetNoDelay(fd); 17 _SetNonblock(fd); 18 AddBaseSocket(pSocket); 19 CEventDispatch::Instance()->AddEvent(fd, SOCKET_READ | SOCKET_EXCEP); 20 // 這里會調用回調函數 21 m_callback(m_callback_data, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL); 22 } 23 }
最后到達啟動流程中在監聽http_listen中設置的回調函數,流程如下:
1 void http_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam) 2 { 3 if (msg == NETLIB_MSG_CONNECT) { 4 CHttpConn* pConn = new CHttpConn(); 5 pConn->OnConnect(handle); 6 } 7 ...
1 // 建立連接成功后讀取數據 2 void CHttpConn::OnConnect(net_handle_t handle) 3 { 4 m_sock_handle = handle; 5 m_state = CONN_STATE_CONNECTED; 6 g_http_conn_map.insert(make_pair(m_conn_handle, this)); 7 8 // 這里重新設置回調函數為httpconn_callback 9 netlib_option(handle, NETLIB_OPT_SET_CALLBACK, (void*)httpconn_callback); 10 netlib_option(handle, NETLIB_OPT_SET_CALLBACK_DATA, reinterpret_cast<void *>(m_conn_handle) ); 11 netlib_option(handle, NETLIB_OPT_GET_REMOTE_IP, (void*)&m_peer_ip); 12 }
當讀取客戶端發送上來的數據時,會到達事件監聽函數並且是讀事件,這樣會到達CBaseSocket::OnRead()中,然后就會調用設置好的回調函數,即httpconn_callback()函數中,最后調用OnRead()中,其流程如下:
1 void httpconn_callback(void* callback_data, uint8_t msg, uint32_t handle, uint32_t uParam, void* pParam) 2 { 3 // convert void* to uint32_t, oops 4 uint32_t conn_handle = *((uint32_t*)(&callback_data)); 5 CHttpConn* pConn = FindHttpConnByHandle(conn_handle); 6 if (!pConn) { 7 return; 8 } 9 10 switch (msg) { 11 case NETLIB_MSG_READ: 12 pConn->OnRead(); 13 break; 14 case NETLIB_MSG_WRITE: 15 pConn->OnWrite(); 16 break; 17 case NETLIB_MSG_CLOSE: 18 pConn->OnClose(); 19 break; 20 ...
流程走到CHttpConn::OnRead(),表示login_server准備讀取客戶端發送的http數據了,這個代碼比較多,就不復制了,簡單說一下流程:
- 調用netlib_recv()接收客戶端發送的請求,請求長度不能超過1024字節
- 解析http數據信息,解析請求行、請求頭、請求體(此次客戶端請求無請求體)
- 如果url為"/msg_server"則調用_HandleMsgServRequest(url, content);繼續處理,否則關閉連接
- 在_HandleMsgServRequest()中會選擇一個msg_server來,並把該msg_server信息作為應答體發送回客戶端,這樣客戶端就用收到的msg_server信息建立新的連接。注意:應答體格式為json格式的。
響應客戶端的json數據格式如下:
1 { 2 "backupIP" : "192.168.1.150", 3 "code" : 0, 4 "discovery" : "http://192.168.1.150/api/discovery", 5 "msfsBackup" : "http://192.168.1.150:8700/", 6 "msfsPrior" : "http://192.168.1.150:8700/", 7 "msg" : "", 8 "port" : "8000", 9 "priorIP" : "192.168.1.150" 10 }
3、小結
OK,到這里login_server已經啟動完成並且開始工作了(進入事件循環),login_server只是TeamTalk中一個小的模塊,它只負責等待客戶端的連接,服務端口是8080,如果客戶端發送數據格式正確,則分配一個負載相對較小的msg_server給客戶端,它相當於是客戶端與msg_server之間的連接模塊。msg_server才是TeamTalk的核心模塊,這個等到后續博客在分析...
TeamTalk底層網絡庫是自己實現的,相應源碼在server\src\base下的netlib.h和netlib.cpp中。
博客中難免會有錯誤或者不恰當的地方,懇請讀者批評指正。
