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采用智能指针也可以实现单例模式,以后可能会介绍