一、概述
(1) 套接字地址结构可以在两个方向上传递:
- 从进程到内核
- 从内核到进程
其中,从内核到进程方向的传递是值-结果参数传递。
(2) 地址转换函数在地址文本表达和它们存放在套接字地址结构中的二进制值之间进行转换。
- 多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数。
- 不过inet_pton和inet_ntop同时适用于IPv4和IPv6两种协议代码
二、套接字地址结构
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。
每个协议族都定义它自己的套接字地址结构。
这些结构的名字均以 sockaddr_ 开头,并以对应每个协议族的唯一后缀结尾。
2.1 IPv4套接字地址结构
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以 sockaddr_in 命名,定义在 <netinet/in.h> 头文件中。
在POSIX规范中对sockaddr_in的定义如图1所示:
2.2 图1网际套接字地址结构说明
2.2.1 长度字段sin_len
(1) 长度字段sin_len是为增加对OSI协议的支持而随4.3BSD-Reno添加的,它是一个uint8_t类型,符合POSIX的系统都提供这种形式的数据类型,如图2所示
(2) 在添加sin_len之前,第一个成员变量是sin_family,它是一个无符号短整数(unsigned short)。
(3) 因为有了长度字段,才简化了长度可变套接字地址结构的处理。但是即使有长度字段,也无须设置和检查它,除非涉及路由套接字。它是由处理来自不同协议族的套接字地址结构的例程在内核中使用的。
长度字段sin_len注意:
注意1: 在源自Berkeley的实现中,从进程到内核传递套接字地址结构的4个套接字函数(bind、connect、sendto和sendmsg)都要调用sockargs函数。该函数从进程复制套接字地址结构,并显式的把它的sin_len字段设置成早先作为参数传递给这4个函数的该地址结构的长度。
注意2: 从内核到进程传递套接字地址结构的5个套接字函数分别是accept、recvfrom、recvmsg、getpeername和getsockname,均在返回到进程之前设置sin_len。
2.2.2 POSIX规范中的3个字段
(1) POSIX规范只需要图1的套接字地址结构中的3个字段:sin_family、sin_addr和sin_port。对于符合POSIX的实现来说,定义额外的结构字段是可以接受的,这对于网际套接字地址结构来说也是正常的。几乎所有的实现都增加了sin_zero字段,所以所有的套接字地址结构大小都至少是16字节。
(2) 接下来说明的是s_addr、sin_family和sin_port的POSIX类型,如下:
- in_addr_t数据类型必须是一个至少32位的无符号整数类型
- in_port_t必须是一个至少16位的无符号整数类型
- sa_family_t可以是任何无符号整数类型,但也有差别,如下描述,
# 1、在支持长度字段的实现中,sa_family_t通常是一个8位无符号整数
# 2、在不支持长度字段的实现中,它则是一个16位的无符号整数。
(3) 后面还将会遇到其它数据类型,如u_char、u_short、u_int和u_long,它们都是无符号的。POSIX规范定义这些类型时特地标记它们已过时,仅是为向后兼容才提供的。
(4) IPv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节序来存储。(还有主机字节序)
(5) 32位IPv4地址存在两种不同的访问方法
假设,serv定义为某个网际套接字地址结构
1、sin_addr访问
serv.sin_addr将按in_addr结构引用其中的32位IPv4地址IPv4地址
2、s_addr访问
serv.sin_addr.s_addr将按in_addr_t(通常是一个无符号的32位整数)引用同一个32位IPv4地址。
注:
早期版本(如4.2BSD)把in_addr结构定义为多种结构的联合(union),允许访问一个32位IPv4地址中的所有4个字节,或者访问它的2个16位值。
如今大多数系统已经废除了该联合,转而把in_addr定义为仅有一个in_addr_t字段的结构。
2.2.3 sin_zero字段
sin_zero字段未曾使用,不过在填写这种套接字地址结构时,总是把该字段置为0。按照惯例,总是在填写该字段前把整个结构置为0,而不是单单把sin_zero字段置为0。
2.2.4 套接字地址结构使用场景
套接字地址结构仅在给定主机上使用:
虽然结构中的某些字段(例如IP地址和端口号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递
2.3 通用套接字地址结构
2.3.1 概述
(1) 当作为一个参数传递给任何套接字函数时,套接字地址结构总是以指针(指向该结构的指针)来传递。
(2) 然而以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构。
(3) 在如何声明所传递指针的数据类型上存在一个问题。
- 有了ANSI C后解决办法很简单:void*是通用的指针类型。
- 然而套接字函数是在ANSI C之前定义的,在1982年采取的办法是在**<sys/socket.h>头文件中定义一个通用的**套接字地址结构,如图3所示:
2.3.2 图3通用套接字地址结构说明
(1) 套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,正如bind函数的ANSI C函数原型所示:
int bind(int, struct sockaddr*,socklen_t);
(2) 这就要求对这些函数的任何调用都必须要将指向特定于协议的套接字地址结构的指针进行类型强制转换(casting),变成指向某个通用套接字地址结构的指针,如下示例代码所示:
/* IPv4 socket address structure */
struct sockaddr_in serv;
/* fill in serv{} */
bind(sockfd, (struct sockaddr*)&serv, sizeof(serv));
注意:省略强制类型转换的警告
- 如果省略了bind函数中的类型强制转换部分 (struct sockaddr *) ,并假设系统的头文件中有bind函数的一个ANSI C原型
- C编译器将会产生如下的警告信息:
原文:warning: passing arg 2 of ‘bind’ from incompatible pointer type.
译文:警告:把不兼容的指针类型传递给‘bind’函数的第二个参数
2.3.3 作用
从应用程序开发人员的观点看,这些通用套接字地址结构的唯一用途就是对指向特定于协议的套接字地址结构的指针执行类型强制转换。
2.4 IPv6套接字地址结构
IPv6套接字地址结构在**<netinet/in.h>头文件中定义**,且关于IPv6对于套接字API的扩展定义在RFC 3493[Gilligan et al.2003],如图4所示:
2.4.1 图4的IPv6套接字地址结构说明
(1) 如果系统支持套接字地址结构中的长度字段,那么SIN6_LEN常值必须定义。
(2) IPv6的地址族是AF_INET6,而IPv4的地址族是AF_INET。
(3) 结构中字段的先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。
(4) sin6_flowinfo字段分成两个字段:
- 低序20位是流标(flow label)
- 高序12位保留
(5)对于具备范围的地址(scope address),sin6_scope_id字段表示其范围
2.5 新的通用套接字地址结构
作为IPv6套接字API的一部分而定义的 新的通用套接字地址结构(struct sockaddr_storage) 克服了现有struct sockaddr的一些缺点。
不像struct sockaddr,新的struct sockaddr_storage足以容纳系统所支持的任何套接字地址结构。
sockaddr_storage结构在<netinet/in.h>头文件中定义,如图5所示:
2.5.1 sockaddr_storage说明
(1) sockaddr_storage类型提供的通用套接字地址结构相比sockaddr存在以下两点差别:
- 如果系统支持的任何套接字地址结构有对齐需要,那么sockaddr_storage能够满足苛刻的对齐要求。
- sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构
(2) 注意:
- 除了ss_family和ss_len外(如果有的话),sockaddr_storage结构中的其它字段对用户来说是透明的。
- sockaddr_storage结构必须类型强制转换成或复制到适合于ss_family字段所给出地址类型的套接字地址结构中,才能访问其它字段。
2.6 套接字地址结构的比较
图6是对IPv4、IPv6、Unix域、数据链路和存储等5种套接字地址结构进行的比较;假设所有的套接字地址结构都包含了一个单字节的长度字段,地址族字段也占用一个字节,其它所有字段都占用确切的最短长度。
2.6.1 图6不同套接字地址结构比较说明
(1) IPv4与IPv6这两种套接字地址结构是固定长度的,而Unix域和数据链路这两种套接字地址结构是可变长度的。
(2) 为了处理长度可变的结构,当把指向某个套接字地址结构的指针作为一个参数传递给某个套接字函数时,也把该结构的长度作为另一个参数传递给这个函数。