- UID
- 4293
- 精华
- 积分
- 11163
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
发表于 2024-2-18 00:07:50
|
显示全部楼层
本帖最后由 YY菌 于 2024-2-18 00:09 编辑
我刚刚又发现一个骚操作能实现进程通信,就是直接使用 PostQueuedCompletionStatus 给IOCP发消息,这就可以不通过任何IO对象(包括文件、控制台、管道、socket等)实现更快的进程通信。CreateIoCompletionPort 可以创建一个IOCP队列,虽然这个API没有提供共享功能,但我们可以利用 DuplicateHandle 来跨进程复制句柄,有了这个骚操作就能实现 进程a 使用 PostQueuedCompletionStatus 给 进程b 发送任何自定义消息了。
虽然我们能用 DuplicateHandle 来让原本不支持共享的IOCP变得共享,但是要知道对方的IOCP句柄和进程id才能去复制到自己进程,怎么办呢?这个时候就需要用到另一个新的骚操作了 MapViewOfFile 来实现共享内存数据,把服务端的IOCP句柄和进程id放进去,这样客户端就可以轻松拿到服务端的数据来复制到客户端进程,这样客户端进程就可以随意通过 PostQueuedCompletionStatus 函数给服务端进程发送任何消息了。
下面给出测试代码的例子,先给共享代码的部分:
- #pragma once
- #define WIN32_LEAN_AND_MEAN
- #include <Windows.h>
- #include <stdlib.h>
- #include <stdio.h>
- // 共享名称
- EXTERN_C DECLSPEC_SELECTANY const TCHAR TestName[] = TEXT("TestProcessMessage{83dd28de-216c-4074-8983-f90d92cae09d}");
- // 共享信息映射
- struct SharedInfoMap
- {
- volatile DWORD pid;
- volatile DWORD handle;
- };
- // 创建共享信息映射
- static SharedInfoMap *CreateSharedInfoMap(LPCTSTR name, HANDLE &hMap)
- {
- // 创建并映射跨进程共享内存段
- hMap = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(SharedInfoMap), name);
- if (!hMap) return nullptr;
- LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(SharedInfoMap));
- //CloseHandle(hMap); // 注意:这里映射完后不能关闭句柄,否则其它进程就没法打开了。
- return static_cast<SharedInfoMap *>(pMap);
- }
- // 只读打开共享信息映射
- static const SharedInfoMap *OpenSharedInfoMap(LPCTSTR name)
- {
- // 打开并映射跨进程共享内存段
- HANDLE hMap = OpenFileMapping(FILE_MAP_READ, FALSE, name);
- if (!hMap) return nullptr;
- LPCVOID pMap = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, sizeof(SharedInfoMap));
- CloseHandle(hMap); // 映射完了就可以关掉打开的句柄了
- return static_cast<const SharedInfoMap *>(pMap);
- }
- // 读写打开共享信息映射(正常情况下还是建议用只读方式打开不要修改服务端共享出来的内存)
- static SharedInfoMap *OpenSharedInfoMapWriteable(LPCTSTR name)
- {
- // 打开并映射跨进程共享内存段
- HANDLE hMap = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, name);
- if (!hMap) return nullptr;
- LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(SharedInfoMap));
- CloseHandle(hMap); // 映射完了就可以关掉打开的句柄了
- return static_cast<SharedInfoMap *>(pMap);
- }
复制代码
然后是服务端的实现代码:
- #include "共享代码.h"
- static HANDLE hIocp;
- static BOOL CALLBACK OnConCtrl(DWORD CtrlType);
- int main()
- {
- HANDLE hMap = nullptr;
- __try {
- // 创建IOCP对象
- if (hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1));
- else __leave; // 失败就直接退出
- // 创建共享信息映射
- if (auto psim = CreateSharedInfoMap(TestName, hMap)) __try {
- // 共享享进程id出去
- psim->pid = GetCurrentProcessId();
- // 共享享IOCP出去
- psim->handle = DWORD(hIocp);
- }
- __finally {
- // 共享完后就不需要这块内存的虚拟地址了,尽早释放为好(只要hMap在结束服务后释放就行了)。
- UnmapViewOfFile(psim);
- }
- else __leave; // 失败就直接退出
- // 注册控制台关闭事件处理
- SetConsoleCtrlHandler(OnConCtrl, TRUE);
- // 进入IOCP消息循环
- OVERLAPPED_ENTRY ole;
- DWORD num;
- // GetQueuedCompletionStatusEx 的用法可以理解为 GetMessage 和 PeekMessage。
- while (GetQueuedCompletionStatusEx(hIocp, &ole, 1, &num, INFINITE, FALSE)) {
- // 如果是退出消息就返回传过来的退出码(可以理解为窗口消息的wParam参数)
- if (WM_QUIT == ole.lpCompletionKey) return ole.dwNumberOfBytesTransferred;
- // 其它的消息打印出来
- printf("%x\t%p\t%lu\n", ole.lpCompletionKey, ole.lpOverlapped, ole.dwNumberOfBytesTransferred);
- }
- }
- __finally {
- // 退出前释放所有资源
- if (hMap) CloseHandle(hMap);
- if (hIocp) CloseHandle(hIocp);
- }
- // 失败退出时获取错误码来作为退出码
- return GetLastError();
- }
- // 控制台关闭事件响应
- BOOL CALLBACK OnConCtrl(DWORD CtrlType)
- {
- switch (CtrlType)
- {
- case CTRL_C_EVENT:
- case CTRL_BREAK_EVENT:
- // Ctrl+C 和 Ctrl+Break 退出时,只要给IOCP发送退出消息成功就阻止控制台杀掉自己进程和向父进程通知事件。
- return PostQueuedCompletionStatus(hIocp, EXIT_SUCCESS, WM_QUIT, nullptr);
- default:
- // 其它情况退出(如点击关闭按钮、系统注销或关机等)不管有没有给IOCP发送成功都不要阻止控制台对父进程的通知。
- if (!PostQueuedCompletionStatus(hIocp, EXIT_SUCCESS, WM_QUIT, nullptr)) break;
- // 需要做点操作来延迟一下返回的时间(否则一返回进程就会立即被控制台杀掉),尽量优先走IOCP的正常退出流程。
- SetConsoleCtrlHandler(OnConCtrl, FALSE); // 取消再次控制台关闭事件的响应
- SwitchToThread(); // 放弃当前线程的CPU执行权,让别的线程(比如IOCP所在线程)优先执行。
- }
- // 返回FALSE保证控制台会继续通知到父进程。
- return FALSE;
- }
复制代码
最后是客户端的代码:
- #include "共享代码.h"
- int main()
- {
- HANDLE hIocp = nullptr, hProcess = nullptr;
- __try {
- // 打开共享信息映射
- if (auto psim = OpenSharedInfoMap(TestName)) __try {
- // 打开服务端进程并获取具有句柄复制和同步权限的进程句柄
- if (hProcess = OpenProcess(PROCESS_DUP_HANDLE | SYNCHRONIZE, FALSE, psim->pid));
- else __leave; // 失败就直接退出
- // 复制服务端的IOCP句柄到客户端进程
- if (DuplicateHandle(hProcess, HANDLE(psim->handle), INVALID_HANDLE_VALUE, &hIocp, 0, FALSE, DUPLICATE_SAME_ACCESS));
- else __leave; // 失败就直接退出
- }
- __finally {
- // 复制完后就不需要这块内存的虚拟地址了,尽早释放为好。
- UnmapViewOfFile(psim);
- }
- else __leave; // 失败就直接退出
- // 给服务端发送一条测试消息
- float x = 0.9f, y = 0.5f;
- if (PostQueuedCompletionStatus(hIocp, GetCurrentProcessId(), (int &)x - (int &)y, nullptr));
- else __leave; // 失败就直接退出
- // 暂停控制台并等待按任意键继续
- if (IDYES == MessageBox(GetConsoleWindow(), TEXT("是否给服务端发送退出消息"), nullptr, MB_YESNO));
- else __leave; // 取消也直接退出
- // 发送退出消息给服务端并等待服务端进程结束
- if (PostQueuedCompletionStatus(hIocp, EXIT_SUCCESS, WM_QUIT, nullptr))
- WaitForSingleObject(hProcess, INFINITE);
- }
- __finally {
- // 退出前释放所有资源
- if (hProcess) CloseHandle(hProcess);
- if (hIocp) CloseHandle(hIocp);
- }
- // 失败退出时获取错误码来作为退出码
- return GetLastError();
- }
复制代码 |
|