bufferevent_socket 客户端实现心跳和重连机制

重连机制
重连可能是一开始就没链接上,也可能是被断开,也可能是网络波动,原因很多,对于一开始就没链接上的socket我们可以复用重新connect,但是对于被断开的socket是不能被复用的,必须重新创建然后connect,所以为了方便我们不管什么原因都选择重新创建并connect

void ClientSockItem::Connect()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::Connect\r\n", GetId());
	OutputDebugStringA(strFomat);
	CCritical lk(m_cs);
	//如果是手动关闭或者已经连接上则不重连
	if (m_bManalClose || m_bConnected)
	{
		return;
	}

	if ((GetTickCount64() - m_LastTryConnectTime) <= PER_TRY_MINITIME)
	{
		struct timeval tTimeout = { 12, 0 };
		bufferevent_set_timeouts(m_pbuf_eve, &tTimeout, nullptr);
		return;
	}

	if (m_pbuf_eve != nullptr)
	{
		bufferevent_free(m_pbuf_eve);
		m_pbuf_eve = nullptr;
	}

	int options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE;
	m_pbuf_eve = bufferevent_socket_new(g_event_base, -1, options);

	//启用读写事件通知,默认只有写
	if (m_pbuf_eve)
	{
		bufferevent_setcb(m_pbuf_eve, SockCallBackMgr::handle_sockitem_read_data_cb, SockCallBackMgr::handle_sockitem_write_data_cb, SockCallBackMgr::handle_sockitem_event_cb, this);
		bufferevent_enable(m_pbuf_eve, EV_READ | EV_WRITE);
	}
	
	sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(m_port);
	addr.sin_addr.S_un.S_addr = inet_addr(m_strip.c_str());
	//inet_pton(AF_INET, m_strip.c_str(), &addr.sin_addr);
	bufferevent_socket_connect(m_pbuf_eve, (const sockaddr *)&addr, sizeof(addr));
	m_LastTryConnectTime = GetTickCount64();
}

由于连接失败的原因很多,所以为了控制重连频率我们选择使用定时器进行控制,防止过快消耗socket。

心跳机制
由于bufferevent_socket_new创建的event,本身支持设置timeoout,所以当链接上之后我们就可以设置定时器,定时发送心跳包(可以增加时间戳,记录最后一次发送数据的时间戳,更加高效的控制心跳),(timeout触发之后会取消EV_READ或者EV_WRITE读事件所以需要重启读事件)。

void ClientSockItem::OnConnectOK()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::OnConnectOK\r\n", GetId());
	OutputDebugStringA(strFomat);

	CCritical lk(m_cs);
	m_bConnected = true;
	m_recvData.clear();
	//心跳定时器
	struct timeval tTimeout = { 30, 0 };
	bufferevent_set_timeouts(m_pbuf_eve, &tTimeout, nullptr);
	//链接上之后立马发送数据,防止空链接被断开
	DoHeartBeat();
	DoSub();
}

void ClientSockItem::OnTimeouted(short what)
{
	CCritical lk(m_cs);
	if (m_bManalClose)
		return;

	//恢复读写
	bufferevent_enable(m_pbuf_eve, EV_READ | EV_WRITE);
	//如果没有链接上则重连否则发送心跳
	if (!m_bConnected)
	{
		bufferevent_set_timeouts(m_pbuf_eve, nullptr, nullptr);
		Connect();
	}
	else
	{
		//调试阶段取消心跳
		DoHeartBeat();
	}
	
}

完整源码:
ISocket.h

#pragma once
extern __int64 g_api_socket_id;
class ISock{
public:
	ISock(){ m_sock_Id = ::InterlockedIncrement64(&g_api_socket_id); }
	virtual uint64_t GetId(){ return m_sock_Id; };
	virtual int SendData(const void *pdata, unsigned __int64 date_len, uint64_t sock_id,const char *channel = nullptr) = 0;
	virtual void Close() = 0;
	virtual void SetSubChannel(std::string channel){};//以|分开
protected:
	uint64_t m_sock_Id;
};

class IServer :public ISock
{
public:
	virtual void OnAccept(evutil_socket_t fd, struct sockaddr *addr, int socklen) = 0;
	virtual void OnAcceptError() = 0;
};

class ISocketItem :public ISock{
public:
	virtual void OnRecvData(struct bufferevent *bev) = 0;
	virtual void OnError() = 0;
	virtual void OnTimeouted(short what) = 0;
	virtual void OnWriteData(struct bufferevent *bev){};
	virtual void OnConnectOK(){};
	virtual void OnDisConnected(){};//链接失败或者断开链接
	
};

SockCallBackMgr.cpp

#include "stdafx.h"
#include "SockCallBackMgr.h"
#include "BaseServer.h"

void SockCallBackMgr::handle_accept_cb(struct evconnlistener * listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
	IServer *pBaseServer = (IServer *)arg;
	if (pBaseServer)
		pBaseServer->OnAccept(fd, addr, socklen);
}

void SockCallBackMgr::handle_accept_errorcb(struct evconnlistener *listener, void *arg)
{
	IServer *pBaseServer = (IServer *)arg;
	if (pBaseServer)
		pBaseServer->OnAcceptError();
}

void SockCallBackMgr::handle_sockitem_read_data_cb(struct bufferevent *bev, void *ctx)
{
	ISocketItem *pSocketItem = (ISocketItem *)ctx;
	if (pSocketItem == nullptr)
		return;

	pSocketItem->OnRecvData(bev);

}

void SockCallBackMgr::handle_sockitem_write_data_cb(struct bufferevent *bev, void *ctx)
{
	ISocketItem *pSocketItem = (ISocketItem *)ctx;
	if (pSocketItem == nullptr)
		return;

	pSocketItem->OnWriteData(bev);
}

void SockCallBackMgr::handle_sockitem_event_cb(struct bufferevent *bev, short what, void *ctx)
{
	ISocketItem *pSocketItem = (ISocketItem *)ctx;
	if (pSocketItem == nullptr)
		return;

	//如果客户端主动断开,则受到BEV_EVENT_EOF|BEV_EVENT_READING
	if (what & BEV_EVENT_CONNECTED)
	{
		pSocketItem->OnConnectOK();
	}
	//链接建立之后断开
	else if (what & BEV_EVENT_EOF)
	{
		pSocketItem->OnDisConnected();
	}
	else if (what & BEV_EVENT_TIMEOUT)
	{
		pSocketItem->OnTimeouted(what);
	}
	//链接出错
	else if (what & BEV_EVENT_ERROR)
	{
		pSocketItem->OnError();
	}
	else if (what & BEV_EVENT_READING)
	{
		int a = 0;
	}
	else if (what & BEV_EVENT_WRITING)
	{
		int a = 0;
	}
	
}

ClientSockItem.cpp

#include "stdafx.h"
#include "ClientSockItem.h"
#include "SockCallBackMgr.h"
#include <atlstr.h>

const int PER_TRY_MINITIME = 10000;//重连最小间隔时间
ClientSockItem::ClientSockItem(const char *szip, unsigned short port,SOCKET_DATA_CALL event_call, void * call_ctx)
{
	::InitializeCriticalSection(&m_cs);
	m_pDataCall = event_call;
	m_pDataCallCtx = call_ctx;
	m_bConnected = false;
	m_LastTryConnectTime = 0;
	m_bManalClose = false;
	m_strip = szip;
	m_port = port;
	int options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE;	
	m_pbuf_eve = nullptr;

}

ClientSockItem::~ClientSockItem()
{
	m_bManalClose = true;
	if (m_pbuf_eve)
		bufferevent_free(m_pbuf_eve);
	m_pbuf_eve = nullptr;
	DeleteCriticalSection(&m_cs);
}

void ClientSockItem::Connect()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::Connect\r\n", GetId());
	OutputDebugStringA(strFomat);
	CCritical lk(m_cs);
	//如果是手动关闭或者已经连接上则不重连
	if (m_bManalClose || m_bConnected)
	{
		return;
	}

	if ((GetTickCount64() - m_LastTryConnectTime) <= PER_TRY_MINITIME)
	{
		struct timeval tTimeout = { 12, 0 };
		bufferevent_set_timeouts(m_pbuf_eve, &tTimeout, nullptr);
		return;
	}

	if (m_pbuf_eve != nullptr)
	{
		bufferevent_free(m_pbuf_eve);
		m_pbuf_eve = nullptr;
	}

	int options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE;
	m_pbuf_eve = bufferevent_socket_new(g_event_base, -1, options);

	//启用读写事件通知,默认只有写
	if (m_pbuf_eve)
	{
		bufferevent_setcb(m_pbuf_eve, SockCallBackMgr::handle_sockitem_read_data_cb, SockCallBackMgr::handle_sockitem_write_data_cb, SockCallBackMgr::handle_sockitem_event_cb, this);
		bufferevent_enable(m_pbuf_eve, EV_READ | EV_WRITE);
	}
	
	sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(m_port);
	addr.sin_addr.S_un.S_addr = inet_addr(m_strip.c_str());
	//inet_pton(AF_INET, m_strip.c_str(), &addr.sin_addr);
	bufferevent_socket_connect(m_pbuf_eve, (const sockaddr *)&addr, sizeof(addr));
	m_LastTryConnectTime = GetTickCount64();
}

void ClientSockItem::OnRecvData(struct bufferevent *bev)
{
	CCritical lk(m_cs);

	CStringA strFomat;
	
	char szBuf[RECV_BUF_LEN] = "";
	size_t iread = 0;
	do
	{
		iread = bufferevent_read(bev, szBuf, RECV_BUF_LEN);
		strFomat.Format("[NetUtil] ClientSockItem[%I64u]::OnRecvData[%d]\r\n", GetId(), iread);
		OutputDebugStringA(strFomat);
		
		if (iread > 0)
		{
			m_recvData.append(szBuf, iread);
		}
		
		while (m_recvData.size() >= COM_HEAD_LEN)
		{
			ComHead *pComHead = (ComHead *)m_recvData.data();
			//如果当前版本不匹配,则关闭socket
			if (pComHead->Version != NOW_COM_VERSION)
			{
				CStringA strFomat;
				strFomat.Format("[NetUtil] ClientSockItem[%I64u]::Version[%d] Not Match()\r\n", GetId(), pComHead->Version);
				OutputDebugStringA(strFomat);

				Close();
				OnDisConnected();
				m_recvData.clear();
				return;
			}

			if(pComHead->iPackLength <= m_recvData.size())
			{
				//心跳和订阅指令内部截留
				if (pComHead->bizType == BIZ_HEARTBEAT)
				{
					OnHeartBeat();
				}
				else if (m_pDataCall)
					m_pDataCall(0, GetId(), pComHead,m_pDataCallCtx);
				//清楚该部分数据
				m_recvData.erase(m_recvData.begin(), m_recvData.begin() + pComHead->iPackLength);
				continue;
			}
			break;
		}
		

	} while (iread == RECV_BUF_LEN);
}

int ClientSockItem::SendData(const void *pdata, unsigned __int64 date_len, uint64_t sock_id, const char *channel)
{
	CStringA strFomat;
	CCritical lk(m_cs);
	int nret = -1;
	if (m_pbuf_eve && m_bConnected){
		nret = bufferevent_write(m_pbuf_eve, pdata, date_len);
		//失败,重连
		if (nret == -1){
			strFomat.Format("[NetUtil] ClientSockItem[%I64u]::SendData[%I64u] failed,ret=-1\r\n", GetId(), date_len);
			OutputDebugStringA(strFomat);
			m_bConnected = false;
			Connect();
		}
		else
		{
			strFomat.Format("[NetUtil] ClientSockItem[%I64u]::SendData[%I64u]\r\n", GetId(), date_len);
			OutputDebugStringA(strFomat);
		}
	}
	else
	{
		strFomat.Format("[NetUtil] ClientSockItem[%I64u]::SendData[%I64u] failed,m_pbuf_eve[%d] m_bConnected[%s]\r\n", GetId(), date_len, m_pbuf_eve, (m_bConnected ? "true":"false"));
		OutputDebugStringA(strFomat);
	}
		
	return nret;
}

void ClientSockItem::OnConnectOK()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::OnConnectOK\r\n", GetId());
	OutputDebugStringA(strFomat);

	CCritical lk(m_cs);
	m_bConnected = true;
	m_recvData.clear();
	//心跳定时器
	struct timeval tTimeout = { 30, 0 };
	bufferevent_set_timeouts(m_pbuf_eve, &tTimeout, nullptr);
	//链接上之后立马发送数据,防止空链接被断开
	DoHeartBeat();
	DoSub();
}

void ClientSockItem::OnDisConnected()
{
	OutputDebugStringA("[NetUtil] ClientSockItem::OnDisConnected\r\n");
	CCritical lk(m_cs);
	m_bConnected = false;
	Connect();
}

void ClientSockItem::OnError()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::OnError\r\n", GetId());
	OutputDebugStringA(strFomat);

	CCritical lk(m_cs);
	m_bConnected = false;
	Connect();
}

void ClientSockItem::OnTimeouted(short what)
{
	CCritical lk(m_cs);
	if (m_bManalClose)
		return;

	//恢复读写
	bufferevent_enable(m_pbuf_eve, EV_READ | EV_WRITE);
	//如果没有链接上则重连否则发送心跳
	if (!m_bConnected)
	{
		bufferevent_set_timeouts(m_pbuf_eve, nullptr, nullptr);
		Connect();
	}
	else
	{
		//调试阶段取消心跳
		DoHeartBeat();
	}
	
}

void ClientSockItem::Close()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::Close\r\n", GetId());
	OutputDebugStringA(strFomat);
	CCritical lk(m_cs);
	m_bManalClose = true;
	if (m_pbuf_eve)
		bufferevent_free(m_pbuf_eve);
	m_pbuf_eve = nullptr;
}

void ClientSockItem::SetSubChannel(std::string channel)
{
	CCritical lk(m_cs);
	m_strchannel = channel;
	m_set_channel.clear();
	ExtractChannel(channel, m_set_channel);
	DoSub();
}

void ClientSockItem::DoHeartBeat()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::DoHeartBeat\r\n", GetId());
	OutputDebugStringA(strFomat);

	ComHead comHead;
	comHead.bizType = BIZ_HEARTBEAT;
	comHead.iPackLength = sizeof(ComHead);
	SendData(&comHead, comHead.iPackLength, GetId());
}

void ClientSockItem::DoSub()
{
	if (m_set_channel.empty())
		return;

	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::DoSub\r\n", GetId());
	OutputDebugStringA(strFomat);

	

	std::string strSendData(sizeof(ComHead) + m_strchannel.size(),0);
	ComHead *pComHead = (ComHead *)strSendData.data();
	pComHead->bizType = BIZ_TYPE_SUB;
	pComHead->Version = NOW_COM_VERSION;
	pComHead->iPackLength = strSendData.size();
	pComHead->nExtraDataLen = m_strchannel.size();
	memcpy(pComHead + 1, m_strchannel.data(), m_strchannel.size());
	SendData(pComHead, pComHead->iPackLength, GetId());
}

void ClientSockItem::OnHeartBeat()
{
	CStringA strFomat;
	strFomat.Format("[NetUtil] ClientSockItem[%I64u]::OnHeartBeat\r\n", GetId());
	OutputDebugStringA(strFomat);
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值