套接字连接到不存在的ip地址上的端口



我有以下代码用于套接字连接到运行在Ubuntu 15.10上的服务器:

    void Connect(std::string address, int port)
    {
            struct addrinfo hints;
            struct addrinfo *result = NULL;
            struct addrinfo *rp = NULL;
            int sfd, s;
            std::cout << "Connecting to address " << address << " port " << port << std::endl;
            std::memset(&hints, 0, sizeof(struct addrinfo));
            hints.ai_family = AF_UNSPEC;        /* Allow IPV4 or IPV6 */
            hints.ai_socktype = SOCK_DGRAM;     /* Datagram socket */
            hints.ai_flags = 0;
            hints.ai_protocol = 0;              /* Any protocol */
            std::string portStr;
            portStr = std::to_string(port);
            s = getaddrinfo(address.c_str(), portStr.c_str(), &hints, &result);
std::cout << "ADDRESS-------------> " << s << std::endl;
            if (s != 0)
            {
                std::stringstream ss;
                ss << "Cannot resolve hostname " << address << gai_strerror(s);
                throw std::runtime_error(ss.str());
            }
            /*
             *  getaddrinfo() returns a list of address structures. We should try each
             *  address until we successfull bind. If socket() or connect() fails, we close the socket
             *  and try the next address until the end.
             */
            for (rp = result; rp != NULL; rp = rp->ai_next)
            {
    std::cout << "loop-----------------> " << rp->ai_family << std::endl;
                sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    std::cout << "sfd-----------------> " << sfd << std::endl;
                if (sfd == -1)
                    continue;
    /*
                 *  If connect succeed, the address was found.
                 */
                int sts = connect(sfd, rp->ai_addr, rp->ai_addrlen);
    std::cout << "sts-----------------> " << sts << std::endl;
                if (sts == 0)
                    break;
                close(sfd);
            }
            /*
             *  Check for failure
             */
            if (rp == NULL)
            {
                std::stringstream ss;
                ss << "Cannot find server address at " << address << " port " << port;
                throw std::runtime_error(ss.str());
            }
            freeaddrinfo(result); /* Object no longer needed */
    std::cout << "SOCKET-----------------> " << sfd << std::endl;
            currentSocket = sfd;
    }

我的问题是,即使IP地址不可用,这个代码也在连接套接字。检查结果:

以下是运行时输出:

    Connecting to address 192.168.0.185 port 9090
    ADDRESS-------------> 0
    loop-----------------> 2
    sfd-----------------> 5
    sts-----------------> 0
    SOCKET-----------------> 5
$ ping 192.168.0.185
PING 192.168.0.185 (192.168.0.185) 56(84) bytes of data.
From 192.168.0.185 icmp_seq=1 Destination Host Unreachable
From 192.168.0.185 icmp_seq=2 Destination Host Unreachable
From 192.168.0.185 icmp_seq=3 Destination Host Unreachable

我想知道这里发生了什么?为什么它将套接字连接到不存在的Ip地址的端口?

您正在向getaddrinfo()传递IPv4地址,而不是主机名(不过,您应该在hints.ai_flags字段中指定AI_NUMERICHOST)。它将输出一个包含该IP地址的sockaddr_in的单个addrinfo,因为它不会尝试验证IP的存在。这就是getaddrinfo()返回0的原因。

您告诉getaddrinfo()您将使用UDP(SOCK_DGRAM)套接字,而不是TCP(SOCK_STREAM)套接字。因此,输出addrinfo包含用于在调用socket()时创建UDP套接字的信息。

然后在UDP套接字上调用connect()。在UDP中,connect()实际上并不像TCP那样创建物理连接。它仅将指定的对等IP分配给套接字,从而可以使用send()recv()来代替sendto()recvfrom()。这就是connect()返回0而不是失败的原因。这样做允许send()始终向同一IP发送分组,并且允许recv()仅接受从同一IP接收的分组。

您实际上还没有发送任何数据,因此在代码的任何步骤都不会验证对等IP。一旦开始发送数据,传输的数据包将最终接收到来自网络的ICMP主机无法访问错误,从而导致send()recv()开始失败。

因此,如果您希望connect()对于不可访问的IP地址失败,请创建一个TCP套接字而不是UDP套接字。否则,如果你继续使用UDP套接字,你需要将数据发送到IP,这样网络就会尝试对其进行物理路由。

顺便说一句,如果connect()确实失败了,那么您将泄漏输出addrinfo,因为只有在connect()成功的情况下才调用freeaddrinfo()。无论如何使用addrinfo数据,只要getaddrinfo()成功,都需要调用freeaddrinfo()

UDP"连接"不是网络操作。它在本地API中设置一个条件,过滤掉来自其他主机的数据报,并允许您使用send()而不是sendto()。它不会失败,但后续发送会失败。

最新更新