背景:在muduo庫中,InetAddress類是一個包含socket地址的數據類型,包括這個ip地址和端口號。
在里面有一個表示socket地址的union。
1 private: 2 union 3 { 4 struct sockaddr_in addr_; 5 struct sockaddr_in6 addr6_; 6 };
當時覺得這個很奇怪,因為這個東西僅僅就是定義了一種數據類型,並沒有定義一個對應的變量,這個怎么用呢?
經查證 這是一個匿名的聯合體,有其特殊的用法。
參見: https://www.cnblogs.com/guozqzzu/p/3626893.html
--------------------------------------------------------------------------------------------------
Anonymous unions—匿名聯合
在 C++ 我們可以選擇使聯合(union)匿名。如果我們將一個 union 包括在一個結構(structure)的定義中,並且不賦予它對象(object)名稱 (就是跟在
花括號{}后面的名字),這個union 就是匿名的。這種情況下我們可以直接使用 union 中元素的名字來訪問該元素,而不需要再在前面加 union 對象的名稱。
在下面的例子中,我們可以看到這兩種表達方式在使用上的區別:
union | anonymous union |
struct { char title[50]; char author[50]; union { float dollars; int yen; } price; } book; |
struct { char title[50]; char author[50]; union { float dollars; int yen; }; } book; |
以上兩種定義的唯一區別在於左邊的定義中我們給了 union 一個名字 price ,而在右邊的定義中我們沒給。
在使用時的區別是當我們想訪問一個對象(object)的元素 dollars 和 yen 時,在前一種定義的情況下,需要使用:
book.price.dollars;
book.price.yen;
而在后面一種定義下,我們直接使用:
book.dollars;
book.yen;
再一次提醒,因為這是一個聯合(union),域 dollars 和 yen 占據的是同一塊內存空間,所以它們不能被用來存儲兩個不同的值。也就是你可以使用
一個 dollars 或 yen 的價格,但不能同時使用兩者。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
總結:
1. 不管是匿名的聯合體或者是命名的聯合體都是共用同一段內存。
2. 匿名的聯合體按照聯合體內的變量直接使用不使用點 . 運算符,聯合體內的變量共享內存且同時只能使用一個。
下面來看一下結構體 sockaddr_in 和 sockaddr_in6 的區別:
首先在在網絡編程中是怎樣來表示socket地址的呢?
#include <bits/socket.h> struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
sa_family是 地址族類型sa_family_t 的變量: 指示是Ipv4(AF_INET)還是ipv6(AF_INET6).
------------------------------------------------------
地址族與協議族:
PF_INET ----- AF_INET -------- ipv4 ------ 16bit端口號和32bit ipv4地址共6個字節
PF_INET6 ----- AF_INET6 -------- ipv6 ------ 16bit端口號和32bit流標識 128bit ipv6地址,32bit范圍ID共26字節
PF開頭的宏和AF開頭的宏通常擁有一樣的值所以二者經常混用。
------------------------------------------
sa_data:保存真正的socket地址。 只有14個字節
從上面的規定可以看出,這個經典的結構體的大小是不能保存ipv6的,因為他擁有的內存不足以保存。
這樣的話就沒有一個通用的結構體可以放下所有的地址,在LINUX中重新定義了一個結構體:
1 #include <bits/socket.h> 2 struct sockaddr_storage 3 { 4 sa_family_t sa_family; 5 unsigned long int __ss_align; 6 char __ss_padding[128-sizeof(__ss_align)]; 7 }
按照書上的解釋:
這個結構體不僅提供了足夠大的空間用於存放地址值,而且是內存對齊的(這是__ss_align成員的作用)
三個問題:
1. 是如何提供了足夠大的空間。
2. 什么是內存對齊,為什么要對齊。
3. __ss_align究竟起到了什么作用。
下面根據我的理解來回答這些問題。
首先,這個結構體里面保存了兩種形式的數據,一個是協議族的類型,另一個是一個socket的套接字地址。
__ss_align就是起到有個對齊的作用,一個結構體有兩個對齊的形式,一個是結構體本身的對齊,另一個是結構體內的成員之間的對齊,根據之前學習的知識。
結構體內的成員按照其自身的對齊規則對齊,結構體本身的初始地址按照成員的最大對齊寬度對齊。
所以這里的__ss_align確實是起到了對齊的作用,但是有什么用呢?
經查閱資料 說是為了統一操作,包括根據起始地址讀取數據的偏移等。
引:
SOCKADDR_STORAGE可以包含IPv6或IPv4地址,並適當填充該結構以實現64位對齊。這樣的對齊使特定於協議的套接字地址數據結構能夠訪問SOCKADDR_STORAGE結構內的字段,而不會出現對齊問題。
通過填充,SOCKADDR_STORAGE結構的長度為128個字節。
以上兩個是通用的socket套接字地址表示法。
書上說這個顯然不好用,主要體現在:
1. 讀取ip地址和端口號的時候需要進行繁瑣的位操作。
LINUX為各個協議族提供了專門的socket地址結構體。
專用的socket地址
1. unix本地域協議族使用如下專用socket地址結構體:
1 #include <sys/un.h> 2 struct sockaddr_un 3 { 4 sa_family_t sin_family; /* 地址族:AF_UNIX */ 5 char sun_path[108]; /*文件路徑名*/ 6 };
2. TCP/IP 協議簇的ipv4地址表示法:
struct sockaddr_in { sa_family_t sin_family; /* 地址族:AF_UNIX */ u_int16_t sin_port; /* 端口號: 要用網絡直接序表示 */ struct in_addr sin_addr; /* ipv4地址結構體, 見下面 */ };
其中 struct in_addr 如下:
struct in_addr { u_int32_t s_addr; /* ipv4地址, 要用網絡字節序表示 */ };
以上不難看出這個32Bit的空間 是一個用於存放ipv4地址的經過簡單封裝的結構體。
3. ipv6的地址表示結構體:
1 struct sockaddr_in6 2 { 3 sa_family_t sin6_family; /* 地址協議簇: AF_INET6 */ 4 u_int16_t sin6_port; /* 端口號, 要用網絡字節序表示 */ 5 u_int32_t sin6_flowinfo /* 流信息, 應設置為0 */ 6 struct in6_addr sin6_addr; /* ipv6 地址結構體, 見下面 */ 7 u_int32_t sin6_socpe_id; /* scope ID, 尚處於實驗階段 */ 8 }; 9 10 struct in6_addr 11 { 12 unsigned char sa_addr[16]; /* ipv6地址, 要使用網絡字節序表示 */ 13 };
以上ipv6的表示形式比ipv4要復雜了許多,但大同小異,那些附加的用法暫時不想去管它。
總結: 專用地址格式意義明確也好用一些。
專用的socket地址和通用地址的關系
引用:
所有專用的socket地址結構體(以及sockaddr_storage)類型的變量在實際使用中都需要轉化為最通用的socket地址類型sockaddr(強制轉換即可),
因為所有socket編程接口使用的地址參數的類型都是sockaddr.