API с использованием хранилища sockaddr
Я пытаюсь сделать некоторое агностическое кодирование IP, и, как было предложено различными источниками, я попытался использовать sockaddr_storage. Однако все вызовы API (getaddrinfo, getnameinfo) по-прежнему зависят от структуры sockaddr. И кастинг между ними не совсем хороший вариант, gves поднимаются до многих других проблем.
и литье в sockaddr_in и sockaddr_in6 отдельно смысла пытаться использовать sockaddr_storage.
любой, кто эффективно использовал sockaddr_storage в разработке простого приложения сокета клиент-сервера.
2 ответов
проблема совместного программирования 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" может легко начинают повторяться снова и снова. Вот чего ты хочешь избежать.
предполагая, что вы используете C++, вы обнаружите, что класс абстракции для объекта адреса сокета невероятно полезен. У меня есть пример на 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. Теперь мой сервер оглушения и клиентские пути кода больше не должны ничего знать о семейном типе адреса сокета. Они просто работают с объектами "CSocketAddress". Единственный IPv4 и IPV6 специфический код во время инициализация клиента и сервера - когда объекты адреса фактически построены. К счастью, это тоже было частично абстрагировано.
вы также можете просмотреть вспомогательные функции здесь. Есть еще несколько полезных вещей для разрешения имен хостов, перечисления адаптеров и т. д... в агностическом смысле. Это код Linux, но некоторые из них должны сопоставляться с Windows и winsock.
я почти закончил добавлять поддержку TCP в эту базу кода. В процесс добавления поддержки SOCK_STREAM, мне не пришлось вносить ни одного изменения или добавлять новый код для устранения различий в структурах адресов IPV4 и IPV6.
я вообще не вижу необходимости в struct sockaddr_storage
. Его цель-выделить достаточно места для структуры sockaddr любого данного протокола, но как часто вам нужно это делать в IP-версии-агностическом коде? В общем звоните getaddrinfo()
и это дает вам кучу struct sockaddr *
s, и вам все равно, являются ли они sockaddr_in
или sockaddr_in6
, вы просто передаете их как-есть bind()
и connect()
(без литья).
в типичном коде клиента / сервера, главное место, которое я могу придумать где struct sockaddr_storage
полезно, чтобы зарезервировать место для второго параметра accept()
. В этом случае я согласен, что это уродливо, чтобы бросить его в struct sockaddr *
один раз accept()
и снова getnameinfo()
. Но я не вижу способа обойти эти гипсы. Это C. наследование структуры всегда включает в себя множество приведений.