一个心跳保活类的设计

心跳在很多的服务器程序中应用很多,作为一种轮询的方案,效率可能没有基于中断的高,但是这种方案非常稳定,因此也被大量采用,心跳说白了就是不断向对方发送数据包,如果对方在确定时间回复就说明对方存活,反之对方已死,在稳定的同时也会造成很多误判,因为如果一旦对方由于一些别的工作耽误了回复心跳,那么这端就会认为对方已经断开从而进行善后处理,这显然是一种误判,解决方案有两种,一种就是在有正常数据收发时不再发送心跳包,试想一下远端服务器为何会耽误回复心跳,就是由于有正常请求要处理,反过来此时发送心跳是没有用的,只会增加服务器的负担,如果服务器断开,那么正常的数据回复就会表现出来;另外一种方式就是将心跳包发送到一个独立的端口,在服务器为该端口启动一个线程专门负责回复心跳包,尽量不让心跳包和其他的数据揉在一起。

我们的系统是这样的,每个端口在服务器开一个线程,所有发往该端口的数据在该线程串行处理,一个端口的每个socket相当于一个通道负责处理一些特殊的命令,起初的想法是将心跳包发往一个8003端口,所有的端口共享一个vector,所有端口从客户端接受的命令都放到这一个vector中,然后由各个端口的处理线程从vector中取出自己负责的命令然后处理,这样看来这个vector就是一个瓶颈,就像一个沙漏一样,这种设计很不合理,当我发现其不合理之处时,我告诉项目经理,我说心跳包发给了服务器的8003端口这没有错,但是如果服务器的vector中在接收到该心跳之前已经排入了一个命令,而这个命令的处理是很耗时的,那怎么办,它很可能会耽误心跳包的回复,经理笑着说,没有问题,客户端做了同步,一个命令发出没有接到回复之前是不让发送第二个命令,我愣了一下,回笑,all right,不错,但是一个客户端做好了自己的同步,它会和别的客户端同样请求服务器8003端口的客户端同步吗?简单说,客户1发送往服务器8003端口的耗时命令可能会影响客户2的心跳包回复,这就是问题!

一个起初的设计缺陷最终导致了一个bug,大改已经不可能了,于是我的想法被采纳了,那就是分离心跳请求,在服务器单开一个8000端口专门负责心跳包处理,虽然服务器的vector这个瓶颈仍然存在,但是不管怎样心跳回复是很迅速的,而且服务器负责8000端口的是一个线程,在负载不是变态大的情况下是可以应付的,我们只能这样改了,这说明设计缺陷可能会导致项目的失败,然而我们不能失败,因为我们做的不是项目,而是产品。我们产品的架构大概就是监控中心客户端维护一个网站镜像列表,网站镜像表现为一个或多个目录,因此每一个目录实际上相当于一个客户端,每个客户端向服务器发送心跳包以确认服务器存活,如果服务器宕机了,那么心跳包仍然被发送,直到心跳包有回复之后,程序逻辑将采取进一步的动作,详细流程没有必要深究,这里主要了解一个目录就是一个客户端就可以了,现在看看服务器,服务器可以有多个,也就是说监控中心的网站镜像可以是多个服务器的镜像,理所当然的设计就是每一个目录绑定一个心跳类,但是问题是,如果有多个网站镜像是同一台服务器的网站镜像,那么多个心跳包将发往一个服务器,这是没有意义的,因此要处理好共享。

我的想法是维护一个容器,每当初始化一个目录的心跳类时先检查容器中是否存在心跳类的IP地址和端口和本次初始化的心跳类的IP地址和端口一致,如果没有找到就把该心跳类放到该容器然后返回NULL,反之如果找到就返回这个找到的心跳类,处理程序检测返回值,如果返回为NULL,那么就使用这个刚刚初始化的心跳类,如果返回不为NULL,将使用返回的心跳类同时释放刚刚初始化的心跳类。问题又来了,这样的话就需要维护一个全局的容器,这样只能使本来就已经很乱的代码更加乱,于是解决办法就是将这个容器设置为static的类型放到心跳类内部,由心跳类自己管理。现在考虑一下释放操作,如果用户删除了网站,绝对不能将心跳类一并停止并析构,因为可能有别的目录在共享这一个心跳类,于是需要在这个心跳类中维持一个计数器,删除的时候,计数器递减1,如果计数器为0了,那么就释放该心跳类。

基本的设计思想就是以上这些,代码用C++或者java会很简单,我是基于VC++开发的,用到了很多的MFC类型,很无奈,本人不喜欢微软的东西,但是由于工作需要实在不爽啊,这个设计有一个要点,就是共享的处理,我用到了很常用的懒惰管理法,就是用一个计数器,linux内核中这种方法很常见的,有空可以读一读,比如文件删除,页面释放等等,现在呈上代码:

以下为.h文件:

class HeartBeatClient;

class SERVER{ //保存服务器,目前就两个字段

public:

char strIP[200];

int iPort;

};

class LIST //保存vector,易于更换容器而不影响心跳类

{

public:

std::vector m_clientlist;

};

typedef struct{ //保存心跳反馈的结果

int m_iHeartBeat;

int m_iStatus;

}CON_RESULT,*PCON_RESULT;

class HeartBeatClient

{

public:

HeartBeatClient();

HeartBeatClient( char * strIP, int port );

~HeartBeatClient(void);

void SetIPandPort(char * strIP, int port);

int StartHeartBeatThread(void);

int StopHeartBeatThread(void);

void SetCheckConResult( CON_RESULT pResult );

void GetCheckConResult( CON_RESULT* pResult );

/*

* 是否已经有了关于这个端口和IP的心跳线程

* 返回值:如果没有,返回NULL;如果已经存在,那么就返回已经存在的那个

*/

HeartBeatClient* Exist( HeartBeatClient * pThis );

public:

<连接实体> m_CheckClient;

SERVER m_pServer;

int m_iRunning;

private:

CON_RESULT m_Result;

HANDLE m_hCheckClient;

int m_iThisOK;

int m_iShared; //共享一个心跳线程的线程数量

public:

static LIST m_slist;

};

以下为.cpp文件

#include "MirHeartBeatClient.h"

#include

LIST HeartBeatClient::m_slist;

DWORD WINAPI CheckClientFunc( LPVOID lpParam )

{

HeartBeatClient *pLocalCheckCon = (HeartBeatClient*)lpParam;

<连接实体> * localCheckClient = pLocalCheckCon->m_pCheckClient;

CON_RESULT result;

while(pLocalCheckCon->m_iRunning)

{

...设置默认result

pLocalCheckCon->SetCheckConResult(result);

...连接实体连接服务器

while(pLocalCheckCon->m_iRunning)

{

...发送实体发送心跳包

if (正确返回)

{

...设置正确反馈结果

pLocalCheckCon->SetCheckConResult(result);

Sleep(3000);

continue;

}

else

{

...设置错误反馈结果

pLocalCheckCon->SetCheckConResult(result);

...连接实体断开连接

Sleep(1000);

break;

}

}

}

return 1;

}

HeartBeatClient::HeartBeatClient( )

{

m_iThisOK = 0;

m_iRunning = 0;

m_hCheckClient = NULL;

m_Result.m_iHeartBeat = 0;

m_Result.m_iStatus = 0;

}

HeartBeatClient::HeartBeatClient( char * strIP, int port )

{

m_iThisOK = 0;

m_iRunning = 0;

m_hCheckClient = NULL;

...初始化连接实体

m_pServer.iPort = port;

strcpy( m_pServer.strIP, strIP );

m_Result.m_iHeartBeat = 0;

m_Result.m_iStatus = 0;

int nFound = -1;

if( m_slist.m_clientlist.size() > 0 )

for (int i=m_slist.m_clientlist.size()-1; i>=0; i--)

{

if( m_slist.m_clientlist[i]->m_pServer.iPort == port && !strcmp(m_slist.m_clientlist[i]->m_pServer.strIP,strIP) )

{

nFound = i;

m_slist.m_clientlist[i]->m_iShared++; //递增共享变量

}

}

if( nFound == -1 )

{

m_slist.m_clientlist.push_back(this);

m_iShared = 0;

m_iThisOK = 1;

m_iRunning = 1;

}

}

HeartBeatClient::~HeartBeatClient()

{

m_iRunning = 0;

...连接实体的断开处理

}

void HeartBeatClient::SetIP( char * strIP, int port )

{

m_pServer.iPort = port;

strcpy( m_pServer.strIP, strIP );

int nFound = -1;

if( m_slist.m_clientlist.size() > 0 )

for (int i=m_slist.m_clientlist.size()-1; i>=0; i--)

{

if( m_slist.m_clientlist[i]->m_pServer.iPort == port && !strcmp(m_slist.m_clientlist[i]->m_pServer.strIP,strIP) )

{

nFound = i;

m_slist.m_clientlist[i]->m_iShared++; //递增共享变量

}

}

if( nFound == -1 )

{

m_slist.m_clientlist.push_back(this);

m_iShared = 0;

m_iThisOK = 1;

m_iRunning = 1;

}

}

int HeartBeatClient::StartHeartBeatThread()

{

DWORD dwCheckThreadId;

m_hCheckClient = CreateThread( NULL, 0, CheckClientFunc, (LPVOID)this, 0, &dwCheckThreadId);

return 1;

}

void HeartBeatClient::GetCheckConResult(CON_RESULT * pConResult)

{

pConResult->m_iHeartBeat = m_Result.m_iHeartBeat;

pConResult->m_iStatus = m_Result.m_iStatus;

}

void HeartBeatClient::SetCheckConResult(CON_RESULT result)

{

m_Result.m_iHeartBeat = result.m_iHeartBeat;

m_Result.m_iStatus = result.m_iStatus;

}

HeartBeatClient* HeartBeatClient::Exist(HeartBeatClient* pThis)

{

int nFound = -1;

if( m_slist.m_clientlist.size() > 0 && m_iThisOK == 0 )

for (int i=m_slist.m_clientlist.size()-1; i>=0; i--)

{

if( m_slist.m_clientlist[i]->m_pServer.iPort == m_pServer.iPort && !strcmp(m_slist.m_clientlist[i]->m_pServer.strIP,m_pServer.strIP) )

{

return m_slist.m_clientlist[i]; //返回找到的

}

}

return NULL;

}

int HeartBeatClient::StopHeartBeatThread(void)

{

this->m_iShared --;

if( this->m_iShared == -1 )

{

if( m_hCheckClient != NULL )

{ //如果最后一个使用该对象的心跳类被停止,那么将自己从vector删除掉

std::vector ::iterator it;

it = find(m_slist.m_clientlist.begin(),m_slist.m_clientlist.end(),this);

if ( it!=m_slist.m_clientlist.end() )

m_slist.m_clientlist.erase(it);

m_iRunning = 0;

WaitForSingleObject( m_hCheckClient, INFINITE );

CloseHandle( m_hCheckClient );

m_hCheckClient = NULL;

}

return 1;

}

if( this->m_iShared < -1 )

this->m_iShared = 0;

return 0;

}

使用方式如下:

if ( m_pHeartBeatClient==NULL )

{

HeartBeatClient *pHeartBeat = NULL;

m_pHeartBeatClient = new HeartBeatClient( sWebIP, 8000 );

pHeartBeat = m_pHeartBeatClient->Exist( m_pHeartBeatClient );

if( !pHeartBeat )

{

m_pHeartBeatClient->StartHeartBeatThread();

}

else

{

m_pHeartBeatClient->StopHeartBeatThread(void)

delete m_pHeartBeatClient;

m_pHeartBeatClient = pHeartBeat;

}

}

想得到心跳结果的话,可以往心跳对象的GetCheckConResult中传入一个传出参数,调用完毕后结果就有了。这里用vector作为容器,其实是为了跨平台,在同样达到跨平台目的的前提下也完全可以用一种C++的特性来自己实现一个更好的容器,就是先实现一个高效的算法,并且实现容器的特有操作,比如插入,删除,查找等等,涉及到比较操作可以重载被比较对象的==操作符,这样就比较美观了,起初我就是这么实现的,但是后来别人说为了这个小玩意搞这么复杂太不值得了,于是就改成了比较传统的方式了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
实现心跳保活机制是为了确保网络连接的稳定性和可靠性,防止连接因长时间不活动而被关闭。在Spring Boot和Netty中,可以通过以下步骤实现心跳保活机制: 1. 创建一个Netty服务器并设置相关参数,如端口号和TCP参数。可以使用Spring Boot提供的`@Configuration`注解和Netty的`ServerBootstrap`来完成这一步骤。 2. 使用Netty的`ChannelInitializer`创建一个处理器来处理客户端的请求,并实现`ChannelInboundHandlerAdapter`的`channelRead`方法。 3. 在处理器的`channelRead`方法中,判断收到的消息是否为心跳消息。可以根据消息内容或自定义的标识来判断是否为心跳消息。 4. 如果接收到的消息是心跳消息,可以通过向客户端发送一个固定的心跳响应消息来维持连接。可以使用Netty的`ctx.writeAndFlush()`方法来发送心跳响应消息。 5. 如果接收到的消息不是心跳消息,可以继续处理其他业务逻辑。 6. 在处理器的`channelInactive`方法中,可以处理连接断开时的逻辑。可以在此方法中关闭连接、释放资源等操作。 7. 在Netty服务器的配置中,设置心跳超时时间。可以使用Netty的`IdleStateHandler`来实现心跳超时的检测和处理。 8. 在上述步骤完成后,运行Spring Boot应用程序,并使用客户端发送心跳消息来保持连接。可以通过不断发送心跳消息,来确保连接保持活动状态。 通过以上步骤,就可以在Spring Boot和Netty中实现心跳保活机制,确保网络连接的稳定性和可靠性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值