网络编程和密码学基础001.zip
1. 线程基本操作
#include <iostream>
#include <windows.h>
// 1. 什么是内核对象?
// - 内核对象本质是一个内核层的结构体。只能使用 win 提供的
// - api 操作结构体的内容。
// 2. 内核对象的特性
// - 操作内核对象需要使用句柄,每一个进程都有一张句柄表保存了自己的句柄
// - 大多数的内核对象在操作的时候都需要提供指定的安全描述符(安全属性)。
// - 内核对象的全局性,不同的进程可以通过id或名称打开同一个内核对象。
// - 内核对象的引用计数。每一个内核对象都有引用计数,当引用计数为 0 的时候
// 内核对象会被销毁。CloseHandle 的作用是将引用计数 -1.
// 3. 什么是进程?什么是线程?
// - 进程: 是内核对象,通常由一个可执行文件产生。最少由一块 4 GB 的虚拟空
// 间,一个进程内核对象和一个线程内核对象以及需要用到的模块组成。
// - 线程: 是内核对象,被用于执行代码。线程之间没有从属关系,但是我们把一个
// 进程的第一个线程称为主线程,主线程一旦退出整个程序就会退出。线程最少
// 由一个线程内核对象,和一个线程的栈帧组成。
// 线程的回调函数,参数一般是一个指针
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100; ++i)
{
printf("hello %08X\n", lpThreadParameter);
// 让出当前线程的时间片
Sleep(1000);
}
return 0;
}
int main()
{
// 1. 如何创建一个线程
// - 参数一: 安全属性,NULL 表示默认
// - 参数二: 栈帧的大小,使用默认,通常是 4M
// - 参数三: 回调函数,指明线程的起始位置
// - 参数四: 传递给线程函数的参数
// - 参数五: 线程的创建标志,使用的比较多的是挂起
// - 参数六: 线程的 id ,不需要可以传递 NULL
HANDLE ThreadHandle = CreateThread(NULL, NULL,
WorkerThread, (LPVOID)0x1515, NULL, NULL);
Sleep(2000); // 让出时间片给 ThreadHandle
// 2. 挂起和恢复线程:每一个线程都有一个挂起计数,没挂
// 起一次,计数 +1,当挂起计数为 0 线程继续运行
SuspendThread(ThreadHandle);
system("pause");
ResumeThread(ThreadHandle);
system("pause");
TerminateThread(ThreadHandle, 0);
// 3. 为了确保在主线程执行完毕之前所有的其它线程都
// 正常退出,需要等待其他线程执行完毕
if (ThreadHandle != NULL)
WaitForSingleObject(ThreadHandle, INFINITE);
return 0;
}
2. 多线程问题
#include <iostream>
#include <windows.h>
int g_Number = 0; // 会被多个线程访问到的全局变量
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
g_Number++;
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
g_Number++;
}
return 0;
}
int main()
{
HANDLE hThread1 = NULL, hThread2 = NULL;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
printf("%d", g_Number);
system("pause");
return 0;
}
// 单条的 g_Number++ 被翻译成了三条汇编指令,如果不能保证三条指令的
// [连续执行],就会由于线程的切换产生问题,最终结果出错。
/*
[1]: mov eax,dword ptr [g_Number (07AA138h)] // g_Number(0)
[1]: add eax,1 // g_Number(0) eax(1)
[1]: mov dword ptr [g_Number (07AA138h)],eax // g_Number(1) eax(1)
[2]: mov eax,dword ptr [g_Number (07AA138h)] // g_Number(1)
[2]: add eax,1 // g_Number(1) eax(2)
[2]: mov dword ptr [g_Number (07AA138h)],eax // g_Number(2) eax(2)
[1]: mov eax,dword ptr [g_Number (07AA138h)] // g_Number(2)
[1]: add eax,1 // g_Number(2) eax(3)
[2]: mov eax,dword ptr [g_Number (07AA138h)] // g_Number(2)
[2]: add eax,1 // g_Number(2) eax(3)
[2]: mov dword ptr [g_Number (07AA138h)],eax // g_Number(3) eax(3)
[1]: mov dword ptr [g_Number (07AA138h)],eax // g_Number(3) eax(3)
*/
3. 原子操作
#include <iostream>
#include <windows.h>
// 原子操作:本质上就是将C语言代码解释成了单条的汇编指令
// 缺陷:只支持对最长8字节的整数类型执行算数运行
// 应用场景: 在进行内联hook的时候可以解决线程安全
// 函数: InterlockedXXXX 系列的函数
long g_Number = 0; // 会被多个线程访问到的全局变量
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
InterlockedIncrement(&g_Number);
// lock inc dword ptr ds:[g_Number]
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
InterlockedIncrement(&g_Number);
}
return 0;
}
int main()
{
HANDLE hThread1 = NULL, hThread2 = NULL;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
printf("%d", g_Number);
system("pause");
return 0;
}
4. 临界区
#include <iostream>
#include <windows.h>
// 临界区(关键段): 是一个结构体,通过结构体内的一些字段判断
// 执行当前代码的线程是否是对应的线程,如果不是,就阻塞。拥
// 有(线程拥有者)的概念
// 优点: 可以保护一段代码,执行速度快
// 缺点: 拥有该临界区的线程一旦崩溃,就会产生死锁。
// 会被多个线程访问到的全局变量
long g_Number = 0;
// 临界区结构体,是一个不确定的结构体,使用前必须初始化
// LONG LockCount: 如果被使用了就是 -2 否则是 -1
// LONG RecursionCount: 表示当前被进入的多少次
// HANDLE OwningThread: 当前被哪一个线程使用了
CRITICAL_SECTION CriticalSection = { 0 };
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
// 进入临界区,写在需要被保护的代码块的上方
EnterCriticalSection(&CriticalSection);
// 需要被保护的代码
g_Number++;
// 离开临界区,写在需要被保护的代码块的下方
// 进入了一个临界区多少次,就应该相应的离开多少次
LeaveCriticalSection(&CriticalSection);
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
EnterCriticalSection(&CriticalSection);
g_Number++;
LeaveCriticalSection(&CriticalSection);
}
return 0;
}
int main()
{
HANDLE hThread1 = NULL, hThread2 = NULL;
// 初始化临界区
InitializeCriticalSection(&CriticalSection);
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
// 使用后需要释放
DeleteCriticalSection(&CriticalSection);
printf("%d", g_Number);
system("pause");
return 0;
}
6. 互斥体
#include <iostream>
#include <windows.h>
// 互斥体: 是一个内核对象
// 优点: 拥有临界区的特性(线程拥有者),但是不会产生死锁,且跨进程
// 缺点: 慢。
// 应用场景: 用于进行防双开
// 相关函数:
// - 保护: WaitForSingleObject
// - 离开: RealseMutex
// 会被多个线程访问到的全局变量
long g_Number = 0;
// 创建互斥体的函数
// 参数1: 安全属性
// 参数2: 是否设置当前线程为拥有者(设置了就是无信号)
// 参数3: 互斥体的名称,用于打开
HANDLE Mutex = CreateMutex(NULL, FALSE, L"my_mutex");
// 初始(有信号) -> 等待函数(有信号->无信号) -> Release(无信号->有信号)
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
// 进入受保护的代码(有信号->无信号)
WaitForSingleObject(Mutex, INFINITE);
// 需要被保护的代码
g_Number++;
// 将互斥体设置为无信号有信号
ReleaseMutex(Mutex);
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Mutex, INFINITE);
g_Number++;
ReleaseMutex(Mutex);
}
return 0;
}
// 对线程拥有者的测试
DWORD WINAPI ThreadPro3(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
// 拥有指定互斥体的线程,可以无限次的等待互斥体
WaitForSingleObject(Mutex, INFINITE);
g_Number++;
}
return 0;
}
int main()
{
HANDLE hThread1 = NULL, hThread2 = NULL;
// 测试线程拥有者的
HANDLE hThread = CreateThread(NULL, NULL, ThreadPro3, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
printf("%d", g_Number);
system("pause");
return 0;
}
7. 事件
#include <iostream>
#include <windows.h>
// 互斥: 通常是多个线程访问同一个资源
// 同步: 通常是需要多个线程按照指定顺序执行
// 事件对象: 是一个内核对象
// 特点: 可以设置为手动操作,也可以设置为自动操作
// 缺点: 慢。
// 相关函数:
// - 保护: WaitForSingleObject
// - 离开: RealseMutex
// 会被多个线程访问到的全局变量
long g_Number = 0;
// 创建事件的函数
// 参数1: 安全属性
// 参数2: 是否是手动状态,一旦设置成手动状态,等待函数就没有副作用了
// 参数3: 事件的初始信号
// 参数3: 互斥体的名称,用于打开
HANDLE Event = CreateEvent(NULL, TRUE, TRUE, L"my_event");
// 当设置为手动状态之后,通常就不能用于进行互斥了,会被用于同步
// 原因是, ResetEvent 函数本身并不是原子操作
// 自动:初始(有信号) -> 等待函数(有信号->无信号) -> Release(无信号->有信号)
// 手动:初始(有信号) -> 等待函数(有信号) -> Release(有信号)
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
// 进入受保护的代码(有信号->无信号)
WaitForSingleObject(Event, INFINITE);
// 需要被保护的代码
g_Number++;
// 将互斥体设置为(无信号->有信号)
SetEvent(Event);
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Event, INFINITE);
g_Number++;
SetEvent(Event);
}
return 0;
}
int main()
{
HANDLE hThread1 = NULL, hThread2 = NULL;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
printf("%d", g_Number);
system("pause");
return 0;
}
5. 等待函数
#include <iostream>
#include <windows.h>
// 等待函数的使用: 等待指定的一个或一组内核对象变为有信号状态
// 使用的函数:
// - 一个: WaitForSingleObject(内核对象句柄,等待时长)
// - 一组: WaitForMultipleObjects()
// 等待函数的副作用:
// - 改变被等待内核对象的信号状态(有信号->无信号)
// - 基于这个原理,才能实现后面的内核对象同步
// 不断的输出传入的参数
DWORD WINAPI WorkerThread1(LPVOID param)
{
for (int i = 0; i < 5; ++i)
{
printf("%d - %d\n", (DWORD)param, i);
Sleep(500);
}
return 0;
}
// 不断的输出传入的参数
DWORD WINAPI WorkerThread2(LPVOID param)
{
for (int i = 0; i < 5; ++i)
{
printf("%d - %d\n", (DWORD)param, i);
Sleep(100);
}
return 0;
}
int main()
{
HANDLE Threads[10] = { 0 };
for (int i = 0; i < 10; ++i)
{
if (i % 2)
Threads[i] = CreateThread(NULL, NULL, WorkerThread1, (LPVOID)(i + 1), NULL, NULL);
else
Threads[i] = CreateThread(NULL, NULL, WorkerThread2, (LPVOID)(i + 1), NULL, NULL);
}
// 1. 需要等待的内核对象的数量(数组的元素个数)
// 2. 保存所有需要等待的内核对象的数组
// 3. 是否等待所有的内核对象成为有信号状态
// 4. 等待时长
WaitForMultipleObjects(10, Threads, true, INFINITE);
// 什么时候函数会退出等待
// 1. 目标对象变成有信号(激发)状态
// 2. 达到了等待时长,函数返回等待失败
return 0;
}
8. 信号量
#include <iostream>
#include <windows.h>
// 互斥: 通常是多个线程访问同一个资源
// 同步: 通常是需要多个线程按照指定顺序执行
// 信号量: 是一个内核对象
// 特点: 可以进行多次的上锁操作
// 缺点: 慢。
// 应用场景:控制同时执行的线程的最大个数
// 信号量通常不会单独使用,一般要结合互斥体或事件
// 会被多个线程访问到的全局变量
long g_Number = 0;
// 创建信号量的函数
// 参数1: 安全属性
// 参数2: 初始的信号个数
// 参数3: 总的信号个数(数量没有限制)
// 参数3: 互斥体的名称,用于打开
HANDLE Semaphore = CreateSemaphore(NULL, 20, 20, L"my_semaphore");
//初始(有n个信号) -> 等待函数(信号n->无信号n-1) -> Release(信号n->信号n+1)
// 线程一: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
// 进入受保护的代码(将信号量的信号数量 - 1)
WaitForSingleObject(Semaphore, INFINITE);
// 需要被保护的代码
g_Number++;
// 参数2: 将信号数量加上价格
// 参数3: 增加信号之前,一共由多少个信号,NULL 表示不需要知道
ReleaseSemaphore(Semaphore, 1, NULL);
// 将信号量的信号数量 + 1
}
return 0;
}
// 线程二: 给 g_Number 自增 100000 次
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter)
{
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(Semaphore, INFINITE);
g_Number++;
ReleaseSemaphore(Semaphore, 1, NULL);
}
return 0;
}
// 输出参数
DWORD WINAPI ThreadPro3(LPVOID lpThreadParameter)
{
while (true)
{
WaitForSingleObject(Semaphore, INFINITE);
printf("%d\n", lpThreadParameter);
Sleep(1000);
ReleaseSemaphore(Semaphore, 1, NULL);
}
return 0;
}
int main()
{
// 控制线程的数量
HANDLE Threads[1000] = { 0 };
for (int i = 0; i < 1000; ++i)
Threads[i] = CreateThread(NULL, NULL, ThreadPro3, (LPVOID)(i + 1), NULL, NULL);
WaitForMultipleObjects(1000, Threads, true, INFINITE);
// 线程的互斥
HANDLE hThread1 = NULL, hThread2 = NULL;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
if (hThread1 != NULL && hThread2 != NULL)
{
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
}
printf("%d", g_Number);
system("pause");
return 0;
}