当前位置: 首页 > news >正文

RemoteCtrl-初步的网络编程框架搭建

利用单例设计模式搭建服务器架构

开发项目时,先开发比较难的模块。好处是:①使得项目的进度具有可控性;②方便对接;③能够进行可行性的评估,提早暴露风险。

1.window网络编程中的套接字初始化

在window网络编程中的套接字初始化,如下代码是固定的,并且该代码只会被执行一次,在程序初始化以及程序被销毁时执行。其中,在windows系统中使用WSAStartup()函数初始化Winsock函数,接着就是网络编程的代码,如创建套接字socket、绑定bind、监听listen等完成之后,使用WSACleanup()函数释放Winsock资源。

WSADATA data; 
WSAStartup(MAKEWORD(1, 1), &data)网络编程代码…WASCleanup();

在套接字初始化的代码中,以上代码是固定的,此时我们就需要考虑在面向对象编程中,是否有手段将这种只会被执行一次的代码(在程序初始化和程序销毁时执行)进一步优化。在C/C++语言中有静态变量,静态变量在首次调用时被初始化,在程序运行结束时被销毁。如果静态变量是全局的,会在main函数调用之前被初始化,在main函数返回之后被析构。

由前文内容可知,首先使用WSADATA data;  WSAStartup(MAKEWORD(1, 1), &data);初始化套接字环境。由于该代码只执行一次,在程序初始化时执行WSAStartup,以及在程序销毁时执行WSACleanup()。此时,我们定义一个类CServerSocket,并在类中定义一个成员函数InitSocketEnv,用来初始化套接字环境。并将InitSocketEnv函数放如构造函数中,在构造函数中初始化socket环境,在析构函数中调用WSACleanup()函数释放资源。具体实现如下:
ServerSocket.h

#pragma once
#include "pch.h"
#include "framework.h"class CServerSocket
{
public://在构造函数中初始化socket环境CServerSocket() {if (InitSockEnv() == FALSE){MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置!"), _T("初始化错误!"), MB_OK | MB_ICONERROR);exit(0);}}//在析构函数中确保WSACleanup()函数被调用。///WSACleanup()是Windows网络编程中用于清理Winsock库资源的函数,它与WSAStartup()配对使用,通常在程序退出前调用。~CServerSocket() {WSACleanup();}//初始化socket环境BOOL InitSockEnv(){//定义一个WSADATA结构体变量data,用于接收WSAStartup()返回的Winsock库的详细信息(如版本、实现细节等)。WSADATA data;//WSAStartup()是Windows提供的初始化Winsock的函数,必须在任何socket操作之前调用。//MAKEWORD(low, high)是一个宏,用于组合主版本号和次版本号(例如 MAKEWORD(1,1)表示 Winsock1.1)。if (WSAStartup(MAKEWORD(1, 1), &data) != 0){return FALSE;}return TRUE;}
};//声明一个外部定义的全局变量server,让多个文件共享同一个实例。
extern CServerSocket server;

WSAStartup()函数以及WSACleanup()函数详细解释如下图所示:

2.借助单例设计模式解决套接字可能被多次初始化的问题

假如我在main函数中再声明一个CServerSocket变量,那么就会调用两次构造函数和析构函数,就初始化了两次环境,并释放了两次资源,这就有问题了。如何避免随意拿extern类去声明多个对象?答案:使用单例模式。通过语法来保证这类情况的出现。
①首先将构造函数、拷贝构造函数、赋值运算符(=)的重载函数均设置为私有的,防止外部通过该三类函数去构造、拷贝构造、赋值构造多个对象;
②那么既然这些函数被设置成了私有,那么怎么来访问这些函数呢?使用静态函数的形式来访问。直接使用 类名::静态函数名 的形式访问该静态函数。注意:静态函数是没有this指针的,所以无法在类中直接访问类中的成员变量。那怎么办呢?将成员变量也设置为静态变量就OK了。

2.1 单线程环境下的单例设计模式介绍

本节的标题是单线程环境下的单例设计模式介绍,那么肯定还有多线程环境下的单例设计模式,后面再介绍。

单例模式的实现要点:
①构造函数和析构函数是私有的,不允许外部生成和释放该对象;
②静态成员变量和静态返回单例的成员函数;(静态成员变量就是需要创建的那个单例对象,静态返回单例的成员函数就是我们要返回单例对象的函数。)
③禁用拷贝构造函数和赋值构造函数。(这里还包括移动拷贝构造和移动拷贝赋值构造,即要禁用4个函数。)

单例模式要解决的问题:一个类只允许有一个实例,并且该实例需要在多处使用,比如日志对象、数据库连接池、线程池对象。只需要一个实例,能不能使用全局变量?不能。全局变量不能保证唯一性,并i企鹅全局变量的初始化不好控制,所以就用到了单例模式。

单例模式传统有两种解决办法,①懒汉模式;②饿汉模式。
懒汉模式:内存在被用到时再申请,不提前申请;
饿汉模式:内存要提前申请。

1)饿汉模式

2)懒汉模式

2.2 项目代码

使用单例设计模式设计windows环境初始化。

具体代码如下:

ServerSocket.h

class CServerSocket
{
public:static CServerSocket*getInstance(){if (m_instance == NULL)//静态函数没有this指针,所以无法直接访问成员变量{m_instance = new CServerSocket();}return m_instance;}
private:CServerSocket& operator=(const CServerSocket& ss) {}CServerSocket(const CServerSocket&ss) {}//在构造函数中初始化socket环境CServerSocket() {if (InitSockEnv() == FALSE){MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置!"), _T("初始化错误!"), MB_OK | MB_ICONERROR);exit(0);}}~CServerSocket() {WSACleanup();}//1、初始化套接字环境BOOL InitSockEnv(){//定义一个WSADATA结构体变量data,//用于接收WSAStartup()返回的Winsock库的详细信息(如版本、实现细节等)。WSADATA data;if (WSAStartup(MAKEWORD(1, 1), &data) != 0){return FALSE;}return TRUE;}static CServerSocket* m_instance;
};

ServerSocket.cpp

//静态成员变量m_instance类内声明、类外定义
CServerSocket* CServerSocket::m_instance = NULL;//初始化pserver指针,指针pserver是全局变量
CServerSocket* pserver = CServerSocket::getInstance();

我们运行以上代码,发现首先调用getInstance()函数,但是析构函数没有被调用,由单例设计模式可知该情况是不允许发生的。那么析构函数为什么没有被调用呢?是因为没有人去调用析构函数。由上面的代码可知,我们通过静态函数getInstance()通过new操作拿到了一个CServerSocket类的实例m_instance。但是并没有delete,所以没有调用析构函数。

这里通过releaseInstance()函数来释放m_instance实例的资源,并通过CServerSocket类下的一个私有类CHelper调用getInstance()以及releaseInstance()函数。代码如下所示:

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;
CServerSocket::CHelper CServerSocket::m_helper;

那么定义CHelper类的作用是什么?我们声明一个CHelper对象实例m_helper,并在ServerSocket.cpp文件中实现该实例。我可以确保实例m_helper是全局唯一的。此时,就调用了析构函数。完整代码如下:

ServerSocket.h

class CServerSocket
{
public:static CServerSocket*getInstance(){if (m_instance == NULL)//静态函数没有this指针,所以无法直接访问成员变量{m_instance = new CServerSocket();}return m_instance;}
private:CServerSocket& operator=(const CServerSocket& ss) {}CServerSocket(const CServerSocket&ss) {}//在构造函数中初始化socket环境CServerSocket() {if (InitSockEnv() == FALSE){MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置!"), _T("初始化错误!"), MB_OK | MB_ICONERROR);exit(0);}}~CServerSocket() {WSACleanup();}//1、初始化套接字环境BOOL InitSockEnv(){//定义一个WSADATA结构体变量data,//用于接收WSAStartup()返回的Winsock库的详细信息(如版本、实现细节等)。WSADATA data;if (WSAStartup(MAKEWORD(1, 1), &data) != 0){return FALSE;}return TRUE;}static void releaseInstance(){if (m_instance != NULL){CServerSocket* tmp = m_instance;m_instance = NULL;delete  tmp;}}static CServerSocket* m_instance;class CHelper{public:CHelper(){CServerSocket::getInstance();}~CHelper(){CServerSocket::releaseInstance();}};static CHelper m_helper;
};

ServerSocket.cpp

#include "pch.h"
#include "ServerSocket.h"//CServerSocket server;//静态成员变量m_instance类内声明、类外定义
CServerSocket* CServerSocket::m_instance = NULL;//初始化pserver指针,指针pserver是全局变量
CServerSocket* pserver = CServerSocket::getInstance();//定义并初始化静态成员m_helper
CServerSocket::CHelper CServerSocket::m_helper;

2.3 网络编程

在构造函数中初始化socket环境之后,就可以创建套接字了,但是这里为什么将创建的socket文件描述符设置为类CServerSocket的成员变量呢?因为在InitSocket()函数、AcceptClient()函数中都需要用到m_client变量。最后在析构函数中调用closesocket(m_sock);关闭服务器套接字。同理,由于m_client变量在后面的DealCommand函数中还要使用,故m_client变量也要设置为类CServerSocket的成员变量,而不是在AcceptClient()函数中定义,如果在AcceptClient()函数中定义,那么m_client变量就是AcceptClient()函数中的局部变量,在DealCommand函数中就无法使用了。

//在构造函数中初始化socket环境
CServerSocket() 
{m_client = INVALID_SOCKET;////初始化为无效的套接字 -1if (InitSockEnv() == FALSE){MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置!"), _T("初始化错误!"), MB_OK | MB_ICONERROR);exit(0);}//windows环境初始化完成后,就可以创建套接字了。m_sock = socket(PF_INET, SOCK_STREAM, 0);
}
//1、初始化
bool InitSocket()
{if (m_sock == -1) return false;sockaddr_in serv_adr;//是一个结构体,用于存储IPv4地址和端口信息//用于将serv_adr的所有字节初始化为0(清零),//该操作是为了防止结构体内存中残留的随机数据影响后续操作(如未初始化的变量可能导致bind()失败)。memset(&serv_adr, 0, sizeof(serv_adr));//指定地址族(Address Family)为AF_INET(表示IPv4)serv_adr.sin_family = AF_INET;//sin_addr.s_addr存储IP地址,INADDR_ANY是一个特殊值(0.0.0.0),//监听本机所有可用的网络接口(如WiFi、以太网、本地回环127.0.0.1)。serv_adr.sin_addr.s_addr = INADDR_ANY;serv_adr.sin_port = htons(9527);//绑定if (bind(m_sock, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){return false;}if (listen(m_sock, 1) == -1)//控制端一般是1对1控制,所以这里为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;
}
~CServerSocket() 
{closesocket(m_sock);//关闭服务器套接字WSACleanup();
}

环境初始化、网络初始化等操作都封装到单例对象中去了。到这里初步的网络编程框架搭建完成,具体代码可参考初步的网络编程框架搭建。

http://www.dtcms.com/a/335023.html

相关文章:

  • Linux 多线程:线程回收策略 线程间通信(互斥锁详解)
  • Easytier异地组网与Nginx反向代理
  • 昇腾AI自学Day2-- 深度学习基础工具与数学
  • 楼宇自控系统赋能建筑全维度管理,实现环境、安全与能耗全面监管
  • 计算分组内时间列的最大差值
  • 【AI论文】NextStep-1:迈向大规模连续令牌自回归图像生成
  • Warning: Unable to create personal MATLAB work folder:E:\绯荤粺榛樿\鏂囨。\MATLAB
  • 1083. 数列极差问题
  • 【深度学习】基于ESRNet模型的图像超分辨率训练
  • pytest介绍(python测试框架)(@pytest.mark.parametrize、@pytest.fixtures)
  • ClaudeCode使用指南
  • 鲁老师深度学习笔记(1)—最大似然估计
  • Flutter Provider 模式实现:基于 InheritedWidget 的状态管理实现
  • 93、23种设计模式之抽象工厂模式
  • 【读论文】医疗AI大模型:百川开源Baichuan-M2
  • 23. CommonJS 和 ES6 Module 区别
  • 19.3 Transformers量化模型极速加载指南:4倍推理加速+75%显存节省实战
  • ArrayList的contains问题
  • 【C++学习篇】:基础
  • Markdown 生成 Gantt 甘特图
  • STM32硬件SPI配置为全双工模式下不要单独使用HAL_SPI_Transmit API及HAL_SPI_TransmitReceive改造方法
  • 【图像算法 - 14】精准识别路面墙体裂缝:基于YOLO12与OpenCV的实例分割智能检测实战(附完整代码)
  • 人工智能之数学基础:条件独立
  • AI提升SEO关键词搜索效果
  • UDP/TCP套接字编程简单实战指南
  • 数据结构 栈与队列
  • Tomcat配置文件深度解析
  • [安洵杯 2019]Attack
  • STM32F407VET6开发板标准库实现DMA空闲接收和发送
  • 同创物流学习记录2·电车光电