带串口的 Win32 IOCP 不工作

Win32 IOCP with serial port not working

收集到更多信息后,我编辑了这个问题。我正在尝试使用 IOCP 通过串行端口进行通信。

我用重叠标志打开串口:

HANDLE hComm = CreateFile(strPortName,
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_OVERLAPPED,
    NULL);

然后我将"hComm"关联到IOCP端口

我正在使用 ReadFile() 启动重叠请求以从串行端口读取。我这样使用 ReadFile():

bool bQueued = false;
DWORD dwRead;
BOOL bResult = ReadFile(GetCommHandle(), lpOverlapped->pbBufferData, lpOverlapped->dwBufferSize, &dwRead, (OVERLAPPED*)lpOverlapped);
if (bResult)
{
    // It completed, but will still trigger the completion routine, so don't need to queue another one here.
    bQueued = true;
}
else
{
    DWORD dwError = GetLastError();
    if (ERROR_IO_PENDING == dwError)
    {
        bQueued = true;
    }
    else
    {
        LogQueueReceiveError(lpOverlapped, dwError);

        ResetConnection();
    }
}

对 ReadFile() 的调用总是 returns 立即产生 'true' 结果,因此 IOCP 请求被排队。但是,只有当指定数量的字节到达串行端口时,操作才会完成。在我的代码中,调用 ReadFile() 时接收缓冲区的大小作为要读取的字节数(这是使用套接字时的处理方式)。

如果我将要读取的字节数更改为值 1,那么一旦数据到达端口,操作就会完成。同样,如果我将要读取的字节数更改为 8,则当第 8 个字节到达端口时操作完成,等等

如果我不知道预期的字节数,我如何在不一次读取一个字节的情况下将 IOCP 与串行端口一起使用,这似乎效率很低?

您可以使用 SetCommTimeouts 更改此行为。

很久以前(大约 2000 年?)我遇到过类似的问题,所以我认为目标操作系统是 Windows NT 4。

我的实现以 IOCP 上的一堆排队的单字节读取结束。虽然您可以使用 SetCommTimeouts 进行设置以允许部分读取,但那时您最终需要为计时器支付时间片以让您填充部分填充的缓冲区。对于应用程序,这引入了不可接受的延迟(10 毫秒或 16 毫秒,具体取决于 SMP 与非 SMP)。

从那时起世界可能已经改变,或者您可能可以接受延迟时间片。无论哪种方式,尝试 SetCommTimeouts 可能会有用。

Win32 IOCP with serial port not working

这当然不是真的。 IOCP 在这里工作完美 - 当且仅当挂起的 io 请求将完成或取消时 - 数据包将排队到 IOCP。所以以通常的方式使用 IOCP 。您的问题不在 IOCP 中,而是在读取时的串行驱动程序行为中。

I've discovered that ... it completes after that many bytes have arrived.

这确实是 documented 行为:

Serial.sys continues to transfer bytes until the requested number of bytes are transferred or a time-out event occurs.

或在 SERIAL_TIMEOUTS (equal to COMMTIMEOUTS 中更详细地说明):

A read or write request successfully completes when either the specified number of bytes is transferred or the requested read or write operation times out. The request returns the STATUS_SUCCESS status code to indicate that the specified number of bytes was transferred. A read request that exceeds this maximum completes when the time-out occurs, and returns the STATUS_TIMEOUT status code. The Information field of the I/O status block indicates the number of bytes successfully read before the time-out occurred.

所以你有两个选择:

  • 始终将 nNumberOfBytesToRead 设置为 1 - 在本例中为读取请求 数据到达端口后立即完成
  • 通过SetCommTimeouts或直接发送设置一些超时 IOCTL_SERIAL_SET_TIMEOUTS控制代码(这个是一样的)

下一个可能的最佳用途:

If both ReadIntervalTimeout and ReadTotalTimeoutMultiplier are set to MAXULONG, and ReadTotalTimeoutConstant is set to a value greater than 0 and less than MAXULONG, a read request behaves as follows:

  • If there are any bytes in the serial port's input buffer, the read request completes immediately with the bytes that are in the buffer
    and returns the STATUS_SUCCESS status code.
  • If there are no bytes in the input buffer, the serial port waits until a byte arrives, and then immediately completes the read request with the one byte of data and returns the STATUS_SUCCESS status
    code.
  • If no bytes arrive within the time specified by ReadTotalTimeoutConstant, the read request times out, sets the Information field of the I/O status block to zero, and returns the STATUS_TIMEOUT status code.

还需要记住 win32 层几乎总是丢失 > 0 的状态(所以 STATUS_TIMEOUT 也是如此)。你直接得到它(在 FileIOCompletionRoutine callback - the dwErrorCode here really is NTSTATUS code) only if you use BindIoCompletionCallback. if you use CreateThreadpoolIo - you already got IoResult == 0 in IoCompletionCallback on timeout (but will be (NTSTATUS)Overlapped->Internal==STATUS_TIMEOUT). if you use own IOCP and GetQueuedCompletionStatus - 它再次丢失 STATUS_TIMEOUT - 它只是 return 你 TRUE 使用此代码完成数据包并且没有设置最后一个错误。但是仍然会(NTSTATUS)lpOverlapped->Internal == STATUS_TIMEOUT 在这种情况下(不要将出队数据包与 STATUS_TIMEOUT 代码和 GetQueuedCompletionStatus 由于等待超时而未使完成数据包出队的情况混淆(在这种情况下 api return false,最后一个错误设置为 WAIT_TIMEOUT,等于 STATUS_TIMEOUT) )