轻松连接IPv6或IPv4?

时间:2014-03-07 06:27:13

标签: c++ windows sockets ipv6 ipv4

我是创建套接字类但我想使我的Connect函数动态并且可以连接到地址(ipv4或ipv6)使用switch进行IPv测试并连接到支持的IPv只是想问我是对还是有一个简单的方法来制作IPv4或IPv6?

bool Connect(short port,std::string addr,bool vlisten,HWND WindowHandle,WSADATA& wsaData,bool async)
    {
        if(!hSocket);
        {
            this->port = port;
            this->addr =addr;
            this->vlisten = vlisten;
            this->WindowHandle = WindowHandle;
            this->wsaData =wsaData;
            this->init = true;

            // Provide big enough buffer, ipv6 should be the biggest
            char ipstr[INET6_ADDRSTRLEN];
            char ipstr2[INET6_ADDRSTRLEN];

            struct sockaddr_in* sockaddr_ipv4;
            struct sockaddr_in6* sockaddr_ipv6;
            //struct sockaddr_in6* sockaddr_ipv6;
            if(WSAStartup(MAKEWORD(2,2),&wsaData) !=0)
            {
                throw runtime_error("Error WSAStartup:" + WSAGetLastError());
            }

            if((this->hSocket = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== INVALID_SOCKET)
            {
                Close();
                throw runtime_error("Error init sockect:" + WSAGetLastError());
            }

            if(addr != "INADDR_ANY")
            {
                struct addrinfo *result = nullptr;
                getaddrinfo(addr.c_str(), nullptr, nullptr, &result);
                struct addrinfo *it;
                for (it = result; it != nullptr; it = it->ai_next)
                {
                    //sockaddr_ipv4 = reinterpret_cast<sockaddr_in*>(it->ai_addr);
                    //addr = inet_ntoa(sockaddr_ipv4->sin_addr);
                    //if (addr != "0.0.0.0") break;
                    switch (it->ai_family) 
                    {
                    case AF_UNSPEC:
                        cout<<"Unspecified\n"<<endl;
                        break;
                    case AF_INET:
                        cout<<"AF_INET (IPv4)\n"<<endl;
                        sockaddr_ipv4 = reinterpret_cast<sockaddr_in*>(it->ai_addr);
                        //printf("\tIPv4 address %s\n",
                        addr = inet_ntoa(sockaddr_ipv4->sin_addr);
                        /*if (addr != "0.0.0.0") break;*/
                        break;
                    case AF_INET6:
                        cout<<"AF_INET (IPv6)\n"<<endl;
                        sockaddr_ipv6 = reinterpret_cast<sockaddr_in6*>(it->ai_addr);
                        addr = inet_ntop(it->ai_family,sockaddr_ipv6,(PSTR)ipstr,sizeof(ipstr));
                        break;
                    case AF_NETBIOS:
                        cout<<"AF_NETBIOS (NetBIOS)\n"<<endl;
                        break;
                    default:
                        printf("Other %ld\n", it->ai_family);
                        break;

                    }
                }
                freeaddrinfo(result);
            }
        }
        SOCKADDR_IN sockAddrIn;
        memset(&sockAddrIn,0,sizeof(sockAddrIn));
        sockAddrIn.sin_port = htons(port);
        sockAddrIn.sin_family =  AF_INET;
        sockAddrIn.sin_addr.s_addr = (addr == "INADDR_ANY" ? htonl(INADDR_ANY) : inet_addr(addr.c_str()));

        if(vlisten && (bind(hSocket,reinterpret_cast<SOCKADDR*>(&sockAddrIn),sizeof(sockAddrIn))== SOCKET_ERROR))
        {
            Close();
            throw runtime_error("Error vlisten & bind: " + WSAGetLastError());
        }

        if(async && WindowHandle)
        {
            if(WSAAsyncSelect(hSocket,WindowHandle,WM_SOCKET,FD_READ|FD_WRITE|FD_CONNECT|FD_CLOSE|FD_ACCEPT) !=0)
            {
                Close();
                throw runtime_error("Error async & WindowHandle: " + WSAGetLastError());
            }

        }

        if(vlisten && (listen(hSocket,SOMAXCONN)== SOCKET_ERROR))
        {
            Close();
            throw runtime_error("Error async & WindowHandle: " + WSAGetLastError());
        }

        if(!vlisten && (connect(hSocket, reinterpret_cast<SOCKADDR*>(&sockAddrIn), sizeof(sockAddrIn)) == SOCKET_ERROR))
        {
            if(async && WindowHandle && (WSAGetLastError() != WSAEWOULDBLOCK))
            {
                Close();
                throw runtime_error("Error async & WindowHandle: " + WSAGetLastError());
            }
        }
    }

1 个答案:

答案 0 :(得分:1)

您的代码有多个问题:

  • 首先,您正确调用了getaddrinfo(),但之后您完全抛弃了结果而不使用它们。
  • 你打电话给listen(),但你似乎打算建立一个传出连接; listen()旨在侦听传入的连接。
  • 您可以忽略它,而不是使用getaddrinfo()中的信息,并在填写sockaddr_in结构时假设为IPv4。这部分代码应该废弃。
  • 无需明确检查返回的地址系列。您将无法获得计算机无法处理的任何地址系列。
  • 您似乎正在编写一个执行多个操作的方法,即同时创建传出连接并接受传入连接。方法应该只做一件事。

让我们回到开头,获得最小的传出连接。我在这里省略了与创建连接没有直接关系的任何内容(例如,对WSAAsyncSelect()的调用以及其他属于不同方法的东西):

// Connect to a remote host, given its address and port number.
bool Connect(short port, std::string addr)
{
    struct addrinfo *result, *rp;

    // TODO: You passed us an integer port number. We need a C string.
    // Clean up this mess.
    char portstr[255];
    portstr = sprintf("%d", port);

    if (getaddrinfo(addr.c_str(), portstr, nullptr, &result)) {
        throw runtime_error("getaddrinfo: " + WSAGetLastError());
    }

    // A host can have multiple addresses. Try each of them in turn until
    // one succeeds. Typically this will try the IPv6 address first, if
    // one exists, then the IPv4 address. The OS controls this ordering
    // and you should not attempt to alter it. (RFC 6724)
    for (rp = result; rp != nullptr; rp = rp->ai_next) {
        this->hSocket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

        // Check socket creation failed; maybe harmless (e.g. computer
        // doesn't have IPv6 connectivity). Real errors will get thrown below.
        if (this->hSocket == -1)
            continue;

        if (connect(this->hSocket, rp->ai_addr, rp->ai_addrlen) != -1)
            break;  // Success

        close(this->hSocket);
    }

    if (rp == NULL) {  // No address succeeded
        throw runtime_error("connect: " + WSAGetLastError());
    }

    freeaddrinfo(result);

    // Now this->hSocket has an open socket connection. Enjoy!
}

需要注意的主要事项是getaddrinfo()为你处理所有繁重的事情。它返回的结构具有创建连接所需的所有信息;你只需要使用它。

如果您需要连接信息(例如地址和系列),则可以复制rp之外的信息,并在它超出范围之前将其存储在某处。

编写单独的方法来处理传入的连接留给读者练习。您的示例代码似乎部分基于the MSDN page for getaddrinfo上的示例; Linux getaddrinfo manual page有更好的示例(示例代码实际上在Windows上的变化很小)。