套接字地址结构

简介

大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义它自己的套接字地址结构。这些结构的名字均以“sockaddr_”开头,并以对应每个协议族的唯一后缀结尾。

IPv4套接字地址结构

IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在头文件中。它的定义如下:

struct in_addr
 {
    in_addr_t s_addr;    /*  32-bit IPv4 Internet address.  */
 };
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;         /* 16-bit TCP or UDP Port number.  */
    struct in_addr sin_addr;        /*  32-bit IPv4 Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
               __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) -
               sizeof (struct in_addr)];
  };

其中宏“__SOCKADDR_COMMON()”的内容如下:

#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family

它会使用参数作为family的前缀,比如上面的 _SOCKADDR_COMMON (sin),对应“sa_family_t sin_family”。

说明

  • sin_zero字段未曾使用,不过在填写这种套接字地址结构时,我们总是把该字段置为0,按照惯例,我 们总是在填写前把整个结构置为0,而不单是把sin_zero字段置为0。

  • 套接字地址结构仅在给定主机上使用,虽然结构中的某些字段(例如IP地址和端口号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递。

  • 在上面的套接字地址结构中,in_addr_t数据类型必须是一个至少32位的无符号整数类型,in_port_t必须是一个至少16位的无符号整数类型,而sa_family_t可以是任何无符号整数类型。在支持长度字段(sin_zero)的实现中,sa_family_t通常是一个8位的无符号整数,而在不支持长度的实现中,它则是一个16位的无符号整数。下图列出了POSIX定义的这些数据类型。
数据类型 说明 头文件
int8_t 带符号的8位整数 sys/types.h
uint8_t 无符号的8位整数
int16_t 带符号的16位整数
uint16_t 无符号的16位整数
int32_t 带符号的32位整数
uint32_t 无符号的32位整数
sa_family_t 套接字地址结构的地址族
socklen_t 套接字地址结构的长度,一般为uint32_5
in_addr_t IPv4地址,一般为uint32_t
in_port_t TCP或UDP端口,一般为uint16_t

通用套接字地址结构

前面提到过,不同主机的套接字地址结构只在本机上使用,而在不同主机之间传递的至少其中的某些字段。然而套接字函数必须处理来所有支持的任何协议族的套接字地址结构。为了解决这个问题,第一种方法是使用ANSI C中的void *指针类型,因为它是通用指针类型。然而套接字函数是在其之前定义的。所以在当时所采用的办法是在定义一个通用的套接字地址结构(不同的操作系统写法可能不一样,但是本质内容是相同的),如下:

struct sockaddr {
    uint8_t    sa_len;    
    sa_family_t    sa_family;    /* address family: AF_xxx value */
    char    sa_data[14];    /* protocol-specific address */
};

于是套接字函数被定义位以指向某个通用套接字地址结构的一个指针作为其参数之一,这正如同bind函数的ANSI C函数原型所示:

int bind(int, struct sockaddr *, socklen_t);

IPv6套接字地址结构

IPv6套接字地址结构在头文件中定义,如下:

struct in6_addr
 {
     uint8_t s6_addr[16];
 };
#define SIN6_LEN    /* required for compile-time tests */
struct sockaddr_in6
  {
    uint8_t sin6_len  /* length of this struct (28) */
                      /* ubuntu没有这个字段 */
    __SOCKADDR_COMMON (sin6_);  /* AF_INET6 */
    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 */
  };

注意

  • 如果系统支持套接字地址结构中的长度字段,那么SIN6_LEN必须定义。
  • IPv6的地址族是AF_INET6, 而IPv4的地址族是AF_INET。
  • 结构中字段的先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐(结构大小是64位的整数倍)的,那么128位的sin6_addr字段也是64位对齐的。这么做的目的是,在一些64位机器上,如果64位数据存储在某个64位边界位置(对齐),那么对它的访问会得到优化处理。
  • sin6_flowinfo字段分为两个字段: 低序20位是流标(flow label);高序12位保留。
  • 对于具备范围的地址(scoped address),sin6_scop_id字段标识其范围(scope)。

新的通用套接字地址结构

作为IPv6套接字API的一部分而定义的新的通用套接字地址克服了现有struct sockaddr的一些缺点。不像struct sockaddr,新的struct sockaddr_storage足以容纳系统所支持的任何套接字地址结构。它的定义如下:

struct sockaddr_storage {
    uint8_t ss_len;    /* length of the struct (implementation dependent) */
    sa_family_t ss_family;    /* address family: AF_xxx value */
    /* implementation-dependent elements to provide:
     * a) alignment sufficient to fulfill the alignment requirement of
     *    all socket address types that the system supports.
     * b) enough storage to hold any type of socket address that the 
     *    system supports.
     * /
};

sockaddr_storage和sockaddr的两点差别:

  1. 如果系统支持的任何套接字地址结构有对齐需要,那么sockaddr_storage能够满足最苛刻的对齐要求。
  2. sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构。
    注意
    除了ss_family和ss_len外(如果有的话),sockaddr_storage结构中的其它字段对用户是透明的。sockaddr_storage结构必须强制类型转换成或复制到适合与ss_family字段所给出地址类型的套接字地址结构中,才能访问其他字段。

你可能感兴趣的:(Unix,Network,Programming)