RemoteControl: 基于VS2022环境下利用C/C++和MFC实现的远程控制项目 (gitee.com)
服务端网络编程
全局区的定义实现总在main函数之前,且肯定是单线程环境,无竞态条件race condition。
网络编程选择的TCP的socket编程
Windows服务端TCP的socket流程:①WSAStartup ②socket() ③bind() ④listen() ⑤accept() ⑥recv/send ⑦closesocket()
单例设计模式
单例设计模式简单来说始终保持全局有且仅有一个实例化的对。我这边应用到服务端对象
为什么要单例模式呢?:当我们分析具体情况时,发现服务端对象有且仅有一个,且在多线程环境下要保证当前线程的服务端对象不会被其他线程释放掉该服务端对象
流程做法:对服务端socket的构造函数私有化,从语法上隔断外部的实例化对象,最后可以通过友元类/友元函数实例化对象或者静态函数实例化对象。这样保证的多线程环境下的全局唯一和避免了race condition的安全性
单例设计模式的分类
饿汉式:我很饿,这个对象必须提前准备好来保证我饿了立马吃
class Singleton {
private:
// 私有的静态成员变量
static Singleton instance;
// 私有构造函数,防止外部实例化
Singleton() {}
public:
// 公共的静态方法,用于获取实例
static Singleton& getInstance() { return instance; }
};
// 在类外初始化静态成员变量
Singleton Singleton::instance;
懒汉式:我很懒,这个对象等我饿了想吃的时候再做着吃
#include <mutex>
class Singleton {
private:
// 私有的静态成员变量
static Singleton* instance;
// 私有构造函数,防止外部实例化
Singleton() {}
// 静态的互斥锁,用于确保多线程环境下的安全性
static std::mutex mutex;
public:
// 公共的静态方法,用于获取实例
static Singleton* getInstance() {
// 使用双重检查锁定提高性能
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};
// 在类外初始化静态成员变量
Singleton* Singleton::instance = nullptr;
懒汉式中总会有一个判断饿了需要对象时才获取,那么必然有race condition是线程不安全的,通常采用加锁和双重检测来保证线程安全
但是又发现一个问题,我们创建的唯一实例是在堆区的,总是要保证好良好的逻辑在合适的地方释放内存delete。这是容易忘容易错的,因此我联想到的智能指针的计数指针
shared_ptr/weak_ptr
shared_ptr是引用计数行智能指针,当引用的计数降为0则销毁对象资源。:控制对象的生命周期,强引用。
weak_ptr也是引用计数行智能指针,但是它不增加对象的引用次数,即弱weak引用:不会控制对象的生命周期,但它知道对象是否还活着。
二者原子操作性能不俗没用锁,安全级别和std::string和STL一样
我通过智能指针结合RAII思想手法即可以保证内存的合理释放
ServerSocket.h初步实现
#pragma once
#include <mutex>
class CServerSocket
{
private:
SOCKET m_client;//客户端的socket
SOCKET m_skt;//当前服务端的socket
//static CServerSocket* m_instance;
static std::shared_ptr<CServerSocket> m_instance; // 唯一实例化的对象且加上了智能指针的管理
static std::mutex mtx;// 静态的互斥锁,用于确保多线程环境下的安全性 C++11后可以不需要类外初始化mutex
public:
//初始化套接字环境(Windows编程特有)
BOOL InitSktEnv();
//初始化套接字
BOOL InitSkt(short port);
//连接客户端
BOOL AcceptClient();
//TODO:发送数据的操作Send
bool Send(const char* pData, int nSize);
//关闭客户端
void CloseClient();
static CServerSocket* GetInstance();
//处理命令
int DealCommand();
//
~CServerSocket() {
closesocket(m_skt);
WSACleanup();
}
private:
CServerSocket() {
m_client = INVALID_SOCKET;// -1
if (InitSktEnv() == FALSE) {
//Windows API 函数用于创建一个消息对话框
MessageBox(NULL, //无父窗口,即该消息对话框为顶层窗口
_T("无法初始化套接字环境,请检查网路设置!"), //要显示的文本
_T("初始化错误!"), //消息对话框的标题
MB_OK | MB_ICONERROR //消息框的风格,表示一个包含 OK 按钮的消息框,并显示错误图标。
); //_T 是一个宏,用于支持在编译时实现 Unicode 和 ANSI 字符串切换。使代码更具可移植性,可以在不同的编译设置下工作。
exit(0);//程序正常退出
}
//创建套接字
m_skt = socket(PF_INET, SOCK_STREAM, 0);
//TODO:m_skt创建失败的日志
}
CServerSocket(const CServerSocket& ss) {
m_skt = ss.m_skt;
m_client = ss.m_client;
}
CServerSocket& operator=(const CServerSocket& ss) {}
};
ServerSocket.cpp初步实现
#include "ServerSocket.h"
//记得类外初始化静态成员变量
std::shared_ptr<CServerSocket> CServerSocket::m_instance = nullptr;
std::mutex CServerSocket::mtx;
CServerSocket* pserver = CServerSocket::GetInstance();//这样就可以外部获取一个全局唯一对象的指针了,且总是在全局区实例化好了对象
BOOL CServerSocket::InitSktEnv()
{
//Windows下Socket网络编程的API简称WSA
WSADATA data;//存储Winsock的版本信息
//WSAStartup初始化Winsock库
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
TRACE(_T("Init Winsock Library Failed\r\n"));
return FALSE;
}
TRACE(_T("Init Winsock Library Succeeded\r\n"));
return TRUE;
}
BOOL CServerSocket::InitSkt(short port)
{
if (m_skt == -1) {
TRACE(_T("m_skt never existed\r\n"));
return false;
}
sockaddr_in serv_adr;// IPv4 地址的结构体
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET; //地址族设置(AF_INET 表示 IPv4)
serv_adr.sin_addr.s_addr = INADDR_ANY;//服务端可以通过任何网络接口连接,而不用担心特定的网络接口或 IP 地址
serv_adr.sin_port = htons(port);//设置连接的端口号
//绑定地址
if (bind(m_skt, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
TRACE(_T("bind m_skt failed\r\n"));
return false;
}
//TODO:
if (listen(m_skt, 1) == -1) {//设置接收队列容量只有 1
TRACE(_T("listen m_skt failed\r\n"));
return false;
}
//一切正常
return true;
}
BOOL CServerSocket::AcceptClient()
{
TRACE("Enter AcceptClient\r\n");
sockaddr_in client_adr;
//char buffer[1024];
int cli_sz = sizeof(client_adr);
m_client = accept(m_skt, (sockaddr*)&client_adr, &cli_sz);
TRACE("m_client socket = %d\r\n", m_client);
if (m_client == -1)return false;
return true;
//recv(client, buffer, sizeof(buffer), 0);
//send(client, buffer, sizeof(buffer), 0);
return 0;
}
void CServerSocket::CloseClient()
{
if (m_client != INVALID_SOCKET) {
closesocket(m_client);
m_client = INVALID_SOCKET;
}
}
CServerSocket* CServerSocket::GetInstance()
{
if (m_instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (m_instance == nullptr) {
m_instance.reset(new CServerSocket());
}
}
return m_instance.get();
}