心跳在很多的服务器程序中应用很多,作为一种轮询的方案,效率可能没有基于中断的高,但是这种方案非常稳定,因此也被大量采用,心跳说白了就是不断向对方发送数据包,如果对方在确定时间回复就说明对方存活,反之对方已死,在稳定的同时也会造成很多误判,因为如果一旦对方由于一些别的工作耽误了回复心跳,那么这端就会认为对方已经断开从而进行善后处理,这显然是一种误判,解决方案有两种,一种就是在有正常数据收发时不再发送心跳包,试想一下远端服务器为何会耽误回复心跳,就是由于有正常请求要处理,反过来此时发送心跳是没有用的,只会增加服务器的负担,如果服务器断开,那么正常的数据回复就会表现出来;另外一种方式就是将心跳包发送到一个独立的端口,在服务器为该端口启动一个线程专门负责回复心跳包,尽量不让心跳包和其他的数据揉在一起。
我们的系统是这样的,每个端口在服务器开一个线程,所有发往该端口的数据在该线程串行处理,一个端口的每个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++的特性来自己实现一个更好的容器,就是先实现一个高效的算法,并且实现容器的特有操作,比如插入,删除,查找等等,涉及到比较操作可以重载被比较对象的==操作符,这样就比较美观了,起初我就是这么实现的,但是后来别人说为了这个小玩意搞这么复杂太不值得了,于是就改成了比较传统的方式了。