重连机制
重连可能是一开始就没链接上,也可能是被断开,也可能是网络波动,原因很多,对于一开始就没链接上的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);
}