如何安全地停止 IOCP WSARecv() 任务,并释放 WSAOVERLAPED 结构?
How to safely stop a IOCP WSARecv() task, and free the WSAOVERLAPPED structure?
我的 IOCP 服务器程序在运行时会消耗越来越多的内存。在跟踪内存泄漏后,我发现一些WSAOVERLAP结构馈送到WSARecv((从未被回收。我认为这是因为某些恶性客户端套接字只建立连接,但从不发送数据或关闭。所以我在每个客户端套接字上设置了一个 TimerQueueTimer(( 来识别超时套接字并删除它们。但是如果我在删除恶性套接字时释放了WSAOVERLAP结构,过了一会儿,我得到了"释放后在014C7D80处修改了自由堆块014C7D80"。
以下是一些相关代码:
typedef struct _SocketState
{
char operation;
SOCKET socket;
DWORD length;
HANDLE hTimer;
HANDLE hCompletion;
WSAOVERLAPPED* thisOvl;
char buf[MAX_BUF];
} SocketState;
static WSAOVERLAPPED* new_overlapped(void)
{
return (WSAOVERLAPPED*)calloc(1, sizeof(WSAOVERLAPPED));
}
static void create_io_completion_port(void)
{
cpl_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!cpl_port)
{
int err = WSAGetLastError();
exit(1);
}
}
static void post_reading(SocketState* socketState, WSAOVERLAPPED* ovl)
{
DWORD flags = 0;
WSABUF wsabuf = { MAX_BUF, socketState->buf };
int err=0;
memset(ovl, 0, sizeof(WSAOVERLAPPED));
socketState->operation = OP_READ;
socketState->thisOvl=ovl;
if (WSARecv(socketState->socket, &wsabuf, 1, NULL, &flags, ovl, NULL)== SOCKET_ERROR)
{
err = WSAGetLastError();
if (err != WSA_IO_PENDING)
{
printf("[%s:%d]WSARecv errorn", __FUNCTION__, __LINE__);
destroy_connection(socketState, ovl);
return ;
}
}
c_WSARecv++;
}
static void destroy_connection(SocketState* socketState, WSAOVERLAPPED* ovl)
{
int err=0;
if(socketState->hTimer != NULL)
{
DeleteTimerQueueTimer(hTimerQueue,socketState->hTimer,INVALID_HANDLE_VALUE);
socketState->hTimer = NULL;
}
socketState->hCompletion=NULL; //newSocketState->hCompletion = cpl_port
closesocket(socketState->socket);
free(socketState);
if(ovl!=0)
{
free(ovl);
}
}
VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
SocketState* clientSocketState=(SocketState*)lpParam;
if (lpParam != NULL)
{
if(clientSocketState->hCompletion != NULL)
{
PostQueuedCompletionStatus(clientSocketState->hCompletion,-2,(ULONG_PTR)clientSocketState,clientSocketState->thisOvl);
//should last parameter be NULL?
//the "-2" is for identify this timeout io after GetQueuedCompletionStatus()
}
}
}
由于我在服务器程序中使用 C 而不是 C++,因此我让自己处于非常尴尬的境地。基本上我在 C :( 中找不到非常好的 IOCP 示例。
OVERLAPPED
我们只能在 I/O 完成后释放。 所以真的所有需要取消I/O操作。 这可以通过调用CancelIoEx
来完成,或者通过调用closesocket
完成更好(closesocket
函数将对未完成的 I/O 操作启动取消(。 当 I/O 完成时 - 你得到了指向OVERLAPPED
传递到此 I/O 的指针,在处理 I/O 结果之后 - 你可以释放或重用OVERLAPPED
您有与每个套接字关联的结构 -SocketState
这是正确的。 但是什么必须和不能有这种结构?
它必须实现引用计数(因为它以复杂且不可预测的顺序从多个线程访问(。 并按住套接字的手柄。 强制对套接字句柄进行某种破损保护,以便在closesocket/CancelIoEx
调用后不使用它(它实现已经是单独的问题(。 这是必需的,因为 Winsock客户端绝不能与另一个 Winsock 函数调用同时在套接字上发出closesocket
但是我们需要随时调用closesocket
以取消丢失的远程端的I/O。
从另一端看,它不能有指向OVERLAPPED
(或 IT shell 类(的指针,因为套接字上可以同时有多个 I/O。 我们可以并行读写。 出于同样的原因,它也不能operation
成员 - 例如,读取和写入操作可以并行处于活动状态。 所以代码像
socketState->operation = OP_READ;
socketState->thisOvl=ovl;
设计是错误的。 也没有意义hCompletion
内部SocketState
因为hCompletion
不是每个插座。 这是错误的存储位置。
此外,我们需要强制使用不是裸OVERLAPPED
结构将其传递给I/O,而是从OVERLAPPED
继承的self类。 您需要在此处具有其他成员 - 指向SocketState
的引用指针 - 因为当I/O完成时 - 您返回指向OVERLAPPED
的指针,并且需要从中获取指向socketState
的指针。operation
也必须在这里(而不是SocketState
(,因为操作是每个I/O但是不是每个插槽。所以在非常一般的情况下可以是下一个:
struct SocketState
{
SOCKET socket;
HANDLE hTimer;
ULONG dwRefCount;
void AddRef();
void Release();
_NODISCARD SOCKET LockHandle();
void Rundown();
void UnlockHandle();// call closesocket on last unlock
void OnIoComplete(ULONG operation, ULONG dwErrorCode, ULONG dwBytesTransfered, PVOID buf);
void StartSomeIo(ULONG operation, PVOID buf, ULONG cb);
void Close()
{
if (LockHandle())
{
Rundown();
UnlockHandle();
}
}
};
struct UIrp : OVERLAPPED
{
SocketState* socketState;
ULONG operation;
PVOID buf;
UIrp(SocketState* socketState, ULONG operation, PVOID buf)
: socketState(socketState), operation(operation), buf(buf)
{
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
socketState->AddRef();
}
~UIrp()
{
socketState->Release();
}
void OnIoComplete(ULONG dwErrorCode, ULONG dwBytesTransfered)
{
socketState->OnIoComplete(operation, dwErrorCode, dwBytesTransfered, buf);
delete this;
}
};
void SocketState::StartSomeIo(ULONG operation, PVOID buf, ULONG cb)
{
if (UIrp* irp = new UIrp(this, operation, buf))
{
ULONG dwError = ERROR_INVALID_HANDLE;
if (SOCKET s = LockHandle())
{
dwError = WSA*(s,... irp, 0) == 0 ? NOERROR : WSAGetLastError();
UnlockHandle();
}
switch (dwError)
{
case NOERROR:
case WSA_IO_PENDING:
break;
default:
irp->OnIoComplete(dwError, 0);
}
}
}
void PortLoop(HANDLE hCompletionPort)
{
for (;;)
{
OVERLAPPED* lpOverlapped;
ULONG dwBytesTransfered;
ULONG_PTR CompletionKey;
ULONG dwError = GetQueuedCompletionStatus(hCompletionPort, &dwBytesTransfered,
&CompletionKey, &lpOverlapped, INFINITE) ? NOERROR : GetLastError();
// probably somehow use CompletionKey
if (!lpOverlapped)
{
break;
}
static_cast<UIrp*>(lpOverlapped)->OnIoComplete(dwBytesTransfered, dwError);
}
}
大约
设置在每个客户端套接字上设置
TimerQueueTimer()
这是可能的,并且从设计上是正确的,但如果您有很多套接字,则认为不是最好的。 我从LIST_ENTRY
继承SocketState
,并将所有活动的套接字插入到某个列表中。 并通过计时器定期检查超时并关闭套接字
- 释放错误后堆使用
- G锁定铸造到基础上会释放模拟行为
- 在将变量声明为引用时,堆在释放后使用
- 在调用FreeLibrary后,释放动态链接到具有相同版本的CRT堆的DLL的内存
- 有没有任务栏API可以立即应用注册表更改
- 正在理解智能指针,但出现错误:未分配正在释放的指针
- C++双重释放或损坏(out)
- 如何在c++中释放内存
- 使用全局声明的向量时,C++双重释放错误/损坏
- 如何创建线程序列以按照启动顺序执行任务?
- 为什么这个 std::queue/指向结构的指针列表直到 List.Size() == 0 才释放内存?
- 为什么瓦尔格林德在不释放恶意内存后没有报告任何问题?
- 调用析构函数以释放动态分配的内存
- C++一个线程如何正确通信其任务已完成?
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- COM :是否可以查看是否存在对我的某个 COM 对象的进程外引用?我可以释放它吗?
- 如何在向量中释放指针?
- 在线程之间拆分任务总是值得的吗?
- std::unordered_map析构函数不释放内存?
- 如何安全地停止 IOCP WSARecv() 任务,并释放 WSAOVERLAPED 结构?