23_添加心跳测试

心跳测试的必要性

  1. 检测非活动连接:如果客户端异常断开(例如,崩溃或网络故障),服务器可能不会立即意识到这一点。心跳测试可以帮助服务器检测出这些“死连接”,从而可以及时清理资源1
  2. 保持连接活跃:在某些网络环境中,如果连接在一段时间内没有数据传输,它可能会被网络设备(如防火墙)关闭。通过定期发送心跳包,可以保持连接的活跃状态,防止被意外关闭2
  3. 快速响应客户端故障:心跳机制允许服务器更快地检测到客户端的故障,并作出响应,例如重新分配资源或通知其他系统组件3
  4. 避免资源浪费:没有心跳测试,服务器可能会保持无效的客户端连接,这会占用宝贵的系统资源,如内存和处理器时间。心跳测试可以帮助避免这种资源浪费4

CELLTimestamp.cpp文件的变化

class CELLTime   //添加获取当前时间的类
{
public:
    //获取当前时间戳 (毫秒)
    static time_t getNowInMilliSec()
    {
        return duration_cast<milliseconds>(high_resolution_clock::now().time_since_epoch()).count();
    }
};

CELLClient.cpp文件的变化

#ifndef _CellClient_hpp_
#define _CellClient_hpp_

#include"CELL.hpp"

//客户端心跳检测死亡计时时间
#define CLIENT_HREAT_DEAD_TIME 60000
//客户端数据类型
class CellClient
{
public:
    void resetDTHeart()
    {
        _dtHeart = 0;
    }

    //心跳检测
    bool checkHeart(time_t dt)
    {
        _dtHeart += dt;
        if (_dtHeart >= CLIENT_HREAT_DEAD_TIME)
        {
            printf("checkHeart dead:s=%d,time=%d\n",_sockfd, _dtHeart);
            return true;
        }
        return false;
    }
private:
    //心跳死亡计时
    time_t _dtHeart;
};

#endif // !_CellClient_hpp_

CELLServer.cpp文件的变化

#ifndef _CELL_SERVER_HPP_
#define _CELL_SERVER_HPP_

#include"CELL.hpp"
#include"INetEvent.hpp"
#include"CELLClient.hpp"

#include<vector>
#include<map>

//网络消息接收处理服务类
class CellServer
{
public:
    //处理网络消息
    //备份客户socket fd_set
    fd_set _fdRead_bak;
    //客户列表是否有变化
    bool _clients_change;
    SOCKET _maxSock;
    void OnRun()
    {
        _clients_change = true;
        while (isRun())
        {
            if (!_clientsBuff.empty())
            {//从缓冲队列里取出客户数据
                std::lock_guard<std::mutex> lock(_mutex);
                for (auto pClient : _clientsBuff)
                {
                    _clients[pClient->sockfd()] = pClient;
                }
                _clientsBuff.clear();
                _clients_change = true;
            }

            //如果没有需要处理的客户端,就跳过
            if (_clients.empty())
            {
                std::chrono::milliseconds t(1);
                std::this_thread::sleep_for(t);
                //旧的时间戳
                _oldTime = CELLTime::getNowInMilliSec();
                continue;
            }

            //伯克利套接字 BSD socket
            fd_set fdRead;//描述符(socket) 集合
                          //清理集合
            FD_ZERO(&fdRead);
            if (_clients_change)
            {
                _clients_change = false;
                //将描述符(socket)加入集合
                _maxSock = _clients.begin()->second->sockfd();
                for (auto iter : _clients)
                {
                    FD_SET(iter.second->sockfd(), &fdRead);
                    if (_maxSock < iter.second->sockfd())
                    {
                        _maxSock = iter.second->sockfd();
                    }
                }
                memcpy(&_fdRead_bak, &fdRead, sizeof(fd_set));
            }
            else {
                memcpy(&fdRead, &_fdRead_bak, sizeof(fd_set));
            }

            ///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
            ///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
            timeval t{ 0,1 };
            int ret = select(_maxSock + 1, &fdRead, nullptr, nullptr, &t);
            if (ret < 0)
            {
                printf("select任务结束。\n");
                Close();
                return;
            }
            //else if (ret == 0)
            //{
            //  continue;
            //}
            ReadData(fdRead);
            CheckTime();
        }
    }
    //旧的时间戳
    time_t _oldTime = CELLTime::getNowInMilliSec();
    void CheckTime()
    {
        //当前时间戳
        auto nowTime = CELLTime::getNowInMilliSec();
        auto dt = nowTime - _oldTime;
        _oldTime = nowTime;

        for (auto iter = _clients.begin(); iter != _clients.end(); )
        {
            if (iter->second->checkHeart(dt))
            {
                if (_pNetEvent)
                    _pNetEvent->OnNetLeave(iter->second);
                _clients_change = true;
                delete iter->second;
                auto iterOld = iter;
                iter++;
                _clients.erase(iterOld);
                continue;
            }
            iter++;
        }
    }

    void ReadData(fd_set& fdRead)
    {
        #ifdef _WIN32
            for (int n = 0; n < fdRead.fd_count; n++)
            {
                auto iter = _clients.find(fdRead.fd_array[n]);
                if (iter != _clients.end())
                {
                    if (-1 == RecvData(iter->second))
                    {
                        if (_pNetEvent)
                            _pNetEvent->OnNetLeave(iter->second);
                        _clients_change = true;
                        delete iter->second;
                        closesocket(iter->first);
                        _clients.erase(iter);
                    }
                }
                else {
                    printf("error. if (iter != _clients.end())\n");
                }
            }
#else
            std::vector<CellClient*> temp;
            for (auto iter : _clients)
            {
                if (FD_ISSET(iter.second->sockfd(), &fdRead))
                {
                    if (-1 == RecvData(iter.second))
                    {
                        if (_pNetEvent)
                            _pNetEvent->OnNetLeave(iter.second);
                        _clients_change = true;
                        close(iter->first);
                        temp.push_back(iter.second);
                    }
                }
            }
            for (auto pClient : temp)
            {
                _clients.erase(pClient->sockfd());
                delete pClient;
            }
#endif
    }
private:
};

#endif // !_CELL_SERVER_HPP_

MessageHeader.hpp的变化

#ifndef _MessageHeader_hpp_
#define _MessageHeader_hpp_

enum CMD
{
    CMD_C2S_HEART,
    CMD_S2C_HEART,
};

struct netmsg_c2s_Heart : public netmsg_DataHeader
{
    netmsg_c2s_Heart()
    {
        dataLength = sizeof(netmsg_c2s_Heart);
        cmd = CMD_C2S_HEART;
    }
};

struct netmsg_s2c_Heart : public netmsg_DataHeader
{
    netmsg_s2c_Heart()
    {
        dataLength = sizeof(netmsg_s2c_Heart);
        cmd = CMD_S2C_HEART;
    }
};

#endif // !_MessageHeader_hpp_

server.cpp的变化

#include "EasyTcpServer.hpp"
#include<thread>

bool g_bRun = true;

class MyServer : public EasyTcpServer
{
public:

    virtual void OnNetMsg(CellServer* pCellServer, CellClient* pClient, netmsg_DataHeader* header)
    {
        EasyTcpServer::OnNetMsg(pCellServer, pClient, header);
        switch (header->cmd)
        {
        case CMD_LOGIN:
        {
            pClient->resetDTHeart();
            //send recv 
            netmsg_Login* login = (netmsg_Login*)header;
            //printf("收到客户端<Socket=%d>请求:CMD_LOGIN,数据长度:%d,userName=%s PassWord=%s\n", cSock, login->dataLength, login->userName, login->PassWord);
            //忽略判断用户密码是否正确的过程
            netmsg_LoginR ret;
            pClient->SendData(&ret);
            //netmsg_LoginR* ret = new netmsg_LoginR();
            //pCellServer->addSendTask(pClient, ret);
        }//接收 消息---处理 发送   生产者 数据缓冲区  消费者 
        break;
        case CMD_LOGOUT:
        {
            netmsg_Logout* logout = (netmsg_Logout*)header;
            //printf("收到客户端<Socket=%d>请求:CMD_LOGOUT,数据长度:%d,userName=%s \n", cSock, logout->dataLength, logout->userName);
            //忽略判断用户密码是否正确的过程
            //netmsg_LogoutR ret;
            //SendData(cSock, &ret);
        }
        break;
        case CMD_C2S_HEART:
        {
            pClient->resetDTHeart();
            netmsg_s2c_Heart ret;
            pClient->SendData(&ret);
        }
        default:
        {
            printf("<socket=%d>收到未定义消息,数据长度:%d\n", pClient->sockfd(), header->dataLength);
            //netmsg_DataHeader ret;
            //SendData(cSock, &ret);
        }
        break;
        }
    }
private:

};

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top