背景:在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.