远程桌面控制(一)

记录的笔记比较杂乱,把自己感觉重要的内容记录在这里了



提交和推送

提交—>存放

推送 就是把存的东西放到服务器上面

分支切换

git一般有很多分支,我们clone到本地的时候一般都是master分支,那么如何切换到其他分支呢?主要命令如下:

1、查看远程分支

1
$ git branch -a

可以看到,我们现在在什么分支下面。

2、查看本地分支

1
$ git branch

3、切换分支

1
$ git checkout -b v0.9rc1 origin/v0.9rc1

个人感觉,分支的作用就是保存代码版本,几个版本就创建几个分支,所有的分支都先提交存放,最后,在存放的代码版本中选择最终的代码进行推送,放到服务器上面。


远程网络编程

使用全局变量,在main函数之前初始化,在main函数之后释放资源

单例

1
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
1
2
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
1
2
3
4
5
6
7
8
【优点】
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
【缺点】
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
1
2
3
4
单例模式的简单理解
1 单例模式 只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等
2 单例的缺点 就是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
用单例模式,就是在适用其优点的状态下使用


一些设置,免得每次都弹出窗口

子系统改成窗口Windows

链接器 入口点 ->mainCRTStarttup

不会设置那就添加代码,对下面的代码进行排列组合

1
2
3
4
#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" )

包的设计与实现

包的结构

本部分结合代码来介绍包的结构,先给出这部分的代码:

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
class CPacket
{
public:
CPacket():sHead(0), nLength(0), sCmd(0), sSum(0) {}
CPacket(const CPacket& pack) {
sHead = pack.sHead;
nLength = pack.nLength;
sCmd = pack.sCmd;
strData = pack.strData;
sSum = pack.sSum;
}
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) {
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;
for (size_t j = 0; j < strData.size(); j++) {
sum += BYTE(strData[i]) & 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;
}
public:
WORD sHead; // 固定位FE FF
WORD sCmd; // 控制命令
DWORD nLength; // 包长度(从控制命令开始,到和校验结束)
std::string strData; // 包数据
WORD sSum; // 和校验
};

一个含了包头包长度控制命令包数据校验和等字段。包中的内容也是按照这个顺序来的。

字段名 数据类型 描述
sHead WORD 包头标识,固定为0xFEFF,用于标识数据包的开始。
nLength DWORD 包长度,表示从控制命令(sCmd)开始,到校验和(sSum)结束的总长度。
sCmd WORD 控制命令,用于标识数据包的类型或操作指令。
strData std::string 包数据,存储实际的数据内容,长度可变。
sSum WORD 校验和,用于验证数据包的完整性。
1、找包头
1
2
3
4
5
6
7
8
size_t i = 0;
for (; i < nSize; i++) {
if (*(WORD*)(pData + i) == 0xFEFF) {
sHead = *(WORD*)(pData + i);
i += 2; // 找到包头后,就跳过包头,然后就是
break; // 跳出循环
}
}
2、找到包头后,判断如果包头后的数据小于8字节,那么就返回了
1
2
3
4
if (i + 4 + 2 + 2 > nSize) { // 包数据可能不全,或者包头未能全部接收到
nSize = 0;
return;
}

这个8个字节包括包长度nLength(4字节)、控制命令sCmd(2字节)、包校验和(2字节),这里判断包是不是完整的,不完整那就让nSize清零。

3、让nLength提取包长度的值(4字节),现在nLength的值就是从控制命令开始到校验和结束,然后加上偏移量跟nSize比较
1
2
3
4
5
6
7
// * 是解引用操作符,表示从 (DWORD*)(pData + i) 所指向的位置提取 4 字节的值。
// 最终,这 4 字节的值被赋值给 nLength。
nLength = *(DWORD*)(pData + i);
if (nLength + i > nSize) {// 包没有完全接收,就返回,解析失败。
nSize = 0;
return;
}
4、读取包长度,然后如果包没有数据部分,那就说明包没有完全接受
1
2
3
4
5
nLength = *(DWORD*)(pData + i); i += 4;
if (nLength + i > nSize) {// 包没有完全接收,就返回,解析失败。
nSize = 0; // 让nSize清零
return;
}
5、读取命令控制的内容
1
2
3
4
5
6
7
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; // 这是数据的长度,相当于跳过了
}
6、提取校验和(sSum)

校验和的计算需要遍历数据是因为校验和通常是根据数据部分的内容计算得出的,而不仅仅是读取校验和字段本身

1
2
3
4
5
6
7
8
9
10
sSum = *(WORD*)(pData + i); i += 2;
WORD sum = 0;
for (size_t j = 0; j < strData.size(); j++) {
sum += BYTE(strData[i]) & 0xFF;
}
if (sum == sSum) {
nSize = i;
return;
}
nSize = 0;

成员变量顺序导致包长度和包控制命令位置互换

1. 成员变量的内存布局

C++ 标准规定,类成员变量在内存中的布局顺序与它们在类定义中的声明顺序一致。例如:

1
2
3
4
5
6
7
8
class CPacket {
public:
WORD sHead;
WORD sCmd;
DWORD nLength;
std::string strData;
WORD sSum;
};

在上述类中,成员变量的内存布局顺序是:sHeadsCmdnLengthstrDatasSum

2. 封包时的内存拷贝

如果你直接将类对象的内存作为数据包发送,例如:

1
2
CPacket packet;
send((const char*)&packet, sizeof(packet));

这种方式会将类的内存布局直接作为数据包发送。如果类的成员变量顺序与数据包的格式要求不一致,会导致数据包的内容错乱。

3. 动态成员变量

如果类中包含动态成员变量(例如 std::string 或指针),不能直接通过内存拷贝的方式发送整个类对象,因为这些变量的实际数据存储在堆上,而不是类对象的连续内存中。