我的IOCP服务器程序在运行时将花费越来越多的内存。跟踪内存泄漏后,我发现WSARecv()的某些WSAOVERLAPPED结构提要从未被回收。我认为这是因为某些恶性客户端套接字仅建立连接,而从不发送数据或关闭。因此,我在每个客户端套接字上设置了一个TimerQueueTimer()来标识超时套接字并删除它们。但是,如果我在删除恶性套接字时释放了WSAOVERLAPPED结构,则过了一会儿,我得到了“释放后在014C7DA8处修改了Free Heap块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 error\n", __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
(或它的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);
}
}
关于
这是有可能的,并且在设计上是正确的,但是如果您有许多插座,则认为不是最好的。我将从
SocketState
继承LIST_ENTRY
并将所有 Activity 套接字插入某些列表。并通过计时器定期检查超时并关闭套接字关于c++ - 如何安全地停止IOCP WSARecv()任务,并释放WSAOVERLAPPED结构?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62148840/