网络编程和密码学基础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;}