Это связано с плохой документацией.
Параметр timeout
только частично используется. WSAConnectByName
- сложная функция, которая выполняет множество операций внутри.
В начале существует такой код:
ULONG time, ms;
if (timeout)
{
time = GetTickCount();
ms = timeout->tv_sec * 1000 + timeout->tv_usec/1000;
}
, а затем несколько раз он вызывает такой код:
if (timeout && GetTickCount() - time > ms) return WSAETIMEDOUT;
но сердце функции вызовов ConnectEx
как это:
if (!ConnectEx(*))
{
if (GetLastError() == ERROR_IO_PENDING)
{
WSAGetOverlappedResult(*); // you wait here and the timeout is not used!
}
}
И ConnectEx
(что является асинхронная функция) и GetOverlappedResult
не имеют параметров для указания таймаута. Вы в конечном итоге ожидаете в GetOverlappedResult
после ConnectEx
выходов, но вы не можете установить для него таймаут.
Существует только одно приятное решение - используйте ConnectEx
напрямую и не используйте никаких таймаутов.
еще одна проблема проблема - GetAddrInfoEx
используется для перевода nodename
на ip-адрес. эта функция вызвана с timeout=0, lpOverlapped=0, lpCompletionRoutine = 0
- так что здесь также можно подождать по запросу DNS. Асинхронный запрос поддерживается только начинается с Windows 8
EDIT
если прямое использование ConnectEx
мы можем использовать тайм-аут (спасибо Remy Lebeau за идею) - создать/использовать OVERLAPPED.hEvent
и
OVERLAPPED Overlapped = {};
Overlapped.hEvent = CreateEvent(0, 0, 0, 0);
//... ConnectEx ...
ULONG NumberOfBytesTransferred = 0;
ULONG err = GetLastError();
if (err == ERROR_IO_PENDING)
{
switch (WaitForSingleObject(Overlapped.hEvent, ms))
{
case STATUS_TIMEOUT:
CancelIoEx((HANDLE)s, &Overlapped);
err = WSAETIMEDOUT;
break;
case WAIT_OBJECT_0:
// really final NT status of operation is Internal and NumberOfBytesTransferred == InternalHigh
// GetOverlappedResult set LastError by translating (NTSTATUS)Internal to win32 error
// with the loss of accuracy, especially if status > 0
GetOverlappedResult((HANDLE)s, &Overlapped, &NumberOfBytesTransferred, TRUE);
err = GetLastError();
// code of GetOverlappedResult in this case for clarity
//if ((NTSTATUS)Overlapped.InternalHigh == STATUS_PENDING)
//{
// WaitForSingleObject(Overlapped.hEvent, INFINITE);
//}
//NumberOfBytesTransferred = (ULONG)Overlapped.InternalHigh;
//if (0 > (NTSTATUS)Overlapped.Internal)
//{
// SetLastError(RtlNtStatusToDosError((NTSTATUS)Overlapped.Internal));
//}
break;
default: __debugbreak();
}
}
или для альтернативного использования GetOverlappedResultEx
с нашим таймаутом (но нужны окна 8+)
или лучший выбор (для моего просмотра) - используйте BindIoCompletionCallback((HANDLE)s,)
или напрямую привязать IOCP для сокета, а не ждать после вызова вообще.
'ConnectEx()' использует стандартный перекрываемый ввод-вывод, поэтому можно использовать таймаут. Вы должны передать структуру 'OVERLAPPED' в' ConnectEx() ', поэтому предоставьте ей дескриптор объекта события. Затем вы можете вызвать 'WaitForSingleObject()' на этом событии перед вызовом 'GetOverlappedResult()'. Если время истекает, отмените операцию 'ConnectEx()', используя 'CancelIo/Ex()'. Если 'WSAConnectByName()' еще не делает это изнутри, я бы предложил отправить запрос функции в M $ с просьбой добавить его. –
@RemyLebeau - да, конечно, можно использовать тайм-аут, если вы сами вызываете 'ConnectEx' -' WSAConnectByName' и создаете/используете hEvent в 'OVERLAPPED'. 'GetOverlappedResult()' в этом случае - точно ждать этого события - поэтому мы можем просто 'WaitForSingleObject' (с нашим таймаутом) без вызова' GetOverlappedResult() '. но я имею в виду, что лучшее решение (на мой взгляд) - использовать «ConnectEx» действительно асинхронно. напрямую привязать IOCP к сокету (или использовать «BindIoCompletionCallback») и не ждать вообще. 'ERROR_IO_PENDING' в порядке. когда операция завершена - наш обратный вызов вызван – RbMm
@RemyLebeau - да, я сейчас проверил последний выигрыш 10 (1607) - все тот же код 'ConnectEx (*)/if (GetLastError() == ERROR_IO_PENDING)/GetOverlappedResult (*);' 'WSAGetOverlappedResult' - если быть более точным, но это не меняет сути – RbMm