目录
一、前言
在项目设计中,单例设计模式是一种非常常用的软件设计模式,有很多模块都会用到它,例如TCP网络模块以及数据库模块等等,它的好处在于它确保了一个类只有一个实例,并提供了一个全局访问点(通常是静态方法)来获取这个实例。这使得在整个应用程序中都可以轻松地访问该类的唯一实例,而无需在多个组件之间传递引用。
它一共有以下几个特点:
1.单例模式只有一个实例。是指整个项目全程只有而且只能创造一个实例对象。
2.单例类必须自己创建自己的唯一实例。是指在类的内部通过自己来创造实例。
3.单例类必须给所有其他对象提供这一实例。是指提供一个全局的接口,让其他类来访问。
以上就是单例设计模式的全部特点,下面我将会以windows中TCP网络服务器为例,来详细讲解单例模式的设计过程。
二、设计单例模式
1.添加类CServerSocket
首先我们先在项目中添加一个CServerSocket类,以后我们添加网络类的功能都会在这个类中。
class CServerSocket
{
public:
private:
}
2.添加构造函数和析构函数
这个过程的重点在于我们必须将构造函数和析构函数设为private私有,这是因为单例模式的特性:单例模式只有一个实例。如果我们将构造函数和析构函数变为public,这样其他类能够自己也能声明一个实例,就违反了原则所在。
class CServerSocket
{
public:
private:
CServerSocket(const CServerSocket& ss) {};
CServerSocket() {};
~CServerSocket() {};
};
3.添加实例和获取实例的接口
这一步很重要,我们要在类的内部声明一个实例 m_instance,同时实现访问实例的接口函数getInstance()和释放实例的接口函数releaseInstance()。
class CServerSocket
{
public:
static CServerSocket* getInstance() {
if (m_instance == NULL)
{
m_instance = new CServerSocket();
}
return m_instance;
};
static void releaseInstance()
{
if (m_instance != NULL)
{
CServerSocket* tmp = m_instance;
m_instance = NULL;
delete tmp;
}
}
private:
// 静态成员变量
static CServerSocket* m_instance;
CServerSocket(const CServerSocket& ss) {};
CServerSocket() {};
~CServerSocket() {};
};
同时需要在cpp文件中,去实现初始化。这是因为类的静态成员函数不同于类内部的函数,我们需要在类的内部声明,同时在类的外部初始化。
#include "ServerSocket.h"
// 静态成员变量的初始化
CServerSocket* CServerSocket::m_instance = NULL;
CServerSocket* pserver = CServerSocket::getInstance();
4.实现CHelper类
CHelp类的作用是帮助m_instance实例进行释放作用,主要作用是调用releaseInstance()接口。
class CServerSocket
{
public:
static CServerSocket* getInstance() {
if (m_instance == NULL)
{
m_instance = new CServerSocket();
}
return m_instance;
};
static void releaseInstance()
{
if (m_instance != NULL)
{
CServerSocket* tmp = m_instance;
m_instance = NULL;
delete tmp;
}
}
private:
// 静态成员变量
static CServerSocket* m_instance;
CServerSocket(const CServerSocket& ss) {};
CServerSocket() {};
~CServerSocket() {};
class CHelper
{
public:
CHelper() {
CServerSocket::getInstance();
}
~CHelper()
{
CServerSocket::releaseInstance();
}
};
static CHelper m_helper;
};
#include "ServerSocket.h"
// 静态成员变量的初始化
CServerSocket* CServerSocket::m_instance = NULL;
CServerSocket* pserver = CServerSocket::getInstance();
CServerSocket::CHelper CServerSocket::m_helper;
三、在单例中添加网络功能函数
以上四步我们的单例模式就基本上成型了,下面我们就是只需要在类中在添加一些网络功能函数就可以在项目中去使用了。而这一部分我之前有讲过,基本上就是一些固定写法,请看多线程网络实战之仿qq群聊的服务器和客户端 这一章节。
1.初始化网络函数环境InitSocketEnv()
// 1.套接字初始化
BOOL InitSocketEnv()
{
WSADATA wsaData;
int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (stu != 0)
{
std::cout << "WSAStartup 错误:" << stu << std::endl;
return 0;
}
return TRUE;
}
2.初始化网络InitSocket()
bool InitSocket(short port)
{
// 2. 创造套接字
if (m_socket == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return false;
}
// 3. bind 绑定套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET; // ipv4 协议
addrSrv.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
addrSrv.sin_port = htons(port);
int ret = bind(m_socket, (sockaddr*)&addrSrv, sizeof(SOCKADDR)); // TODO
if (ret != 0) return false;
// 4. 监听
ret = listen(m_socket, 1); // TODO
if (ret != 0) return false;
return true;
};
3.接收客户端连接AcceptClient()
bool AcceptClient()
{
SOCKADDR_IN addrCli;
int cli_size = sizeof(addrCli);
m_client = accept(m_socket, (sockaddr*)&addrCli, &cli_size);
if (m_client == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return false;
}
TRACE("%s\r\n", "client connect success !!!");
return true;
};
4.处理客户端消息DealCommand()
#define BUFF_SIZE 409600
int DealCommand()
{
if (m_client == -1) return false;
char* buffer = new char[BUFF_SIZE];
if (buffer == NULL)
{
delete[]buffer;
TRACE("内存不足!!");
return -1;
}
memset(buffer, BUFF_SIZE, 0);
static size_t index = 0;
while (true)
{
size_t len = recv(m_client, buffer + index, BUFF_SIZE - index,0);
if ((len <= 0) && (index == 0))
{
delete[]buffer;
return -1;
}
index += len;
len = index;
m_packet = CPacket((BYTE*)buffer, len);
if (len > 0)
{
memmove(buffer, buffer + len, BUFF_SIZE - len);
index -= len;
delete[]buffer;
return m_packet.sCmd;
}
}
delete[]buffer;
return -1;
}
5.发送消息Send()
bool Send(const char* pData, int nSize)
{
if (m_client == -1) return false;
return send(m_client, pData, nSize, 0) > 0;
}
6.断开连接CloseClient()
void CloseClient()
{
if (m_client != INVALID_SOCKET)
{
closesocket(m_client);
m_client = INVALID_SOCKET;
}
}
7.声明套接字变量以及完善构造和析构函数
class CServerSocket
{
public:
static CServerSocket* getInstance() {
if (m_instance == NULL)
{
m_instance = new CServerSocket();
}
return m_instance;
};
static void releaseInstance()
{
if (m_instance != NULL)
{
CServerSocket* tmp = m_instance;
m_instance = NULL;
delete tmp;
}
}
protected:
bool InitSocket(short port)
{
// 2. 创造套接字
if (m_socket == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return false;
}
// 3. bind 绑定套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET; // ipv4 协议
addrSrv.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
addrSrv.sin_port = htons(port);
int ret = bind(m_socket, (sockaddr*)&addrSrv, sizeof(SOCKADDR)); // TODO
if (ret != 0) return false;
// 4. 监听
ret = listen(m_socket, 1); // TODO
if (ret != 0) return false;
return true;
};
bool AcceptClient()
{
SOCKADDR_IN addrCli;
int cli_size = sizeof(addrCli);
m_client = accept(m_socket, (sockaddr*)&addrCli, &cli_size);
if (m_client == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return false;
}
TRACE("%s\r\n", "client connect success !!!");
return true;
};
#define BUFF_SIZE 409600
int DealCommand()
{
if (m_client == -1) return false;
char* buffer = new char[BUFF_SIZE];
if (buffer == NULL)
{
delete[]buffer;
TRACE("内存不足!!");
return -1;
}
memset(buffer, BUFF_SIZE, 0);
static size_t index = 0;
while (true)
{
size_t len = recv(m_client, buffer + index, BUFF_SIZE - index,0);
if ((len <= 0) && (index == 0))
{
delete[]buffer;
return -1;
}
index += len;
len = index;
if (len > 0)
{
memmove(buffer, buffer + len, BUFF_SIZE - len);
index -= len;
delete[]buffer;
}
}
delete[]buffer;
return -1;
}
bool Send(const char* pData, int nSize)
{
if (m_client == -1) return false;
return send(m_client, pData, nSize, 0) > 0;
}
void CloseClient()
{
if (m_client != INVALID_SOCKET)
{
closesocket(m_client);
m_client = INVALID_SOCKET;
}
}
private:
SOCKET m_client;
SOCKET m_socket;
CServerSocket& operator=(const CServerSocket& ss) {};
CServerSocket(const CServerSocket& ss) {
m_client = ss.m_client;
m_socket = ss.m_socket;
};
CServerSocket() {
m_client = INVALID_SOCKET;
m_socket = INVALID_SOCKET;
if (InitSocketEnv() == FALSE)
{
MessageBox(NULL, _T("无法初始化套接字,检查网络设置"), _T("初始化错误!"),MB_OK | MB_ICONERROR);
exit(0);
}
// 2. 创建套接字
m_socket = socket(AF_INET, SOCK_STREAM, 0);
};
~CServerSocket() {
closesocket(m_socket);
WSACleanup();
};
// 1.套接字初始化
BOOL InitSocketEnv()
{
WSADATA wsaData;
int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (stu != 0)
{
std::cout << "WSAStartup 错误:" << stu << std::endl;
return 0;
}
return TRUE;
}
private:
// 静态成员变量
static CServerSocket* m_instance;
CServerSocket(const CServerSocket& ss) {};
CServerSocket() {};
~CServerSocket() {};
class CHelper
{
public:
CHelper() {
CServerSocket::getInstance();
}
~CHelper()
{
CServerSocket::releaseInstance();
}
};
static CHelper m_helper;
};
四、结尾
以上就是TCP网络实现单例的整个过程,重要的更多是实现单例模式的思想,当然对于网络套接字而言,最重要的就是数据。而如何设计和封装发送和接收的网络包,这才是一大难点。