03数据包在各个服务器之间的流动

参考:https://balloonwj.blog.csdn.net/article/details/71518551

下面我们以pc端登录为例来具体看一个数据包在服务器端各个服务之间走过的流程:

1、login_server初始化侦听socket

设置新连接客户端到来的回调函数,8080端口,该端口是为http服务配置的(这个怎么理解?就是说这是用于监听用户客户端的,像PC端这种)

HttpListenIP=0.0.0.0
HttpPort=8080
//在8080上监听客户端的连接,用户连接进来的(采用的是http协议,http连接)
CStrExplode http_listen_ip_list(http_listen_ip, ';'); 
for (uint32_t i = 0; i < http_listen_ip_list.GetItemCnt(); i++) 
{
    //http_callback():当监听到有一个新客户端连接进来的读事件时,就会回调这个函数
    ret = netlib_listen(http_listen_ip_list.GetItem(i), http_port, http_callback, NULL);
    if (ret == NETLIB_ERROR)
        return ret;
}

(1)netlib_listen()

netlib_listen()调用如下:

int netlib_listen(const char* server_ip,uint16_t port,callback_t callback,void* callback_data)
{
    // CBaseSocket类的实例化,对于监听套接字是CBaseSocket类,是全部的父类类型
    CBaseSocket* pSocket = new CBaseSocket();
    if (!pSocket) return NETLIB_ERROR;
    // CBaseSocket类对象的监听函数
    int ret =  pSocket->Listen(server_ip, port, callback, callback_data);//启动监听
    if (ret == NETLIB_ERROR)
        delete pSocket;
    return ret;
}

(2)pSocket->Listen()

pSocket->Listen()调用:

int CBaseSocket::Listen(const char* server_ip, uint16_t port, callback_t callback, void* callback_data)
{
    m_local_ip = server_ip;
    m_local_port = port;
    m_callback = callback; // 监听的回调函数
    m_callback_data = callback_data;

    m_socket = socket(AF_INET, SOCK_STREAM, 0);// 创建 IPV4,字节流 套接字
    if (m_socket == INVALID_SOCKET)
    {
        log_error("socket failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, port);
        return NETLIB_ERROR;
    }
    _SetReuseAddr(m_socket);//将套接字设置复用
    _SetNonblock(m_socket);//将套接字设置非阻塞
    // 将ip和端口装载到sockaddr_in中
    sockaddr_in serv_addr;
    _SetAddr(server_ip, port, &serv_addr);
    //将套接字和ip端口绑定在一起    “::”表示引用的是全局的函数
    int ret = ::bind(m_socket, (sockaddr*)&serv_addr, sizeof(serv_addr));
    if (ret == SOCKET_ERROR)
    {
        log_error("bind failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, port);
        closesocket(m_socket);
        return NETLIB_ERROR;
    }
    //操作系统的监听套接字
    ret = listen(m_socket, 64);
    if (ret == SOCKET_ERROR)
    {
        log_error("listen failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, port);
            closesocket(m_socket);
        return NETLIB_ERROR;
    }
    //将CBaseSocket对象的状态m_state设置成SOCKET_STATE_LISTENING
    m_state = SOCKET_STATE_LISTENING;//处于监听状态
    log_debug("CBaseSocket::Listen on %s:%d", server_ip, port);
    //把该套接字加入到map容器g_socket_map中
    AddBaseSocket(this);
    // 往epoll添加事件,监听读事件和异常事件
    CEventDispatch::Instance()->AddEvent(m_socket, SOCKET_READ | SOCKET_EXCEP);
    return NETLIB_OK;
}

AddBaseSocket将该socket加入hash_map中。AddEvent()设置需要关注的socket上的事件,这里只关注可读和出错事件。

2、客户端调用connect()函数连接login服务器

客户端调用connect()函数连接login_server的8080端口。(客户端的代码还没有开始挖掘,暂时我们先理解成connect()了)

3、login_server收到连接请求

(1)_AcceptNewSocket()

login_server收到连接请求后调用OnRead方法,OnRead()方法里面调用_AcceptNewSocket(),_AcceptNewSocket()接收新连接,创建新的socket,并调用之前初始化阶段netlib_listen设置的回调函数http_callback。

//新用户接进来时,生成一个客户端套接字
void CBaseSocket::_AcceptNewSocket()
{
    SOCKET fd = 0; // 保存客户端套接字
    sockaddr_in peer_addr;
    socklen_t addr_len = sizeof(sockaddr_in);
    char ip_str[64];
    //accept()函数
    while ( (fd = accept(m_socket, (sockaddr*)&peer_addr, &addr_len)) != INVALID_SOCKET )
    {
        //实例化一个CBaseSocket对象,用于将客户端套接字和这个CBaseSocket对象联系起来
        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_debug("AcceptNewSocket, socket=%d from %s:%d\n", fd, ip_str, port);
        //记录fd,m_callback,m_callback_data,SOCKET_STATE_CONNECTED,ip_str,port
        pSocket->SetSocket(fd);
        //pSocket是客户端套接字的,对他加的回调函数是属于原来监听套接字的回调函数,可以加吗?
        //在我看来这里在之后会被覆盖这个回调函数,其实这里可以不用设置这个回调函数。
        pSocket->SetCallback(m_callback);//之后会被覆盖的***
        pSocket->SetCallbackData(m_callback_data);//之后会被覆盖的***
        pSocket->SetState(SOCKET_STATE_CONNECTED);
        //记录客户端(远程)的ip和端口
        pSocket->SetRemoteIP(ip_str);
        pSocket->SetRemotePort(port);
        //设置非延时
        _SetNoDelay(fd);
        //设置非阻塞
        _SetNonblock(fd);
        //加入到套接字和BaseSocket的map容器
        AddBaseSocket(pSocket);
        //往epoll树中添加节点,客户端连接(套接字)加入epoll中,监听客户端的读事件和异常事件
        CEventDispatch::Instance()->AddEvent(fd, SOCKET_READ | SOCKET_EXCEP);
        //对于login_server而言,监听到一个新用户进来了,调用的是这个http_callback()
        m_callback(m_callback_data, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL);
    }
}

(2)http_callback()

accept()的动作已经完成,新客户端连接进来的读事件时,就会回调这个函数。

// Android、IOS、PC等客户端请求连接事件 (属于新连接进来触发的事件)
// 调用者 m_callback(m_callback_data, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL);
void http_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
    //NETLIB_MSG_CONNECT:我觉得使用这个是为了保证代码不出错误,类似于双重保障这种
    if (msg == NETLIB_MSG_CONNECT)
    {
        // 这里是不是觉得很奇怪,为什么new了对象却没有释放?
        // 实际上对象在被Close时使用delete this的方式释放自己
        // 创建http连接对象,CHttpConn类,作为login_server与用户客户端的连接
        CHttpConn* pConn = new CHttpConn();
        //启动连接
        //handle:客户端套接字fd
        pConn->OnConnect(handle);
    }
    else
    {
        log_error("!!!error msg: %d ", msg);
    }
}

(3)pConn->OnConnect(handle)

pConn->OnConnect(handle)中设置http数据的回调函数httpconn_callback:

//CHttpConn类的开启连接函数
void CHttpConn::OnConnect(net_handle_t handle)
{
    log("OnConnect, handle=%d\n", handle);
    m_sock_handle = handle;
    //设置已连接状态
    m_state = CONN_STATE_CONNECTED;
    //将该项连接插入g_http_conn_map的map容器中
    //PS:m_conn_handle和 handle 不一样,handle是fd套接字,m_conn_handle是关于连接的句柄
    g_http_conn_map.insert(make_pair(m_conn_handle, this));
    
    //对连接进行参数设置(回调函数,回调函数参数,ip地址)
    //httpconn_callback():http连接回调函数
    //什么时候会调用这个函数:
    //httpconn_callback(),当客户端来数据的时候(强调不是第一次新连接进来),有读事件来的时候就会调用
    //位置:
    //这里就是对于一个已连接的连接来读数据的时候,调用的位置 src/base/BaseSocket.cpp - void CBaseSocket::OnRead()
        //    m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL);
    // handle还是客户端套接字fd
    //为handle添加内容(回调函数,回调函数参数,ip地址)
    netlib_option(handle, NETLIB_OPT_SET_CALLBACK, (void*)httpconn_callback);//设置回调函数
    netlib_option(handle, NETLIB_OPT_SET_CALLBACK_DATA, reinterpret_cast<void *>(m_conn_handle) );//设置回调参数
    netlib_option(handle, NETLIB_OPT_GET_REMOTE_IP, (void*)&m_peer_ip);//获取远程的ip地址
}

对于维护两端设备连接的map

typedef hash_map<uint32_t, CHttpConn*> HttpConnMap_t;
static HttpConnMap_t g_http_conn_map;
//将该项连接插入g_http_conn_map的map容器中
g_http_conn_map.insert(make_pair(m_conn_handle, this));

(4)netlib_option()

netlib_option()网络库配置:

/**
 * @brief 为handle的pSocket对象配置选项信息
 * 
 * @param handle 套接字句柄
 * @param opt 选项类型
 * @param optval 选项参数值
 * @return int 
 */
int netlib_option(net_handle_t handle, int opt, void* optval)
{
    //根据key客户端套接字找到对应的CBaseSocket的val
    CBaseSocket* pSocket = FindBaseSocket(handle);
    if (!pSocket)
        return NETLIB_ERROR;
    if ((opt >= NETLIB_OPT_GET_REMOTE_IP) && !optval)
        return NETLIB_ERROR;
    //
    switch (opt)
    {
    case NETLIB_OPT_SET_CALLBACK:
        pSocket->SetCallback((callback_t)optval);    // 设置回调函数
        break;
    case NETLIB_OPT_SET_CALLBACK_DATA:
        pSocket->SetCallbackData(optval);    // 设置回调函数的参数
        break;
    case NETLIB_OPT_GET_REMOTE_IP:
        *(string*)optval = pSocket->GetRemoteIP(); // 获取远程IP
        break;
    case NETLIB_OPT_GET_REMOTE_PORT:
        *(uint16_t*)optval = pSocket->GetRemotePort(); // 获取远程端口
        break;
    case NETLIB_OPT_GET_LOCAL_IP:
        *(string*)optval = pSocket->GetLocalIP(); // 获取本地IP
        break;
    case NETLIB_OPT_GET_LOCAL_PORT:
        *(uint16_t*)optval = pSocket->GetLocalPort(); // 获取本地端口
        break;
    case NETLIB_OPT_SET_SEND_BUF_SIZE:
        pSocket->SetSendBufSize(*(uint32_t*)optval); // 设置发送缓冲区的尺寸
        break;
    case NETLIB_OPT_SET_RECV_BUF_SIZE:
        pSocket->SetRecvBufSize(*(uint32_t*)optval); // 设置接收缓冲区的尺寸
        break;
    }
    pSocket->ReleaseRef();       // 释放(引用)计数
    return NETLIB_OK;
}

(5)httpconn_callback()

httpconn_callback()中处理http可读可写出错事件:

//调用位置:void CBaseSocket::OnRead()==》
//回调调用:m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL);
//httpconn_callback():http连接回调函数
void httpconn_callback(void* callback_data, uint8_t msg, uint32_t handle, uint32_t uParam, void* pParam)
{
    //作用???,用于删除警告C4100,未引用参数
    //做'多此一举'的强制转换是为了消除编译器警告, 告诉编译器这些变量我用过了, 你就别给我发警告了.
        NOTUSED_ARG(uParam);
        NOTUSED_ARG(pParam);

        // convert void* to uint32_t, oops
    // 为什么这里是用callback_data来查conn_handle(非常的不理解???)没有毛病
    // 原来如此:
    // 看这里
    // netlib_option(handle, NETLIB_OPT_SET_CALLBACK_DATA, reinterpret_cast<void *>(m_conn_handle) )
    // conn_handle是一个连接句柄
    uint32_t conn_handle = *((uint32_t*)(&callback_data));
    //根据fd寻找CHttpConn,因为呀,在之前,
    //关于连接的句柄m_conn_handle和CHttpConn对象作为一对值插入到了g_http_conn_map容器中
    CHttpConn* pConn = FindHttpConnByHandle(conn_handle);
    if (!pConn) {
        return;
    }

    //根据msg类型找到相应的处理函数
    switch (msg)
    {
    //作为读处理
    case NETLIB_MSG_READ:
        pConn->OnRead();
        break;
    //作为写处理
    case NETLIB_MSG_WRITE:
        pConn->OnWrite();
        break;
    //作为关闭处理
    case NETLIB_MSG_CLOSE:
        pConn->OnClose();
        break;
    default:
        log_error("!!!httpconn_callback error msg: %d ", msg);
        break;
    }
}

根据fd句柄找HttpConn对象

//根据fd句柄找HttpConn对象
CHttpConn* FindHttpConnByHandle(uint32_t conn_handle)
{
    CHttpConn* pConn = NULL;
    HttpConnMap_t::iterator it = g_http_conn_map.find(conn_handle);
    if (it != g_http_conn_map.end()) {
        pConn = it->second;
    }

    return pConn;
}

(6)httpconn_callback和StartDispatch比较

PS:分析下比较两个极其相似的读、写、关闭:

  • void httpconn_callback():在void CHttpConn::OnConnect(net_handle_t handle)中
  • CEventDispatch::StartDispatch():在事件分发器中
1.一个客户端连接连入login服务器是先经过StartDispatch(),再经过httpconn_callback();
2.StartDispatch()的处理对象不仅可以是客户端连接套接字,还可以是监听套接字,而httpconn_callback的处理对象只能是客户端连接套接字;
3.void httpconn_callback()是针对客户端和login_server的CHttpConn这个连接而言的,要是针对其他的连接而言,那么处理函数还是有些出入的;
4.对于StartDispatch传给httpconn_callback之前,会给事件加上一些读写关闭的标记。

4、客户端连接成功后发送http请求

客户端连接成功以后,发送http请求,方法是get,请求url:http://192.168.226.128:8080/msg_server。(具体网址与你的机器配置的网址有关)
image
客户端发了什么样的数据包,之后再分析。

5、login_server检测到该socket可读

login_server检测到该socket可读,调用pConn->OnRead()方法。(PS:pConn是CHttpConn类,区别于CImConn类)

(1)CHttpConn::OnRead()

CHttpConn::OnRead()先用recv收取数据,接着解析数据,如果出错或者非法数据就关闭连接。

如果客户端发送的请求的http object正好是/msg_server,则调用_HandleMsgServRequest(url, content);进行处理。

//CHttpConn的读处理
void CHttpConn::OnRead()
{
    //猜测:因为我们加入epoll的事件是采用的ET模式,所以,读数据的时候必须要循环的读
    for (;;)
    {
        //计算空闲尺寸
        uint32_t free_buf_len = m_in_buf.GetAllocSize() - m_in_buf.GetWriteOffset();
        if (free_buf_len < READ_BUF_SIZE + 1)  // 剩余的空间小于2049时扩充空间到
            m_in_buf.Extend(READ_BUF_SIZE + 1);

        //网络库接收数据函数,在m_sock_handle的fd中读取数据,接收数据
        int ret = netlib_recv(m_sock_handle, m_in_buf.GetBuffer() + m_in_buf.GetWriteOffset(), READ_BUF_SIZE);
        if (ret <= 0)
            break;//这里表示读完了,退出for(;;)循环

        m_in_buf.IncWriteOffset(ret);//更新已写入位置

        m_last_recv_tick = get_tick_count(); //更新最近的接收信息的时间
    }
    //读出的数据读到 m_in_buf 中了

    // 每次请求对应一个HTTP连接,所以读完数据后,不用在同一个连接里面准备读取下个请求
    char* in_buf = (char*)m_in_buf.GetBuffer();
    uint32_t buf_len = m_in_buf.GetWriteOffset();
    in_buf[buf_len] = '\0';//在这里给字符数组加一个'\0',安全,直接截断字符数组

    // 如果buf_len 过长可能是受到攻击,则断开连接
    // 正常的url最大长度为2048,我们接受的所有数据长度不得大于1K
    if(buf_len > 1024)
    {
        log_error("get too much data:%s ", in_buf);
        Close();
        return;
    }

    //log("OnRead, buf_len=%u, conn_handle=%u\n", buf_len, m_conn_handle); // for debug

    //解析http内容
    //CHttpParserWrapper是teamtalk为http_parse实现的一个包裹类。
    m_cHttpParser.ParseHttpContent(in_buf, buf_len);

    if (m_cHttpParser.IsReadAll())                              //获取http消息体
    {
        string url =  m_cHttpParser.GetUrl();                   // 获取Url
        if (strncmp(url.c_str(), "/msg_server", 11) == 0)       // 路由判断
        {
            string content = m_cHttpParser.GetBodyContent();    // 获取http消息体

            _HandleMsgServRequest(url, content);    //请求一个负载最小的msg服务器来工作
        }
        else
        {
            log_error("url unknown, url=%s ", url.c_str());
            Close();
        }
    }
}

(2)_HandleMsgServRequest()

该方法login_server分配msg_server给客户端根据客户端ip地址将msg_server的地址组装成json格式,返回给客户端。

void CHttpConn::_HandleMsgServRequest(string& url, string& post_data)
{
    msg_serv_info_t* pMsgServInfo;
    uint32_t min_user_cnt = (uint32_t)-1;
    map<uint32_t, msg_serv_info_t*>::iterator it_min_conn = g_msg_serv_info.end();
    map<uint32_t, msg_serv_info_t*>::iterator it;
    log("url:%s, post_data:%s", url.c_str(), post_data.c_str());
    
    //这个是没有一个msg服务器
    //g_msg_serv_info是保存所有msg_server服务器的容器
    if(g_msg_serv_info.size() <= 0)	// 没有可用的msg_server
    {
        //json序列化的算法
        Json::Value value;
        value["code"] = 1;
        value["msg"] = "没有msg_server";
        string strContent = value.toStyledString();//将json类对象转换成string对象
        char* szContent = new char[HTTP_RESPONSE_HTML_MAX];//定义一个字符数组
        //snprintf()函数的解释:int snprintf ( char * str, size_t size, const char * format, ... );
        //szContent:目标字符串
        //HTTP_RESPONSE_HTML_MAX:拷贝字节数(Bytes)
        //format:格式化成字符串
        //...:可变参数。
        //链接:https://www.runoob.com/cprogramming/c-function-snprintf.html
        snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, strContent.length(), strContent.c_str());
        Send((void*)szContent, strlen(szContent));//发送字符串
        delete [] szContent;//new出来就要去delete
        return ;
    }

    // 查找负载最小的msg_server
    for (it = g_msg_serv_info.begin() ; it != g_msg_serv_info.end(); it++) 
    {
        pMsgServInfo = it->second;
        if ( (pMsgServInfo->cur_conn_cnt < pMsgServInfo->max_conn_cnt) &&
            (pMsgServInfo->cur_conn_cnt < min_user_cnt)) 
        {
            it_min_conn = it;
            min_user_cnt = pMsgServInfo->cur_conn_cnt;
        }
    }
    
    // MsgServer 满了
    if (it_min_conn == g_msg_serv_info.end()) {
        log("All TCP MsgServer are full ");
        //打包Json文件
        Json::Value value;
        value["code"] = 2;
        value["msg"] = "负载过高";
        string strContent = value.toStyledString();
        char* szContent = new char[HTTP_RESPONSE_HTML_MAX];
        snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, strContent.length(), strContent.c_str());
        //CHttpConn的发送数据
        Send((void*)szContent, strlen(szContent));
        delete [] szContent;
        return;
    } 
    else // 找到合适的msg_server
    {
        Json::Value value;
        value["code"] = 0;
        value["msg"] = "";
        //isTelcome():是否是电信网络
        //ip_addr1和ip_addr2的区别??
        if(pIpParser->isTelcome(GetPeerIP()))
        {
            value["priorIP"] = string(it_min_conn->second->ip_addr1);
            value["backupIP"] = string(it_min_conn->second->ip_addr2);
            value["msfsPrior"] = strMsfsUrl;//http://127.0.0.1:8700/
            value["msfsBackup"] = strMsfsUrl;
        }
        else
        {
            value["priorIP"] = string(it_min_conn->second->ip_addr2);
            value["backupIP"] = string(it_min_conn->second->ip_addr1);
            value["msfsPrior"] = strMsfsUrl;
            value["msfsBackup"] = strMsfsUrl;
        }
        value["discovery"] = strDiscovery;//http://127.0.0.1/api/discovery 作用?
        value["port"] = int2string(it_min_conn->second->port);
        string strContent = value.toStyledString();
        char* szContent = new char[HTTP_RESPONSE_HTML_MAX];
        uint32_t nLen = strContent.length();
        snprintf(szContent, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, nLen, strContent.c_str());
        //CHttpConn的发送数据
        Send((void*)szContent, strlen(szContent));
        delete [] szContent;
        return;
    }
}

在这里存在一个问题:通过登陆IP来优选电信还是联通IP,的区别是什么?

(3)Send()发送策略:

挖掘一下发送数据的逻辑过程,注意,发送数据给客户端调用的是Send方法,该方法会先尝试着调用底层的send()函数去发送,如果不能全部发送出去,则将剩余数据加入到对应的写数据缓冲区内。这样这些数据会在该socket可写时再继续发送。这是也是设计网络通信库一个通用的技巧,即先试着去send,如果send不了,将数据放入待发送缓冲区内,并设置检测可写标识位,当socket可写时,从待发送缓冲区取出数据发送出去。如果还是不能全部发送出去,继续设置检测可写标识位,下次再次发送,如此循环一直到所有数据都发送出去为止。(这里可以参照王健伟的网络编程课程)

//CHttpConn的发送数据
int CHttpConn::Send(void* data, int len)
{
    //更新最近发送数据的时间
    m_last_send_tick = get_tick_count();

    //m_busy表示:系统的发送缓冲区已经装满了,我们无法再先缓冲区添加数据,
    //那怎么办? 策略是把后面还没发送完的数据作为事件添加到epoll管理器中,
    //使用事件驱动的方式来发送剩余的数据
    //系统的epoll控制器会是不是的检测发送缓冲区是否可写
    if (m_busy)
    {
        m_out_buf.Write(data, len);
        return len;
    }

    /*
        发送数据给客户端调用的是Send方法,
        该方法会先尝试着调用底层的send()函数去发送,
        如果不能全部发送出去,则将剩余数据加入到对应的写数据缓冲区内。
        这样这些数据会在该socket可写时再继续发送
    */
    //网络库发送数据
    int ret = netlib_send(m_sock_handle, data, len);//============================(1)
    if (ret < 0)
        ret = 0;

    //上面的动作把发送缓冲区填满了,我们有必要把
    if (ret < len)
    {
        //Write()方法功能:
        //把未发送的数据,保存在CHttpConn连接的发送缓冲区的字符数组中
        m_out_buf.Write((char*)data + ret, len - ret);//============================(2)
        //标记系统的发送缓冲区装满了
        m_busy = true;
        //log("not send all, remain=%d\n", m_out_buf.GetWriteOffset());
    }
    else
    {
        OnWriteComlete();//写完成,直接关闭套接字
    }

    return len;
}
//(1)的位置
//网络库发送数据
int netlib_send(net_handle_t handle, void* buf, int len)
{
	CBaseSocket* pSocket = FindBaseSocket(handle);//根据套接字fd找到CBaseSocket对象
	if (!pSocket)
	{
		return NETLIB_ERROR;
	}
	//BaseSocket对象的发送数据动作
	int ret = pSocket->Send(buf, len);//调用CBaseSocket的方法======================(1.1)
	pSocket->ReleaseRef();
	return ret;
}
//(1.1)位置
//BaseSocket的发送
int CBaseSocket::Send(void* buf, int len)
{
	if (m_state != SOCKET_STATE_CONNECTED)
		return NETLIB_ERROR;
	//系统发送数据函数
	int ret = send(m_socket, (char*)buf, len, 0);
	if (ret == SOCKET_ERROR)
	{
		int err_code = _GetErrorCode();
		if (_IsBlock(err_code))
		{
#if ((defined _WIN32) || (defined __APPLE__))
			CEventDispatch::Instance()->AddEvent(m_socket, SOCKET_WRITE);
#endif
			ret = 0;
			//log("socket send block fd=%d", m_socket);
		}
		else
		{
			log_error("send failed, err_code=%d, len=%d", err_code, len);
		}
	}
	return ret;
}
//(2)位置
//往缓冲区数组写数据
uint32_t CSimpleBuffer::Write(void* buf, uint32_t len)
{
	if (m_write_offset + len > m_alloc_size)
	{
		Extend(len);
	}
	if (buf)
	{
		memcpy(m_buffer + m_write_offset, buf, len);
	}
	m_write_offset += len;
	return len;
}

(4)json格式内容如下

{
    "code" : 0,
    "msg" : "",
    "priorIP" : "localhost",
    "backupIP" : "localhost",
    "msfsPrior" : "http://127.0.0.1:8700/",
    "msfsBackup" : "http://127.0.0.1:8700/",
    "discovery" : "http://127.0.0.1/api/discovery",
    "port" : "8000",
}

3.6.6 用户客户端再连接msg_server服务器

客户端收到http请求的应答后,根据收到的json得到msg_server的ip地址,这里是ip地址是127.0.0.1(我的msg_server也是部署在这台电脑上),端口号是8000。客户端开始连接这个ip地址和端口号,连接过程与msg_server接收连接过程与上面的步骤相同

接着客户端给服务器发送登录数据包。

(1)msg_server服务器下客户端的连接

//msg_server的main()中,监听用户客户端(PC客户端)的连接。
//监听ip列表一个一个的取ip,并监听
CStrExplode listen_ip_list(listen_ip, ';');
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;
}

还是那句话,网络模型还是和login_server一样的网络模型。

(2)msg_serv_callback()

//本身作为服务器角色,新用户客户端连接进来,触发的回调函数
void msg_serv_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
	if (msg == NETLIB_MSG_CONNECT)
	{
		CMsgConn* pConn = new CMsgConn();
		//CMsgConn连接处理
		pConn->OnConnect(handle);
	}
	else
	{
		log("!!!error msg: %d ", msg);
	}
}

(3)CMsgConn::OnConnect(net_handle_t handle)

void CMsgConn::OnConnect(net_handle_t handle)
{
	m_handle = handle;
	m_login_time = get_tick_count();

	g_msg_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*)&g_msg_conn_map);//回调函数的参数是g_msg_conn_map
	netlib_option(handle, NETLIB_OPT_GET_REMOTE_IP, (void*)&m_peer_ip);
	netlib_option(handle, NETLIB_OPT_GET_REMOTE_PORT, (void*)&m_peer_port);
}

(4)imconn_callback()

//即时通信的连接回调函数
void imconn_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
	//作用:去警告
	NOTUSED_ARG(handle);
	NOTUSED_ARG(pParam);

	if (!callback_data)
		return;

	//回调数据,把连接的map容器取出
	ConnMap_t* conn_map = (ConnMap_t*)callback_data;
	//根据fd寻找CImConn,因为呀,在之前fd和CImConn作为一对值插入到了map容器中
	//这里有必要解释下:
	//callback_data的来源是g_msg_conn_map的指针
	//而g_msg_conn_map.insert(make_pair(handle, this));
	//this的对象可能有很多种,例如CMsgConn,
	//插入的时候是子类,取出的是父类,class CMsgConn : public CImConn
	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_error("!!!imconn_callback error msg: %d ", msg);
		break;
	}
	pConn->ReleaseRef();
}

3.6.7 msg_server在CImConn::OnRead()收取数据

msg_server收到登录请求后,在CImConn::OnRead()收取数据,解包,调用子类CMsgConn重写的HandlePdu,处理登录请求

(1)CImConn::OnRead()

有必要提一句:

这个服务器框架,对于读事件的处理很多都是走这个函数的,CImConn::OnRead(),有的不走这个像login_server,它有自己定义的读方法。

//CImConn的读处理
void CImConn::OnRead()
{
	//猜测:因为我们加入epoll的事件是采用的ET模式,所以,读数据的时候必须要循环的读
	for (;;)
	{
		uint32_t free_buf_len = m_in_buf.GetAllocSize() - m_in_buf.GetWriteOffset();
		if (free_buf_len < READ_BUF_SIZE)
			m_in_buf.Extend(READ_BUF_SIZE);
		log_debug("handle = %u, netlib_recv into, time = %u\n", m_handle, get_tick_count());
		//网络库接收数据函数
		int ret = netlib_recv(m_handle, m_in_buf.GetBuffer() + m_in_buf.GetWriteOffset(), READ_BUF_SIZE);
		if (ret <= 0)
			break;

		m_recv_bytes += ret;
		m_in_buf.IncWriteOffset(ret);

		//更新最近的接包的时间
		m_last_recv_tick = get_tick_count();
	}
    CImPdu* pPdu = NULL;
	//try-catch语句
	try
	{
		//CImPdu::ReadPdu(),功能:m_in_buf转到pPdu中,处理数据包时就用pPdu
		while ( ( pPdu = CImPdu::ReadPdu(m_in_buf.GetBuffer(), m_in_buf.GetWriteOffset()) ) )
		{
			uint32_t pdu_len = pPdu->GetLength();
			log_debug("handle = %u, pdu_len into = %u\n",m_handle, pdu_len);
			//处理数据(协议数据单元)
			//这个handlePdu()的调用具体要看是哪个子类对象调用的
			HandlePdu(pPdu);

			//Read()处理,作用???
			m_in_buf.Read(NULL, pdu_len);
			delete pPdu;
			pPdu = NULL;
//			++g_recv_pkt_cnt;
		}
	} catch (CPduException& ex) 
	{
		log_error("!!!catch exception, sid=%u, cid=%u, err_code=%u, err_msg=%s, close the connection ",
				ex.GetServiceId(), ex.GetCommandId(), ex.GetErrorCode(), ex.GetErrorMsg());
		if (pPdu) {
			delete pPdu;
			pPdu = NULL;
		}
		OnClose();
	}
}

CImPdu类:

//调用位置:CImPdu::ReadPdu(m_in_buf.GetBuffer(), m_in_buf.GetWriteOffset())
CImPdu* CImPdu::ReadPdu(uchar_t *buf, uint32_t len)
{
	uint32_t pdu_len = 0;
	//判断数据包的真实性
	if (!IsPduAvailable(buf, len, pdu_len))
		return NULL;

	//CByteStream类作用:字节流数据处理
	//实现了字节数组和uint16_t int6_t uint32_t int32_t等数字的相互转换。
	//比如:一个uint16_t数字转换成2个字节表示的uint8_t数组
	//CByteStream内部提供了许多static方法,方便外部直接使用
	uint16_t service_id = CByteStream::ReadUint16(buf + 8);//取出service_id
	uint16_t command_id = CByteStream::ReadUint16(buf + 10);//取出command_id
	CImPdu* pPdu = NULL;

	pPdu = new CImPdu();//
	//pPdu->_SetIncomingLen(pdu_len);
	//pPdu->_SetIncomingBuf(buf);
	pPdu->Write(buf, pdu_len);
	pPdu->ReadPduHeader(buf, IM_PDU_HEADER_LEN);//====

	return pPdu;
}
//解包头
//读取数据包的包头
int CImPdu::ReadPduHeader(uchar_t* buf, uint32_t len)
{
	int ret = -1;
	if (len >= IM_PDU_HEADER_LEN && buf) 
	{
		CByteStream is(buf, len);

		//对象is是CByteStream,一个一个的取数据,内部有m_len和m_pos来维护我们读到哪里了
		is >> m_pdu_header.length;		//pdu的长度,4个字节
		is >> m_pdu_header.version;		//pdu的版本数,2个字节
		is >> m_pdu_header.flag;		//2个字节
		is >> m_pdu_header.service_id;	//2个字节
		is >> m_pdu_header.command_id;	//2个字节
		is >> m_pdu_header.seq_num;		//包序号,2个字节
		is >> m_pdu_header.reversed;	//2个字节
										//16个字节,包头长度
		ret = 0;
	}
	return ret;
}

(2)HandlePdu()的业务处理

调用子类CMsgConn重写的HandlePdu,处理登录请求,如何处理呢?

处理如下:

//msg_server作为服务器处理客户端发来的数据包
void CMsgConn::HandlePdu(CImPdu* pPdu)
{
    if( pPdu->GetCommandId()!= CID_OTHER_HEARTBEAT)log("HandlePdu cmd:0x%04x\n", pPdu->GetCommandId());// 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_LOGIN_REQ_REGIST:
            _HandleRegistRequest(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;
    }
}

(3)登录请求的处理函数

分支case CID_LOGIN_REQ_USERLOGIN即处理登录请求处理函数为_HandleLoginRequest(pPdu)

// process: send validate request to db server
// 向db服务器发送验证请求
void CMsgConn::_HandleLoginRequest(CImPdu* pPdu)
{
    // refuse second validate request 拒绝第二次验证请求
    if (m_login_name.length() != 0) 
    {
        log("duplicate LoginRequest in the same conn ");//不可以重复登录
        return;
    }

    // check if all server connection are OK 检查所有服务器连接是否正常
    uint32_t result = 0;
    string result_string = "";
    CDBServConn* pDbConn = get_db_serv_conn_for_login();//============================(2)
    if (!pDbConn) 
    {
        result = IM::BaseDefine::REFUSE_REASON_NO_DB_SERVER;//拒绝原因无DB服务器
        result_string = "服务端异常";
    }
    else if (!is_login_server_available()) 
    {
        result = IM::BaseDefine::REFUSE_REASON_NO_LOGIN_SERVER;//拒绝原因无LOGIN_SERVER
        result_string = "服务端异常";
    }
    else if (!is_route_server_available()) 
    {
        result = IM::BaseDefine::REFUSE_REASON_NO_ROUTE_SERVER;//拒绝原因无ROUTE_SERVER
        result_string = "服务端异常";
    }

    //result不为0是被各种原因拒绝了,直接返回”连接不到DB服务器“的信息给客户端
    if (result) 
    {
        IM::Login::IMLoginRes msg;//登录请求
        msg.set_server_time(time(NULL));//捕获下当前的时间
        msg.set_result_code((IM::BaseDefine::ResultType)result);
        msg.set_result_string(result_string);

        CImPdu pdu;//创建一个pdu数据包
        pdu.SetPBMsg(&msg);//
        pdu.SetServiceId(SID_LOGIN);//服务id号
        pdu.SetCommandId(CID_LOGIN_RES_USERLOGIN);  //命令id 如果db_proxy_server没有启动
        pdu.SetSeqNum(pPdu->GetSeqNum());   //序号,实际用途
        SendPdu(&pdu);  //发送数据包
        Close();    // 关闭 CMsgConn* pConn = new CMsgConn(); 什么时候释放资源???
        return;
    }
    //IM::Login::IMLoginReq登录请求结构体
    IM::Login::IMLoginReq msg;
    //宏定义CHECK_PB_PARSE_MSG检查pb解析的信息是否OK, ParseFromArray解析pdu的包体
    CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
    //假如是汉字,则转成拼音
    m_login_name = msg.user_name();
    string password = msg.password();
    uint32_t online_status = msg.online_status();
    if (online_status < IM::BaseDefine::USER_STATUS_ONLINE || online_status > IM::BaseDefine::USER_STATUS_LEAVE) 
    {
        log("HandleLoginReq, online status wrong: %u ", online_status);
        online_status = IM::BaseDefine::USER_STATUS_ONLINE;
    }
    m_client_version = msg.client_version();
    m_client_type = msg.client_type();
    m_online_status = online_status;
    log("HandleLoginReq, user_name=%s, status=%u, client_type=%u, client=%s, ",
        m_login_name.c_str(), online_status, m_client_type, m_client_version.c_str());
    CImUser* pImUser = CImUserManager::GetInstance()->GetImUserByLoginName(GetLoginName());
    if (!pImUser) // 只允许一个user存在,允许多个端同时登陆
    {
        pImUser = new CImUser(GetLoginName());      // 新建一个用户
        CImUserManager::GetInstance()->AddImUserByLoginName(GetLoginName(), pImUser);
    }
    pImUser->AddUnValidateMsgConn(this);        //未验证的msgconn
    // attach_data 这个对象作用是什么?他绑定了m_handle,实际是fd
    //附加数据
    CDbAttachData attach_data(ATTACH_TYPE_HANDLE, m_handle, 0);
    // continue to validate if the user is OK 继续验证用户是否OK

    //IMValidateReq:验证数据请求
    IM::Server::IMValidateReq msg2;
    msg2.set_user_name(msg.user_name());
    msg2.set_password(password);
    msg2.set_attach_data(attach_data.GetBuffer(), attach_data.GetLength());
    CImPdu pdu;
    pdu.SetPBMsg(&msg2);
    pdu.SetServiceId(SID_OTHER);
    pdu.SetCommandId(CID_OTHER_VALIDATE_REQ);   // 请求验证
    pdu.SetSeqNum(pPdu->GetSeqNum());
    pDbConn->SendPdu(&pdu);//把验证的数据发送DB_server了===================(1)
}

(4)SendPdu(CImPdu* pPdu)处理

//(1)位置
int SendPdu(CImPdu* pPdu) { return Send(pPdu->GetBuffer(), pPdu->GetLength()); }
//
//CImConn类的发送函数
int CImConn::Send(void* data, int len)
{
	m_last_send_tick = get_tick_count();//最近发送时间
//	++g_send_pkt_cnt;

	if (m_busy)//有忙碌标记
	{
		m_out_buf.Write(data, len);
		return len;
	}

	int offset = 0;
	int remain = len;
	while (remain > 0) 
	{
		int send_size = remain;
		if (send_size > NETLIB_MAX_SOCKET_BUF_SIZE) //对长度进行限制
		{
			send_size = NETLIB_MAX_SOCKET_BUF_SIZE;
		}

		int ret = netlib_send(m_handle, (char*)data + offset , send_size); //发送
		if (ret <= 0) {
			ret = 0;
			break;
		}

		offset += ret;
		remain -= ret;
	}

	if (remain > 0)//这里是说还有数据没有发送出去
	{
		m_out_buf.Write((char*)data + offset, remain);//写入连接的发送缓冲区中====在前面的流程中有类似的代码段解释
		m_busy = true;
		log_debug("send busy, remain=%d ", m_out_buf.GetWriteOffset());
	}
	else
	{
		OnWriteCompelete();//为空内容,这里没有想login_server那样直接断开连接
	}

	return len;
}

(5)get_db_serv_conn_for_login()

关于在登录请求处理的函数中,需要在与数据库db服务器的连接中获取一个实例

//为了登录而获取一个db服务器连接
CDBServConn* get_db_serv_conn_for_login()
{
	// 先获取login业务的实例,没有就去获取其他业务流程的实例
	CDBServConn* pDBConn = get_db_server_conn_in_range(0, g_db_server_login_count);
	if (!pDBConn) 
	{
		pDBConn = get_db_server_conn_in_range(g_db_server_login_count, g_db_server_count);
	}
	return pDBConn;
}
// get a random db server connection in the range [start_pos, stop_pos)
// 获取范围[start_pos,stop_pos]内的随机db服务器连接
static CDBServConn* get_db_server_conn_in_range(uint32_t start_pos, uint32_t stop_pos)
{
	uint32_t i = 0;
	CDBServConn* pDbConn = NULL;

	// determine if there is a valid DB server connection
	// 确定是否存在有效的DB服务器连接
	for (i = start_pos; i < stop_pos; i++) 
	{
		pDbConn = (CDBServConn*)g_db_server_list[i].serv_conn;
		if (pDbConn && pDbConn->IsOpen()) 
		{
			break;
		}
	}

	// no valid DB server connection
	// 没有有效的DB服务器连接
	if (i == stop_pos) {
		return NULL;
	}

	// return a random valid DB server connection
	// 返回一个随机的连接
	while (true) 
	{
		int i = rand() % (stop_pos - start_pos) + start_pos;
		pDbConn = (CDBServConn*)g_db_server_list[i].serv_conn;
		if (pDbConn && pDbConn->IsOpen()) 
		{
			break;
		}
	}
	return pDbConn;
}

这段代码大致流程是先获取一个msg_server与后端db_proxy_server的一个连接实例,保存msg_server与db_proxy_server连接实例的是一个数组结构,

保存在一个全局静态对象中static serv_info_t* g_db_server_list = NULL; 这些连接对象在msg_server初始化时建立,在其main函数中。

(6)关于这个g_db_server_list全局对象

//在main()中,有一段关于连接db服务器的代码
//---------------------------------------------------------------------
// 到BusinessServer(DB数据库)的开多个并发的连接
uint32_t concurrent_db_conn_cnt = DEFAULT_CONCURRENT_DB_CONN_CNT;//DEFAULT_CONCURRENT_DB_CONN_CNT=10
uint32_t db_server_count2 = db_server_count * DEFAULT_CONCURRENT_DB_CONN_CNT;//计数
//上面是默认使用多少个连接,下面是在配置文件中有配置项时的值
char* concurrent_db_conn = config_file.GetConfigName("ConcurrentDBConnCnt");
if (concurrent_db_conn) 
{
	concurrent_db_conn_cnt  = atoi(concurrent_db_conn);
	//db_server_count是db服务器的数量,concurrent_db_conn_cnt为并发数据库连接
	db_server_count2 = db_server_count * concurrent_db_conn_cnt;			// db_server_count2 = 4 个
}

//服务器消息
serv_info_t* db_server_list2 = new serv_info_t [ db_server_count2];
//往db_server_list2列表中装服务器的端口和地址
//db_server_list2和db_server_list的区别
//db_server_list要是2个,那么db_server_list2就是4个(×2),一个db_server有2个连接,这样的
for (uint32_t i = 0; i < db_server_count2; i++) 
{
	db_server_list2[i].server_ip = db_server_list[i / concurrent_db_conn_cnt].server_ip.c_str();
	db_server_list2[i].server_port = db_server_list[i / concurrent_db_conn_cnt].server_port;
}
//-------------------------------------------------------------------------
//下面还有关于连接db_server的初始化
//初始化与数据库服务器的连接
init_db_serv_conn(db_server_list2, db_server_count2, concurrent_db_conn_cnt);
//-------------------------------------------------------------------------
//初始化与数据库服务器的连接
void init_db_serv_conn(serv_info_t* server_list, uint32_t server_count, uint32_t concur_conn_cnt)
{
	g_db_server_list = server_list;
	g_db_server_count = server_count;

	uint32_t total_db_instance = server_count / concur_conn_cnt;//这个表示db_server的数量
	g_db_server_login_count = (total_db_instance / 2) * concur_conn_cnt;//进行登录处理的DBServer的总连接数
	//用于登录业务的连接有[0,g_db_server_login_count),用于登录连接的有[0,g_db_server_count)
	log("DB server connection index for login business: [0, %u), for other business: [%u, %u) ",
			g_db_server_login_count, g_db_server_login_count, g_db_server_count);

	//模板函数的使用
	serv_init<CDBServConn>(g_db_server_list, g_db_server_count);//============================g_db_server_list

	//数据服务器连接的的定时器回调函数db_server_conn_timer_callback()
	netlib_register_timer(db_server_conn_timer_callback, NULL, 1000);
	//群组聊天单例类和文件处理类的生成
	s_group_chat = CGroupChat::GetInstance();
	s_file_handler = CFileHandler::getInstance();
}

(7)服务器初始化的serv_init<T>()

这个初始化的工作是:

该端(这里指msg_server服务器)作为客户端去连接其他服务器的连接初始化

//服务器初始化
//
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();
		//相当于msg_server作为客户端去连接其他服务器
		pConn->Connect(server_list[i].server_ip.c_str(), server_list[i].server_port, i);//Connect()函数的调用依赖于模板参数T
		server_list[i].serv_conn = pConn;
		server_list[i].idle_cnt = 0;
		server_list[i].reconnect_cnt = MIN_RECONNECT_CNT / 2;
	}
}

(8)依赖于模板参数T的Connect()函数

// msg_server作为客户端去连接DB_server服务器的连接函数
void CDBServConn::Connect(const char* server_ip, uint16_t server_port, uint32_t serv_idx)
{
	log("Connecting to DB Storage Server %s:%d ", server_ip, server_port);

	m_serv_idx = serv_idx;
	m_handle = netlib_connect(server_ip, server_port, imconn_callback, (void*)&g_db_server_conn_map);//

	if (m_handle != NETLIB_INVALID_HANDLE) {
		g_db_server_conn_map.insert(make_pair(m_handle, this));
	}
}
//---------------------------------------------------------------
//前端(客户端)连接后端(服务器)
net_handle_t netlib_connect(const char* server_ip, uint16_t	port,callback_t callback, void* callback_data)
{
	CBaseSocket* pSocket = new CBaseSocket();
	if (!pSocket)
		return NETLIB_INVALID_HANDLE;
    //解释一波:
    //第三个参数是回调函数 imconn_callback() 
    // (1)接收服务器发来数据时 (2)客户端发送数据给服务器 (3)关闭的情况 等等
	net_handle_t handle = pSocket->Connect(server_ip, port, callback, callback_data);
	if (handle == NETLIB_INVALID_HANDLE)
		delete pSocket;
	return handle;
}
//--------------------------------------------------------------
//基础套接字的connect连接函数
net_handle_t CBaseSocket::Connect(const char* server_ip, uint16_t port, callback_t callback, void* callback_data)
{
	log_debug("CBaseSocket::Connect, server_ip=%s, port=%d", server_ip, port);
	m_remote_ip = server_ip;
	m_remote_port = port;
	m_callback = callback;//回调函数
	m_callback_data = callback_data;//回调函数的参数
	m_socket = socket(AF_INET, SOCK_STREAM, 0);//fd

	if (m_socket == INVALID_SOCKET)
	{
		log_error("socket failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, port);
		return NETLIB_INVALID_HANDLE;
	}
	_SetNonblock(m_socket);
	_SetNoDelay(m_socket);
	sockaddr_in serv_addr;
	_SetAddr(server_ip, port, &serv_addr);
	//底层系统调用的连接函数
	int ret = connect(m_socket, (sockaddr*)&serv_addr, sizeof(serv_addr));
	if ( (ret == SOCKET_ERROR) && (!_IsBlock(_GetErrorCode())) )
	{
		log_error("connect failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, port);
		closesocket(m_socket);
		return NETLIB_INVALID_HANDLE;
	}
	m_state = SOCKET_STATE_CONNECTING;
	//添加到map<fd,基础套接字类CBaseSocket>
	AddBaseSocket(this);
	//添加事件
	CEventDispatch::Instance()->AddEvent(m_socket, SOCKET_ALL);
	return (net_handle_t)m_socket;
}

(9)登录信息装包发给db_proxy_server

回到登录流程上来继续说,与db_proxy_server的连接实例获得以后,将登录信息装包发给db_proxy_server:

void CMsgConn::_HandleLoginRequest(CImPdu* pPdu)
{
    // ...........................
    CImPdu pdu;
    pdu.SetPBMsg(&msg2);
    pdu.SetServiceId(SID_OTHER);
    pdu.SetCommandId(CID_OTHER_VALIDATE_REQ);   // 请求验证
    pdu.SetSeqNum(pPdu->GetSeqNum());
    pDbConn->SendPdu(&pdu);//把验证的数据发送DB_server了
}

3.6.8 登录验证的请求到达db_proxy_server

接下来就是db_proxy_server的事情了,上面登录的请求命令号是CID_OTHER_VALIDATE_REQ(牢记)。

(1)db_proxy_server的连接与监听

db_proxy_server作为服务器监听的是msg_server客户端的消息

//---------------------------------------------------
for (uint32_t i = 0; i < listen_ip_list.GetItemCnt(); i++) 
{
	ret = netlib_listen(listen_ip_list.GetItem(i), listen_port, proxy_serv_callback, NULL);//===========监听到连接进来
	if (ret == NETLIB_ERROR)
		return ret;
}
//----------------------------------------------------
void proxy_serv_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam)
{
	if (msg == NETLIB_MSG_CONNECT)
	{
		CProxyConn* pConn = new CProxyConn();
		pConn->OnConnect(handle);
	}
	else
	{
		log("!!!error msg: %d", msg);
	}
}
//----------------------------------------------------
void CProxyConn::OnConnect(net_handle_t handle)
{
	m_handle = handle;

	g_proxy_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*)&g_proxy_conn_map);
	netlib_option(handle, NETLIB_OPT_GET_REMOTE_IP, (void*)&m_peer_ip);
	netlib_option(handle, NETLIB_OPT_GET_REMOTE_PORT, (void*)&m_peer_port);

	log("connect from %s:%d, handle=%d", m_peer_ip.c_str(), m_peer_port, m_handle);
}

在循环事件监听过程中,当网络io事件过来的时候(客户端进行通信),调用的是imconn_callback()

(2)触发CProxyConn::OnRead()

imconn_callback()函数中读事件触发的是ProxyConn::OnRead(),与许多子类对象的OnRead()方法类似,查看前面类似的解释。

// 由于数据包是在另一个线程处理的,所以不能在主线程delete数据包,所以需要Override这个方法
void CProxyConn::OnRead()
{
	for (;;) 
	{
		uint32_t free_buf_len = m_in_buf.GetAllocSize() - m_in_buf.GetWriteOffset();
		if (free_buf_len < READ_BUF_SIZE)
			m_in_buf.Extend(READ_BUF_SIZE);

		int ret = netlib_recv(m_handle, m_in_buf.GetBuffer() + m_in_buf.GetWriteOffset(), READ_BUF_SIZE);
		if (ret <= 0)
			break;

		m_recv_bytes += ret;
		m_in_buf.IncWriteOffset(ret);
		m_last_recv_tick = get_tick_count();
	}

	uint32_t pdu_len = 0;
	try {
		while ( CImPdu::IsPduAvailable(m_in_buf.GetBuffer(), m_in_buf.GetWriteOffset(), pdu_len) )
		{
			HandlePduBuf(m_in_buf.GetBuffer(), pdu_len);//=================看这个HandlePduBuf做了什么
			m_in_buf.Read(NULL, pdu_len);
		}
	} catch (CPduException& ex) {
		log("!!!catch exception, err_code=%u, err_msg=%s, close the connection ",
			ex.GetErrorCode(), ex.GetErrorMsg());
		OnClose();
	}
}

(3)业务逻辑处理CProxyConn::HandlePduBuf()

void CProxyConn::HandlePduBuf(uchar_t* pdu_buf, uint32_t pdu_len)
{
    CImPdu* pPdu = NULL;
    pPdu = CImPdu::ReadPdu(pdu_buf, pdu_len);
    //要是心跳包就不处理
    if (pPdu->GetCommandId() == IM::BaseDefine::CID_OTHER_HEARTBEAT) {
        return;
    }
    // 根据命令找到处理函数
    pdu_handler_t handler = s_handler_map->GetHandler(pPdu->GetCommandId());//----------ps

    if (handler)
    {
        CTask* pTask = new CProxyTask(m_uuid, handler, pPdu);
        //把任务插入到线程池任务队列中
        g_thread_pool.AddTask(pTask);
    } else {
        log("no handler for packet type: %d", pPdu->GetCommandId());
    }
}

(4)关于CHandlerMap类对象s_handler_map的引入

//什么时候初始化的
//------------------------------
//main中 初始化线程池和任务队列
init_proxy_conn(thread_num);
//------------------------------
int init_proxy_conn(uint32_t thread_num)
{
	//以命令和处理函数作为key_val插入到map容器中
	s_handler_map = CHandlerMap::getInstance();//-----------------s_handler_map的出现
	g_thread_pool.Init(thread_num);

	//proxy_loop_callback()的循环回调函数
	netlib_add_loop(proxy_loop_callback, NULL);
	// 4.1 signal();信号设置,让db_proxy_server能够平滑退出
	signal(SIGTERM, sig_handler);

	//注册一个定时器函数
	return netlib_register_timer(proxy_timer_callback, NULL, 1000);
}
//---------------------------------------
CHandlerMap* CHandlerMap::getInstance()
{
	if (!s_handler_instance) {
		s_handler_instance = new CHandlerMap();
		s_handler_instance->Init();//
	}

	return s_handler_instance;
}

在db_proxy_server初始化的时候,将这个命令号与对应的处理函数绑定起来,位置s_handler_map = CHandlerMap::getInstance()的s_handler_instance->Init()

//-----------------------------
typedef map<uint32_t, pdu_handler_t> HandlerMap_t;
//-----------------------------
HandlerMap_t 	m_handler_map;
//-----------------------------
void CHandlerMap::Init()
{
    // Login validate
    m_handler_map.insert(make_pair(uint32_t(CID_OTHER_VALIDATE_REQ), DB_PROXY::doLogin));
    m_handler_map.insert(make_pair(uint32_t(CID_LOGIN_REQ_PUSH_SHIELD), DB_PROXY::doPushShield));
    m_handler_map.insert(make_pair(uint32_t(CID_LOGIN_REQ_QUERY_PUSH_SHIELD), DB_PROXY::doQueryPushShield));

    // recent session
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_RECENT_CONTACT_SESSION_REQUEST), DB_PROXY::getRecentSession));
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_REMOVE_SESSION_REQ), DB_PROXY::deleteRecentSession));

    // users
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_USER_INFO_REQUEST), DB_PROXY::getUserInfo));
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_ALL_USER_REQUEST), DB_PROXY::getChangedUser));
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_DEPARTMENT_REQUEST), DB_PROXY::getChgedDepart));
    m_handler_map.insert(make_pair(uint32_t(CID_BUDDY_LIST_CHANGE_SIGN_INFO_REQUEST), DB_PROXY::changeUserSignInfo));

    // message content
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_DATA), DB_PROXY::sendMessage));
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_LIST_REQUEST), DB_PROXY::getMessage));
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_UNREAD_CNT_REQUEST), DB_PROXY::getUnreadMsgCounter));
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_READ_ACK), DB_PROXY::clearUnreadMsgCounter));
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_GET_BY_MSG_ID_REQ), DB_PROXY::getMessageById));
    m_handler_map.insert(make_pair(uint32_t(CID_MSG_GET_LATEST_MSG_ID_REQ), DB_PROXY::getLatestMsgId));

    // device token
    m_handler_map.insert(make_pair(uint32_t(CID_LOGIN_REQ_DEVICETOKEN), DB_PROXY::setDevicesToken));
    m_handler_map.insert(make_pair(uint32_t(CID_OTHER_GET_DEVICE_TOKEN_REQ), DB_PROXY::getDevicesToken));

    //push 推送设置
    m_handler_map.insert(make_pair(uint32_t(CID_GROUP_SHIELD_GROUP_REQUEST), DB_PROXY::setGroupPush));
    m_handler_map.insert(make_pair(uint32_t(CID_OTHER_GET_SHIELD_REQ), DB_PROXY::getGroupPush));

    // group
    m_handler_map.insert(make_pair(uint32_t(CID_GROUP_NORMAL_LIST_REQUEST), DB_PROXY::getNormalGroupList));
    m_handler_map.insert(make_pair(uint32_t(CID_GROUP_INFO_REQUEST), DB_PROXY::getGroupInfo));
    m_handler_map.insert(make_pair(uint32_t(CID_GROUP_CREATE_REQUEST), DB_PROXY::createGroup));
    m_handler_map.insert(make_pair(uint32_t(CID_GROUP_CHANGE_MEMBER_REQUEST), DB_PROXY::modifyMember));

    // file
    m_handler_map.insert(make_pair(uint32_t(CID_FILE_HAS_OFFLINE_REQ), DB_PROXY::hasOfflineFile));
    m_handler_map.insert(make_pair(uint32_t(CID_FILE_ADD_OFFLINE_REQ), DB_PROXY::addOfflineFile));
    m_handler_map.insert(make_pair(uint32_t(CID_FILE_DEL_OFFLINE_REQ), DB_PROXY::delOfflineFile));

}

(5)CID_OTHER_VALIDATE_REQ 对应 DB_PROXY::doLogin()

void doLogin(CImPdu* pPdu, uint32_t conn_uuid)
{
    
    CImPdu* pPduResp = new CImPdu;
    
    IM::Server::IMValidateReq msg;//请求
    IM::Server::IMValidateRsp msgResp;//响应
    if(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()))
    {
        
        string strDomain = msg.user_name();
        string strPass = msg.password();
        
        msgResp.set_user_name(strDomain);
        msgResp.set_attach_data(msg.attach_data());//作用?
        
        //先判断登录次数,再验证账号和密码是否正确
        do
        {
            CAutoLock cAutoLock(&g_cLimitLock);
            list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];//列出出错的时间,尝试登录一次就记录一次
            uint32_t tmNow = time(NULL);
            
            //清理超过30分钟的错误时间点记录
            /*
             清理放在这里还是放在密码错误后添加的时候呢?
             放在这里,每次都要遍历,会有一点点性能的损失。
             放在后面,可能会造成30分钟之前有10次错的,但是本次是对的就没办法再访问了。
             */
            auto itTime=lsErrorTime.begin();//list的迭代器
            for(; itTime!=lsErrorTime.end();++itTime)
            {
                if(tmNow - *itTime > 30*60)
                {
                    break;
                }
            }
            if(itTime != lsErrorTime.end())
            {
                lsErrorTime.erase(itTime, lsErrorTime.end());
            }
            
            // 判断30分钟内密码错误次数是否大于10
            if(lsErrorTime.size() > 10)
            {
                itTime = lsErrorTime.begin();
                if(tmNow - *itTime <= 30*60)
                {
                    msgResp.set_result_code(6);
                    msgResp.set_result_string("用户名/密码错误次数太多");
                    pPduResp->SetPBMsg(&msgResp);
                    pPduResp->SetSeqNum(pPdu->GetSeqNum());
                    pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
                    pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
                    CProxyConn::AddResponsePdu(conn_uuid, pPduResp);//
                    return ;
                }
            }
        } while(false);
        
        log("%s request login.", strDomain.c_str());//某某人请求登录

        
        IM::BaseDefine::UserInfo cUser;
        
        //登录策略
        //成功验证通过
        if(g_loginStrategy.doLogin(strDomain, strPass, cUser))//====================里面有对mysql进行查询,密码比较
        {
            IM::BaseDefine::UserInfo* pUser = msgResp.mutable_user_info();
            pUser->set_user_id(cUser.user_id());
            pUser->set_user_gender(cUser.user_gender());
            pUser->set_department_id(cUser.department_id());
            pUser->set_user_nick_name(cUser.user_nick_name());
            pUser->set_user_domain(cUser.user_domain());
            pUser->set_avatar_url(cUser.avatar_url());
            
            pUser->set_email(cUser.email());
            pUser->set_user_tel(cUser.user_tel());
            pUser->set_user_real_name(cUser.user_real_name());
            pUser->set_status(0);

            pUser->set_sign_info(cUser.sign_info());
           
            msgResp.set_result_code(0);
            msgResp.set_result_string("成功");
            
            //如果登陆成功,则清除错误尝试限制
            CAutoLock cAutoLock(&g_cLimitLock);
            list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];
            lsErrorTime.clear();
        }
        else
        {
            //密码错误,记录一次登陆失败
            uint32_t tmCurrent = time(NULL);
            CAutoLock cAutoLock(&g_cLimitLock);
            list<uint32_t>& lsErrorTime = g_hmLimits[strDomain];
            lsErrorTime.push_front(tmCurrent);
            
            log("get result false");
            msgResp.set_result_code(1);
            msgResp.set_result_string("用户名/密码错误");
        }
    }
    else
    {
        msgResp.set_result_code(2);
        msgResp.set_result_string("服务端内部错误");
    }
    
    
    pPduResp->SetPBMsg(&msgResp);
    pPduResp->SetSeqNum(pPdu->GetSeqNum());
    pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
    pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
    CProxyConn::AddResponsePdu(conn_uuid, pPduResp);//把响应包放入响应包列表s_response_pdu_list中
}

(6)内部登录策略CInterLoginStrategy::doLogin()

DoLogin中接着调用g_loginStrategy.doLogin(strDomain, strPass, cUser)验证用户名和密码:

//内部登录策略
bool CInterLoginStrategy::doLogin(const std::string &strName, const std::string &strPass, IM::BaseDefine::UserInfo& user)
{
    bool bRet = false;
    CDBManager* pDBManger = CDBManager::getInstance();
    CDBConn* pDBConn = pDBManger->GetDBConn("teamtalk_slave");//获取mysql的DB连接
    if (pDBConn) 
    {
        //组织mysql查询语句
        string strSql = "select * from IMUser where name='" + strName + "' and status=0";
        //执行查询
        CResultSet* pResultSet = pDBConn->ExecuteQuery(strSql.c_str());
        if(pResultSet)
        {
            string strResult, strSalt;
            uint32_t nId, nGender, nDeptId, nStatus;
            string strNick, strAvatar, strEmail, strRealName, strTel, strDomain,strSignInfo;
            while (pResultSet->Next()) 
            {
                nId = pResultSet->GetInt("id");
                strResult = pResultSet->GetString("password");//数据库的密码(加了混淆码)和用户密码(没有混淆码)有区别
                strSalt = pResultSet->GetString("salt"); // 混淆码
                
                strNick = pResultSet->GetString("nick");
                nGender = pResultSet->GetInt("sex");
                strRealName = pResultSet->GetString("name");
                strDomain = pResultSet->GetString("domain");
                strTel = pResultSet->GetString("phone");
                strEmail = pResultSet->GetString("email");
                strAvatar = pResultSet->GetString("avatar");
                nDeptId = pResultSet->GetInt("departId");
                nStatus = pResultSet->GetInt("status");
                strSignInfo = pResultSet->GetString("sign_info");
            }

            //用户发来的密码为strPass,在此基础上加上混淆码
            string strInPass = strPass + strSalt; // 用户密码 + 混淆码
            char szMd5[33];
            //md5计算
            CMd5::MD5_Calculate(strInPass.c_str(), strInPass.length(), szMd5);
            //把szMd5转成string类
            string strOutPass(szMd5);
            //真正判断密码是否相等的位置
            if(strOutPass == strResult)
            {
                bRet = true;//bool型返回值
                user.set_user_id(nId);
                user.set_user_nick_name(strNick);
                user.set_user_gender(nGender);
                user.set_user_real_name(strRealName);
                user.set_user_domain(strDomain);
                user.set_user_tel(strTel);
                user.set_email(strEmail);
                user.set_avatar_url(strAvatar);
                user.set_department_id(nDeptId);
                user.set_status(nStatus);
                user.set_sign_info(strSignInfo);
            }
            //把查询结果没什么用了,直接delete掉
            delete  pResultSet;
        }
        pDBManger->RelDBConn(pDBConn);
    }
    return bRet;
}

CInterLoginStrategy::doLogin通过sql从数据库中的用户信息比对,得到该用户的登录结果。然后放到发送队列中:

void doLogin(CImPdu* pPdu, uint32_t conn_uuid)
{
   //..................

    pPduResp->SetPBMsg(&msgResp);
    pPduResp->SetSeqNum(pPdu->GetSeqNum());
    pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
    pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
    CProxyConn::AddResponsePdu(conn_uuid, pPduResp);//把响应包放入响应包列表s_response_pdu_list中
}
//---------------------------
void CProxyConn::AddResponsePdu(uint32_t conn_uuid, CImPdu* pPdu)
{
    ResponsePdu_t* pResp = new ResponsePdu_t;
    pResp->conn_uuid = conn_uuid;
    pResp->pPdu = pPdu;

    s_list_lock.lock();
    s_response_pdu_list.push_back(pResp);//把响应包放入响应包列表
    s_list_lock.unlock();
}

3.6.9 验证结果返回给msg_server,再返回给用户客户端

最终发给msg_server,msg_server再发给客户端

(1)db_proxy_server返回给msg_server

//说一说返回时,数据包的变化:
//在DB_proxy_server服务器上
//不管登录验证是否成功,都会返回一个这样的数据包给msg_server
//---------------------------------------------------------
    pPduResp->SetServiceId(IM::BaseDefine::SID_OTHER);
    pPduResp->SetCommandId(IM::BaseDefine::CID_OTHER_VALIDATE_RSP);
//---------------------------------------------------------
//msg_server收到这个数据包之后到业务逻辑调用的是
        case CID_OTHER_VALIDATE_RSP:
            _HandleValidateResponse(pPdu );
//---------------------------------------------------------

(2)msg_server发给客户端

//处理DB_proxy_server服务器验证登录的响应数据包
void CDBServConn::_HandleValidateResponse(CImPdu* pPdu)
{
    IM::Server::IMValidateRsp msg;
    CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
    string login_name = msg.user_name();
    uint32_t result = msg.result_code();
    string result_string = msg.result_string();
    CDbAttachData attach_data((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());
    log("HandleValidateResp, user_name=%s, result=%d", login_name.c_str(), result);

    CImUser* pImUser = CImUserManager::GetInstance()->GetImUserByLoginName(login_name);
    CMsgConn* pMsgConn = NULL;
    if (!pImUser) {
        log("ImUser for user_name=%s not exist", login_name.c_str());
        return;
    } else {
        pMsgConn = pImUser->GetUnValidateMsgConn(attach_data.GetHandle());
        if (!pMsgConn || pMsgConn->IsOpen()) {
            log("no such conn is validated, user_name=%s", login_name.c_str());
            return;
        }
    }

    if (result != 0) {
        result = IM::BaseDefine::REFUSE_REASON_DB_VALIDATE_FAILED;//拒绝原因DB验证失败
    }

    if (result == 0)
    {
        IM::BaseDefine::UserInfo user_info = msg.user_info();
        uint32_t user_id = user_info.user_id();
        CImUser* pUser = CImUserManager::GetInstance()->GetImUserById(user_id);
        if (pUser)
        {
            pUser->AddUnValidateMsgConn(pMsgConn);
            pImUser->DelUnValidateMsgConn(pMsgConn);
            if (pImUser->IsMsgConnEmpty())
            {
                CImUserManager::GetInstance()->RemoveImUserByLoginName(login_name);
                delete pImUser;
            }
        }
        else
        {
            pUser = pImUser;
        }

        pUser->SetUserId(user_id);
        pUser->SetNickName(user_info.user_nick_name());
        pUser->SetValidated();
        CImUserManager::GetInstance()->AddImUserById(user_id, pUser);

        pUser->KickOutSameClientType(pMsgConn->GetClientType(), IM::BaseDefine::KICK_REASON_DUPLICATE_USER, pMsgConn);

        //为什么要使用路由服务器连接?
        CRouteServConn* pRouteConn = get_route_serv_conn();
        if (pRouteConn) {
            IM::Server::IMServerKickUser msg2;
            msg2.set_user_id(user_id);
            msg2.set_client_type((::IM::BaseDefine::ClientType)pMsgConn->GetClientType());
            msg2.set_reason(1);
            CImPdu pdu;
            pdu.SetPBMsg(&msg2);
            pdu.SetServiceId(SID_OTHER);
            pdu.SetCommandId(CID_OTHER_SERVER_KICK_USER);
            pRouteConn->SendPdu(&pdu);//本身作为客户端,发给路由服务器(作为服务器)的消息
        }

        log("user_name: %s, uid: %d", login_name.c_str(), user_id);
        pMsgConn->SetUserId(user_id);
        pMsgConn->SetOpen();
        pMsgConn->SendUserStatusUpdate(IM::BaseDefine::USER_STATUS_ONLINE);
        pUser->ValidateMsgConn(pMsgConn->GetHandle(), pMsgConn);

        IM::Login::IMLoginRes msg3;
        msg3.set_server_time(time(NULL));
        msg3.set_result_code(IM::BaseDefine::REFUSE_REASON_NONE);
        msg3.set_result_string(result_string);
        msg3.set_online_status((IM::BaseDefine::UserStatType)pMsgConn->GetOnlineStatus());
        IM::BaseDefine::UserInfo* user_info_tmp = msg3.mutable_user_info();
        user_info_tmp->set_user_id(user_info.user_id());
        user_info_tmp->set_user_gender(user_info.user_gender());
        user_info_tmp->set_user_nick_name(user_info.user_nick_name());
        user_info_tmp->set_avatar_url(user_info.avatar_url());
        user_info_tmp->set_sign_info(user_info.sign_info());
        user_info_tmp->set_department_id(user_info.department_id());
        user_info_tmp->set_email(user_info.email());
        user_info_tmp->set_user_real_name(user_info.user_real_name());
        user_info_tmp->set_user_tel(user_info.user_tel());
        user_info_tmp->set_user_domain(user_info.user_domain());
        user_info_tmp->set_status(user_info.status());
        CImPdu pdu2;
        pdu2.SetPBMsg(&msg3);
        pdu2.SetServiceId(SID_LOGIN);
        pdu2.SetCommandId(CID_LOGIN_RES_USERLOGIN);//CID_LOGIN_RES_USERLOGIN命令id的数据包
        pdu2.SetSeqNum(pPdu->GetSeqNum());
        pMsgConn->SendPdu(&pdu2);//本身作为服务器,发给用户客户端(像pc客户端)的消息
    }
    else
    {
        IM::Login::IMLoginRes msg4;
        msg4.set_server_time(time(NULL));
        msg4.set_result_code((IM::BaseDefine::ResultType)result);
        msg4.set_result_string(result_string);
        CImPdu pdu3;
        pdu3.SetPBMsg(&msg4);
        pdu3.SetServiceId(SID_LOGIN);
        pdu3.SetCommandId(CID_LOGIN_RES_USERLOGIN);
        pdu3.SetSeqNum(pPdu->GetSeqNum());
        pMsgConn->SendPdu(&pdu3);
        pMsgConn->Close();
    }
}

至此一个登录流程就完成了。这个流程中存在很多值得学习的地方,也存在一些不足。有哪些值得学习的地方和不足之处,将在这个系列分析完每一个服务程序后再一起讨论。这里只是作为一个小小的引子来说明数据在各个服务之间的流动

posted @ 2022-06-09 21:48  豪崽_ZH  阅读(75)  评论(0)    收藏  举报