IPv4 和 IPv6 的套接字地址结构

    大多数套接字函数都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义有它自己的套接字地址结构。这些结构的名字均以“sockaddr_”开头,并以对应每个协议准的唯一后缀结尾。
    IPv4 套接字地址结构通常也称为“网际套接字地址结构”,它以“sockaddr_in”命名,定义在 <netinet/in.h> 头文件中。下面是它的 POSIX 定义。
#include <netinet/in.h>

struct in_addr{
    in_addr_t    s_addr;          // 32-bit IPv4 address, network byte ordered
    /* 其它大多历史字段现已经被废除 */
};

struct sockaddr_in{
    uint8_t      sin_len;      // length of structure (16)
    sa_family_t  sin_family;   // AF_INET
    in_port_t    sin_port;     // 16-bit TCP or UDP port number, network byte ordered
    struct in_addr sin_addr;   // 32-bit IPv4 address, network byte ordered
    char   sin_zero[8];        // unused
};

    根据该定义,需要对套接字地址结构做几点一般性的说明。
    1、长度字段 sin_len 是为增加对 OSI 协议的支持而随 4.3 BSD-Remo 添加的。正是因为有了该字段,才简化了长度可变套接字地址结构的处理。并非所有的厂家都支持该字段,而且 POSIX 规范也不要求有这个成员。
    2、即使有长度字段,我们也无需设置和检查它(除非涉及路由套接字),它是由处理来自不同协议族的套接字地址结构的例程(如路由表处理代码)在内核中使用的(在源自 Berkeley 的实现中,从进程到内核传递套接字地址结构的 4 个套接字函数(bind、connect、sendto 和 sendmsg)都要调用 sockargs 函数,该函数从进程复制套接字地址结构,并显示地把它的 sin_len 字段设置成早先作为参数传递给这 4 个函数的该地址结构的长度。从内核到进程传递套接字地址结构的 5 个套接字函数(accept、recvfrom、recvmsg、getpeername 和 getsockname)均在返回到进程之前设置 sin_len 字段)。
    3、POSIX 规范只需要这个结构中的 3 个字段:sin_family、sin_port 和 sin_addr。对于符合 POSIX 的实现来说,定义额外的结构字段是可以接受的。另外,几乎所有的实现都增加了 sin_zero 字段,所以所有的套接字地址结构大小都至少是 16 字节。
    4、在字段 s_addr、sin_family 和 sin_port 的 POSIX 数据类型中,in_addr_t 数据类型必须是一个至少 32 位的无符号整数类型,in_port_t 必须是一个至少 16 位的无符号整数类型,而 sa_family_t 可以是任何无符号整数类型。在支持长度字段的实现中,sa_family_t 通常是一个 8 位的无符号整数,而在不支持长度字段的实现中,它则是一个 16 位的无符号整数。下表列出了 POSIX 定义中常用的这种数据类型。

    5、套接字地址结构仅在给定主机上使用:虽然结构中的 IP 地址和端口号等字段用在不同主机之间的通信中,但是结构本身并不在主机之间传递。
    当作为任何套接字函数的参数时,套接字地址结构总是以引用形式来传递,所以这就要求对应的套接字函数必须处理来自所支持的任何协议族的套接字地址结构。为解决如何声明所传递的指针的数据类型这一问题,在 ANSI C 定义之前(那时还没有“void *”这一通用的指针类型)所采取的办法是定义一个如下的通用套接字地址结构。
#include <sys/socket.h>

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

    于是套接字函数就被定义成如下形式:
        int bind(int, struct sockaddr *, socklen_t);
    这就要求在将指向特定于协议的套接字地址结构的指针传递给任何的套接字函数之前都要进行强制类型转换。比如:
            struct sockaddr_in serv;      // IPv4 socket address structure.
            /* fill in serv() */
            bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
    从内核的角度看,使用指向通用套接字地址结构的指针另有原因:内核必须取调用者的指针,把它转换成“struct sockaddr *”类型,然后检查其中 sa_family 字段的值来确定该结构的真实类型。而从应用程序开发人员的角度来看,这些通用套接字地址结构的唯一用途就是对指向特定于协议的套接字地址机构的指针执行强制类型转换。要是“void *”指针类型可用就更简单了,因为无须显示进行类型转换。
   
    说完 IPv4 套接字地址结构,现在说说 IPv6 套接字地址结构。
#include <netinet/in.h>

struct in6_addr{
    uint8_t     s6_addr[16];   // 128-bit IPv6 address, network byte ordered.
};

#define SIN6_LEN               // required for compile-time tests

struct sockaddr_in6{
    uint8_t          sin6_len;       // length of this struct (28)
    sa_family_t      sin6_family;    // AF_INET6
    in_port_t        sin6_port;      // transport layer port, network byte ordered
    uint32_t         sin6_flowinfo;  // flow information, undefined
    struct in6_addr  sin6_addr;      // IPv6 address, network byte ordered
    uint32_t         sin6_scope_id;  // set of interfaces for a scope
};

    这里需要注意以下几点:
    1、如果系统支持套接字地址结构中的长度字段,那么 SIN6_LEN 常量必须定义。
    2、IPv6 的地址族是 AF_INET6,而 IPv4 的是 AF_INET。
    3、结构中字段的先后顺序做过编排,使得如果 sockaddr_in6 的结构本身是 64 位 对齐的,那么 128 位的 sin6_addr 字段也是 64 位对齐的。
    4、sin6_flowinfo 字段分成两部分:a) 低序 20 位是流标(flow label);b) 高序 12 位保留。
    5、对于具备范围的地址,sin6_scope_id 字段标识其范围。最常见的是链路局部地址的接口索引。
    IPv6 套接字 API 中定义了一种新的通用套接字地址结构 sockaddr_storage,它克服了现有“struct sockaddr”的一些缺点,足以容纳系统所支持的任何套接字地址结构。
#include <netinet/in.h>

struct sockaddr_storage{
    uint8_t       ss_len;     // length of this struct (implementation dependent)
    sa_family_t   ss_family;  // address family: AF_xxx value
 /* implementation-dependent elements to provide:
  * a) alignment sufficient to fullfill the alignment requirements 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 字段所给出地址类型的套接字地址结构中,才能访问其他字段。

猜你喜欢

转载自aisxyz.iteye.com/blog/2387687