我正在尝试使用 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;
}