一、两种通用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字节的文件路径名。