将 IPv4/IPv6 地址和端口设置为 sockaddr_storage 结构

Posted

技术标签:

【中文标题】将 IPv4/IPv6 地址和端口设置为 sockaddr_storage 结构【英文标题】:Setting IPv4/IPv6 address and port to a sockaddr_storage structure 【发布时间】:2012-07-12 07:04:57 【问题描述】:

我正在将 IPv4 应用程序移植到 AF 独立 代码库(它应该与 IPv4 和 IPv6 一起使用)。现在我尽可能地使用 sockaddr_storage,但是现在我必须设置(填充)一个 sockaddr_storage。但我不知道正确的方法是什么。之前的代码是:

// defined in data_socket.h
struct sockaddr_in laddr;

现在有这个函数可以设置sin_addrsin_port

void DataSocket::SetLocalAddr(const char *addr, const int port)

    this->laddr.sin_port = htons(port);
    if(addr != NULL)
        this->laddr.sin_addr.s_addr = inet_addr(addr);
    else
        this->laddr.sin_addr.s_addr = inet_addr("0.0.0.0");

如您所见,这是旧样式(使用 IPv4)。

现在我的更改如下。首先,我将 sockaddr_in 更改为 sockaddr_storage

// defined in data_socket.h
struct sockaddr_storage laddr;

然后我把上面的代码改成支持IPv4IPv6

void DataSocket::SetLocalAddr(const char *addr, const int port)
 
switch (this->GetAddrFamily(addr)) 
    case AF_INET:
        (struct sockaddr_in *) this->laddr.sin_port = htons(port);
        if(addr != NULL)
            inet_pton(AF_INET, addr, (struct sockaddr_in *) this->laddr.sin_addr);
        else
            inet_pton(AF_INET, "0.0.0.0", (struct sockaddr_in *) this->laddr.sin_addr);
        break;

    case AF_INET6:
        (struct sockaddr_in6 *) this->laddr.sin6_port = htons(port);
        if(addr != NULL)
            inet_pton(AF_INET6, addr, (struct sockaddr_in6 *) this->laddr.sin6_addr);
        else
            inet_pton(AF_INET6, "0:0:0:0:0:0:0:0", (struct sockaddr_in6 *) this->laddr.sin6_addr);
        break;

    default:
        return NULL;


GetAddrFamily() 在哪里:

int DataSocket::GetAddrFamily(const char *addr)

    struct addrinfo hints, *res;
    int status, result;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(addr, 0, &hints, &res)) != 0)
    
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return false;
    

    result = res->ai_family; // This might be AF_INET, AF_INET6,etc..
    freeaddrinfo(res); // We're done with res, free it up

    return result;

看来我的方法很复杂。这是正确的方法吗?因为我已经把 sockaddr_in 改成了 sockaddr_storage,其实我只是想要这个问题的反面:Getting IPV4 address from a sockaddr structure

我正在尝试找到最佳解决方案,例如这里:http://www.kame.net/newsletter/19980604/ 它说永远不要使用inet_ntop()inet_pton(),但是其他一些(如Beej 的网络教程)说inet_ntop() 和@987654334 @ 应该用于基于 IPv6 的应用程序。

我的实现方式正确还是应该改变它?

【问题讨论】:

这是我喜欢boost::asio::ip::address的原因之一,它抽象了boost::asio::ip::address_v4boost::asio::ip::address_v6 当 C 获得成员函数时告诉我。 @DeadMG 抱歉 c 标签 【参考方案1】:

我强烈建议让getaddrinfo 完成所有繁重的工作,例如。

void DataSocket::SetLocalAddr(const char *addr, const unsigned short int port)

    struct addrinfo hints, *res;
    int status;
    char port_buffer[6];

    sprintf(port_buffer, "%hu", port);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    /* Setting AI_PASSIVE will give you a wildcard address if addr is NULL */
    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;

    if ((status = getaddrinfo(addr, port_buffer, &hints, &res) != 0)
    
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return;
    

    /* Note, we're taking the first valid address, there may be more than one */
    memcpy(&this->laddr, res->ai_addr, res->ai_addrlen);

    freeaddrinfo(res);

【讨论】:

感谢您的代码。但我对memcpy(&this->laddr, res->ai_addr, res->ai_addrlen); 行感到困惑。 res->ai_addr 是一个 sockaddr 结构,我猜它会自动转换为 sockaddr_insockaddr_in6?它是如何存储到this->laddr中的(因为this->laddrsockaddr_storage结构)? 我想我应该在getaddrinfo 中使用port_buffer 而不是port,因为getaddrinfo() 除了一个const char *(又名字符串)? res->ai_addr 指向struct sockaddr 的一些变体,sockaddr_insockaddr_in6。没有进行转换,结构只是按原样复制。 struct sockaddr_storage 的大小和布局允许将其转换为任何 sockaddr 变体,所有这些变体都共享一个公共标头。回复:传递一个字符串作为端口,我不会打扰,但这是你的代码。 (另外,修正了错字,getaddrinfo 本来是缓冲区) 我问是因为我将在另一个函数中使用this->laddr 结构。但是我不t know which structure it is based on. I've initially set it as sockaddr_storage. Does it mean I have to check now this->laddr.ss_family`(假设它是sockaddr_storage) and cast it to sockaddr_in`或sockaddr_in6,如果我想在其他功能中使用它?或者我应该检查this->laddr.sa_family(因为它是一个普通的sockaddr 结构)? @FatihArslan:检查laddr.ss_family 和检查(struct sockaddr)laddr.sa_family 是相同的。在大多数情况下,您只需传递 struct sockaddr_storage,在需要时将其转换为 sockaddr(或 _in/in6 变体)。【参考方案2】:

如果我理解正确,您将this 投射到第一个成员?不要那样做,给成员命名。

我还将通过为这两种情况引入 范围和局部变量来使其更易于阅读,例如:


 struct sockaddr_in * in4 = reinterpret_cast< struct sockaddr_in * >(&this->addr);
 in4->laddr.sin_port = htons(port);
 ... etc
 

由于您使用的是 C++ 而不是 C,因此请使用 C++ 样式转换。 C++ 中的 C 风格转换非常含糊。

【讨论】:

以上是关于将 IPv4/IPv6 地址和端口设置为 sockaddr_storage 结构的主要内容,如果未能解决你的问题,请参考以下文章

将 IPv4、IPv6 地址存储为字符串的大小

ipv6设置

windows winsock2 socket从WSAGetLastError返回10014 WSAEFAULT(ipv4 / ipv6相关)

为何局域网lan设置里的代理服务器的地址和端口为灰色无法设置

ubuntu服务器只有ipv6监听服务

android studio 配置HTTP proxy