0

我正在尝试使用 Windows 7/8 X64 上的重叠 IO 模式来模拟 Linux 的开放标志支持的非阻塞模式 ( IO_NONBLOCK ) 行为。这里的代码是跨平台串行 API 的 windows 部分的一部分。

我可以使用SerialCommWnt对象的构造函数参数以阻塞或非阻塞 (OVERLAPPED) 模式打开 COMM 端口。就这个问题而言,我的所有问题都与 COMM 端口何时以 OVERLAPPED 模式打开(由流控制构造函数参数指定)有关。对于 Read 方法,我指定了一个 timeout 参数,当成功从串行端口检索至少 1 个字节的数据时,它应该指示 rTimeout 参数在数据位于串行通信的输入缓冲区时的剩余时间(我相信串行驱动程序在收到任何数据时通知重叠结构中的手动重置事件)。

我阅读了许多有关如何处理这些 API 的 StackOverflow 线程,其中许多都引用了 Microsoft Win32 API。到目前为止我能找到的最好的信息是

http://msdn.microsoft.com/en-us/library/ff802693.aspx 这个 API 对 Overlapped IO 来说是令人困惑的(特别是当它传递一个指向在重叠模式下调用 ReadFile 时接收到的字节数的指针时),但是据我所知,它们都没有解决如何结合 COMMTIMEOUTS 正确使用重叠 IO 模式。在过去的几天里,我尝试了 COMMTIMEOUTS 和与::WaitForSingleObject一起使用的超时参数的设置组合. 最后显示了似乎最有效的组合。关于与重叠 IO 结构和 COMMTIMEOUTS 关联的手动重置事件对象相关的超时,我有一些可靠性问题。我不完全确定,但似乎为了在从串行端口读取时超时正常工作,必须在 COMMTIMEOUTS 中指定超时。我尝试了一种组合,我在SetCommTimeouts中禁用超时,而是在::WaitForSingleObject的超时参数中使用显式超时,但这不起作用,相反我通过在 COMMTIMEOUTS 中指定超时并指定带有::WaitForSingleObject的 INFINITE方法调用。但是,我不确定是否存在这种情况会永远挂起,如果是这样,我该如何处理。我将不胜感激有关如何正确处理可能挂在这里的任何信息。

这是我用来打开 COMM 端口的方法——在这种情况下,我有超时问题,我指定 FILE_FLAG_OVERLAPPED。

    /**
     * Open the serial port using parameters set in theconstructor.<p>
     * The Port Number, Speed, Overlapped IO mode, #data bits &
     * async mode etc. are specified as constructor arguments.
     *
     * @return OS_FAILED, OS_SUCCESS
     */
    OsStatus
    SerialCommWnt::open()
    {
        // Critical Section
        std::lock_guard<std::recursive_mutex> lock (mMutexGuard);
        OsStatus result = OS_FAILED;
        std::ostringstream os;
        os << "\\\\.\\COM" << mCommPort;
        std::string deviceName = os.str();
        DWORD dwFlagsAndAttrs = (mFlowControl ==
            SerialCommBase::FCTL_OVERLAPPED)?
            FILE_FLAG_OVERLAPPED : 0;
        // open the underlying device for read and write
        mOsFileHandle = CreateFile (
            deviceName.c_str(),
            GENERIC_READ | GENERIC_WRITE,
            0,                      //(share) 0:cannot share the COM port
            NULL,                   // no security attributes
            OPEN_EXISTING,          // COMM devices must use OPEN_EXISTING
            dwFlagsAndAttrs,        // optional FILE_FLAG_OVERLAPPED
            NULL);                  // hTemplate must be NULL for comm devices
        if ( mOsFileHandle != INVALID_HANDLE_VALUE ) {
            // reserve an 8k communications channel buffer (both directions)
            BOOL isOK = SetupComm(mOsFileHandle, 8200, 8200);
            // Omit the call to SetupComm to use the default queue sizes.
            // Get the current configuration.
            DCB dcb;
            SecureZeroMemory(&dcb, sizeof(DCB));
            isOK = GetCommState (mOsFileHandle, &dcb);
            if (isOK) {
                // Fill in the DCB: baud=125000, 8 data bits, even parity, 1 stop bit.
                // This is the standard baud rate. The card we have has a custom crystal
                // changing this baud rate to 125K.
                dcb.BaudRate = static_cast<DWORD>(mBaudRate);
                dcb.ByteSize = static_cast<BYTE>(mByteSize);
                // enum values are ame as dcb.Parity defines
                dcb.Parity   = static_cast<BYTE>(mParity);
                dcb.fParity  = (mParity == SerialCommBase::PRTY_NONE)? FALSE : TRUE;
                dcb.StopBits = ONESTOPBIT;
                // ----------------------------------------------------
                // When running in win32 loopback with the simulator
                // in loopback mode, we must enable the RTS/CTS handshake
                // mode as there seems to be a 4K limit in the input
                // buffer when the DBU Simulator performs reads.
                // ----------------------------------------------------
                if (mFlowControl == SerialCommBase::FCTL_RTS_CTS) {
                    dcb.fOutxCtsFlow = 1;
                    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
                }
                // Not absolutely necessary as the DTR_CONTROL_DISABLE is default
                dcb.fDtrControl = DTR_CONTROL_DISABLE;
                isOK = SetCommState (mOsFileHandle, &dcb);
                if (isOK) {
                    COMMTIMEOUTS commTimeouts;
                    SecureZeroMemory(&commTimeouts, sizeof(COMMTIMEOUTS));
                    // These settings will cause ReadFile to return
                    // immediately if there is no data available at the port
                    // A value of MAXDWORD, combined with zero values for both the
                    // ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members,
                    // specifies that the read operation is to return immediately with
                    // the bytes that have already been received, even if no bytes
                    // have been received.
                    //isOK = GetCommTimeouts (mOsFileHandle, &CommTimeouts);
                    commTimeouts.ReadIntervalTimeout = MAXDWORD;
                    // ReadTotalTimeoutConstant - when set with a ms timeout value
                    // in conjunction with will ReadIntervalTimeout == MAXDWORD &&
                    // ReadTotalTimeoutMultiplier set to 0 be used to control the
                    // timeout for the read operation.   Each time the read with a
                    // timeout is called, we compare the existing timeouts in CommTimeouts
                    // before changing it.
                    commTimeouts.ReadTotalTimeoutConstant = 0;
                    commTimeouts.ReadTotalTimeoutMultiplier = 0;
                    // timeouts not used for write operations
                    commTimeouts.WriteTotalTimeoutConstant = 0;
                    commTimeouts.WriteTotalTimeoutMultiplier = 0;
                    isOK = SetCommTimeouts (mOsFileHandle, &commTimeouts);
                    if (isOK) {
                        // test for asynchronous mode
                        if (mFlowControl == SerialCommBase::FCTL_OVERLAPPED) {
                            // allocate & initialize overlapped
                            // structure support for rx & tx
                            mpOverlappedTx.reset(new(OVERLAPPED));
                            mpOverlappedRx.reset(new(OVERLAPPED));
                            if (mpOverlappedTx && mpOverlappedRx) {
                                SecureZeroMemory(mpOverlappedTx.get(), sizeof(OVERLAPPED));
                                SecureZeroMemory(mpOverlappedRx.get(), sizeof(OVERLAPPED));
                                // create an unsignaled manual reset (2nd Param TRUE)
                                // event used for GetOverlappedResult. This event will
                                // be signaled by the ReadFile to indicate when
                                // IO operations are complete or encounter errors
                                mpOverlappedTx->hEvent = CreateEvent(
                                    NULL, TRUE, FALSE, NULL);
                                if (mpOverlappedTx->hEvent != NULL) {
                                    // now do the same for the RX side
                                    mpOverlappedRx->hEvent = CreateEvent(
                                        NULL, TRUE, FALSE, NULL);
                                    if (mpOverlappedRx->hEvent != NULL) {
                                        setState(COMM_OPENED);
                                        result = OS_SUCCESS;
                                    } else {
                                        result = handleError(deviceName);
                                    }
                                } else {
                                    result = handleError(deviceName);
                                }
                                // close the handle and set error
                                if (result != OS_SUCCESS) {
                                    close();
                                    setState(COMM_OPEN_FAILED);
                                }
                            } else {
                                // close the handle and overlapped event
                                close();
                                setState(COMM_OPEN_FAILED);
                                result = OS_NO_MEMORY;
                            }
                        } else { // blocking mode
                            setState(COMM_OPENED);
                            result = OS_SUCCESS;
                        }
                    } else {
                        result = handleError(deviceName);
                        close();
                    }
                } else { // unable to set the baud rate or something
                    result = handleError(deviceName);
                    close();
                }
            }
        } else {
            result = handleError(deviceName);
            close();
        }
        return result;
    }

这是执行定时读取的代码

    /**
     * Read a block of data into the specified raw buffer.
     * See http://msdn.microsoft.com/en-us/library/ms810467(v=MSDN.10).aspx
     * for details for Overlapped IO usage, in particular note that setting
     * the timeout each time is tricky.
     *
     * @param pData     [in/out] data buffer
     * @param rNumBytes [in] buffer size
     * @param rTimeout  [in/out] timeout specified in milliseconds.
     *                  This parameter is updated to reflect the
     *                  remaining time.
     * @param rNumBytesRead
     *                  [out] number of bytes read
     *
     * @return OS_SUCCESS, OS_WAIT_TIMEOUT, OS_INVALID_ARGUMENT or
     *         OS_FAILED
     */
    OsStatus
    SerialCommWnt::read(
        void* pData,
        const size_t& rNumBytes,
        milliseconds& rTimeout,
        size_t& rNumBytesRead)
    {
        OsStatus result = OS_WAIT_TIMEOUT;
        rNumBytesRead = 0;
        DWORD numBytesRead = 0;
        DWORD commError;
        COMSTAT commStatus;
        auto startTime = system_clock::now();
        if (mpOverlappedRx) {
            // update the timeout used for ReadFile - note that the
            // magic combination that works for an absolute timeout is
            // MAXDWORD, timeoutMS, 0.
            COMMTIMEOUTS commTimeouts;
            GetCommTimeouts(mOsFileHandle, &commTimeouts);
            if (commTimeouts.ReadTotalTimeoutConstant != rTimeout.count()) {
                commTimeouts.ReadIntervalTimeout = MAXDWORD;
                commTimeouts.ReadTotalTimeoutConstant =
                    static_cast<DWORD>(rTimeout.count());
                SetCommTimeouts (mOsFileHandle, &commTimeouts);
            }

            // asynchronous overlapped IO mode.
            // reset the manual event to the non-signaled.
            // No Need for this as ReadFile resets it by itself
            // ResetEvent(mpOverlappedRx->hEvent);
            BOOL isOK = ReadFile(
                mOsFileHandle, pData, (DWORD)rNumBytes,
                reinterpret_cast<DWORD*>(&rNumBytesRead),
                mpOverlappedRx.get());
            // get the result to date - only valid to call this
            // if ReadFile returns !isOK (FALSE) &&
            // last error set to ERROR_IO_PENDING
            //milliseconds elapsedTime;
            if (!isOK) {
                DWORD dwLastError = GetLastError();
                if (dwLastError == ERROR_IO_PENDING) {
                    // pending IO, wait to complete using the COMMTIMEOUTS timer.
                    // when the COMMTIMEOUTS timer expires it will signal the 
                    // manual mpOverlappedRx->hEvent
                    DWORD ovlStatus = ::WaitForSingleObject(
                        mpOverlappedRx->hEvent, static_cast<DWORD>(
                            /*rTimeout.count()*/INFINITE));
                    switch (ovlStatus) {
                    case WAIT_TIMEOUT:
                        // timeout - update the remaining time to 0
                        rTimeout = milliseconds::zero();
                        result = OS_WAIT_TIMEOUT;
                        //elapsedTime = duration_cast<milliseconds>(
                        //    system_clock::now() - startTime);
                        break;
                    case WAIT_OBJECT_0:
                        // now that we have some data avaialable
                        // read it from overlapped IO
                        isOK = ::GetOverlappedResult(
                            mOsFileHandle, mpOverlappedRx.get(),
                            reinterpret_cast<DWORD*>(&rNumBytesRead),
                            FALSE);
                        result = (isOK && rNumBytesRead>0)?
                            OS_SUCCESS : OS_FAILED;
                        //elapsedTime = duration_cast<milliseconds>(
                        //    system_clock::now() - startTime);
                        // update the remaing time (cannot be < 0)
                        rTimeout = std::max<milliseconds>(
                            rTimeout - duration_cast<milliseconds>(
                                system_clock::now() - startTime),
                            milliseconds::zero());
                        break;
                    default:
                        rTimeout = milliseconds::zero();
                        break;
                    }
                } else if (dwLastError == ERROR_HANDLE_EOF) {
                    ClearCommError(mOsFileHandle, &commError, &commStatus);
                    result = OS_FILE_EOF;
                } else {
                    ClearCommError(mOsFileHandle, &commError, &commStatus);
                    result = OS_FAILED;
                }
            } else { // Success
                //elapsedTime = duration_cast<milliseconds>(
                //    system_clock::now() - startTime);
                rTimeout = std::max<milliseconds>(
                    rTimeout - duration_cast<milliseconds>(
                        system_clock::now() - startTime),
                    milliseconds::zero());
                result = OS_SUCCESS;
            }
        } else { // sync mode
            BOOL isOK = ReadFile ( mOsFileHandle, pData, (DWORD)rNumBytes,
                reinterpret_cast<LPDWORD>(&numBytesRead), NULL);
            if ( isOK && (numBytesRead > 0) ) {
                rNumBytesRead = (size_t) numBytesRead;
                result = OS_SUCCESS;
            } else {
                ClearCommError(mOsFileHandle, &commError, &commStatus);
                // @JC Changed from simple test if lpErrors == 9)
                // which is equivalent to (CE_BREAK | CE_RXOVER)
                //if ((lpErrors & (CE_BREAK | CE_FRAME | CE_OVERRUN |
                //     CE_RXOVER | CE_RXPARITY)) != 0x00) {
                if (commError == 9) {
                    result = OS_FAILED;
    //              printf ("ClearCommError - lpErrors[%02x]", lpErrors);
                }
            }
            // update the remaing time (cannot be < 0)
            rTimeout = std::max<milliseconds>(
                rTimeout - duration_cast<milliseconds>(
                    system_clock::now() - startTime),
                milliseconds::zero());
        }
        return result;
    }
4

2 回答 2

2
    if (dwLastError == ERROR_IO_PENDING) {
        DWORD ovlStatus = ::WaitForSingleObject(mpOverlappedRx->hEvent, ...);
        //...
    }

当程序员使用重叠 I/O 时,这是一个非常常见的错误。核心思想是您使用它来允许设备驱动程序通过第一个 ReadFile() 调用开始工作。这将需要一段时间,I/O 总是如此,尤其是串行端口,因为它们是非常慢的设备。

所以你问司机“开始吧”,它就开始工作了。驱动程序最终将通过调用 OVERLAPPED.hEvent 上的 SetEvent() 方法发出信号已完成。这完成了您的 WaitForSingleObject() 调用。

当司机正在处理它时你应该做的是另一件事。您的线程应该做的另一项工作,在驱动程序处理 I/O 请求时很有用。例如,您可以用它点亮 MsgWaitForMultipleObjects()。这会泵出一个消息循环,因此您的 UI 仍然可以响应。并且还会告诉您串行端口何时有新数据可用。

代码中的缺陷是你不知道还能做什么。它立即调用 WaitForSingleObject() 以等待重叠 I/O 完成。在驱动程序处理读取请求时阻塞线程并且不做任何有用的工作。这是一个非常普遍的问题。

换句话说,您还没有找到使用重叠 I/O 的充分理由。通过使用同步 ReadFile() 调用,您将获得完全相同的结果。就像您当前的代码一样,它将阻塞,直到串行端口有可用数据。

所以不要打扰它。也解决了超时困境。

于 2013-08-13T23:21:07.970 回答
1

这是直接来自串行驱动程序的评论,可能会对您有所帮助:

if (timeoutsForIrp.ReadIntervalTimeout == MAXULONG) {

//
// We need to do special return quickly stuff here.
//
// 1) If both constant and multiplier are
//    0 then we return immediately with whatever
//    we've got, even if it was zero.
//
// 2) If constant and multiplier are not MAXULONG
//    then return immediately if any characters
//    are present, but if nothing is there, then
//    use the timeouts as specified.
//
// 3) If multiplier is MAXULONG then do as in
//    "2" but return when the first character
//    arrives.
//

首先,让我们看一下您的同步读取:

假设没有与COMMTIMEOUT您在初始化中设置的值混淆,其中间隔设置为MAXDWORD,其他所有内容为 0,您的同步ReadFile总是立即返回任何可用字节数,包括 0(案例 #1)。如果您指定了读取常量超时,那么如果没有数据可用,则将使用它,这意味着ReadFile如果没有数据到达,您的调用可能会超时(案例 #2)。最后,如果您将读取乘数和读取间隔都设置为,MAXDWORD那么它本质上是#2 的一种特殊情况,ReadFile当第一个字节存在时调用返回(因此,除非您使用 USB 串行桥接器或某些可以提供数据块,写入的字节值很可能是 1)。

现在,让我们看一下异步读取:

对于您的异步调用,您需要知道无论您设置的值ReadFile组合如何,该函数都会立即返回。COMMTIMEOUT你检查的方式是正确的ERROR_IO_PENDING。如果ReadFile调用返回待处理,那么您应该等待重叠的对象并获取调用的重叠结果。这与同步之间的区别在于ReadFile,重叠读取现在具有 return 的附加返回值,以防ERROR_IO_PENDING同步ReadFile只是阻塞。

您似乎在异步方法中对超时进行了太多不必要的修改。我只是将读取常量超时设置为一个合理的值,其余的设置为 0 仅在初始化时,不要管它。在同步情况下,它将阻塞,直到数据到达或超时。在异步情况下,它将返回,您可以发布一个等待,该等待可以通过自身超时WAIT_TIMEOUT或发出信号以显示原始请求的成功、超时或其他一些失败的完成。

为了进一步评论 Hans 所说的,这里的行为将是非ReadFile异步的 a 将在串行驱动程序中阻塞,而ReadFile异步的 a 将为您提供调用的机会WaitForSingleObject,但如果您调用它,您现在将阻塞在您的应用。您需要决定哪个更适合您的解决方案。

于 2013-08-14T06:20:54.827 回答