WSAConnectByName 超时
WSAConnectByName timeout
我正在尝试使用 WSAConnectByName()
连接到一个地址。但是,它似乎忽略了 timeout
参数。
这是来自 MS 示例的(仅稍作修改的)代码:
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;
}
当我使用不存在的地址(如本地 IP 地址)时,代码不会因超时而失败,而是会停止,直到发生其他超时。
知道这里发生了什么吗?
这是由于错误的文档。
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
都没有指定参数暂停。在 ConnectEx
退出后,您最终在 GetOverlappedResult
中等待,但您无法为其设置超时。
只有一个好的解决方案 - 直接使用 ConnectEx
并且不使用任何超时。
另外一个问题点 - GetAddrInfoEx
用于将 nodename
转换为 ip 地址。这个函数用 timeout=0, lpOverlapped=0, lpCompletionRoutine = 0
调用 - 所以这里也可以等待 DNS 请求。只支持从 Windows 8
开始的异步查询
编辑
如果直接使用ConnectEx
我们可以使用超时(感谢Remy Lebeau的想法)-create/useOVERLAPPED.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
我们的超时(但需要 windows 8+)
或最佳选择(以我的观点)- 使用 BindIoCompletionCallback((HANDLE)s,)
或直接为套接字绑定自身 IOCP,根本不在调用后等待。
我在工作线程中使用过它。超时似乎仅在连接实际超时(10060)时才适用。如果连接被拒绝 (10061),调用可能会在几秒钟内完成。其他错误也是如此。我没有经历过任何挂起(运行 on Windows 10)。
1秒对于WSAConnectByName
来说可能有点短
我正在尝试使用 WSAConnectByName()
连接到一个地址。但是,它似乎忽略了 timeout
参数。
这是来自 MS 示例的(仅稍作修改的)代码:
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;
}
当我使用不存在的地址(如本地 IP 地址)时,代码不会因超时而失败,而是会停止,直到发生其他超时。
知道这里发生了什么吗?
这是由于错误的文档。
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
都没有指定参数暂停。在 ConnectEx
退出后,您最终在 GetOverlappedResult
中等待,但您无法为其设置超时。
只有一个好的解决方案 - 直接使用 ConnectEx
并且不使用任何超时。
另外一个问题点 - GetAddrInfoEx
用于将 nodename
转换为 ip 地址。这个函数用 timeout=0, lpOverlapped=0, lpCompletionRoutine = 0
调用 - 所以这里也可以等待 DNS 请求。只支持从 Windows 8
编辑
如果直接使用ConnectEx
我们可以使用超时(感谢Remy Lebeau的想法)-create/useOVERLAPPED.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
我们的超时(但需要 windows 8+)
或最佳选择(以我的观点)- 使用 BindIoCompletionCallback((HANDLE)s,)
或直接为套接字绑定自身 IOCP,根本不在调用后等待。
我在工作线程中使用过它。超时似乎仅在连接实际超时(10060)时才适用。如果连接被拒绝 (10061),调用可能会在几秒钟内完成。其他错误也是如此。我没有经历过任何挂起(运行 on Windows 10)。
1秒对于WSAConnectByName