我的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/

10-11 16:04