Ответ 1
Проблема совместного программирования IPV6 и IPV4 заключается в том, что сама чистая структура sockaddr недостаточно велика для хранения sockaddr_in6. Поэтому, если вам нужно слепо обходить адрес, который может быть либо sockaddr_in, либо sockaddr_in6, sockaddr_storage немного проще в использовании.
В конце дня, независимо от того, используете ли вы sockaddr_in, sockaddr_in6 или sockaddr_storage, вам нужно будет указать эти указатели, чтобы сделать вызов sendto, recvfrom, connect, accept и многих других функций сокетов. Это всего лишь известный нюанс программирования сокетов. Просто отпустите чувство что-то небезопасное. Ваш код будет в порядке.
Теперь, когда вы пишете сетевой код, предназначенный для работы как для IPV4, так и для IPV6, вы можете легко попасть в ловушку наличия множества операторов switch для обработки разных типов сетей. Затем код становится беспорядочным, как показано ниже:
if (addr.ss_family == AF_INET)
sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in))
else (addr.ss_family == AF_INET6)
sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in6));
И тогда этот тип выражения "if family == AF_INET" может легко начинать повторяться снова и снова. Это то, чего вы хотите избежать.
Предполагая, что вы используете С++, вы обнаружите, что класс абстракции для объекта сокета-адреса невероятно полезен. У меня есть пример на github здесь и здесь. Класс CSocketAddress поддерживается объединением {sockaddr, sockaddr_in, sockaddr_in6} и может быть построен с помощью sockaddr_storage. Если бы я знал о sockaddr_storage, прежде чем я начал этот класс, я бы использовал это вместо объединения. В любом случае, это позволяет мне написать код следующим образом:
CSocketAddress addr;
...
sendto(sock, buffer, len, 0, addr.GetSockAddr(), addr.GetSockAddrLength());
Аналогично, выражение "accept" выглядит следующим образом:
sockaddr_storage addrstorage = {};
int len = sizeof(sockaddr_storage);
accept(sock, (sockaddr*)&addrstorage, &len);
CSocketAdddress addr(addrstorage); // construct an address object to pass around everywhere else
Это было невероятно полезно для путей кода, которые вызывают bind, send и recv. Теперь мои пути к серверу STUN и клиентским кодам больше не должны знать ничего о семейном типе адреса сокета. Они просто работают с объектами "CSocketAddress". Единственный специфичный для IPV4 и IPV6 код - во время инициализации клиента и сервера - когда объекты адреса фактически построены. К счастью, это было частично абстрагировано.
Вы также можете ознакомиться с вспомогательными функциями здесь. Там есть еще один полезный материал для решения имен хостов, перечисления адаптеров и т.д. В агностическом смысле IP. Это код Linux, но некоторые из них должны соответствовать окну Windows и winsock.
Я почти сделал добавление поддержки TCP к этой базе кода. В процессе добавления поддержки для SOCK_STREAM мне не пришлось делать ни одного изменения, ни добавлять новый код для устранения различий в структурах адресов IPV4 и IPV6.