io 完成端口问题,每个 GetQueuedCompletionStatus 调用多个 wsarecv 或 wsasend

Posted

技术标签:

【中文标题】io 完成端口问题,每个 GetQueuedCompletionStatus 调用多个 wsarecv 或 wsasend【英文标题】:io completion ports issue with calling multiple wsarecv or wsasend per GetQueuedCompletionStatus 【发布时间】:2014-07-19 14:17:04 【问题描述】:

我有一个应与套接字(udp)和设备通信的应用程序,我正在使用 IOCP 进行通信。它的工作方式是从通过套接字向远程对等方发送和接收一些数据开始,然后开始读取和写入设备,所有操作都在单个线程中完成(以避免锁定)。因此,对句柄的读取和写入是相互依赖的,如果它们没有正确调用,它们中的一个就会被饿死。像我所做的大多数 IOCP 编码风格一样:

WSASendTo(...)

while(true)

    GetQueuedCompletionStatus(...,event,...)

    switch (event->e_type)
    
      case READ_EVENT:
        if(e->event->handle == socket)
        
                 get_socket_data(...);
                 start_read_socket(...);
                 start_write_socket(...);
                 start_write_dev(...);
        
        else if(event->handle == dev)
        
                 get_dev_data(...);
                 start_read_dev(...);
                 start_write_socket(...);
                 start_write_dev(...);
        
        break;
      case WRITE_EVENT:
        if(event->handle == socket)
        
                 check_socket_write(...);
                 start_write_socket(...);
                 start_read_socket(...);
                 start_read_dev(...);
        
        else if(event->handle == dev)
        
                check_dev_write(...);
                start_write_dev(...);
                start_read_dev(...);
                start_read_socket(...);
        
            break;
    

但在我的情况下,不同的是在获取 GetQueuedCompletionStatus 之后 我对套接字和开发进行多次异步读写以确保应用程序始终 愿意从 socket 和 dev 获取数据。我还创建了一个 io 数据结构并为每个句柄分配了两个。一读一写:

typedef struct

    WSAOVERLAPPED io_ovl;

    int e_type;

    HANDLE handle;

    WSABUF wsabuf;

    BUFFER buffer;

    uint8_t pending;

 ESTATE;

...

ESTATE eread_socket;

ESTATE eread_dev;

ESTATE ewrite_socket;

ESTATE ewrite_dev;

我还在检查是否有待处理的读写(意味着我已经请求它们)然后我没有注册新操作。 使用这种风格,一切正常,那些日子我正在将小数据传输到套接字和设备,直到有一天我决定让套接字发送和接收更大的数据,它开始显示有线的东西。

虽然应用程序本身甚至 Wireshark 都显示数据发送得很好,但另一个对等方正在获取大数据包的修改数据!!!甚至其他对等点上的wireshark 也显示udp 校验和是错误的。如您所知,计算校验和是内核空间中的东西(如果启用了校验和卸载,则在网络驱动程序中)我开始认为驱动程序存在问题......所以我编写了一个简单的 IOCP 客户端来发送它大量的大数据,发现它的工作正常!

我无法想到我的应用程序与 Windows 内核发生了冲突,其中数据可以正常传递到 Windows 内核,但内核在将其传递到网络驱动程序时却出现了问题。 (我是有linux内核背景的。内核空间,用户空间......)

但是我开始以多种方式调试我的应用程序并发现 WSASendTo 何时无法发送数据(因为它很大并且可能可以分配那么多缓冲区)并进入 WSA_IO_PENDING 状态,从此时起任何其他调用任何其他 ESTATE 都会导致发送有线结果。考虑没有错误弹出。它正常发送。但是另一端接收到的数据被修改,因此变得无用。

当我说“对任何其他 ESTATE 的任何其他调用都会产生有线结果”时,即使我将它们传递给函数并在函数开头放置一个断点(意味着我不会在该函数内更改它们,只需传递它们)它会在发送时修改数据包内容,如果我在每次发送后延迟例如 1 秒(进入 WSA_IO_PENDING)并因此为 Windows 购买一些时间来发送它们,它工作正常。

例如在这个块中:

case WRITE_EVENT:
        if(event->handle == socket)
        
                 check_socket_write(...);
                 start_write_socket(...);
                 start_read_socket(...);
                 start_read_dev(...);
        

当它说套接字成功写入时,我会 check_socket_write(...) 做一些后期处理,然后 start_write_socket(...) 如果它进入 WSA_IO_PENDING,则调用 start_read_socket(...) 使其行为有线。

因为所有这些写入和读取函数都使用他们自己的 ESTATE,首先我认为这是因为这些 ESTATE 数据有些混乱(如果是因为这个,我会仔细检查是否未挂起,如果它正在等待我从这些函数中优雅地返回并且不更改任何数据)我多次检查不使用这些 ESTATE 数据而不是彼此数天,并确保这不是我的应用程序错误并且如此连线可能是因为一些内部iocp api 的地址搞砸了!毕竟只是将数据结构传递给函数应该不是问题,并提醒我 SEGMENTATION FAULT 行为。

所有在 Internet 中使用 IOCP 的示例在获取 GetQueuedCompletionStatus 的结果后仅调用一个 WSA(send/recv),那么我在调用多个写入和读取请求时做错了吗?

感谢阅读这篇长篇小说

编辑 即使我在提到的块中注释掉 start_read_socket(...) 和 start_read_dev(...) 对其他结构的其他调用使其修改发送数据包因此听起来更像是IOCP缺乏挂起的发送请求。 在调用进入挂起状态的 WSASendTo 之后,不要对包含或接近 WSASend 正在消耗的重叠结构的结构执行任何操作,直到到达 GetQueuedCompletionStatus!!! 我'我真的陷入了困境:|

【问题讨论】:

请将问题标题与实际问题相匹配,当前标题具有误导性 【参考方案1】:

如果您要发出多个异步读写操作,您需要为每个操作创建并初始化一个单独的WSAOVERLAPPED 结构。如果您尝试在第一个请求完成之前为第二个请求重用 WSAOVERLAPPED 结构,将会发生奇怪的事情。

【讨论】:

非常感谢罗斯,实际上我确实为每个使用 ESTATE 结构的请求创建了一个单独的 OVERLAPPED。今天早上醒了很久才发现自己犯了多么愚蠢的错误!!!我现在正在写答案 当您说“我对套接字和开发人员进行多次异步读写以确保应用程序始终愿意从套接字和开发人员获取数据”时,听起来您发出了不止一次读取并为每个套接字写入请求,以确保您始终有未完成的读取和写入请求。不管怎样,很高兴听到你发现了这个错误。像这样的问题很难解决。 :) 是的,你是对的,听起来像。但是我将 uint8_t 放在 ESTATE 中以指示我是否有待处理的读取或写入(这意味着我已经请求了一个但尚未完成)我不做一个新的。无论如何,非常感谢您关注这个基于错误的问题的长篇故事,并且像在 Windows api 中发现错误的人一样编写! ;)【参考方案2】:

我得到了这个工作,一切都是基于一个错误!

在 start_write_socket(...) 中,当我为 WSABUF 分配有效缓冲区的地址时,我正在从队列中读取内容,然后获取它的地址,但没有注意到该地址仅在该函数内部有效。这就是为什么当它没有进入挂起状态时,它工作正常。但在挂起状态和函数之外,该地址不再有效并产生有线结果。

【讨论】:

以上是关于io 完成端口问题,每个 GetQueuedCompletionStatus 调用多个 wsarecv 或 wsasend的主要内容,如果未能解决你的问题,请参考以下文章

窗口 HTTP IO 完成端口

IO 完成端口:WSARecv() 是如何工作的?

也谈IO完成端口

将多个文件关联到同一个 io 完成端口,同时保持文件流顺序 c#

使用套接字时,哪些 IO 操作会导致完成数据包发送到完成端口?

IO 完成端口初始读取和双向数据