• TeamTalk源码分析之login_server


      login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端):

    1、login_server启动流程

      login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为serversrclogin_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数据了,这个代码比较多,就不复制了,简单说一下流程:

    1. 调用netlib_recv()接收客户端发送的请求,请求长度不能超过1024字节
    2. 解析http数据信息,解析请求行、请求头、请求体(此次客户端请求无请求体)
    3. 如果url为"/msg_server"则调用_HandleMsgServRequest(url, content);继续处理,否则关闭连接
    4. 在_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底层网络库是自己实现的,相应源码在serversrcase下的netlib.h和netlib.cpp中。

      博客中难免会有错误或者不恰当的地方,恳请读者批评指正。

    参考

      1、新版TeamTalk完整部署教程

      2、TeamTalk源码分析之服务端描述

  • 相关阅读:
    Spring DI模式 小样例
    java中经常使用的日期格式化(全)
    循环-15. 统计素数并求和(20)
    [Unity3D]Unity3D游戏开发之自己主动寻路与Mecanim动画系统的结合
    【UVA】11732
    Mac OS使用技巧之十六:系统失去响应怎么办?
    C# 保存窗口为图片(保存纵断面图)
    Linux Resin 安装
    Etcd学习(二)集群搭建Clustering
    android中选择控件与选择界面自然过度效果的实现--一种新的交互设计
  • 原文地址:https://www.cnblogs.com/luoxn28/p/5351020.html
Copyright © 2020-2023  润新知