1. 网络资源的初始化与释放(C++ RAII惯用法)
C++ RAII 惯用法
RAII (Resource Acquisition Is Initialization)资源获取即初始化
我们拿到资源的时候就已经初始化,一旦不需要该资源,该资源就会被释放
资源:
在 C++ 的语境下,资源代表一些可以必须先被获取才能使用的对象。例如堆内存是资源,文件句柄是资源,互斥锁也是资源。
千言万语,比不上一段代码:
ServerSocket.h
class CServerSocket
{
public:
static CServerSocket* getInstance()
{
if(m_instance == nullptr)
{
m_instance = new CServerSocket();
}
return m_instance;
}
bool InitSocket()
{
if (m_sock == -1) return false;
sockaddr_in serv_adr;
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.S_un.S_addr = INADDR_ANY;
serv_adr.sin_port = htons(18888);
//绑定
if (bind(m_sock, (sockaddr*)&serv_adr, sizeof(sockaddr_in) ) == -1) return false;
//TODO: 返回值
if( listen(m_sock, 5) == -1) return false;
return true;
}
bool AcceptClient()
{
sockaddr_in client_adr;
int cli_sz = sizeof(client_adr);
m_client = accept(m_sock, (sockaddr*)&client_adr, &cli_sz);
if (m_client == -1) return false;
return true;
}
int DealCommand()
{
if (m_client == -1)
{
return -1;
}
char* buffer = new char[4096];
memset(buffer, 0, 4096);
size_t index = 0;
while(true)
{
size_t len = recv(m_client, buffer +index, sizeof(buffer) - index, 0);
if(len <= 0 )
{
return -1;
}
//TODO: 处理命令
index += len;
len = index;
m_packet = CPacket ((BYTE*)buffer, len);
if(len > 0)
{
memmove(buffer, buffer + len, 4096 - len);
index -= len;
return m_packet.sCmd;
}
}
return -1;
}
bool Send(const char* pData,size_t nSize)
{
if (m_client == -1) return false;
return send(m_client, pData, nSize, 0) > 0;
}
bool Send( CPacket& pack)
{
if (m_client == -1) return false;
return send(m_client, pack.Data(), pack.size(), 0) > 0;
}
CServerSocket& operator=(const CServerSocket& ss) = delete;
CServerSocket(const CServerSocket&) = delete;
private:
SOCKET m_sock;
SOCKET m_client;
CPacket m_packet;
CServerSocket():m_client(INVALID_SOCKET)
{
if(InitSockEnv() == FALSE)
{
MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置"), _T("初始化错误"), MB_OK | MB_ICONERROR);
exit(0);
}
m_sock = socket(PF_INET, SOCK_STREAM, 0);
};
~CServerSocket()
{
closesocket(m_sock);
WSACleanup();
};
BOOL InitSockEnv()
{
WSADATA data;
if(WSAStartup(MAKEWORD(1, 1), &data) != 0)
{
return FALSE;
}
return TRUE;
//TODO: 返回值处理
}
static CServerSocket* m_instance;
static void releaseInstance()
{
if(m_instance != NULL)
{
CServerSocket* tmp = m_instance;
m_instance = NULL;
delete tmp;
}
}
class CHelper
{
public:
CHelper()
{
CServerSocket::getInstance();
}
~CHelper()
{
CServerSocket::releaseInstance();
}
};
static CHelper m_helper;
};
ServerSocket.cpp
CServerSocket* CServerSocket::m_instance = NULL;
CServerSocket::CHelper CServerSocket::m_helper;
CServerSocket* pserver = CServerSocket::getInstance();
main.cpp
CServerSocket* pserver = CServerSocket::getInstance();
int count = 0;
if (pserver->InitSocket() == false)
{
MessageBox(NULL, _T(""), _T("网络初始化失败"), MB_OK | MB_ICONERROR);
exit(0);
}
while (pserver != NULL)
{
if (pserver)
{
if (pserver->AcceptClient() == false)
{
if (count >= 3) {
MessageBox(NULL, _T("多次无法正常接入,程序退出"), _T("接入用户失败"), MB_OK | MB_ICONERROR);
exit(0);
}
MessageBox(NULL, _T("无法正常接入用户,自动重试"), _T("接入用户失败"), MB_OK | MB_ICONERROR);
count++;
}
}
int ret = pserver->DealCommand();
以上就是RAII的一种实现,但是又采用单例模式(下文讲解)
我们在构造函数CServerSocket()
调用InitSockEnv()
获取并初始化网络资源,在进入main
函数后,获取实例
在main()
函数结束后,应该调用析构函数,但事实上调用不了,因为此时全局数据区中,存储的并不是一个实例对象,而是一个实例对象的指针,即一个地址变量而已!实例对象呢?在堆区,因为是通过new得来的!虽然这样能够减小全局数据区的占用,把实例对象这一大坨都放到堆区。
所以我你们需要一个CHelper
类,来帮助我们关闭套接字,释放网络资源我们在Server.cpp文件中创建了一个CHelper
的静态对象,当main()
函数结束时,由于m_helper
对象在全局数据区,所以会调用CHelper
的析构函数,从而调用CServerSocket::releaseInstance()
帮助我们释放资源
在main()
函数开始时获取并初始化网络资源,在main()
函数结束时,释放回收网络资源,这就是RAII
因为网络资源在整个程序运行时.只需要初始化一次,释放一次,所以``CServerSocket`采用单例模式,这样就避免了重复初始化
C++11采用智能指针也可以实现单例模式,以后可能会介绍