聯合完成IPV6和IPV4編程的問題在於純粹的sockaddr結構本身並不足以容納sockaddr_in6。因此,如果您需要盲目傳遞可能是sockaddr_in或sockaddr_in6的地址,則sockaddr_storage更容易使用。
在一天結束時,無論您是使用sockaddr_in,sockaddr_in6還是sockaddr_storage,您都必須強制轉換這些指針來調用sendto,recvfrom,connect,accept和其他許多套接字函數。這只是套接字編程的已知細微差別。只是放棄做不安全的感覺。你的代碼會好的。
現在編寫網絡代碼可以同時適用於IPV4和IPV6時,您可以很容易陷入擁有大量交換語句來處理不同網絡類型的陷阱。然後,代碼就會變得混亂類似如下:
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));
然後該類型的:「如果家庭== AF_INET」表達可以輕鬆地開始一遍又一遍地重演。這就是你想要避免的。
假設您使用的是C++,您會發現套接字地址對象的抽象類非常有用。我在github here和here上有一個例子。 CSocketAddress類由{sockaddr,sockaddr_in,sockaddr_in6}的聯合支持,並且可以使用sockaddr_storage構造。如果我在開始這個課程之前就已經瞭解了sockaddr_storage,我會用它來代替union這個東西。在任何情況下,它可以讓我寫出如下代碼:
CSocketAddress addr;
...
sendto(sock, buffer, len, 0, addr.GetSockAddr(), addr.GetSockAddrLength());
同樣,「接受」的聲明看起來是這樣的:
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的代碼路徑難以置信的幫助,發送和接收。現在我的STUN服務器和客戶端代碼路徑不再需要知道任何有關套接字地址的系列類型。他們只使用「CSocketAddress」對象。唯一的IPV4和IPV6特定代碼是在客戶端和服務器初始化期間 - 地址對象實際構建時。幸運的是,這也被部分抽象出來了。
您可能還想仔細閱讀幫助函數here。有一些更有用的東西來解決主機名,枚舉適配器等...以IP不可知的方式。這是Linux代碼,但其中一些應該可以映射到Windows和winsock。
我幾乎完成添加TCP支持這個代碼庫。在添加對SOCK_STREAM的支持的過程中,我不必進行單個更改,也不需要添加任何新代碼來處理IPV4和IPV6地址結構中的差異。
請注意,gethostbyname()返回的內容與sockaddr_in不兼容 - 它返回指向4字節主機地址的指針,而不是完整的sockaddr實例。因此,將h_addr_list [0]字段複製到IPV6的sockaddr_in的sin_addr字段中,並複製到sockaddr_ipv6的sin6_addr字段中。 – 2016-08-06 21:27:07
@JonWatte:是的,這只是另一個不使用'gethostbyname()'的理由。改用'getaddrinfo()'。 – Celada 2016-08-07 08:58:44
是的,在我擁有那種自由的代碼基礎上,那更好。 – 2016-08-12 18:15:34