Problem ze wspólną obsługą programowania IPV6 i IPV4 polega na tym, że sama struktura sockaddr nie jest wystarczająco duża, aby pomieścić sockaddr_in6. Więc jeśli musisz ślepo omijać adres, który może być sockaddr_in lub sockaddr_in6, sockaddr_storage jest nieco łatwiejszy w użyciu.
Pod koniec dnia, niezależnie od tego, czy używasz sockaddr_in, sockaddr_in6, czy sockaddr_storage, będziesz musiał rzucić te wskaźniki, aby nawiązać połączenie z sendto, recvfrom, connect, accept i wieloma innymi funkcjami gniazda. To tylko znany niuans programowania gniazd. Po prostu puść poczucie, że robisz coś niebezpiecznego. Twój kod będzie w porządku.
Teraz, pisząc kod sieciowy, który ma działać zarówno w IPV4, jak i IPV6, możesz łatwo wpaść w pułapkę posiadania dużej liczby instrukcji switchowych do obsługi różnych typów sieci. Kod następnie dostaje bałagan tak:
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));
I wtedy ten rodzaj „jeśli rodzina == AF_INET” ekspresja może łatwo zacząć powtarzać w kółko. Tego właśnie chcesz uniknąć.
Zakładając, że korzystasz z C++, okaże się, że klasa abstrakcji dla obiektu adresu gniazda jest niesamowicie przydatna. Mam przykład na github here i here. Klasa CSocketAddress jest wspierana przez powiązanie {sockaddr, sockaddr_in, sockaddr_in6} i może być zbudowana z sockaddr_storage. Gdybym wiedział o sockaddr_storage zanim zacząłem tę lekcję, użyłbym tego zamiast rzeczy związkowej. W każdym razie, to pozwala mi napisać kod w następujący sposób:
CSocketAddress addr;
...
sendto(sock, buffer, len, 0, addr.GetSockAddr(), addr.GetSockAddrLength());
Podobnie, „przyjąć” zestawienie wygląda następująco:
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
To było niezwykle pomocne dla ścieżek kodowych, które wymagają wiązania, wyślij i przywróć. Teraz mój serwer STUN i ścieżki kodu klienta nie muszą już nic wiedzieć o typie rodziny adresu gniazda.Działają tylko z obiektami "CSocketAddress". Jedyny specyficzny kod IPV4 i IPV6 jest podczas inicjowania klienta i serwera - kiedy obiekty adresów są faktycznie konstruowane. Na szczęście zostało to częściowo wyabstrahowane.
Możesz także chcieć zapoznać się z funkcjami pomocnika here. Jest więcej przydatnych rzeczy do rozwiązywania nazw hostów, wyliczania kart itp. ... w sposób niepodatny na IP. Jest to kod Linuksa, ale niektóre z nich powinny być dobrze odwzorowane na Windows i winsock.
Już prawie skończyłem dodawać obsługę TCP do tej bazy kodu. W procesie dodawania obsługi SOCK_STREAM nie musiałem wprowadzać jednej zmiany ani dodawać nowego kodu, aby poradzić sobie z różnicami w strukturach adresów IPV4 i IPV6.
Należy zauważyć, że funkcja gethostbyname() zwraca niezgodność z sockaddr_in - zwraca wskaźniki do czterobajtowych adresów hostów, a nie do pełnych instancji sockaddr. W ten sposób skopiujesz pole h_addr_list [0] do pola sin_addr pliku sockaddr_in dla IPV6 i do pola sin6_addr pliku sockaddr_ipv6. –
@JonWatte: true, a to jeszcze jeden powód, dla którego nie należy używać 'gethostbyname()'. Zamiast tego użyj 'getaddrinfo()'. – Celada
Tak, w bazach kodu, w których mam tę wolność, to jest lepsze. –