sockaddr 和 sockaddr_in

struct sockaddr_in {
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
	
	/* Pad to size of `struct sockaddr'.  */                                                                                                      
	unsigned char sin_zero[sizeof (struct sockaddr) -
		sizeof ((unsigned short int)) -
		sizeof in_port_t -
		sizeof (struct in_addr)];
}
struct in_addr {
	in_addr_t s_addr;
}

这两个结构体定义在 netinet/in.h 里,sa_family_t 和 in_port_t 都是 unsigned short,代表协议族和端口,in_addr 结构体中的 in_addr_t 是 uint32_t,表示一个 IPv4 地址。而最后的 sin_zero 是 char [],用于承载 sockaddr 剩余的部分,不会被使用,一般置 0,介于 sockaddr 结构体在 Linux 上为 16 bytes,所以该数组是 8 bytes。

struct sockaddr {
	sa_family_t sa_family;    /* Common data: address family and length. */
	char sa_data[14];    /* Address data. */
}

sa_family 和 sockaddr_in 一样,而对于 IPv4,sa_data 是用来存放 32 位 IPv4 地址和 16 位端口号的。

这个结构体定义在 sys/socket.h 里。我们通常需要处理各种套接字地址结构,但是各种结构又不尽相同,ANSI C 之后可以使用 void * 来传递指针,但是套接字函数早于 ANSI C 出现,所以当时的解决方案是定义一个通用地址结构,即 sockaddr。

所以我们通常会这么使用:

struct sockaddr_in addr;

/*  填写 addr */

socket_function(..., (struct sockaddr *) addr, ...);

IPv6 又如何呢?

struct sockaddr_in6 {
	sa_family_t sin6_family;
	in_port_t sin6_port;	/* Transport layer port # */
	uint32_t sin6_flowinfo;	/* IPv6 flow information */
	struct in6_addr sin6_addr;	/* IPv6 address */
	uint32_t sin6_scope_id;	/* IPv6 scope-id */
};

这个结构体也在 netinet/in.h 里,而 struct in6_addr 比较复杂,看做类似 IPv4 的 128 bits 数组就可以了。该结构体大小为 28 bytes。

sin6_flowinfo 是 IPv6 流信息,包含流标签,流标签的描述在 RFC1752 和 RFC 2460 中,可以理解为“给属于特殊流的分组加上标签,这些特殊流是发送方要求特殊处理的流,如非默认服务质量或需要实时服务的流”。该特性的使用仍在研究中。

sin_scope_id 标示 IPv6 地址范围。关于 IPv6 scope 可以参看 RFC 7346。

sockaddr_storage

由于 IPv6 的出现,之前的通用地址结构 sockaddr 显然以及不够用了,所以我们开发了新的 struct sockaddr_storage,它的定义依赖具体的实现,但一定大到足够容纳系统中任何套接字地址结构。该结构体定义在 sys/socket.h 里。

struct sockaddr_storage {
	sa_family_t ss_family;
	unsigned long int __ss_align;
	char __ss_padding[_SS_SIZE - (2 * sizeof (__ss_aligntype))];
}

__SS_SIZE 被定义为 128(依赖具体实现),看起来结构体大小是 122,但是由于对齐,sizeof 会返回 128。

__ss_align 是为了使结构体强制对齐到 64 bits。

总结

这里仅仅提到两种通用结构,和用于 TCP/IP 网络的两种结构,其他还有各种各样的套接字结构体,像是用于数据链路的 sockaddr_dl,用于 Unix 域协议的 sockaddr_un,不过都类似这几种。

本文列出的结构体,都是直接从系统(ubuntu 16.04)头文件中 copy 来的,而这些实现是比较依赖系统的,所以,请以自己的系统为准。