1 TCP网络通信流程
2 原始服务器代码
#include <iostream> #include <WinSock2.h> // 网络头文件,Windows socket第二版 #pragma comment(lib, "ws2_32.lib") // 加载网络库,Windows socket第二版,32位; int main() { // 1 打开网络库 WSADATA data; // MAKEWORD:使用库版本号 data:系统获取网络配置信息,然后返回给此参数 WSAStartup(MAKEWORD(2, 2), &data); // 2 创建服务端socket SOCKET socket_server = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addr_server; addr_server.sin_family = AF_INET; addr_server.sin_addr.S_un.S_addr = INADDR_ANY; addr_server.sin_port = htons(9999); // 3 给服务端socket绑定地址,bind bind(socket_server, (sockaddr*)&addr_server, sizeof(SOCKADDR_IN)); // 4 将套节字置入正在传入侦听连接的状态,listen listen(socket_server, 2); SOCKADDR_IN addr_client; int addr_client_len = sizeof(SOCKADDR_IN); while (true) { char buffer[4096] = ""; // 5 等待客户端连接,accept SOCKET socket_clietn = accept(socket_server, (sockaddr*)&addr_client, &addr_client_len); // 6 接收数据 int len = recv(socket_clietn, buffer, sizeof(buffer), 0); // 7 发送数据 send(socket_clietn, buffer, sizeof(buffer), 0); // 关闭客户端套接字 closesocket(socket_clietn); } // 关闭服务端套接字 closesocket(socket_server); // 清除windows socket环境 WSACleanup(); }
从以上代码易看出,main函数里面代码结构混乱,无法一眼看出服务器执行逻辑 ==》可以使用类将接口封装
而且有些代码只会执行一次而又只会执行一次--》全局静态全局变量结合类
3 如何从原始服务器代码优化
3.0 其他代码优化
优化一:
代码只会执行一次但有只会执行一次,而且是在main函数之前执行,main函数之后执行,一些初始化环境的代码
例如以下代码
// 打开网络库 WSADATA data; // MAKEWORD:使用库版本号 data:系统获取网络配置信息,然后返回给此参数 WSAStartup(MAKEWORD(2, 2), &data); // 关闭服务端套接字 closesocket(socket_server); // 清除windows socket环境 WSACleanup();
优化一方案:
使用全局静态变量,全局静态变量只会初始化一次,而且是在main函数之前初始化,main函数之后销毁
main函数之前不存在多线程,是个单线程环境,不存在上锁这些情况,而且mian函数结束后,可以放心的清理内存,不用担心
还有谁在使用某块内存
我们可以使用一个静态单例对象来解决,这个对象在main函数之前就会初始化,调用构造函数,在main函数之后才会析构,调用析构函数
3.1 抽象服务器通信过程
-
套接字环境初始化
-
服务器套接字初始化
-
等待连接
-
接收数据
-
发送数据
3.2 设计单例
为什么使用单例:
从服务器类可知,该类只需要一个实例,不然会导致构造、析构函数被频繁调用,导致环境崩溃
怎么实现单例:
单例的本质是利用语法限制来使得一个类只允许有一个实例 -- 只能通过类里面的接口来创建对象
ServerSocket.h
class CServerSocket { public: static CServerSocket* getInstance() { if (m_instance == NULL) { m_instance = new CServerSocket(); } return m_instance; } private: CServerSocket& operator=(const CServerSocket& s){} CServerSocket(const CServerSocket& s){} CServerSocket(){} ~CServerSocket(){} static CServerSocket* m_instance; };
ServerSocket.cpp
CServerSocket* CServerSocket::m_instance = NULL CServerSocket* pserver = CServerSocket::getInstance();
代码详解:实现一个单例类
-
将所有的构造方法变成私有 -- 外部无法通过构造方法创建对象
-
在类里面声明一个该类的静态指针成员
-
在类里面定义一个getInstance() 接口,接口里面new 创建一个当前类的对象 -- 由于这个getInstance 方法是位于类里面的所有可以访问该类的构造方法
存在问题 -- 析构没有调用
如何在main函数结束后调用单例对象的析构?
在类里面再声明一个类CHelper,并且再声明一个该类的静态成员m_helper,在单例类里面,这个类的构造方法就调用单例类的getInstance 方法,析构就delete 单例类对象
因为这个m_helper是一个全局静态变量,所以程序初始化之前就会调用这个类的构造方法,main函数结束后就会调用这个对象的析构方法,这样在这个类的析构方法里面去delete 单例对象就没有问题了
代码实现
ServerSocket.h
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();
3.3 服务器功能封装
为什么要封装?
-
代码复用 -- 其它地方想用这些功能可以直接拷贝走
-
代码可读性
怎么去封装
!!!抽象化每个步骤, 先把上层框架搭好,再考虑具体功能代码实现!!!
从最原始的代码开始,抽象化每个具体步骤,根据每个步骤来实现接口简化主函数的逻辑
例如:服务器可分为以下几大步骤
-
套接字环境初始化
-
套接字初始化
-
accept
-
recv
-
send
服务器几大步骤:套接字环境初始化、创建服务器socket、为服务器初始化地址信息bind、将服务器套接字置于监听状态并开启一个完成三次握手的队列(传入连接队列)listen、从传入连接队列里面取出套接字上的连接返回一个新的连接套接字,如果侦听传入队列为空,该函数会阻塞accept、从连接套接字上接收数据recv、从连接套接字上发送数据
3.4 封装后的服务器逻辑
// 0 创建服务器对象 CServerSocket* pserver = CServerSocket::getInstance(); int acceptCount = 0; // 1 套接字环境初始化 if (pserver->InitSocket() == false) { MessageBox(NULL, _T("网络初始化失败,请检查网络状态"), _T("网络初始化失败!!"), MB_OK | MB_ICONERROR); exit(0); } while (CServerSocket::getInstance() != NULL) { // 等待连接 if (pserver->AcceptClient() == false) { MessageBox(NULL, _T("接入用户失败,自动重试"), _T("接入用户失败!!"), MB_OK | MB_ICONERROR); acceptCount += 1; if (acceptCount >= 3) { MessageBox(NULL, _T("多次用户失败,退出程序"), _T("多次接入用户失败!!"), MB_OK | MB_ICONERROR); exit(0); } } // 处理命令 int ret = pserver->DealCommand(); }
5 完整代码
5.0 main.cpp
int main() { // 0 创建服务器对象 CServerSocket* pserver = CServerSocket::getInstance(); int acceptCount = 0; // 1 套接字环境初始化 if (pserver->InitSocket() == false) { MessageBox(NULL, _T("网络初始化失败,请检查网络状态"), _T("网络初始化失败!!"), MB_OK | MB_ICONERROR); exit(0); } while (CServerSocket::getInstance() != NULL) { // 等待连接 if (pserver->AcceptClient() == false) { MessageBox(NULL, _T("接入用户失败,自动重试"), _T("接入用户失败!!"), MB_OK | MB_ICONERROR); acceptCount += 1; if (acceptCount >= 3) { MessageBox(NULL, _T("多次用户失败,退出程序"), _T("多次接入用户失败!!"), MB_OK | MB_ICONERROR); exit(0); } } // 处理命令 int ret = pserver->DealCommand(); } }
5.1 ServerSocket.cpp
CServerSocket* CServerSocket::m_instance = NULL; CServerSocket::CHelper CServerSocket::m_helper; CServerSocket* pserver = CServerSocket::getInstance();
5.2 ServerSocket.h
class CServerSocket { public: static CServerSocket* getInstance() { if (m_instance == NULL) { m_instance = new CServerSocket(); } return m_instance; } // 初始化套接字,true成功,false失败 bool InitSocket() { if (m_socket_server == INVALID_SOCKET) { return false; } SOCKADDR_IN addr_server; addr_server.sin_family = AF_INET; addr_server.sin_port = htons(60000); addr_server.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(m_socket_server, (sockaddr*)&addr_server, sizeof(addr_server)) == SOCKET_ERROR) { return false; } if (listen(m_socket_server, 1) == SOCKET_ERROR) { return false; } return true; } // accept 客户端,true accept成功,false失败 bool AcceptClient() { SOCKADDR_IN addr_client; int naddr_client_len = sizeof(addr_client); m_socket_client = accept(m_socket_server, (sockaddr*)&addr_client, &naddr_client_len); if (m_socket_client == INVALID_SOCKET) { return false; } return true; } #define BUFFER_SIZE 4096 // 循环接收处理数据 int DealCommand() { char buffer[BUFFER_SIZE] = ""; recv(socket_clietn, recv, sizeof(buffer), 0); // TODO:处理数据 } // send 原始数据 bool Send(const char* pData, size_t nSize) { if (m_socket_client == INVALID_SOCKET) { return false; } return send(m_socket_client, pData, sizeof(nSize), 0) > 0; } private: SOCKET m_socket_server, m_socket_client; CServerSocket& operator=(const CServerSocket& s) { m_socket_client = s.m_socket_client; m_socket_server = s.m_socket_server; } CServerSocket(const CServerSocket& s) { m_socket_client = s.m_socket_client; m_socket_server = s.m_socket_server; } CServerSocket() { if (InitSocketEnv() == FALSE) { MessageBox(NULL, _T("无法初始化套接字环境,请检查网络环境"), _T("初始化错误"), MB_OK|MB_ICONERROR); exit(0); } m_socket_server = socket(AF_INET, SOCK_STREAM, 0); m_socket_client = INVALID_SOCKET; } ~CServerSocket() { closesocket(m_socket_server); WSACleanup(); } bool InitSocketEnv() { // 套接字环境初始化、socket、bind、listen、accept、recv、send WSAData data; if (WSAStartup(MAKEWORD(2, 2), &data) != 0) { return false; } return true; } 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; };