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

时间:2016-04-12 22:13:39

标签: c++ sockets

我有以下代码用于套接字连接到在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地址将套接字连接到端口?

2 个答案:

答案 0 :(得分:3)

您将IPv4地址(而非主机名)传递给getaddrinfo()(您应该在AI_NUMERICHOST字段中指定hints.ai_flags)。它将按原样为该IP地址输出包含addrinfo的单个sockaddr_in,它不会尝试验证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成功时才会调用freeaddrinfo()来泄露输出connect()。无论您如何使用freeaddrinfo()数据,都需要随时getaddrinfo()成功致电addrinfo

答案 1 :(得分:1)

UDP&#39; connect&#39;不是网络操作。它在本地API中设置条件,从其他主机过滤掉数据报,并允许您使用send()而不是sendto()。它不会失败,但随后的发送将会失败。