【问题标题】:Best & Fast way to find out if an ip address is reachable找出IP地址是否可达的最佳和快速方法
【发布时间】:2010-12-29 15:37:30
【问题描述】:

我需要最快的方法来查看 IP 地址是否可达。在每个 IP 地址上都有一个服务器在侦听特定端口,因此请告诉我您的方法是否要查找服务器是否正在侦听端口。

这样做的原因是假设我有 10 个 IP 地址,其中 10 个服务器在每个 IP 地址的 101 端口上侦听。我希望我的客户能够找到一个可访问的 IP 地址并尽可能快地连接到它(我不希望他等待 30 秒来确定一个 IP 地址是否可以访问,然后尝试下一个 IP 地址在列表)

可能必须同时进行。

【问题讨论】:

    标签: c++ windows ip-address


    【解决方案1】:

    虽然您可以快速确定 IP 可访问,但您的问题是确定 IP 不可访问。原因是您不能始终明确确定 IP 不可访问。虽然在某些情况下您会收到 IP 不可访问的肯定通知,但通常您的代码不会听到答案,并且在等待一段时间后,您的代码将假定 IP 不可访问。

    决定超时的问题是网络拓扑。如果您有一个大型拓扑(例如 Internet),如果您尝试连接到“远”的 IP,则需要一个较大的超时时间来处理潜在的高延迟。

    根据您的描述,最好的办法是尝试同时连接到所有服务器并使用第一个接受连接的服务器。您可以使用线程,也可以使用非阻塞套接字。在非阻塞连接中,连接调用立即返回,然后您使用select 有效地确定连接调用何时完成(成功或出错)。

    【讨论】:

    • 正是我要找的...
    【解决方案2】:

    您可以使用线程,但它会为此任务引入不必要的开销。

    在这里使用非阻塞套接字(并尽可能避免使用非阻塞套接字!真的,但在这种情况下它们是有意义的):

    // initiate tcp connects...
    for( each of your target host+port pairs ) {
        int socket = socket( AF_INET, SOCK_STREAM );
        ...
    #ifdef WIN32
        unsigned long mode = 1;
        ioctlsocket( socket, FIONBIO, &mode );
    #else
        int value = fcntl( socket, F_GETFL, 0 );
        fcntl( socket, F_SETFL, value | O_NONBLOCK );
    #endif
        ...
        int code = connect( s, target, ... );
        if( code == 0 ) { /* ok, this one did connect, DONE */ }
        // now remember the socket in a list ...
    }
    // now find the first socket that was successfully connected
    while( still not found ) {
        struct timeval tval;
        memset( &tval, 0, sizeof(tval) );
        fd_set write_set, error_set;
        int largest_socket = 0;
        // add sockets to write and error set, and determine largest socket no.
        for( each socket that did not fail until now ) {
            FD_SET( socket, write_set );
            FD_SET( socket, error_set );
            if( socket > largest_socket ) largest_socket = socket;
        }
        // now use select to wait until something happens on the sockets
        int code = select( largest_socket+1, NULL, &write_set, &error_set, &tval );
        if( code < 0 ) { something went terribly wrong }
        else {
            for( each socket that did not fail until now ) {
                if( FD_ISSET( socket, write_set ) ) { you've found it! DONE }
                if( FD_ISSET( socket, error_set ) ) { error, remove this socket from list (for next iteration) }
            }
        }
    }
    

    查看connectselect 的文档了解更多详情!

    【讨论】:

    • 你应该选择这个答案而不是另一个......它包含更多信息......你甚至看过我的吗?
    【解决方案3】:

    通常在短时间内随机尝试连接就足够了。

    可达性不是很重要,从你到服务器的路由存在这一事实并不重要,你是否可以连接到所述服务器是重要的。通常,您自己的代码运行速度与您可以设计的任何其他可达性方法一样快。

    如果您在处理时间过长时遇到问题,请尝试调整响应的长度,或者设置更短的超时时间。

    简单算法:

    shuffle IP addresses
    foreach IP in addresses
        attempt connect
        if succeed then
           break
    

    【讨论】:

    • -1,因为他的问题是关于并行操作的问题,而不是关于如何让单个线程打开套接字的伪代码(他已经证明他知道该怎么做)。
    • @Downvotes:我是说他的问题有缺陷。他自己给出了性能要求,
    • @Guvante:您的方法需要非常短的超时时间,在某些情况下,这可能会导致连接失败,而当超时时间长几毫秒时,连接可能会成功。这对于通过互联网等大型网络的“新”路由尤为重要。
    • @frunsi:3000 毫秒的超时时间可以满足他的性能要求。一切都是相对的。
    【解决方案4】:

    尝试使用BSD socket library 中的connect() 函数打开一个套接字。它尽可能快,如果端口未打开,它将不会响应 SYN 数据包。

    您意识到,关键问题是占用了一个线程,该线程必须等待 SYN-ACK 才能执行其他任何操作。 幸运的是,parallelise IO anymore 不需要线程;但是编程异步操作可能很微妙;因此,我建议使用 libevent library 并行调度 TCP/IP 连接操作...因为 内核正在做繁重的工作,您只需要 一个线程来完成它 开。您可以使用 libevent 每秒进行 100 次或数千次连接 - 取决于您的网络硬件。

    另一种选择是Boost::ASIO,它更复杂。但是由于您使用的是 C++,因此可能更适合您。

    【讨论】:

      【解决方案5】:

      以下是可用于同时创建传出连接的代码。

      在您的循环中迭代您的 IP 和 SpawnOutgoing 连接。
      每个连接conn_t* 作为 LParam 发布在窗口消息中 - 同时。

      您应该监控消息并仅将第一个连接保存在某处 - 忽略(删除)其他连接。

      #define MSG_NEW_CONNECTION  (WM_USER + 1)
      
      struct conn_t {
          SOCKET       s;
          sockaddr_in  server;
      };
      
      static
      UINT OutgoingWorker(LPVOID param)
      {
        // `param` holds "conn_t*"
        assert(param);
        if (!param) return 0;
        conn_t* c = (conn_t*)param;
        if (SOCKET_ERROR == connect(c->s, (SOCKADDR*)&c->server, sizeof c->server)) {
          closesocket(c->s);
          return 0;
        }
        PostMessage(mainwnd, MSG_NEW_CONNECTION, 0, (LPARAM)c); // <-- mainwnd handle
        return 0;
      }
      
      conn_t*
      SpawnOutgoing(const char* dest_ip, const USHORT dest_port)
      {
        if (!dest_ip) return NULL;
        SOCKET  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (INVALID_SOCKET == s) {
          return NULL;
        }
        conn_t* c = new conn_t;
        // Create the socket here but connect it in the worker
        memset(&c->server, 0, sizeof sockaddr_in);
        c->server.sin_family = AF_INET;
        c->server.sin_addr.s_addr = inet_addr(dest_ip);
        c->server.sin_port = htons(dest_port);
        c->s = s;
      
        CreateThread(0, 0, OutgoingWorker, c);
        return c;
      }
      

      【讨论】:

      • 不完美的解决方案,你到底为什么要创建线程?我们走出了洞穴,发现了如何耕种!阅读异步编程。
      • @Hassan Syed,我知道异步编程。多路复用或非多路复用设计。阻塞或非阻塞套接字等。我们将选择哪种方法取决于项目。
      猜你喜欢
      • 2014-08-20
      • 2016-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多