代码之家  ›  专栏  ›  技术社区  ›  user474901

使连接到IPv6或IPv4变得容易?

  •  0
  • user474901  · 技术社区  · 10 年前

    我正在创建套接字类,但我想使我的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 回复  |  直到 10 年前
        1
  •  1
  •   Michael Hampton    10 年前

    您的代码有多个问题:

    • 首先,您正确地调用了 getaddrinfo() ,但随后您完全丢弃了结果而不使用它们。
    • 你打过电话 listen() 但您似乎打算进行传出连接; 列表() 用于侦听传入连接。
    • 而不是使用来自 获取地址信息() 在填写您的 sockaddr_in 结构这部分代码应该被废弃。
    • 无需显式检查返回的地址族。你不会得到任何计算机无法处理的地址族。
    • 您似乎正在编写一个方法,它可以做多件事,即既可以进行传出连接,也可以接受传入连接。方法只能做一件事。

    让我们回到开头,建立一个最小的传出连接。这里我省略了与创建连接无关的任何内容(例如 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!
    }
    

    主要需要注意的是 获取地址信息() 为您处理所有的重物。它返回的结构具有创建连接所需的所有信息;你只需要使用它。

    如果您需要连接信息,例如地址和家庭,可以将这些信息复制到 rp 并在其超出范围之前将其存储在某处。

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