远程桌面控制(一)

Cpacket类 (协议包封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#pragma once
#include "pch.h"
#include "framework.h"
#pragma pack(push)
#pragma pack(1)
class CPacket
{
public:
CPacket():sHead(0), nLength(0), sCmd(0), sSum(0) {}
// 打包
CPacket(WORD nCmd, const BYTE* pData, size_t nSize)
{
sHead = 0xFEFF; // 设置数据包头部标识
nLength = nSize + 4; // 计算设置数据包长度
sCmd = nCmd; // 设置命令号
if (nSize > 0){ // 处理数据部分
strData.resize(nSize);
memcpy((void*)strData.c_str(), pData, nSize);
}
else{
strData.clear();
}
// 计算并设置校验和
sSum = 0;
for (size_t j = 0; j < strData.size(); j ++ )
{
sSum += BYTE(strData[j]) & 0xFF;
}
}

// 拷贝构造函数
CPacket(const CPacket& pack) {
sHead = pack.sHead;
nLength = pack.nLength;
sCmd = pack.sCmd;
strData = pack.strData;
sSum = pack.sSum;
}
//nSize 表示 输入缓冲区 pData 的数据总长度(字节数)
// 解包
CPacket(const BYTE* pData, size_t& nSize) {
size_t i = 0;
for (; i < nSize; i++) {
if (*(WORD*)(pData + i) == 0xFEFF) {
sHead = *(WORD*)(pData + i);
i += 2;
break;
}
}
if (i + 4 + 2 + 2 > nSize) { // 包数据可能不全,或者包头未能全部接收到
nSize = 0;
return;
}
nLength = *(DWORD*)(pData + i); i += 4;
if (nLength + i > nSize) {// 包没有完全接收,就返回,解析失败。
nSize = 0;
return;
}
sCmd = *(WORD*)(pData + i); i += 2;
if (nLength > 4) {
// 调整数据的大小 让 nLength 减去控制命令和检验和的大小
strData.resize(nLength - 2 - 2);
memcpy((void*)strData.c_str(), pData + i, nLength - 4);
i += nLength - 4;
}
sSum = *(WORD*)(pData + i); i += 2;
WORD sum = 0;
// 让数据&0xFF 得到校验和与包中的校验和进行比较
for (size_t j = 0; j < strData.size(); j++) {
sum += BYTE(strData[j]) & 0xFF;
}
if (sum == sSum) {
nSize = i;
return;
}
nSize = 0;
}
~CPacket(){}
// 运算符重载,用于对象之间的赋值操作
CPacket& operator=(const CPacket& pack) {
if (this != &pack){
sHead = pack.sHead;
nLength = pack.nLength;
sCmd = pack.sCmd;
sSum = pack.sSum;
}
return *this;
}
int Size() { // 包数据的大小
return nLength + 6;
}
// 返回二进制包
const char* Data() {
strOut.resize(nLength + 6); // 总长度 = 包头(2) + nLength(4) + 数据 + 校验和(2)
BYTE* pData = (BYTE*)strOut.c_str();
*(WORD*)pData = sHead; pData += 2; // 写入包头
*(DWORD*)(pData) = nLength; pData += 4; // 写入长度
*(WORD*)pData = sCmd; pData += 2; // 写入命令
// 复制数据
memcpy(pData, strData.c_str(), strData.size()); pData += strData.size();
*(WORD*)pData = sSum; // 写入校验和
return strOut.c_str();
}

public:
WORD sHead; // 固定 位0xFEFF
DWORD nLength; // 包长度(从控制命令开始,到和校验结束)
WORD sCmd; // 控制命令
std::string strData; // 包数据
WORD sSum; // 和校验
std::string strOut; // 整个包的二进制数据
};

CServerSocket 类(服务器通信封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#pragma pack(pop)
class CServerSocket
{
public:
// 使用静态函数的方式访问private的
// 获取单例
static CServerSocket* getInstance()
{ // 静态函数没有this指针,没办法访问成员变量
// 把成员变量也搞成静态的
if (m_instance == NULL)
{
m_instance = new CServerSocket();
}
return m_instance;
}
// 初始化监听
bool InitSocket()
{
if (m_sock == -1) return false;
// ToDo 校验
sockaddr_in serv_adr;
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
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) // 允许一个待连接队列
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;
}


#define BUFFER_SIZE 4096
// 处理命令
int DealCommand()
{
if (m_client == -1) return -1;
//char buffer[1024] = "";
char* buffer = new char[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
size_t index = 0;
while (true)
{
size_t len = recv(m_client, buffer + index, BUFFER_SIZE - index, 0);
if (len <= 0) // 连接错误
return -1;
index += len;
len = index;
m_packet = CPacket((BYTE*)buffer, len); // 解析数据
if (len > 0) {
memmove(buffer, buffer + len, BUFFER_SIZE - len);
index -= len;
return m_packet.sCmd; // 返回有效命令
}
}

return -1;

}

// 原始数据发送
bool Send(const char* pData, int nSize)
{
if (m_client == -1) return false;
return send(m_client, pData, nSize, 0) > 0;
}

// 发送CPacket封装的包
bool Send(CPacket& pack) {
if (m_client == -1) return false;
// (const char*)&pack:将 CPacket 对象 pack 的地址强制转换为 const char* 类型
// 表示发送的数据的起始地址。
return send(m_client, pack.Data(), pack.Size(), 0) > 0;
}

// 解析包中的文件路径
bool GetFilePath(std::string& strPath) {
if (m_packet.sCmd == 2) { // 控制命令为2代表获取文件列表 只有控制命令为获取文件列表
strPath = m_packet.strData;
return false;
}
return true;
}
private:
CPacket m_packet; // 数据包对象
SOCKET m_client; // 客户端套接字
SOCKET m_sock; // 服务器套接字
// 赋值和构造都是私有,不允许别人使用
CServerSocket& operator=(const CServerSocket& ss){}
CServerSocket(const CServerSocket& ss){
m_sock = ss.m_sock;
m_client = ss.m_client;
}
// 私有构造函数
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();
};
// Windows 套接字环境初始化
BOOL InitSockEnv()
{
WSADATA data;
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) // TODO:返回值处理
{
return false;
}
return true;
}

static void releaseInstance()
{
if (m_instance != NULL)
{
CServerSocket* tmp = m_instance;
// 指针为空
m_instance = NULL;
delete tmp;
}
}
// 把这个成员变量也搞成静态的,这样静态函数就能访问这个成员变量了
//单例实例
static CServerSocket* m_instance;
// 当构造的时候,就调用getInstance()
// 析构的时候就调用releaseInstance()
class CHelper {
public:
CHelper() {
CServerSocket::getInstance();
}
~CHelper()
{
CServerSocket::releaseInstance();
}
};
//辅助类实例
static CHelper m_helper;
};

单例模式是怎么实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CServerSocket {
private:
static CServerSocket* m_instance;
static CHelper m_helper; // 辅助类,确保构造和析构
public:
static CServerSocket* getInstance() {
if (!m_instance) m_instance = new CServerSocket();
return m_instance;
}
private:
CServerSocket() { ... } // 私有构造
~CServerSocket() { ... } // 私有析构
class CHelper { // RAII 管理单例生命周期
public:
CHelper() { CServerSocket::getInstance(); }
~CHelper() { CServerSocket::releaseInstance(); }
};
};

设计思想

  • 禁止用户直接构造,必须通过 getInstance() 获取唯一对象。
  • CHelper 辅助类:
    • 构造时自动创建 CServerSocket 单例。
    • 析构时自动释放,防止内存泄漏。

总结

模块 核心功能
CPacket 协议封装,提供 打包/解包 能力
CServerSocket 服务器核心,单例管理收发数据
单例模式 确保全局唯一服务器实例
字节对齐 (#pragma pack) 保证数据包严格对齐,避免解析错误

适用场景

  • 小型客户端-服务器通信(如文件传输、远程控制等)。
  • 可扩展性较强,sCmd 可用于扩展不同业务逻辑。

CServerSocket 单例实现与 CPacket 打包构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ServerSocket.cpp

#include "pch.h"
#include "ServerSocket.h"

////CServerSocket server;
CServerSocket* CServerSocket::m_instance = NULL; // 静态成员初始化
CServerSocket::CHelper CServerSocket::m_helper;
//指针没有对象,是全局的
CServerSocket* pserver = CServerSocket::getInstance();

// 打包
inline CPacket::CPacket(WORD nCmd, const BYTE* pData, size_t nSize) {
sHead = 0xFEFF; // 固定协议标识符
nLength = nSize + 4; // +4 = sCmd(2) + sSum(2)
sCmd = nCmd; // 存储业务命令
strData.resize(nSize);
memcpy((void*)strData.c_str(), pData, nSize);
sSum = 0;
for (size_t j = 0; j < strData.size(); j++) {
sSum += BYTE(strData[j]) & 0xFF;
}
}

第一部分:CServerSocket 单例模式实现

1
2
3
CServerSocket* CServerSocket::m_instance = NULL;  // 静态成员初始化
CServerSocket::CHelper CServerSocket::m_helper; // RAII辅助对象
CServerSocket* pserver = CServerSocket::getInstance(); // 全局访问点

设计模式解析

  1. 单例控制

    • m_instance 作为静态指针,保证全进程唯一实例
    • 通过 getInstance() 获取唯一实例,首次调用时构造对象
  2. RAII助手类 (CHelper)

    1
    class CHelper {public:    CHelper() { CServerSocket::getInstance(); }  // 构造时触发单例创建    ~CHelper() { CServerSocket::releaseInstance(); } // 析构时释放单例};
    • 构造阶段:程序启动时,全局 m_helper 的构造自动触发单例创建
    • 析构阶段:程序退出时,自动调用 releaseInstance() 释放资源
  3. 全局访问点

    • pserver 提供全局统一访问入口

    • 使用示例:

      1
      pserver->InitSocket();  // 任何地方都可直接使用

第二部分:CPacket 打包构造函数

构造函数原型

1
CPacket::CPacket(WORD nCmd, const BYTE* pData, size_t nSize) 

参数说明

参数 类型 说明
nCmd WORD 协议命令字(2字节)
pData const BYTE* 负载数据指针
nSize size_t 负载数据长度

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// RemoveCtrl.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include "framework.h"
#include "RemoveCtrl.h"
#include "ServerSocket.h"
#include <direct.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//#pragma comment(linker, "/subsystem:windows /entry:WinMainCRTStartup" )
//#pragma comment(linker, "/subsystem:windows /entry:mainCRTStartup" )
//#pragma comment(linker, "/subsystem:console /entry:WinMainCRTStartup" )
//#pragma comment(linker, "/subsystem:console /entry:mainCRTStartup" )


// 唯一的应用程序对象

CWinApp theApp;

using namespace std;

// 将给定的二进制数据以十六进制格式打印出来,并将输出发送到调试器(通过OutputDebugStringA)
void Dump(BYTE* pData, size_t nSize) {
//
std::string strOut;
for (size_t i = 0; i < nSize; i++) {
char buf[8] = "";
if (i > 0 && (i % 16 == 0)) strOut += "\n";
snprintf(buf, sizeof(buf), "%02X ", pData[i] & 0xFF);
strOut += buf;
}
strOut += "\n";
OutputDebugStringA(strOut.c_str());
}


int MakeDriverInfo() { // 1 => A 2 => B 3 => C
std::string result;
for (int i = 1; i <= 26; i++) {
//查看每个驱动器是否存在(通过_chdrive函数)
if (_chdrive(i) == 0) {
//将存在的驱动字母(如A、B、C)拼接成一个字符串,用逗号分隔。
if (result.size() > 0)
result += ',';
result += 'A' + i - 1;
}
}
//最后调用 CServerSocket::getInstance()->Send(CPacket()) 发送数据包。
//CServerSocket::getInstance()->Send(CPacket(1,(BYTE*)result.c_str(),result.size()));
CPacket pack(1, (BYTE*)result.c_str(), result.size());
Dump((BYTE*)pack.Data(), pack.Size());
//CServerSocket::getInstance()->Send(pack);

return 0;
}

#include <stdio.h>
#include <io.h>
#include <list>
#include "RemoteCtrl.h"
/*结构体和类十分相似 区别是结构体默认是public 类默认是private*/
typedef struct file_info{
file_info() {
IsInvalid = 0;
IsDirectory = -1;
HasNext = true;
memset(szFileName, 0, sizeof(szFileName));
}
BOOL IsInvalid; // 是否有效
BOOL HasNext; // 是否还有后续 0 否 1 是
char szFileName[256]; // 文件名
BOOL IsDirectory; // 是否为目录 0 否 1 是


}FILEINFO, *PFILEINFO;
int main()
{
int nRetCode = 0;

HMODULE hModule = ::GetModuleHandle(nullptr);

if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 在此处为应用程序的行为编写代码。
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
// 套接字初始化

// 观察文件
int nCmd = 1;
switch (nCmd)
{
case 1: // 查看磁盘分区
MakeDriverInfo();
break;
case 2:
MakeDirectoryInfo();
break;
}
}
}
else
{
// TODO: 更改错误代码以符合需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}

return nRetCode;
}