WINSOCK - 如何在连接不存在的IP时设置连接尝试的超时时间?

18
我正在用C++开发一个RTSP源过滤器,并且使用WINSOCK 2.0的阻塞套接字。当我创建一个阻塞套接字时,我会将它的SO_RCVTIMEO设置为3秒,如下所示:
int ReceiveTimeout = 3000; 
int e = setsockopt(Socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&ReceiveTimeout, sizeof(int));

我的过滤器尝试连接到IP_ADDRESS:554(554是RTSP服务器端口)。如果有一个服务器在该IP的554端口上监听,一切都很顺利,但是:
  1. 如果我的过滤器创建了一个套接字到一个现有的IP地址,但是在没有人侦听的随机端口上,connect()等待3秒并返回WSAETIMEDOUT。因此,在3秒后,我知道提供的URL是错误的。
  2. 如果我的过滤器创建了一个套接字到一个不存在的IP地址,并尝试连接它,在返回SOCKET_ERROR之前会挂起约10秒。因此,如果IP在网络上不存在,则SO_RCVTIMEO将被忽略...
问题: 在第二种情况下,我如何设置不存在的IP的超时?我需要先发送ICMP PING来查看IP是否存在,还是执行其他类似的检查?
任何帮助将不胜感激。 谢谢。 :) 我的问题的答案 因为我正在使用阻塞套接字,所以调用connect()会被阻塞,直到连接成功或连接失败,因为主机没有响应或者拒绝连接。如果我将套接字的超时设置为3秒,并尝试连接一个不存在的主机,我的电脑(客户端)将发送TCP数据包并设置SYN标志,以启动三次握手。通常情况下,如果主机正常,它会回复一个包含ACKSYN标志的TCP数据包,然后客户端(我)会发送一个设置了ACK标志的TCP数据包。然后连接就建立了。但是,如果主机已经关闭,并且发送了SYN,客户端会等待3秒超时时间到期,然后重试,再重试,直到达到了MICROSOFT ARTICLE中设置的TcpMaxConnectRetransmissions注册表设置,因为主机可能是正常的,但SYN数据包可能会丢失...我的Windows XP设置为4,每次尝试发送SYN时,它会等待3秒,当第四次尝试失败时,在12秒后返回SOCKET_ERROR,并设置WSAETIMEDOUT作为最后的WSA错误。
解决这个问题的方法是使用非阻塞套接字,并尝试手动测量连接尝试时间(因为现在connect()不会被阻塞),就像Martin James建议的那样。

另一种方法是调整注册表,这是最后的手段...


这是一个Windows应用程序还是控制台应用程序?这个问题很重要,因为它让我知道您在wsapi中有哪些可用的工具。 - johnathan
这是一个DirectShow Push Source过滤器,DLL库。 - Cipi
我忘了,DLLMain是否传入一个hwnd?如果没有的话,最好的选择是创建一个只有hwnd的窗口(因此你得到的唯一的东西就是一个消息泵和WndProc),将套接字设置为非阻塞,使用WSAAsync,从套接字上创建一个计时器(它是一个无符号整数,正是create timer所需的),当你的计时器消息到达消息循环时,你就知道它已经超时了。(这就是当我制作一个socks 5检查器来检查socks5代理的有效性时所做的。) - johnathan
我没有WndProc,但非阻塞套接字的想法很好。;) - Cipi
好的,要获取一个wind proc(相信我你想要它),你需要创建一个只是HWND的窗口,即它没有可视用户界面,但它提供了一个消息循环,通过它你可以告诉套接字使用WSAAsyncSelect() ;) - johnathan
也许select在这里很有用? - Bojan Hrnkas
3个回答

2
咬紧牙关。远程IP可能没有运行PING服务器,或者PING可能被某个路由器阻止,所以它没有帮助。你不能只等待10秒钟,然后进行任何错误指示吗?如果你非常需要在3秒后超时尝试连接,你可以自己设置超时时间。

3
我明白“ping catch”的含义,但我该如何自己设置超时时间?当套接字仍在尝试连接时,我如何停止另一个线程中的套接字连接尝试?我需要connect()函数返回以便我能够拥有可断开连接的套接字。或者我错了吗? - Cipi

1

实际上,伯克利套接字在连接时没有超时设置,因此您无法设置它。 ICMP PING并不可取,我不知道为什么,但如果主机不存在,则PING会花费约1秒钟的时间。尝试使用ARP来检测主机是否存在。


1
实际上,ARP不是一个选项,因为服务器在WAN上,而不是LAN上,所以ARP不起作用...或者我错了? - Cipi
维基百科称ARP存在于IEEE 802.11中。 - xandox
1
好的,就像我说的...在局域网上,我可以UDP广播ARP请求“WHO HAS 192.168.0.22”,然后带有“192.168.0.22”的接口会响应“我有,这是我的MAC地址00:11:22:33:44:55”。但所有电脑都能收到我在广域网上的ARP请求,然后只有其中一个会响应,这听起来似乎不可能。ARP请求永远不会超出本地网络。所以,我尝试了一下,结果只在局域网上有效...正如预期的那样。 :) - Cipi
所以它只是单向的,就像说Martin James一样。 - xandox

-1

通过 cmd,您可以像这样使用超时来 ping IP 地址:'ping -w 100 -n 1 192.168.1.1'

它将在 100 毫秒内返回

您可以通过 'echo %errorlevel% 0 = ok, 1 = fail' 检查返回代码,然后您就知道是否应该尝试连接

在 C++ 中实现:

bool pingip_nowait(const char* ipaddr)
{
    DWORD exitCode;

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    si.hStdOutput =  GetStdHandle(STD_OUTPUT_HANDLE);
    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    si.wShowWindow = SW_HIDE;

    CString cmd = "ping -w 100 -n 1 ";
    cmd += ipaddr;
    if (!CreateProcess(NULL,
        cmd.GetBuffer(),
        NULL,
        NULL,
        FALSE,
        0,
        NULL,
        NULL,
        &si,
        &pi)) {
            TRACE("ERROR: Cannot launch child process\n");
            return false;
    }

    // Give the process time to execute and finish
    WaitForSingleObject(pi.hProcess, 200L);

    if (GetExitCodeProcess(pi.hProcess, &exitCode))
    {
        TRACE("ping returned %d\n", exitCode);
        // Close process and thread handles. 
        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );
        return exitCode==0 ? true : false;
    }
    TRACE("GetExitCodeProcess() failed\n");
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    return false;
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接