2017-02-11 17 views
4

У меня есть объект sockaddr_storage, и мне нужно заполнить его значениями, предоставленными пользователем. Обратите внимание, что пользователь может предоставить либо AF_INET, либо AF_INET6 как домен для заполнения структуры.Заполнить sockaddr_storage struct со значениями sockaddr_in


void fill(sockaddr_storage &addrStruct, int domain, 
     const char addr[], const int port) 
{ 
    std::memset(&addrStruct, 0, sizeof(addrStruct)); 

    switch(domain) { 

     case AF_INET: addrStruct.sin_family = AF_INET; 
         addrStruct.sin_port= htons(port); 

         inet_pton(AF_INET, addr, addrStruct.sin_addr); 

     case AF_INET6: .... 
     .... 
     .... 
     default: .... 
    } 
} 

Довольно уверен, что это не работает, так как addrStruct имеет тип struct sockaddr_storage и эти элементы присутствуют в struct sockaddr_in. Я также пробовал static_cast<sockaddr_in>(addrStruct).sin_port и аналогичный, но это снова не работает. Итак, как я должен заполнять эту структуру, чтобы она сохраняла допустимые значения при соблюдении выравнивания литых структур.

+0

Используйте 'getaddrinfo' вместо того, что вы делаете, и не беспокойтесь об этом. – zwol

+0

@zwol getaddrinfo может увеличить сложность моей функции, так как я уже возвращаю значения (или успехи) вызовов 'inet_pton', мне придется принять еще один вызов. Также 'getaddrinfo' имеет тенденцию возвращать несколько адресов по случаю, я не очень хорошо помню этот сценарий, но это снова увеличило бы сложность. –

+0

Попробуйте. Вы обнаружите, что сложность идет _down_ значительно. – zwol

ответ

4

Вы должны сначала использовать соответствующую структуру адреса, и заполнить ее:

struct sockaddr_in sin; 

sin.sin_family = AF_INET; 
sin.sin_port= htons(port); 

Затем, после того, как вы закончите, memcpy() его в sockaddr_storage:

memcpy(reinterpret_cast<char *>(&addrStruct), 
     reinterpret_cast<char *>(&sin), sizeof(sin)); 

Обратите внимание, что вам должен по-прежнему полностью отключить полный буфер sockaddr_storage, как и вы.

Другой способ сделать это - определить свои union всех структур адресов, включая sockaddr_storage, а затем инициализировать соответствующий член union.

+0

Да, я думал об определении союза с 'sockaddr',' sockaddr_in', 'sockaddr_in6' и' sockaddr_storage', но я боюсь, что это может нарушить некоторые правила псевдонимов (?), Поскольку я где-то читал только SO. –

+0

Копирование с помощью 'memcpy', как это безопасно, и так же является« объединением », если вы всегда работаете с ним через тип объединения. С другой стороны, ответ, который вы приняли, определенно нарушает правила псевдонимов. – zwol

+0

@Sam Varshavchik почему 'reinterpret_cast ', так как 'memcpy' обычно принимает' void * '. Если это из-за проблем с псевдонимом, то почему теперь 'unsigned char *'? –

1

Вам необходимо сделать реинтерпрет из addrStruct по номеру sockaddr_in. В вашем случае код будет выглядеть следующим образом:

case AF_INET: 
    { 
     struct sockaddr_in * tmp = 
      reinterpret_cast<struct sockaddr_in *> (&addrStruct); 
     tmp->sin_family = AF_INET; 
     tmp->sin_port = htons(port); 
     inet_pton(AF_INET, addr, tmp->sin_addr); 
    } 
    break; 

Но я рекомендую вам использовать getaddrinfo(3) вместо вашего подхода.

+1

Этот код нарушает правила «строгого сглаживания» и имеет неплохие шансы сломаться современными компиляторами. Печально, но верно. – zwol

+0

@zwol Почему он нарушает правила «строгого сглаживания»? Я знаю, что 'sockaddr_storage' может содержать как' sockaddr_in/6' structs, так и 'sockaddr_storage' struct, как говорят, полностью выровнены с обоими из них .. см. Http://pubs.opengroup.org/onlinepubs/009696699/basedefs/ sys/socket.h.html, https://msdn.microsoft.com/en-us/library/windows/desktop/ms740504(v=vs.85).aspx –

+0

@zwol ясно сказано, что «структура должна выравниваться на соответствующей границе, чтобы указатели на нее могли быть выбраны в качестве указателей на структуры адресов, специфичные для протокола, и использовались для доступа к полям этих структур без проблем выравнивания »и« Такое выравнивание позволяет структурам данных сокета, связанным с протоколом, обращаться к полям в структуре SOCKADDR_STORAGE без проблем выравнивания. " –

2

Практически всегда лучше использовать getaddrinfo, чем inet_* семейство функций преобразования адресов. Он выполняет все выделение и инициализацию объектов sockaddr для вас - вам не обязательно связываться с sockaddr_storage. Он обрабатывает IPv4 и IPv6 без проблем, он обрабатывает «многосетевые» хосты с несколькими адресами и может (необязательно) выполнять поиск DNS.

Чтобы использовать getaddrinfo, вы полностью выбрасываете функцию fill, которую вы показали. Код, который используетfill, вероятно, выглядит примерно так:

struct sockaddr_storage ss; 
fill(&ss, AF_INET, "10.0.0.21", 25); 
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
if (sock == -1) { 
    perror("socket"); 
    return -1; 
} 
if (connect(sock, (struct sockaddr*)&ss, sizeof(struct sockaddr_in)) { 
    perror("10.0.0.21:25"); 
    return -1; 
} 
/* use connected sock here */ 

Вы заменить, что с

struct addrinfo hints; 
struct addrinfo *rp, *result; 
memset(hints, 0, sizeof hints); 
hints.ai_family = AF_UNSPEC; 
hints.ai_flags = AI_ADDRCONFIG; 
hints.ai_socktype = SOCK_STREAM; 

int err = getaddrinfo("10.0.0.21", "25", &hints, &result); 
if (err) { 
    if (err == EAI_SYSTEM) { 
     fprintf(stderr, "10.0.0.21:25: %s\n", strerror(errno)); 
    } else { 
     fprintf(stderr, "10.0.0.21:25: %s\n", gai_strerror(err)); 
    } 
    return 1; 
} 

int sock; 
for (rp = result; rp; rp = rp->ai_next) { 
    sock = socket(rp->ai_family, rp->ai_socktype, 
        rp->ai_protocol); 
    if (sock == -1) 
     continue; 
    if (connect(sock, rp->ai_addr, rp->ai_addrlen)) 
     break; 
    close(sock); 
} 
if (rp == 0) { 
    perror("10.0.0.21:25"); 
    return -1; 
} 
freeaddrinfo(result); 
/* use sock here */ 

Это выглядит более сложным, но помните, что это полностью заменяет функцию fill вы не знал, как писать, а также помните, что он будет без проблем обрабатывать DNS для вас. (Если у вас есть конкретная причина для обработки только числовых адресов, для этого вы можете установить флаги в hints.)

Для более подробного использования примера см. Справочную страницу, к которой я привязан.