套接字编程:connect() 因不存在的 IP 而挂起

socket programming: connect() hangs for a non-existent IP

下面是创建套接字连接的代码,如果 IP 存在,returns 正套接字描述符,如果 IP 不存在,它会卡在例程中connect()

Connection::Connection(string& ip) : sock(0), status(0), conn(0){
    struct sockaddr_in sin;

    sock = socket(AF_INET, SOCK_STREAM, 0);//socket() returns -1 on failure.
    sin.sin_family = AF_INET;
    sin.sin_port = htons(22);
    sin.sin_addr.s_addr = inet_addr(ip.c_str());
    cout << "sock: " << sock << endl;
    //fcntl(sock, F_SETFL, O_NONBLOCK);
    if(sock != -1){
        conn = connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in));
        cout << "conn: " << conn << endl;
        if ( conn != 0){
            status = -2;
        }
    }
    else{
        status = -1;
    }
}

出于调试目的,我在 socket()connect() 之后放置了 cout。而且我已经测试过,如果向构造函数提供了不存在的 IP,cout << "conn: " << conn << endl; 永远不会执行并继续等待。

这些代码适用于现有 IP。

我曾在某处读到,将 socket descriptor 设置为 O_NONBLOCK 可以解决挂起问题。是的,确实如此,但是出现了一个新问题;我什至无法连接到现有 IP。

请帮我解释为什么会这样以及如何解决这个问题。

我认为您需要退后一步,考虑一下您所说的 IP 'exists'.

是什么意思

当你调用connect时,OS会发送一个数据包(一个SYN数据包)到目标IP。它不知道 IP 'exists' 是否存在。事实上,这个概念没有明确定义 - 它可能会或可能不会被分配。该设备可能会或可能不会打开或插入。它可能位于 DHCP 池中,该 IP 的租约已经或尚未分发。 OS 知道 none 这个。 OS 只知道它是否得到回复。并且两个方向都可能丢包,需要回复

一般来说,OS 可以获得三种类型的回复(您可以使用 tcpdump 或 wireshark 来查看发生了什么):

  1. 目标 IP 回复 SYN+ACK 数据包。这是三次握手的下一阶段。目标 IP 显然有效。

  2. 目标 IP 回复 RST。这意味着 'go away';你会看到 'connection refused'.

  3. 目标 IP 或某个中间路由器回复 ICMP 主机无法访问或网络无法访问,在这种情况下,您将看到主机无法访问或网络无法访问。如果主机或网络无法访问,则不能保证会发生这种情况。

还有第四种可能,就是根本没有收到回复。本例connect等待重试几次,最后超时。这就是你所看到的。在防火墙中过滤掉 ICMP 会将上面的情况 (3) 转换为这种情况,但重要的是要注意这无论如何都可能发生。所以这是一个自然的事态,你应该准备好处理。

使用非阻塞connect()(通过首先设置O_NONBLOCK)使connect() return立即-甚至在功能IP建立连接之前。在任何情况下,您都需要等待 一些 时间才能建立连接。在缓慢 link 或数据包丢失的情况下,功能正常的 IP 可能需要数十秒才能连接。因此,在这种情况下,您需要实现自己的超时(例如通过套接字上的 select()-ing)。没有(无论如何在 Linux 下)将您自己的超时设置为 connect() 的选项,因此如果您想更改超时,您必须使用非阻塞连接来实现它。 Stephens(一本优秀的书 - 购买)关于非阻塞的详细信息 connect() here.