一、两种通用socket结构体
1. sockaddr
struct sockaddr { sa_family_t sa_family; // 地址族 char sa_data[14]; // 存放socket地址值 };
补:由于不同的协议栈的地址值具有不同的含义和长度(如PF_INET6的地址值占用26字节,更不必说PF_UNIX的地址值最大可达到108字节),所以14字节的sa_data根本无法容纳多数协议族的地址值。
2. sockaddr_storage
struct sockaddr_storage { sa_family_t sa_family; // 地址族 unsigned long int __ss_align; // 用于内存对齐 char __ss_padding[128-sizeof(__ss_align)]; // 提供足够大的空间用于存放地址值 };
这两个通用socket地址结构体显然很不好用,比如设置与获取IP地址和端口号就需要执行繁琐的位操作。所以,Linux为各个协议族提供了专门的socket地址结构体。
二、专用socket结构体
1. sockaddr_in
struct sockaddr_in { sa_family_t sin_family; // 地址族 u_int16_t sin_port; // 端口号 struct in_addr sin_addr; // IPv4地址结构体 }; struct in_addr { u_int32_t s_addr; // IPv4地址 };
2. sockaddr_in6
struct sockaddr_in6 { sa_family_t sin6_family; // 地址族 u_int16_t sin6_port; // 端口号 struct in6_addr sin6_addr; // IPv6地址结构体 u_int32_t sin6_flowinfo; // 流信息 u_int32_t sin6_scope_id; // scope ID }; struct in6_addr { unsigned char sa_addr[16]; // IPv6地址 };
3. sockaddr_un
struct sockaddr_un { sa_family_t sin_family; // 地址族 char sun_path[108]; // 文件路径名 };
所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr,因为所有socket编程接口使用的地址参数的类型都是sockaddr。
三、余音绕梁
1. socket地址结构体大小的数据类型为:socklen_t
typedef uint32_t socklen_t;
2. 为何socket编程接口使用的地址参数的类型是sockaddr?
因为socket编程接口需要处理sockaddr_in、sockaddr_in6、sockaddr_un等socket地址结构,为了满足这些需求,于是socket编程接口中的指针参数便指向通用socket地址结构。言下之意就是,我们只需知道sockaddr存在的唯一用途就是对指向特定于协议的socket地址结构体的指针执行强制类型转换。
3. sockaddr_storage结构体往往用于事先不知道地址族的类型这一情况
/* 获取socket的地址族 */ int getFamily(int fd) { struct sockaddr_storage ss; socklen_t len = sizeof(ss); getsockname(fd, (struct sockaddr*)&ss, &len); return ss.sa_family; }
补:既然事先不知道要分配的socket地址结构体的类型,我们只能采用sockaddr_storage这个通用socket地址结构体,因为它能够承载系统支持的任何socket地址结构体。也就是说,它能够容纳下sockaddr_in6结构体的26字节的地址值,也能够容纳下sockaddr_un结构体的108字节的文件路径名。