如何安全地停止IOCP WSARecv()任务,并释放WSAOVERLAPPED结构?

问题描述 投票:1回答:1

我的IOCP服务器程序在运行过程中会消耗越来越多的内存。在追踪内存泄漏后,我发现一些WSAOVERLAPPED结构体反馈给WSARecv()的内存永远不会被回收。我想这是因为一些恶意的客户端套接字只建立连接而不发送数据或关闭。所以我在每个客户端套接字上设置了一个TimerQueueTimer()来识别超时的套接字,并将其删除。但如果我在删除恶意套接字时释放WSAOVERLAPPED结构,过了一会儿,得到 "Free Heap block 014C7D80 modified at 014C7DA8 after it was freed"。

下面是一些相关的代码,由于我使用的是C语言,而不是C++语言,所以我的代码很简单,就是在删除恶性套接字的时候,我看到了 "Free Heap block 014C7D80 modified at 014C7DA8 after it freed"。

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例子:(。)。

c++ c iocp
1个回答
2
投票

OVERLAPPED 所以真正需要的是取消IO操作,这可以通过调用 CancelIoEx 或致电 closesocket (closesocket 函数将启动取消未完成的IO操作。). 当IO完成时--你得到了指向 OVERLAPPED 传递给这个IO,在处理IO结果后--你可以释放或重新使用。OVERLAPPED


你有与每个插座相关联的结构 - SocketState 这是正确的.但是什么是必须的,什么是不必须的结构?

它必须实现引用计数(因为它以复杂和不可预测的顺序从多个线程访问),并持有套接字句柄。closesocket/CancelIoEx 调用(它的实现已经是单独的问题),这是因为 一个Winsock客户端绝对不能发出 closesocket 在套接字上与另一个Winsock函数调用同时进行。 但我们需要随时能够调用 closesocket 在丢失的远程端取消IO。

在另一边,它不能有指针到 OVERLAPPED (或它的shell类),因为socket上可以同时有多个IO,我们可以并行读写,它一定没有 operation 成员也是出于同样的原因--例如,读写操作可以并行进行,所以像

socketState->operation = OP_READ;
socketState->thisOvl=ovl;

错在设计上,也没有意义 hCompletion 里面 SocketState 因为 hCompletion 不是每一个插座,这里是错误的存储位置。

而且我们需要强制使用而不是裸露的 OVERLAPPED 结构,以便将其传递给IO,但自类继承于 OVERLAPPED......你需要有更多的成员在这里 - 引用指针到你的。SocketState - 因为当IO完成的时候--你得到的是指向 OVERLAPPED 并需要从它那里得到指向您的 socketState................................................................................................................................................................................................................................................ operation 也必须在这里填写(而不是 SocketState)因为操作是每一个IO而不是每一个socket.所以在很一般的情况下可以下一个。

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);
    }
}

关于

套套a TimerQueueTimer() 在每个客户端套接字上

这是可能的,正确的设计,但认为不是最好的情况下,你有很多的插座。SocketStateLIST_ENTRY 并将所有活动的套接字插入到some列表中,并通过定时器定期检查超时并关闭套接字。

© www.soinside.com 2019 - 2024. All rights reserved.