IPv4 的主要缺陷有地址问题、安全问题、性能问题和自动配置不够人性化等。这些问题在 IPv4 的框架下不能完全有效地进行解决,仅能进行个别问题的修补,例如 IPv4 的NAT技术用户尝试性地解决 IP 地址空间问题只能获得局部性的成功。
①.IPv4 的地址空间危机
IPv4 的地址以32位数值表示,最多可达40 多亿个地址,如果IP 地址是以递增的方式分配,即第一个主机为 1, 第二个主机的地址为2的方式来分配 IP 地址才能达到预定的40 亿个数据。
目前 IP 地址的分配策略是按照树状进行划分的,即把地址分配给机构,然后由机构对IP 地址进行再次分配,这造成了IP 地址的分配不足,总有部分IP 地址作为预留或者其他的用途没有分配给主机。
IP 地址分为5 类,其中的 3 类地址用于IP 网络,按照规划,这 3 类地址足够用于网络的构建。
其中A类地址为 126个,分给了最大的实体,例如政府机关、高校及先入的企业部门,主要分配在美国。这类地址很庞大,但是由于历史原因,利用却很不足。
B类地址有 16000 个,用于一 些大型的机构,如大学和大公司。
C类地址数量比较多,每个网络上的主机数量为 255个,用于IP 网络的其他机构。
A类地址的少数公司并不能高效率地利用IP地址,而获得C类地址的小机构只有几个主机而不能真正使用此类地址,造成B类地址的获得越来越难。
NAT技术将网络分为了内网和外网,主要就是因为IP地址不够造成的。电信网通等运营部门的动态IP分配,也是为了高效地利用IP地址的方法,使得用户的IP可以多人多次使用。
②.IPv4的性能
IPv4的设计最初是一个试验品,当时没有考虑到实现时的某些现实情况,对目前的Internet网络的广泛应用也没有预料到。所以在某些方面存在不足,例如最大传输单元、最大包的长度、校验和、IP的头部设计、IP选路等都没有考虑到其性能。
③.IPv4的安全
由于IPv4将网络安全放在了应用层考虑,没有在协议栈层进行设计,存在很大的隐患。
④.IPv4的自动配置和移动
IPv4的自动配置主要体现在移动方面,在一个主机从一个地点移动到新的地点的时候,需要重新配置,并且由于提供服务的ISP
不同,可能的配置千差万别,例如IP、网关、 DNS都要发生变化,甚至还要加上浏览器的代理出口。而原来主机的所在位置,即使足够空闲也不能带到新的IP紧张的新地点。
IPv6就是能够无限制地增加IP网址数量、拥有巨大网址空间和卓越网络安全性能等特点的新一代互联网协议。IPv6的技术特点如下:
IPv6提供128位
的地址空间,全球可分配地址数为340282366920938463463374607431768211456
个。IPv6的地址结构采用128位的另一个原因是采用了层次化的地址结构设计,允许对地址进行层次化的划分,提供大量不同类型的IP地址。
IPv6将自动IP地址分配功能作为标准功能。具有网络功能的机器一旦连接上网络便可自动设定地址。它有两个优点:一是最终用户用不着花精力进行地址设定,二是可以大大减轻网络管理者的负担。
IPv6对报文数据报头结构作了简化,用来减少处理器的开销并节省网络带宽。数据报文的头部采用了流线型的设计,IPv6的报头由一个基本报头和多个扩展报头(Extension Header)构成,基本报头具有固定的长度(40字节),放置所有路由器都需要处理的信息。
IPv6的安全性使用了鉴别和加密扩展头部数据结构的方法。作为lPSec
的一项重要应用,IPv6集成了**虚拟专用网(VPN)**的功能,使用IPv6可以更容易地实现更为安全可靠的虚拟专用网。
IPv6 协议在设计之初,采用 “尽最大努力” 传输的服务质量保证方式。文本数据、静态图像数据等传输对 QoS 没有要求,随着 IP 网上多媒体业务的增加,如 IP 电话、VoD、电视会议等实时应用,对传输延时和延时抖动均有严格的要求。
IPv6 数据包包含了服务质量的特性,能更好地支持多媒体和其他对服务质量有较高要求的应用。
IPv6 地址是独立接口的标识符,所有的 IPv6 地址都被分配到接口,而没有分配到节点。IPv6有以下 3种类型地址:
IPv6 单播地址与某个接口相关联。发给单播地址的包传送到由该地址标识的某个接口上。但是为了满足负载平衡系统,在 RFC2373
中允许多个接口使用同一 地址。IPv6的单点传送 IP 地址包括:可聚集全球单点传送地址、链路本地地址、站点本地地址和其他特殊的单点传送地址。
如果一 个单播IP 地址所有位均为0, 那么该地址称为未指定的地址。以文本形式表示为 "::"
。单播地址 "::1"
"0:0:0:0:0:0:0:1"
称为环回地址。节点向自已发送数据包时采用环回地址。
IPv6为端对端通信设计了 一 种可分级的地址结构,这种地址被称为可聚集全球单播地址 (AggregatableGlobal Unicast Address)
。可聚集全球单播地址,是可以在全球范围内进行路由转发的地址,格式前缀为 001
, 与IPv4 公共地址相似。
字段格式前缀 FP 之后,分别是 13 位的TLAID、8位的RES、24位的NLA ID、16位 SLA ID 和 64位主机接口ID
。TLA (Top Level Aggregator, 顶级聚合体)、 NLA (Next Level Aggregator, 下级聚合体)、 SLA (Site Level Aggregator,节点级聚合体)
三者构成了自顶向下排列的3个网络层次。
FP (Fonnat prefIX)
: 格式前缀,值为001, 用于区别其他地址类型。TLA ID (Top-level Aggregation Identifier)
: 顶级聚集标识符,是与长途服务供应商和电话公司相互连接的公共骨干网络接入点,其ID的分配由国际Internet注册机构lANA严格管理。RES (Reserved for future use)
: 8位保留位,将来用做扩充。NLA ID (Next-Level Aggregation Identifier)
: 下一 级聚集标识符。SLA ID (Site-Level Aggregation Identifier)
: 站点级聚集标识符,它可以是 一 个机构或一 个小型ISP。分层结构的最底层是网络主机。Interface ID
: 接口标识符,IPv6单播地址中的接口标识符用千在链路中标识接口。本地单播地址的传送范围限于本地,又分为链路本地地址和站点本地地址两类,分别适用于单条链路和一个站点内。
①.链路本地地址
链路本地地址,格式前缀为1111111010
, 用于同一 链路的相邻节点间通信。链路本地地址用于邻居发现,且总是自动配置的,包含链路本地地址的包不会被IPv6路由器转发。
②.站点本地地址
站点本地地址,格式前缀为1111111011
,相当于10.0.0.0/8、172.16.0.0/12和192.168.0.0/16
等IPv4私用地址空间。
例如企业专用局域网,如果没有连接到IPv6 Internet上,在企业内部可以使用本地站点地址,其他站点不能访问站点本地地址,包含站点本地地址的包不会被路由器转发到企业专用局域网之外。
一个站点通常是位于同一地理位置的机构网络或子网。与链路本地地址不同的是,站点本地地址不是自动配置的,而必须使用无状态或全状态地址配置服务。
①.IPv4 兼容地址
为了与IPv4地址地址相兼容,IPv6支持一种 IPv4兼容地址,这种地址在原有IPv4 地址的基础上构造 IPv6 地址。通过在 IPv6 的低 32 位上
携带 IPv4 的 IP 地址
,使具有IPv4和 IPv6 两种地址的主机可 以在IPv6 网络上进行通信 。这种地址 的表示格式为 "0:0:0:0:0:0:a.b.c.d"
或者 "::a.b.c.d "
, 其中 "a.b.c.d "
是点分十进制表示的 IPv4地址。
例:一 个主机的IPv4 地址为 "192.168.0.1"
, 其 IPv6 的兼容地址为 "::192.168.0.1"
。
②.IPv4 映射地址
IPv4 兼容地址用于具有 IPv4和 IPv6双栈的主机在IPv6 网络上的通信,而今支持IPv4协议栈的主机可以使用IPv4 映射地址在IPv6 网络上进行通信。IPv4 映射地址是另一 种内嵌IPv4地址的IPv6地址,它的表示格式为"0:0:0:0:0:FFFF:a.b.c.d "
或 "::FFFF:a.b.c.d "
。使用这种地址时,需要应用程序支持 IPv6 地址和IPv4 地址。这种 “映射 IPv4 的 IPv6 地址” 的表示方式如下所示。
例:一 个 主 机 的 1Pv4 地 址 为 " 192.168.0.1 "
, 其 1Pv6 的 映 射 地 址 "::FFFF:192.168.0.1"
。
③.6to4 地址
IPv6地址中嵌入IPv4地址的表示方法用于在IPv6的地址上进行通信,如果网络是IPv4协议,则需要使用 6to4地址。6to4地址用于在IPv4地址上支待IPv4和IPv6两种协议的节点间的通信。
6to4 方式通过多种技术在主机和路由器间传递IPv6数据分组。
IPv6 的多播与IPv4 运作相同。多播可以将数据传输给组内所有成员
。组的成员是动态的,成员可以在任何时间加入或者退出 一 个组。IPv6 多播地址格式前缀为 11111111
, 此外还包括标志(Flags)、范围域和组1D等字段,
0xFF
, 表示地址为IPv6多播地址。000T
。其中高三位保留,必须初始化成0。T=0表示一 个被ANA永久分配的多播地址; T=1表示一个临时的多播地址。"FF02:0:0:0:0:0:0:1 "
表示链路地址的所有节点地址,地址"FF02:0:0:0:0:0:0:2"
表示链路地址的所有路由器地址,地址"FF05:0:0:0:0:0:0:2"
表示站点本地的所有路由器地址。IPv6的任播地址是一组接口的集合,这些接口通常属于不同的节点。数据向任播地址发送的时候,会发送到路由算法中距离最近的一 个接口。
多播
地址是一 对多的通信,即接收方是多个接口,任播
地址是一 对一 组中的任一 个的场合,发送方可以从一 组接收方中选一 个。
路由器任播地址必须经过预定义,该地址从子网前缀中产生。为构造一 个子网一路由器任播地址,子网前缀必须固定,余下的位数置为全0。
即使一 个主机只有一 个单接口,该主机也可以有多个IPv6地址。即可以同时拥有以下J几种单点传送地址:
每个接口的链路本地地址;
每个接口的单播地址(可以是一 个站点本地地址和一 个或多个可聚集全球地址);
回环(loopback)接口的回环地址(::1)
。
此外,每台主机还需要时刻保持收听以下多播地址上的信息:
IPv6 的包头共 40个字节,其中包含了IPv6 的主要概念。
IPv6包头格式:
在 IPv4
中,所有包头以32位为单位,即以4 个字节为长度单位。在 IPv6
中的头部中,长度是以 64位为单位。
下面是 IPv6协议的包含字段含义:
版本字段:表示协议版本号,长度为4位,对于IPv6, 该字段必须为 6。
类别字段:表示报文的类别和优先级,它与 IPv4 的服务类型字段的含义类似。字段的长度为 8位,该字段的默认值是全 0。
流标签字段:用于标识属于同一业务流的包,是IPv6 的新增字段,长度为20 位。
净荷长度字段:表示报文中的有效载荷的长度,它与 IPv4头部的总长度字段不同,这个字段的值不包含头部的长度。它没有将 IPv6 的 40 位报头计算在内,只计算报头后面的扩展和数据部分的长度。字段的长度为16位,最多可以表示长度为64KB 有效数据载荷。
下一 个头字段:与 IPv4 头部的协议字段相似,但略有不同。IPv4 的 IP 协议的上层协议, TCP 和 UDP 协议始终在 IP 包头后面,而 IPv6 的扩展部分可以放在包头部分。扩展部分字段可以用来表示验证、加密和分片等功能。
跳限字段:与IPv4 中的生存时间字段含义类似,它表示包经过的路由器个数。包在转发的过程中,每经过 一 个路由器,这个字段的值就会减去 1, 如果这个字段的值为0后,报文就会被丢弃。这个字段的长度为8位。
源地址字段:表示发送数据报文主机的 IP地址,字段的长度为128位。
目的地址字段:表示接受数据的目的主机的 IP地址,这个地址可以使一 个单播、组播或任意点播地址,字段的长度为 128位。
虽然 IPv6报头的字节长度两倍于Ipv4报头(40个字节与20个字节),但IPv6对报头结构进行了精简。 IPv6 的报头丢弃了IPv4字段中的几个,从而使得数据的处理更有效率。
IPv6协议的头部与IPv4的头部结构不同,IPv4的头部如图如下所示:
两种协议的头部主要有如下不同:
版本字段在两种协议中没有变化, IPv4中为4,IPv6 中为6,表示两种不同的协议类型。
IPv6 丢弃了IPv4的首部长度、服务类型、标识、标志、片偏移和头部校验和字段。总长度、生存时间和协议类型字段在 IPv6 中有了新名字,功能稍微进行了重新定义。
IPv4 中的选项字段已从报头中消失,改为扩展功能。
IPv6 加入了两个新字段:流量类别和流标记。
如下所示包含发送数据主机的源端口号、接收数据主机的目的端口号、发送数据的序列号、上 一 个报文的应答号、窗口大小、当前报文分片前的偏移量、校验和及紧急数据的偏移量指针等。
IPv6 的UDP头部与TCP相似,包含发送端的源端口号、接收端的目的端口号、UDP数据的长度和校验和。如下图所示,它的含义与IPv4相同。
如下所示IPv6 的ICMP头部基本结构与IPv4的相同,但是其含义发生了很大的变化。IPv6的ICMP 叫做ICMPv6
, 主要包含IPv6 的控制信息。
具体值和含义下表所示:
查看是否加载IPv6协议栈:
没有inet6,加载,并设置本地IPv6:
modprobe ipv6
ifconfig ens33 inet6 and fe80::20c:29ff:felt:35/64
①.使用命令ping来检测网卡的IPv6地址
IPv4地址类型的网络ping的使用不需要指定网络接口,系统自动选择。IPv6使用命令ping6时必须指定网卡接口,I表示Interface、ens33第一个网卡。
②.使用ip命令
使用IP命令查看IPv6的路由表:
使用route命令添加一个路由表:
IPv6的网络邻居发现继承了IPv4的ARP(地址解析协议)可以重新得到网络邻居的信息,并且可以编辑/删除它。
IP命令查看邻居设定:(00:01:24:45:67:89为网络设备的数据链路层的MAC地址)
IPv6的结构定义在文件sys/socket.h
中。
IPv6新定义了地址族和协议族常量,常量在文件
中定义。地址族为常量AF_INET6
,用来表示与IPv4的不同。同时定义了PF_INET6
表示协议族,这个变量也在
中定义,这两个常量的值是相同的,因为有:
#define PF_INET6 AF_INET6
IPv6的地址为128位,与IPv4的32位地址显著不同,所以定义了一个新的结构表示IPv6的地址。
①.新的IPv6地址结构in6_addr
//这个结构在文件中定义,它包含16个8位的元素,表示IPv6的地址,以网络字节序保存。
struct in6_addr{
Uint_t s6_addr[16]; //IPv6地址
};
联合定义方式:
struct in6_addr{
union{
uint8_t _S6_u8[16];//16个8位变量
uint32_t _S6_u32[4];//4个32 位变量
uint64_t _S6_u64[2];//2个64 位变量
}_S6_un;
};
#define s6_addr _S6_un._S6_u8
②.新的IPv6 套接字地址结构 sockaddr_in6
与IPv4 的地址结构 struct sockaddr _ in
相对应,IPv6 定义了新的地址结构表示IPv6的套接字地址,型如下:
//
// sin6_family 必须定义为AF_INET6, 表示IPv6 地址族。
struct sockaddr_in6{
sa_family_t sin6_family;AF INET6协议族
in_port_t sin6_prot;//端口地址
uint32_t sin6_flowinfo;//IPv6 传输类信息
struct in6_addr sin6_addr;//IPv6地址
uint32_t sin_scope_id;//网络接口范围
};
③.IPv4 地址结构和IPv6 地址结构的对比
IPv4 的地址结构通过 struct in_ addr
和 struct sockaddr _ in
定义,结构定义分别如下:
struct sockaddr_in
{
u_char sin_len;
u_char sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];//是为了增强系统的可扩展性而设计的
};
struct in_addr{
u_int32_t s_addr;
}
IPv6 的地址结构通过 struct in6 _ addr
和 struct sockaddr _ in6
定义:
struct sockaddr_in6
{
u_char sin_len;
u_char sin_family;
u_int16_t sin6_port;
u_int32_t sin6_flowinfo;//用于设置流标记和流量类型的。
struct in6_addr sin6_addr;
u_int32_t sin6_scpoe_id;//设置IPv6地址的作用范围;
};
struct in6_addr
{
u_int8_t _u6_addr[16];
};
为了IPv4地址兼容,IPv6的地址可以将IPv4的地址映射到IPv6地址的一 部分。把IPv4的地址放到IPv6地址的低32位
,并且高96位为0:0:0:0:FFFF
,
这样IPv4到IPv6的映射为如下的方式:
::FFFF:<IPv4地址>
//头文件定义了通用的IPv6 地址
//作用与IPv 的INADDR_ ANY相似
//用于绑定任意的本地地址。
extern const struct in6_addr in6addr_any;
//例:IPv6类型地址结构绑定到23端口的任意地址
struct sockaddr_in6 sin6;
...
sin6.sin6_family = AF_INET6;
sin6.sin6_flowinfo = 0;
sin6.sin6_port = htons(23);
sin6.sin6_addr = in6addr_any;
...
if (bind(s,(struct sockaddr*) &sin6,sizeof(sin6)) == -1)
//IN6ADDR_ANY_INIT用于指定本地的任意IP地址
//头文件
struct in6_addr anyaddr = IN6ADDR_ANY_INIT
//IPv4版本的INADDR_xxx常量定义为主机字节序不同,IPv6版本的IN6ADDR_ XXX定义为网络字节序。
...
用户经常有使用UDP发数据包或者使用TCP协议连接本地的情况,在IPv4中有一个IPv4常量地址INADDR_ LOOP BACK
方便操作,IPv6中也定义了这样一个变量。 in6addr_loopback
是个全局in6_addr结构类型的变量,
在文件
//使用UDP发数据包或者使用TCP协议连接本地时,
//IPv4中有IPv4常量地址INADDR_ LOOPBACK方便操作
//IPv6中也定义了in6addr_loopback,且是个全局in6_addr结构类型的这样的变量
extern const struct in6_addr in6addr_loopback;
//IPv4中使用INADDR_LOOPBACK来初始化本地环回变量
//IPv6下使用IN6ADDR_LOOPBACK_INIT来初始化in6addr_loopback
//IN6ADDR_LOOPBACK_INIT定义如下:
#define IN6ADDR_LOOPBACK_INIT {{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}}
//回环变量:
struct sockaddr_in6 sin6;
...
sin6.sin6_family = AF_INET6;
sin6.sin6_flowinfo = 0;
sin6.sin6_port = htons(23);
sin6.sin6_addr = in6addr_loopback;
...
if (connect(s,(struct sockaddr*) &sin6,sizeof(sin6)) == -1)
socket()函数的原型没有发生改变,但是在建立 IPv6 协议族的套接字选项时发生了变化。
//建立一个IPv4流式套接字
s = socket(PF_INET,SOCK_STREAM,0);
//建立IPv4数据报套接字,应用层采用:
s = socket(PF_INET,SOCK_DGRAM,0);
//建立IPv6的TCP和UDP套接字的方式如下:
//TCP
s = socket(PF_INET16,SOCK_STRRAM,0);
//UDP
s = socket(PF_INET16,SOCK_DGRAM,0);
当建立 PF_INET6
套接字后,必须使用地址结构 sockaddr_ in6
传递给其他调用的参数,如下面的函数由于没有包含地址结构,所以其原型没有发生变化。
函数需要传递地址结构:
函数的原型其不用发生改变,因为地址结构的传入传出是通过 struct sockaddr
来传送的,需要进行强制转换。
IPv6中新增了接字选项和 ioctl
的控制命令,在IPv6 的通用协议选项中有一 族IPV6_xx
的选项名,用于进行IPv6 的套接字控制。其他部分主要是ICMPV6
部分,即IPv6 的ICMP协议控制部分。
IPV6_ADDFORM
套接字选项:允许套接字从 IPv4 向IPv6转换,或者相反。
IPV6_ CHECKSUM
套接字选项:当此选项为非负值的时候,Linux 内核将计算并存储所有发送的数据分组并对输入的数据分组计算校验和。此选项不影响ICMPv6的原始套接字接口。
IPV6_DSTOPTS
套接字选项:设置此选项将使任何接收到的 IPv6 目标选项都由recvmsg 作为辅助数据返回。
IPV6_HOPLIMIT
套接字选项:设置此选项将使任何接收到的路由跳限字段都由recvmsg 作为辅助数据返回。
IPV6 _ HOPOPTS
套接字选项:设置此选项将使任何接收到的路由步跳选项都由recvmsg 作为辅助数据返回。
IPV6_NEXTHOP
套接字选项:这是一个函数类型的选项,给 sendmsg 指定辅助数据对象的类型,指定下 一个路由跳点。
IPV6_FKTINFO
套接字选项:此选项的设置可以使 recv sg返回接收到的IPv6数据报的 IPv6地址
和到达接口索引
两个信息。
IPV6 _PKTOPTIONS
套接字选项:获取 UDP 套接字的辅助数据。
IPV6_RTHDR
套接字选项:设置此选项将使接收到的IPv6路由头部由recvmsg 作为辅助数据返回。
IPV6_UNICAST_HOPS
套接字选项操作路由跳限。
套接字选项 IPV6_ADD_MEMBERSHIP 、 IPV6_DROP_ MEMBERSHIP 、 IPV6 _JOIN_ GROUP 和 IPV6_LEAVE_GROUP
与 IPv4 的广播一 样,用于向组播中加入一 个主机或者从组播中删除一 个主机。
ICMPV6 _FILTER
套接字选项可以设置或者获得一 个 ICMPV6 类型的消息,在ICMPV6 中,共有 256种消息可以进行选择。
套接字选项单播跳限 IPV6_UNICAST_HOPS
用于控制 IPv6 外出数据的跳限。
hoplimit = 10;
if(setsockopt(s,IPPROTO_IPV6,IPV6_UNICAST_HOPS,(char *)&hoplimit,sizeof(hoplimit))==1)
perror("setsockopt IPV6_UNICAST_HOPS");
//使用了setsockopt()设置了IPV6_UNICAST_HOPS时,之后一直遵循这个设置,没有这个选项,采用系统默认值。
//:跳限值如下
/*
hoplimit < -1 : 返回错误EINVAL;
hoplimit== -1: 使用内核默认值;
O<=hoplimit<=255: 采用用户的值;
hoplimit>256: 返回错误EINVAL。
*/
//获取跳限值:
int hoplimit;
size_ t len = sizeof(hoplimit) ;
if (getsockopt (s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char*)&hoplimit, &len,) == -1)
perror("socketopt IPV6_UNICAST_HOPS");
else
printf("Using %d for hop limit.\n",hoplimit);
IPv6发送多播的UDP包与IPv4有很大的不同,在IPv6协议中可以指定发送的多播地址,直接使用sendto()
函数发送。
设置和获取发送多播的选项如下:
IPV6_MULTICAST_IF
: 设置发送多播包的网络接口,参数为使用中的网络接口的序号。
IPV6_MULTICAST_HOPS
: 设置发送多播包的跳限,参数的含义与单播跳限设置选项的含义一 致。
IPV6_MULTICAST_LOOP
: 当多播包发送的对象包含本地的网络接口时,这个设置才有效。此选项用于设置当多播发送到本地时是否将数据的复制返回。参数为1时,数据会返回本地;为0时,不返回;为其他值可能出错。此项的默认值为发送数据返回本地。
接收多播的选项有下面两个,当操作失败时,返回EOPNOTSUPP
:
IPV6_JOIN_GROUP
: 加入某个多播对象,并和某个网络接口绑定。当网络接口序号设置为0, 则内核选定本地网络接口。例如,某些内核在多播对象中查找IPv6的路由,以决定使用的网络接口。
IPV6_LEAVE_GROUP
:将某个主机从群组中取出,取消对其的广播。
广播的选项参数为struct ipv6_mreq
,头文件
:
struct ipv6_mreq{
struct in6_addr ipv6mr_multiaddr;//IPv6多播地址
unsigned int ipv6mr_interface;//接口索引
};
在IPv4中获得时间戳的命令为SIOCGSTAMP
,返回数据报到达的时间,时间的精度是纳秒,IPv6新增SIOCGSTAMPNS
,用户返回数据报到达以纳秒为精度的时间戳。参数为struct timespec类型
的变量,在头文件
中定义:
struct timespec{
time_t tv_sec;//秒
long tv_nsec;//纳秒
};
//IPv4协议族中,通常使用函数inet_ntoa()、inet_ aton()和inet_addr()进行十进制字符串和32位的点分4段式网络字节序之间的转化
int inet_aton(const char *strptr, struct in_addr *ddrptr); //字符串转换为点分四段式
in_addr_t inet_addr(const char *strptr);//字符串转换为点分四段式
char *inet_ntoa(struct in_addr inaddr); //点分四段式的字符串转换为十进制字符串
//由于兼容性,IPv6设计了新的函数用于完成文本表示的IPv6地址和128位的网络字节序地址之间的相互转换。
int inet_pton(int family,const char *strptr,void *addrptr);//字符串转128位
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);//128转字符串
//family成员需要设置为AF_INET6
// addrptr为in_addr6结构的变量
//应该抛弃之前的inet_ aton()函数和inet_ ntoa()函数,使用inet_pton()和inet_ ntop()代替。
//IPv4完成主机名或域名到IPv4地址解析:
struct hostent *gethostbyname(const char *hostname);
//hostname:主机名或域名。
//函数返回值的结果存放在struct hostent[1]中。
//IPv6特有的协议无关函数:getaddrinfo()(由名字获得IP地址)和getnameinfo()(由IP地址获得名字)
//使用它们几乎可以得到所有有关主机信息
#include
#include
#include
int getaddrinfo(const char *node,const char *service, const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo (struct addrinfo *res);
const char *gai_strerror(int errcode); //将错误值翻译为可读的字符串
//getaddrinfo()函数将之前的getipnodebyname()函数、getipnodebyaddr()函数、 getservbyname()函数和getservbyport()函数提供的功能结合在一起,提供一个接口来提供。
//这个函数按照hints参数输入的限制条件,提供一个或者多个套接字地址结构 供之后的bind()和connect()函数使用。与gethostbyname()函数不能在多个线程功能使用的缺点相比较, getaddrinfo()函数是线程安全的。
//getaddrinfo()的函数参数含义:
//node:是一个字符串指针,可以是主机名地址或实际IPv4 或者IPv6 地址。
//service是一个字符串指针,可以是服务器名或十进制端口号。
//成员node和service参数,其中的一 个可以为NULL, 但是不能都为NULL。
//它们指定主机的网络地址或者主机名称,其将解析获得。
//如果参数hints.ai_flags设置为AI_NUMERICHOST标记,则node的值必须为一个数字类型的网络地址。标志 AI_NUMERICHOST设定任何长度的主机网络地址。
//hints相当于一个过滤器,只有符合hints机构的内容才会返回到res指针中。当hints为空时,函数假定ai_tlag、ai_socktype和ai_protocol的值为O, ai_familly的值为AF_UNSPEC。设置时需获得地址类型,即过滤条件。为NULL:获得所有网络接口和协议类型;not NULL:返回ai_family、ai_socktype和ai_protocol设置的套接字类型地址。
//res是一个指向指针的指针,符合hints所设置条件的地址会通过这个指针传入;res中的变量是动态申请内存获得一个地址链表,并且构成了一个链表,销毁这个链表需要使用freeaddrinfo()函数
//由于主机可能是多穴的,或者某个服务对多种协议类型都开启( 一 个SOCK_STREAM地址,一个SOCK_DGRAM地址),所以可能返回的地址是多个;
//getaddrinfo()函数返回的是一 组所有符合条件的链表,可以遍历整个链表后选择其中一个使用。
struct addrinfo结构如下:
struct addrinfo{
int ai_flags;
int ai_family;//PF_xxx地址类型
int ai_socktype; //SOCK_xxxSOCK类型
int ai_protocol;//协议的序号,为0或者IPv4和IPv6协议中的某个协议ipproto_xxx
size_t ai_addrlen; //ai_addr地址长度
char *ai_canonname; //主机名称
struct sockaddr *ai_addr; //地址结构
struct addrinfo *ai_next;//下一个地址结构
};
//ai_family、ai_socktype和ai_protocol与建立套接字文件描述符的socket()函数具有相同的选项值。
//参数ai_socktype为0或者参数ai_protocol为0, 表示返回任何套接字类型和任何协议类型。
//参数ai_flags成员可以是符合值。
getaddrinfo()函数成功时返回0,失败返回如下所示:
其他获得主机信息函数:对IPv4和IPv6协议均适用:
#include
#include
#include
struct hostent *getipnodebyname(const char *name,
int af,int flags,
int *error_mum);
struct hostent *getipnodebyaddr(const char *addr,
size_t len, int af,
int *error_num);
void freehostent(struct hostent *ip);
//struct hostent结构:
struct hostent{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
IPv6中有 用于测试的宏,以方便进行判断,例如判定是否回环、是否IPv4 地址映射的地址、是否全局的IPv6地址等,这些宏在
文件中定义,代码如下:
int IN6_IS_ADDR_UNSPECIFIED (const struct in6 addr *);/*地址没有指定?*/
int IN6_IS_ADDR_LOOPBACK(const struct in6 addr *); /*回环接口*/
int IN6_IS_ADDR_MULTICAST(conststruct in6 addr *); /*多播地址*/
int IN6_IS_ADDR_LINKLOCAL(const struct in6 addr *); /*本地IPv6连接*/
int IN6_IS_ADDR_SITELOCAL(const struct in6 addr *); . /*本地1Pv6地址*/
int IN6_IS_ADDR_V4MAPPED(const struct in6addr *); /*IPv4映射地址*/
int IN6_IS_ADDR_V4COMPAT(const struct in6 addr *);/*IPv4兼容地址*/
int IN6_IS_ADDR_MC_NODELOCZUL(const struct in6 addr *);/*判断多播地址的范围*/
int IN6_IS_ADDR_MC_LINKLOCAL(conststructin6 addr *);/*多播地址的范围为link域内*/
int IN6_IS_ADDR_MC_SITELOCAL(conststructin6addr *);/*多播地址的范围为site域内*/
int IN6_IS_ADDR_MC_ORGLOCAL(const struct in6 addr);/*多播地址的范围为指定的范围内*/
int IN6_IS_ADDR_MC_GLOBAL(const struct in6 addr*);/*多播地址的范围为全球*/
IPv6协议的编程主要需要注意IPv6地址与1Pv4地址的不同,以及几个相关的函数。例如函数socket()、connect()、send()、recv()等。服务器端建立套接字并侦听后,会使用accept()等待客户端的连接。当客户端成功连接后,将客户端的信息打印出来,然后发送信息并接收客户端信息,最后关闭客户端,等待下一个客户端连接。
//服务器程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_LEN 1024 /*缓冲区长度*/
#define MYPORT 8888 /*服务器侦听端口*/
#define BACKLOG 10 /*服务器侦听长度*/
int main(int argc, char **argv)
{
int s_s; /*服务器端套接字文件描述符*/
int s_c; /*客户端套接字文件描述符*/
socklen_t len;
int err = -1;
struct sockaddr_in6 local_addr; /*本地地址结构*/
struct sockaddr_in6 client_addr; /*客户端地址结构*/
char buf[BUF_LEN + 1];
s_s = socket(PF_INET6, SOCK_STREAM, 0); /*建立IPv6套接字*/
if (s_s == -1) { /*判断错误*/
perror("socket error");
return(1);
} else{
printf("socket() success\n");
}
bzero(&local_addr, sizeof(local_addr)); /*清空地址结构*/
local_addr.sin6_family = PF_INET6; /*协议族*/
local_addr.sin6_port = htons(MYPORT); /*协议端口*/
local_addr.sin6_addr = in6addr_any; /*IPv6任意地址*/
err = bind(s_s, (struct sockaddr *) &local_addr, sizeof(struct
sockaddr_in6));//服务器进程就开始等待客户端连接到这个命名套接字。
if (err == -1) { /*判断错误*/
perror("bind error");
return (1);
} else{
printf("bind() success\n");
}
err = listen(s_s, BACKLOG); /*创建一个队列用来存放来自客户的进入连接。设置侦听队列*/
if (err == -1) { /*判断错误*/
perror("listen error");
exit(1);
} else{
printf("listen() success\n");
}
while (1)
{
len = sizeof(struct sockaddr); /*地址长度*/
/*等待客户端连接*/
s_c = accept(s_s, (struct sockaddr *)&client_addr, &len);//accept函数指定服务端去接受客户端的连接,接收后,返回了客户端套接字的标识,且获得了客户端套接字的“地方”(包括客户端IP和端口信息等)。
if (s_c == -1) { /*判断错误*/
perror("accept error");
return (errno);
} else{
/*将客户端的地址转换成字符串*/
inet_ntop(AF_INET6, &client_addr.sin6_addr, buf, sizeof(buf));
printf("a client from ip: %s, port %d, socket %d\n",
buf, /*客户端地址*/
client_addr.sin6_port, /*客户端端口*/
s_c); /*客户端套接字文件描述符*/
}
/* 开始处理每个新连接上的数据收发 */
bzero(buf, BUF_LEN + 1); /*清零缓冲区*/
strcpy(buf,"From Server"); /*复制数据*/
/* 发消息给客户端 */
len = send(s_c, buf, strlen(buf), 0);
if (len < 0) { /*错误信息*/
printf("message '%s' send error,errno:%d,'%s'\n",
buf, errno, strerror(errno));
} else{ /*成功信息*/
printf("message '%s' send success, %dbytes\n",buf, len);
}
/*清零缓冲区*/
bzero(buf, BUF_LEN + 1);
/* 接收客户端的消息 */
len = recv(s_c, buf, BUF_LEN, 0);
if (len > 0)
printf("recv message success:'%s',%dbytes\n",buf, len);
else
printf("recv message failure, errno: %d,'%s'\n",errno, strerror(errno));
/* 处理每个新连接上的数据收发结束 */
close(s_c);
}
close(s_s);
return 0;
}
//客户端程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_LEN 1024 /*缓冲区长度*/
#define MYPORT 8888 /*服务器侦听端口*/
#define BACKLOG 10 /*服务器侦听长度*/
int main(int argc, char *argv[])
{
int s_c; /*客户端套接字文件描述符*/
socklen_t len;
int err = -1;
struct sockaddr_in6 server_addr; /*本地地址结构*/
char buf[BUF_LEN + 1]; /*收发缓冲区*/
s_c = socket(PF_INET6, SOCK_STREAM, 0); /*建立IPv6套接字*/
if (s_c == -1) /*判断错误*/
{
perror("socket error");
return(1);
}
else
{
printf("socket() success\n");
}
bzero(&server_addr, sizeof(server_addr)); /*清空地址结构*/
server_addr.sin6_family = PF_INET6; /*协议族*/
server_addr.sin6_port = htons(MYPORT); /*协议端口*/
server_addr.sin6_addr = in6addr_any; /*IPv6任意地址*/
/*连接服务器*/
err = connect(s_c, (struct sockaddr *) &server_addr, sizeof(server_addr));//将服务器的命名套接字作为一个地址。
if (err == -1) /*判断错误*/
{
perror("connect error");
return (1);
}
else
{
printf("connect() success\n");
}
/*清零缓冲区*/
bzero(buf, BUF_LEN + 1);
len = recv(s_c, buf, BUF_LEN, 0); /*接收服务器数据*/
/*打印信息*/
printf("RECVED %dbytes:%s\n",len,buf);
bzero(buf, BUF_LEN + 1); /*清零缓冲区*/
strcpy(buf,"From Client"); /*复制客户端的信息*/
len = send(s_c, buf, strlen(buf), 0); /*向服务器发送数据*/
close(s_c); /*关闭套接字*/
return 0;
}
编译:
运行:
客户端输出:
服务器端输出: