I'm trying to use WSAConnectByName()
to connect to an address. However, it seems like it disregards the timeout
parameter.
Here is the (only slightly modified) code from MS's example:
SOCKET ConnSocket = INVALID_SOCKET;
int iResult;
BOOL bSuccess;
SOCKADDR_STORAGE LocalAddr = {0};
SOCKADDR_STORAGE RemoteAddr = {0};
DWORD dwLocalAddr = sizeof(LocalAddr);
DWORD dwRemoteAddr = sizeof(RemoteAddr);
ConnSocket = socket(AF_INET, SOCK_STREAM, 0);
if (ConnSocket == INVALID_SOCKET){
wprintf(L"socket failed with error: %d\n", WSAGetLastError());
return INVALID_SOCKET;
}
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
bSuccess = WSAConnectByName(ConnSocket, NodeName,
PortName, &dwLocalAddr,
(SOCKADDR*)&LocalAddr,
&dwRemoteAddr,
(SOCKADDR*)&RemoteAddr,
(struct timeval *)&tv,
NULL);
if (!bSuccess){
wprintf(L"WsaConnectByName failed with error: %d\n", WSAGetLastError());
closesocket(ConnSocket);
return INVALID_SOCKET;
}
When I use an address that doesn't exist (like a local IP address), instead of failing with a timeout, the code stops until some other timeout occurs.
Any idea what's going on here?
This is due to bad documentation.
The timeout
parameter is only partially used. WSAConnectByName
is a complex function, which performs many operations internally.
At the beginning exists code like this:
ULONG time, ms;
if (timeout)
{
time = GetTickCount();
ms = timeout->tv_sec * 1000 + timeout->tv_usec/1000;
}
and then several times it calls code like this:
if (timeout && GetTickCount() - time > ms) return WSAETIMEDOUT;
but the heart of the function calls to ConnectEx
like this:
if (!ConnectEx(*))
{
if (GetLastError() == ERROR_IO_PENDING)
{
WSAGetOverlappedResult(*); // you wait here and the timeout is not used!
}
}
Both ConnectEx
(which is an asynchronous function) and GetOverlappedResult
have no parameter for specifying a timeout. You end up waiting in GetOverlappedResult
after ConnectEx
exits, but you can not set a timeout for it.
There exists only one nice solution - use ConnectEx
directly and not use any timeouts.
else one problem point - GetAddrInfoEx
used to translate nodename
to ip address. this function called with timeout=0, lpOverlapped=0, lpCompletionRoutine = 0
- so here also can wait on DNS request. asynchronous query supported only begin from Windows 8
EDIT
if direct use ConnectEx
we can use timeout (thank Remy Lebeau for idea) - create/use OVERLAPPED.hEvent
and
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();
}
}
or for alternative use GetOverlappedResultEx
with our timeout (but need windows 8+)
or the best choice (for my view) - use BindIoCompletionCallback((HANDLE)s,)
or direct bind self IOCP for socket and not wait after call at all.