Я пытаюсь использовать перекрывающийся режим ввода-вывода в Windows 7/8 X64 для эмуляции неблокирующего режима (IO_NONBLOCK) поведение поддерживается открытыми флагами Linux. Код здесь является частью оконной части кроссплатформенного последовательного API.
Я могу открыть порт COMM в режиме блокировки или неблокирования (OVERLAPPED), используя параметры конструктора для SerialCommWnt объект. Что касается этого вопроса, все мои вопросы касаются открытия порта COMM в режиме OVERLAPPED (как указано в параметре конструктора управления потоком). Для метода Read я указываю параметр времени ожидания, который при успешном получении по крайней мере 1 байта данных из последовательного порта должен указывать оставшееся время параметра rTimeout, когда данные находились во входном буфере последовательной связи (я считаю, что последовательный порт драйвер уведомляет о событии ручного сброса в перекрывающейся структуре, когда получает какие-либо данные).
Я прочитал много потоков StackOverflow о том, как обрабатывать эти API, многие из них ссылаются на Microsoft Win32 API. Лучшая информация, которую я могу найти,
http://msdn.microsoft.com/en-us/library/ff802693.aspx
Этот API сбивает с толку перекрывающийся ввод-вывод (особенно когда речь идет о передаче указателя на количество байтов, полученных при вызове ReadFile в перекрывающемся режиме), но, насколько я могу судить, ни один из них не говорит о том, как правильно использовать перекрывающийся ввод-вывод Режим в сочетании с COMMTIMEOUTS. Я провел последние пару дней, экспериментируя с комбинациями настроек для COMMTIMEOUTS и параметра timeout, используемого с :: WaitForSingleObject. Наконец, комбинация, которая, кажется, в основном работает, показана. У меня есть некоторые вопросы надежности в отношении тайм-аутов, связанных с объектом события ручного сброса, связанным с перекрывающейся структурой ввода-вывода и COMMTIMEOUTS. Я не совсем уверен, но кажется, что для правильной работы тайм-аута при чтении из последовательного порта необходимо обязательно указать тайм-аут в COMMTIMEOUTS. Я попробовал комбинацию, где я отключил тайм-ауты в SetCommTimeouts и вместо этого использовал явное время ожидания в :: WaitForSingleObjectПараметр timeout, но это не сработало, вместо этого я сделал это наоборот, указав тайм-аут в COMMTIMEOUTS и указав INFINITE с помощью :: WaitForSingleObject вызов метода. Однако я не уверен, могут ли быть ситуации, когда это будет зависать вечно, и если да, то как я могу справиться с этим. Буду признателен за любую информацию о том, как правильно обрабатывать потенциально висящие здесь.
Вот метод, который я использую, чтобы открыть порт 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;
}
if (dwLastError == ERROR_IO_PENDING) {
DWORD ovlStatus = ::WaitForSingleObject(mpOverlappedRx->hEvent, ...);
//...
}
Это очень распространенная ошибка, когда программисты используют перекрывающиеся операции ввода-вывода. Основная идея заключается в том, что вы используете его, чтобы позволить драйверу устройства начать работу с заданием с первым вызовом ReadFile (). Это займет некоторое время, I / O всегда делает, особенно с последовательными портами, так как они очень медленные устройства.
Таким образом, вы спрашиваете водителя «начать на нем», и он идет о своей работе. Драйвер, в конце концов, сообщит об этом, вызвав метод SetEvent () для OVERLAPPED.hEvent. Это завершает ваш вызов WaitForSingleObject ().
Кто ты есть предполагаемый делать, пока водитель работает над этим что-то другое. Другая работа, которую должен выполнить ваш поток, что-то полезное, пока драйвер работает над запросом ввода-вывода. Вы можете, например, зажечь MsgWaitForMultipleObjects () с ним. Что качает цикл сообщений, так что ваш пользовательский интерфейс все еще реагирует. А также сообщает, когда на последовательном порту появятся новые данные.
Недостаток в коде состоит в том, что вы не могли понять, что еще делать. Он немедленно вызывает WaitForSingleObject (), чтобы дождаться завершения перекрывающегося ввода-вывода. Блокирует поток и не выполняет никакой полезной работы, пока драйвер работает над запросом на чтение. Это очень распространенная проблема.
Другими словами, вы еще не нашли вескую причину использовать перекрывающиеся операции ввода-вывода. Ты получишь именно так тот же результат, используя синхронный вызов ReadFile (). Он будет блокировать, как и ваш текущий код, пока на последовательном порту не будут доступны данные.
Так что просто не беспокойтесь об этом. Исправляет дилемму тайм-аута тоже.
Вот комментарий прямо из последовательного драйвера, который может вам помочь:
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
call возвращает, когда есть первый байт (поэтому, если вы не используете последовательный мост USB или какой-либо канал, который может доставить блок данных, записанное значение байтов, скорее всего, будет равно 1).
Теперь давайте посмотрим на асинхронное чтение:
Для вашего асинхронного ReadFile
Для вызова необходимо знать, что функция всегда будет возвращаться немедленно, независимо от комбинации COMMTIMEOUT
значения, которые вы установили. Вы правы в том, как вы проверяете ERROR_IO_PENDING
, Если ReadFile
вызов возвращается в ожидании, тогда вы должны подождать перекрывающийся объект и получить перекрывающийся результат вызова. Разница между этим и синхронным ReadFile
является то, что перекрывающееся чтение теперь имеет дополнительное возвращаемое значение ERROR_IO_PENDING
возвращается в случаях, когда синхронный ReadFile
будет просто заблокировать.
Кажется, что вы делаете слишком много ненужных модификаций тайм-аутов в вашем асинхронном методе. Я бы просто установил разумное значение времени ожидания постоянной чтения, а остальное — только при инициализации и оставил его в покое. В синхронном случае он будет блокироваться до тех пор, пока не поступят данные или не истечет время ожидания. В асинхронном случае он вернется, и вы можете опубликовать ожидание, которое может отключиться через WAIT_TIMEOUT
или быть уведомленным, чтобы показать завершение исходного запроса с успехом, тайм-аутом или некоторым другим отказом.
Чтобы дополнительно прокомментировать то, что говорил Ганс, поведение здесь будет ReadFile
это не асинхронный будет блокировать в последовательном драйвере и ReadFile
это асинхронный даст вам возможность позвонить WaitForSingleObject
, но если вы называете это, вы теперь блокируете в своем приложении. Вам нужно будет решить, что лучше для вашего решения.