<think>
我们正在讨论的是非阻塞recv的hook。在非阻塞模式下,recv函数在没有数据可读时会立即返回,并设置错误码为WSAEWOULDBLOCK。我们的目标是在这种情况下能够主动触发HookedRecv函数,以便执行自定义操作(例如注入数据或触发事件)。
然而,用户的问题是“如何主动进入HookedRecv函数”,这意味着用户希望在不依赖目标进程正常调用recv的情况下,能够主动触发我们的钩子函数。在非阻塞模式下,由于recv不会被阻塞,所以通常不会主动调用recv,而是通过其他机制(如select、WSAPoll等)来检测可读事件。因此,我们需要一种机制来模拟recv被调用的情况,从而进入钩子函数。
思路如下:
1. 在hook的DLL中,我们不仅钩取recv,还要创建一个专门的触发机制。
2. 这个触发机制可以由外部进程(控制程序)通过进程间通信(IPC)来激活,告诉DLL:“现在请主动触发某个socket的recv钩子函数”。
3. 在DLL内部,我们可以创建一个隐藏的线程,该线程等待一个事件(Event)信号。当外部进程需要触发时,设置这个事件,然后该线程就会执行我们指定的操作,例如调用recv(但注意,直接调用recv可能会干扰正常的网络通信,所以我们需要谨慎设计)。
但是,直接调用recv可能会被hook本身捕获,导致递归。因此,我们可以:
- 在hook的DLL中保存原始recv函数的指针,这样我们可以绕过钩子直接调用原始recv。
- 但是,我们并不想真的去调用原始recv(因为可能没有数据,会返回错误),而是想触发我们的钩子函数。因此,我们可以模拟一次recv调用,即直接调用HookedRecv函数(但注意,HookedRecv函数内部会调用原始recv,所以我们不能直接这样调用,否则会真的进行recv操作,而且还会再次进入钩子逻辑,造成递归)。
另一种思路:我们不想实际进行recv操作,而是想让钩子函数被调用,然后执行我们设定的回调(比如发送假数据)。那么,我们可以设计一个假的recv调用,但是这样需要目标进程中的一个线程来执行这个调用。
因此,我们可以:
1. 在目标进程中创建一个线程,这个线程执行一个循环,等待触发信号(例如事件对象)。
2. 当事件被触发时(由控制程序通过进程间通信设置),该线程就会调用recv函数(注意,这个调用会被我们的钩子捕获),从而进入HookedRecv函数。
但是,这里有一个关键点:非阻塞socket上调用recv,如果没有数据,会立即返回WSAEWOULDBLOCK,然后我们的钩子函数就会执行回调。所以,我们可以通过这种方式来主动触发钩子函数的回调部分。
具体步骤:
1. 在DLL中,除了钩取recv,我们还创建一个事件对象(命名事件或匿名事件,由于跨进程,建议命名事件)和一个线程。该线程等待事件对象。
2. 外部控制程序通过设置这个事件对象来通知目标进程中的DLL触发某个socket的钩子。
3. 当事件被触发时,DLL中的线程就会对指定的socket调用recv函数(使用原始recv或者通过钩子后的recv?注意,我们调用recv函数,无论它是否阻塞,都会被我们的钩子捕获。但由于是非阻塞,如果没有数据,就会返回错误,然后我们的钩子函数就会执行回调。
注意:我们必须确保对socket的recv调用是安全的,因为同一个socket可能在多个线程中被操作。因此,我们需要考虑线程安全(比如使用临界区或互斥量保护对socket的访问)。
实现方案修改:
在DLL中:
1. 创建一个全局事件对象(命名事件,以便控制程序可以打开并设置它)。
2. 创建一个线程,该线程等待这个事件对象。
3. 当事件被触发时,该线程从队列中取出一个socket(或者我们预先设定一个socket),然后对这个socket调用recv(使用我们钩子后的recv函数?但实际上,我们想要进入钩子函数,所以直接调用recv即可,因为recv已经被钩取,所以调用recv会进入HookedRecv)。
但是,我们不能直接调用HookedRecv,因为那样会跳过原始recv的调用。我们希望在调用recv时,就像目标进程自己调用的一样,这样就会经过我们的钩子函数。
因此,我们可以这样:
在DLL的触发线程中,使用原始recv函数指针来调用recv?不对,这样就不会进入钩子。所以我们应该调用recv(即钩子后的函数),但是我们在DLL中已经将IAT中的recv替换成了HookedRecv,所以目标进程中任何对recv的调用(包括我们在这个线程中的调用)都会进入HookedRecv。
所以,我们可以在触发线程中这样写:
SOCKET s = ...; // 需要触发的socket
char buf[1];
recv(s, buf, 0, 0); // 调用recv,但长度为0,这样不会实际读取数据,但会触发一次recv调用
注意:recv的flags参数为0,长度为0。根据标准,recv可以指定长度为0,此时函数会立即返回0(如果有数据则会返回0,但非阻塞且没有数据则会返回SOCKET_ERROR,错误码为WSAEWOULDBLOCK)。因此,我们使用长度为0的recv调用来触发钩子函数,而不会实际读取数据。
这样,当触发线程调用recv(s, buf, 0, 0)时:
- 如果socket有数据,则recv可能会返回0(实际上,长度为0的recv调用总是返回0,不管有没有数据,因为不读取任何数据)[参考MSDN]。
- 如果socket处于非阻塞模式且没有数据,则recv调用会失败,错误码为WSAEWOULDBLOCK,然后我们的钩子函数就会检测到这个错误并触发回调。
因此,我们可以通过这种方式来主动触发回调,而不会实际读取数据。
但是,需要注意的是:长度为0的recv调用在阻塞模式下可能会阻塞(如果没有数据),但我们这里是非阻塞,所以会立即返回错误。
修改后的DLL代码:
在DLL中,我们需要:
1. 创建事件和线程。
2. 线程函数等待事件,当事件被触发时,对指定的socket执行recv(s, NULL, 0, 0)(注意,缓冲区可以为NULL,因为长度为0,不会读取数据)。
另外,我们需要一个队列来存储需要触发的socket,因为事件触发可能针对多个socket。所以,我们使用一个线程安全的队列(例如用临界区保护)。
控制程序如何指定要触发的socket?我们可以通过进程间通信(例如命名管道、共享内存)来传递socket值。但为了简单,我们可以在DLL中导出一个函数,让控制程序调用(通过跨进程调用函数的方式)。
由于控制程序在另一个进程,不能直接调用目标进程中的函数,所以我们可以:
1. 使用事件对象来通知目标进程中的DLL线程。
2. 同时,使用共享内存(或文件映射)来传递socket句柄。
但是,socket句柄在目标进程中有效,在控制进程中无效。所以,控制程序只能通过DLL注入时保存的socket列表来指定(或者通过其他方式,比如在DLL中根据某个标识来获取socket)。不过,更直接的方法是:控制程序将要触发的socket句柄写入一个共享内存区域,而这个共享内存区域在目标进程和控制进程之间是共享的。注意:socket句柄是进程相关的,但是我们可以通过`DuplicateHandle`来复制句柄使它在目标进程中有效。然而,这里我们假设控制程序已经知道目标进程中的socket句柄(实际上,在hook recv的时候,我们可以记录下所有被hook的socket,然后控制程序通过某种方式获取这些socket句柄)。
为了简化,我们假设控制程序已经通过其他方式获取了目标进程中的socket句柄(例如,通过DLL将socket句柄列表发送给控制程序),并且通过`DuplicateHandle`复制了一个在控制进程中可用的句柄。但是,在触发的时候,我们需要目标进程中的socket句柄。因此,控制程序传递的socket句柄必须是目标进程中的有效值。
更简单的方法:控制程序不直接传递句柄,而是让DLL自己记录所有被hook的socket(在每次调用recv时,记录下socket到一个全局列表)。然后,当控制程序触发事件时,DLL线程遍历这个列表,对每个socket都调用一次recv(长度为0)?或者控制程序指定一个socket(通过某种标识,比如目标进程中的socket值,这个值在控制进程中虽然无效,但数值上是相同的,我们可以直接传递这个数值,然后在目标进程中使用)。
我们可以这样设计:
在DLL中,我们维护一个全局的socket列表(使用std::set或vector),并在recv钩子函数中,每当有新的socket调用recv时,就将其加入到这个列表中(注意去重和移除关闭的socket,这需要钩子close/closesocket)。
然后,我们导出一个函数,让控制程序可以触发指定的socket(通过传递socket值,这个值在目标进程和控制进程中表示的数值相同,但注意:64位和32位进程间传递时要确保类型匹配)。
但是,控制程序如何调用DLL中的导出函数?我们可以通过远程线程注入代码来调用,或者通过已经注入的DLL中的事件触发机制。
我们采用以下步骤:
1. 在DLL中导出一个函数:`TriggerRecvHook(SOCKET s)`,该函数将socket s添加到一个触发队列中,然后设置一个事件(触发线程等待的事件)来通知触发线程。
2. 控制程序通过创建远程线程调用这个导出函数(需要知道DLL在目标进程中的基址和函数地址)。
但是,这样比较复杂。我们也可以简化:控制程序通过触发一个命名事件(DLL中创建的事件)来通知DLL,同时将需要触发的socket句柄值写入共享内存(或文件映射)。DLL中的触发线程在事件触发后,从共享内存中读取socket句柄,然后对该socket调用recv(0)。
考虑到时间,我们实现一个简单版本:在DLL中,我们只触发一个特定的socket(这个socket由控制程序在注入DLL后通过DLL的导出函数设置)。这样,每次触发事件时,触发线程就对这个特定的socket调用recv(0)。
改进:我们允许设置多个socket,但为了简化,我们只设置一个全局的socket(我们称之为trigger_socket)。然后,导出一个函数用来设置trigger_socket。
具体步骤:
在DLL中:
1. 定义一个全局的SOCKET类型变量:g_trigger_socket = INVALID_SOCKET;
2. 导出一个函数:`void SetTriggerSocket(SOCKET s) { g_trigger_socket = s; }`
3. 触发线程等待事件,当事件被触发时,检查g_trigger_socket是否为有效的socket,如果是,则调用`recv(g_trigger_socket, NULL, 0, 0);`
控制程序在注入DLL后,需要调用SetTriggerSocket来设置要触发的socket(注意:这个socket必须是目标进程中有效的socket,并且已经被设置为非阻塞模式)。
控制程序如何调用SetTriggerSocket?
我们可以通过远程线程的方式,在目标进程中调用这个函数。具体:
- 获取SetTriggerSocket函数在目标进程中的地址(因为DLL已经注入,所以这个地址在目标进程中是有效的,我们可以通过`GetProcAddress`在控制进程中获取DLL中该函数的地址,但是注意:这个地址在控制进程的地址空间中,不能直接在目标进程中使用。然而,由于DLL被注入到目标进程,所以函数在目标进程中的地址与在控制进程中的地址不同?实际上,我们可以通过DLL在目标进程中的基址和函数偏移来计算目标进程中的函数地址,但通常使用`CreateRemoteThread`和`LoadLibrary`注入后,我们可以再次使用`CreateRemoteThread`来执行这个函数,需要将socket值作为参数传递。
另一种方法是:在注入DLL后,我们在DLL的DllMain中创建事件和线程,然后控制程序通过一个命名事件来触发,同时通过共享内存传递socket值。这样可以避免远程线程的复杂操作。
我们选择使用共享内存和事件对象:
1. 在DLL中,创建一块共享内存(使用文件映射),命名为"Global\\MySharedMem",其中存放一个结构体,包含一个SOCKET成员(注意:SOCKET在进程间传递需要复制句柄,或者直接传递句柄值。这里我们传递句柄值,因为目标进程中的句柄值可以直接使用)。
2. 创建一个命名事件"Global\\MyTriggerEvent"。
3. 触发线程等待这个命名事件。
4. 控制程序将要触发的socket句柄值(即目标进程中的socket数值)写入共享内存,然后触发命名事件。
5. DLL中的触发线程被激活,从共享内存中读取socket句柄值,然后调用recv(s, NULL, 0, 0)。
注意:由于同一个socket句柄值在不同进程中可能代表不同的内核对象,但在同一个进程中使用是有效的。我们写入共享内存的socket句柄值是目标进程中的值,所以目标进程中的DLL可以直接使用。
但是,控制程序如何知道目标进程中的socket句柄值?这需要DLL在钩取recv时将socket句柄通过某种方式传递给控制程序(例如通过另一个共享内存区域,或者通过命名管道发送给控制程序)。这个问题我们暂时不解决,假设控制程序通过其他方式获得了目标进程中的socket句柄值。
下面我们修改DLL代码,加入触发线程和共享内存机制。
由于代码较长,我们只写出关键部分:
在DLL中:
```cpp
#include <Windows.h>
#include <winsock2.h>
#include <stdio.h>
#include <tchar.h>
// 原始函数指针
typedef int (WINAPI *RealRecv)(SOCKET, char*, int, int);
RealRecv originalRecv = nullptr;
// 回调函数类型
typedef void (*RecvCallback)(SOCKET);
// 全局回调指针
RecvCallback g_callback = nullptr;
// 触发线程相关
HANDLE hTriggerEvent = NULL;
HANDLE hFileMapping = NULL;
SOCKET* pSharedSocket = NULL; // 指向共享内存的指针
// 钩取函数
int WINAPI HookedRecv(SOCKET s, char* buf, int len, int flags) {
int result = originalRecv(s, buf, len, flags);
if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) {
if (g_callback) g_callback(s);
}
return result;
}
// 设置回调
extern "C" __declspec(dllexport) void SetRecvCallback(RecvCallback cb) {
g_callback = cb;
}
// 触发线程函数
DWORD WINAPI TriggerThread(LPVOID lpParam) {
// 打开命名事件
hTriggerEvent = OpenEvent(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, _T("Global\\MyTriggerEvent"));
if (hTriggerEvent == NULL) {
// 创建事件(可能是第一个实例)
hTriggerEvent = CreateEvent(NULL, FALSE, FALSE, _T("Global\\MyTriggerEvent"));
}
if (hTriggerEvent == NULL) {
return 1;
}
// 创建或打开共享内存
hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(SOCKET), _T("Global\\MySharedMem"));
if (hFileMapping == NULL) {
return 1;
}
pSharedSocket = (SOCKET*)MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SOCKET));
if (pSharedSocket == NULL) {
CloseHandle(hFileMapping);
return 1;
}
while (true) {
// 等待触发事件
WaitForSingleObject(hTriggerEvent, INFINITE);
// 从共享内存中读取socket
SOCKET s = *pSharedSocket;
if (s != INVALID_SOCKET) {
// 调用recv,长度为0,非阻塞socket会立即返回,如果没有数据则触发WSAEWOULDBLOCK
char tempBuf[1];
// 注意:这里调用recv会被钩取,所以会进入HookedRecv
recv(s, tempBuf, 0, 0);
// 或者直接使用:recv(s, NULL, 0, 0); 但是根据MSDN,buf参数可以为NULL当且仅当len为0。
// 所以我们可以:
// recv(s, NULL, 0, 0);
}
}
return 0;
}
// 安装钩子
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule); // 减少线程通知,非必须
// 获取原始recv地址...(省略,同之前)
// 启动触发线程
CreateThread(NULL, 0, TriggerThread, NULL, 0, NULL);
}
return TRUE;
}
```
在控制程序中,触发主动调用:
```cpp
// 触发目标进程中的HookedRecv被调用(通过socket句柄s,注意s是目标进程中的socket句柄值)
void TriggerRecvHook(SOCKET targetSocket) {
// 打开共享内存
HANDLE hMapFile = OpenFileMapping(FILE_MAP_WRITE, FALSE, _T("Global\\MySharedMem"));
if (hMapFile == NULL) {
// 创建(如果不存在)
hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(SOCKET), _T("Global\\MySharedMem"));
}
if (hMapFile == NULL) return;
SOCKET* pSharedSocket = (SOCKET*)MapViewOfFile(hMapFile, FILE_MAP_WRITE, 0, 0, sizeof(SOCKET));
if (pSharedSocket) {
// 将socket句柄值写入共享内存
*pSharedSocket = targetSocket;
UnmapViewOfFile(pSharedSocket);
}
// 触发事件
HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, _T("Global\\MyTriggerEvent"));
if (hEvent) {
SetEvent(hEvent);
CloseHandle(hEvent);
}
if (hMapFile) CloseHandle(hMapFile);
}
```
注意:
- 共享内存和事件对象都使用"Global"命名空间,这样在多个会话中可见(需要管理员权限)。
- 目标进程中的socket句柄值(targetSocket)可以通过DLL在钩取recv时记录并传递给控制程序(例如通过另一个共享内存区域),或者控制程序通过其他方式获取(例如枚举目标进程的句柄表,但比较复杂)。
这种方法可以在不增加远程线程调用的情况下,实现跨进程触发。
但是,我们也要注意:
- 在目标进程中,对同一个socket频繁调用recv(0)是否会有副作用?根据标准,长度为0的recv调用不会读取任何数据,所以不会有数据被移出缓冲区,因此是安全的。而且非阻塞模式下它会立即返回。
- 多线程下,如果我们主动触发的recv调用与目标进程自己的recv调用同时发生,可能会导致竞争条件。因此,在HookedRecv函数中,我们需要注意线程安全(可以使用临界区)。
因此,在HookedRecv中加入临界区保护:
```cpp
CRITICAL_SECTION g_csRecv;
int WINAPI HookedRecv(SOCKET s, char* buf, int len, int flags) {
EnterCriticalSection(&g_csRecv);
int result = originalRecv(s, buf, len, flags);
if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) {
if (g_callback) g_callback(s);
}
LeaveCriticalSection(&g_csRecv);
return result;
}
// 在DllMain中初始化临界区
if (reason == DLL_PROCESS_ATTACH) {
InitializeCriticalSection(&g_csRecv);
// ...其他初始化
}
```
这样,无论recv调用是来自目标进程的线程还是我们的触发线程,都会同步执行。
总结:通过创建一个触发线程,等待一个命名事件,并读取共享内存中的socket句柄,然后对该socket进行长度为0的recv调用,我们可以主动触发钩子函数。在非阻塞模式下,这个recv调用会立即失败并触发WSAEWOULDBLOCK,从而执行回调。
这种方法实现了“主动进入HookedRecv函数”的需求。
注意:以上代码为示例,实际使用中需要处理错误检查和资源释放。
</think>
### Windows平台下C++跨进程Hook非阻塞recv函数主动触发实现
在跨进程Hook非阻塞socket recv函数时,主动触发钩子函数的核心挑战在于**如何安全地模拟recv调用环境**,同时确保不影响原始socket状态。以下是具体实现方案:
#### 主动触发原理
当非阻塞recv返回`WSAEWOULDBLOCK`时,表示内核缓冲区无数据可读。主动触发需构造一个**虚假的recv调用链**,通过跨进程通信机制通知目标进程执行特定操作[^1][^2]。
```mermaid
sequenceDiagram
ControlApp->>+TargetProcess: 注入DLL(安装recv钩子)
TargetProcess->>+HookDLL: 初始化触发线程和事件
ControlApp->>+SharedMemory: 写入触发指令(SOCKET, ACTION)
ControlApp->>+TriggerEvent: 设置事件信号
HookDLL->>+TriggerThread: 检测到事件唤醒
HookDLL->>+HookedRecv: 调用伪造的recv(NULL,0)
HookDLL->>+OriginalRecv: 执行原始调用(WSAEWOULDBLOCK)
HookedRecv->>+ControlApp: 触发回调通知
```
#### 关键实现代码
##### 1. DLL钩子模块(新增触发机制)
```cpp
// HookRecv.cpp
#include <Windows.h>
#include <winsock2.h>
// 共享内存结构(进程间通信)
#pragma pack(push, 1)
struct TriggerCommand {
SOCKET targetSocket; // 需触发的socket句柄
DWORD triggerAction; // 操作类型
};
#pragma pack(pop)
// 全局钩子状态
HANDLE hTriggerEvent = NULL;
HANDLE hFileMapping = NULL;
TriggerCommand* pSharedCmd = nullptr;
CRITICAL_SECTION g_csHook; // 线程安全锁
int WINAPI HookedRecv(SOCKET s, char* buf, int len, int flags) {
EnterCriticalSection(&g_csHook);
// 执行原始recv调用
int result = OriginalRecv(s, buf, len, flags);
// 检测非阻塞状态
if (result == SOCKET_ERROR &&
WSAGetLastError() == WSAEWOULDBLOCK) {
if (g_recvCallback) g_recvCallback(s); // 触发回调
}
LeaveCriticalSection(&g_csHook);
return result;
}
// 触发线程函数
DWORD WINAPI TriggerThread(LPVOID) {
// 创建全局事件(跨进程同步)
hTriggerEvent = CreateEvent(NULL, FALSE, FALSE, L"Global\\RecvTriggerEvent");
// 创建共享内存(跨进程数据传递)
hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0,
sizeof(TriggerCommand),
L"Global\\RecvTriggerMem");
pSharedCmd = (TriggerCommand*)MapViewOfFile(hFileMapping,
FILE_MAP_ALL_ACCESS, 0, 0, sizeof(TriggerCommand));
while (true) {
WaitForSingleObject(hTriggerEvent, INFINITE); // 等待触发信号
if (pSharedCmd->targetSocket != INVALID_SOCKET) {
// 关键:构造无数据recv调用
HookedRecv(pSharedCmd->targetSocket, NULL, 0, 0);
}
}
return 0;
}
// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
InitializeCriticalSection(&g_csHook);
HookRecvFunction(); // 安装recv钩子(原有逻辑)
CreateThread(NULL, 0, TriggerThread, NULL, 0, NULL);
}
return TRUE;
}
```
##### 2. 控制程序(主动触发端)
```cpp
// ControllerApp.cpp
void TriggerRecvHook(SOCKET targetSocket) {
// 访问共享内存
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\RecvTriggerMem");
TriggerCommand* pCmd = (TriggerCommand*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
// 写入触发命令
pCmd->targetSocket = targetSocket;
pCmd->triggerAction = 1; // RECV_TRIGGER
// 通知目标进程
HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, L"Global\\RecvTriggerEvent");
SetEvent(hEvent);
// 清理资源
UnmapViewOfFile(pCmd);
CloseHandle(hMap);
CloseHandle(hEvent);
}
// 示例:主动触发指定socket的recv钩子
int main() {
SOCKET targetSock = GetTargetSocket(); // 获取目标socket句柄
InjectDLL(targetPid, "HookRecv.dll"); // 注入DLL
while (needTrigger) {
TriggerRecvHook(targetSock); // 主动触发
Sleep(1000); // 每秒触发一次
}
}
```
#### 核心技术创新点
1. **安全触发机制**
- 使用`recv(s, NULL, 0, 0)`调用:
```cpp
HookedRecv(targetSocket, NULL, 0, 0);
```
- 长度参数为0时不会读取实际数据[^2]
- 非阻塞socket必定返回`WSAEWOULDBLOCK`
- 完全符合recv调用规范,避免破坏socket状态
2. **跨进程通信优化**
- **命名事件(Event)**:`Global\\RecvTriggerEvent`
- 使用`Global`命名空间实现跨会话访问
- 信号量机制保证实时唤醒
- **文件映射(FileMapping)**:`Global\\RecvTriggerMem`
- 共享内存传递SOCKET等关键参数
- 避免复杂的进程间消息传递
3. **线程安全设计**
```cpp
EnterCriticalSection(&g_csHook);
// 临界区代码
LeaveCriticalSection(&g_csHook);
```
- 防止原始recv与触发调用产生竞争
- 确保回调函数执行原子性
4. **资源占用优化**
- 触发线程仅在事件激活时运行
- 共享内存按需映射,无持续开销
- 轻量级事件通知机制
#### 应用场景示例
1. **网络测试工具**:模拟特定网络条件(如延迟、丢包)
```cpp
// 回调函数中注入模拟数据
void OnRecvBlocked(SOCKET s) {
const char* testData = "TEST_PACKET";
send(s, testData, strlen(testData), 0);
}
```
2. **安全审计系统**
```cpp
// 记录无数据事件
void LogRecvBlock(SOCKET s) {
printf("[AUDIT] Recv blocked on socket 0x%p\n", s);
// 触发安全策略检查...
}
```
3. **协议分析工具**
```mermaid
graph LR
A[触发recv] --> B{有无数据?}
B -->|WSAEWOULDBLOCK| C[记录时间戳]
C --> D[分析响应延迟]
B -->|有数据| E[正常协议解析]
```
#### 注意事项
1. **句柄有效性验证**
- 触发前确认socket未被关闭
- 使用`getsockopt(s, SOL_SOCKET, SO_TYPE, ...)`验证有效性
2. **64/32位兼容**
```cpp
#if defined(_WIN64)
#define SHARED_MEM_NAME L"Global\\RecvTriggerMem64"
#else
#define SHARED_MEM_NAME L"Global\\RecvTriggerMem32"
#endif
```
3. **错误处理增强**
```cpp
if (OriginalRecv(s, NULL, 0, 0) == SOCKET_ERROR) {
DWORD err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) { // 非预期错误
ReportError("Unexpected socket error", err);
}
}
```
4. **性能监控**
- 添加触发频率计数器
- 避免高频触发导致CPU飙升