前言
本章讲述网际协议(IP)。第1章讨论到,要通过 Winock 建立通信,必须了解如何针对特定协议(已在第1中以IPv4为例作了说明)为工作站寻址本章要讨论IP4 和IPV6,第4 章将叙述 Windows平台上最常见的协议。
IPv4 是一种用于Internet 的网络协议,已经广为人知。IP 可广泛用于人多数计算机操价系统上也可用于大多数局域网(LAN,例如办公室小型网络)和广域网(WAN,例如Internet)。
随着Interet中计算机数量的爆炸式增长,IPv4 的局限性越来越明显。自然而然地,下一代IP 被开发出来,称为IPv6
。
本将讨论IPv4 及IPv6 的背景、寻址方案、名称解析及 Winsock 细节。然后讨论怎样编写在两个版本的 IP中都能够无缝运行的应用程序。
3.1 IPv4
IPv4 由美国国防部ARPA(Advanced Research Project Agency,高级研究计划局)开发,该局在20世纪 60 年代建立了一个试验性的数据包开关网络。最初的网络协议很麻烦,这致使在 70 年代中期开发出了一种较好的协议。这项研究最终不仅发展到 TCP,还进一步发展到 IPv4。
3.1.1 寻址
IPv4 中,计算机都分配有–个 IP 地址,IP 地址用一个32 位数来表示,式的称呼是“IP4 地业”。IPv4 地址通常表示为点分上进制格式,地址中的每8位字节被转换为一个十进制数值,并由句点分隔。
IPv4 地址被分为几个种类,分别描述地址被分配到网络的部分及分配到端点的部分。表3.1 列出了各种不同的种类。
指定·个I 地址时,表明网络部分位数的数字可以在反斜线““后附加到点分十进制地址后边例如,地址 172.31.28.120/16 表明,地址的前面 6 位数字组成了地址的网络部分。这相当于使255.255.0.0子网掩码起到的作用。
表3.1 中的最后两项是IPv4 地址的特殊种类。D 类地址是为P4 多播预的, 类地为试验性地址。另外,有几块地址是为专门用途预留的,不能被 Intermt 中的系统使用,这些地址如下所示
- 10.0.0.0-10.255.255.255(10.0.0.0/8)
- 172.16.0.0-172.31.255.255(172.16.0.0/12)
- 192.168.0.0-192.168.255.255(192.168.0.0/16)
最后,还有一个环回地址(127.0.0.1),该地址为特殊地址
,指向本地计算机。
要列出分配给本地接口的IP4 地址,可以使用 IPCONFIC.EXE 命令,该命令用来列出每个网络适配器及其分配到的 IPv4 地址。如果应用程序需要通过编程获取 IP4 地址的列表,可以使用SIO ADDRBSS LIST QUERY 命调用 WSAIoctl 函数,这在第7章有所叙述。另外,IP 助手API提供这个函数,这在第 16 章中叙述。
我们已讨论了 IP4v 地址空闻的细分类,在这些互不相同的地址种类中,又有3 种类型的PV4地址;
- 单播
- 多播
- 广播。
随后的几个小节将对每种类型的地址展开叙述。
3.1.1.1 单播
分配到单个计算机接口上的地址称为单播地址
。该地址仪可以分配到一个接口上。如果网络上其他计算机也配置了该地址,就会发生错误,导致数据的错误传输。A、B、C3 类地址成 IPV4 的单地址空间。
一般说来,为主机上的接口分配 IPv4(单播)地址时,要么静态地配置,要么由配置协议分配,例如用DHCP(Dynamic Host Confguration Protocol,动态主机配置协议)。如果不能访 DHCP 服务器则系统将使用APIPA(Automatic PrivateP Addressing,自动专用卫导址)自动分配一个169.254.0.0/16范围内的地址
为了避免记忆大量的IP 址,可以使用DNS(Domain Name System,域名系统)
将IP4 地址和主机计算机名称相关联。稍后将讨论如何将主机名称解析为其 Pv4 地址(以及其 Pv6 地址)。
3.1.1.2 多播(组播)
多播地址未被分配到某个特定接1。相反,多个计算机可以“加入”一个多播组,监听某个特定的多播地址。加入该组的每个计算机都将收到发往该多播地址的任何数据。多播地址是D 类地址。多播最大的一个好处是,能够将多播数据仅传送到对该数据感兴趣的那些计算机。IP 多播将在第 9 章详细讨论。
3.1.1.3 广播
IPv4 支持数广播。这意味着发送到受限广播地址 255.255.255.255 的广播数据将被局城网内的每个计算机接收并处。因为即使是那些对广播数据不感兴趣的计算机,也必须处理数据包,所以通常认为这种做法不好。
如果应用程序需要进行广播,最好使用子网直接广播的方式。这种方式也是广播数据,但顾名思义,这种方式仅将数据直接广播到某个特定子网内的计算机。例如,发送到172.31.28.255 的一个数据报将只被同一个子网内的计算机接收到。
拓展
组播和广播常常用于服务发现协议,比如ssdp协议。而airplay协议, dlna协议等都有不同程度上实现类似组播和广播的功能
3.1.2 IPv4 管理协议(ARP,ICMP,IGMP)
IPv4 需要依赖几个其他的协议才能实现其功能。其中我们感兴趣的3 个支持协议为:
- ARP(Address Resolution Protocol,地址解析协议)、
- ICMP(Intemet Control Message Protocol,Internet 控制消息协议)
- IGMP(Internet Group Management Protocol,Intemnet 组管理协议)。
ARP
ARP 用来将一个32 IPv4 地址解为一个物理地址或地址,使得IPv4 数据能够被包装在适当的媒体帧(如以太网)中。在线路上发送数据之前,主机必须将下个跃点的 IP4 地址解析为对应的硬件地址。如果目标地址位于局域网内,则 ARP 请求将针对目的地的物地址。如果一个或多个路由器将源和目的地分离开来,则 ARP 请求将针对缺省网关,数据包也将发往该网关。第 16章叙述的IP 助手 API包含了一些 ARP例程。
简单说,查路由表,把IP地址解析为MAC地址。
RARP协议则是MAC地址转为IP
ICMP
设计 ICMP 的目的是为了在IPv4 主机之间发送状态信息和错误信息。
信息类型包括回应请求和答复、目标不可抵达以及超时。
ICMP 也被用来搜寻邻近路由器。第 11 将对ICMP 展开详细讨论并说明如何发送ICMP 信息。
IGMP
IGMP 用来管理多播组的成员。土机上的应用程序加入多播组后,土机就会向外发送IGMP 成报表,通知该网络段的路由器,应该由哪些多播组来接收数据。路由器需要这种信息,以伴将发往这些多播组的数据包向前发送一-如果某网络段有对该数据感兴趣的接收者,则发往该网络段,否则不
以上就是今天要讲的内容,本文仅仅简单介绍了linux命令行的使用。
3.1.3 Winsock 中的IPv4 寻址
在Winsock中,应用程序通过 SOCKADDR IN结构来指定IPv4 地址和服务端口信息,该结构的定义为:
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zerol8];
}
子段必须设为 AP_INET,以告知 Winsock 我们此时正在使用IP 地址族。sin port 字段定义了用来标识服务器服务的 TCP 或UDP 通信端口。应注意到,口号其实没有应用到IPV4 协议中而是封装在 IPv4 头内的传输层协议(如TCP UDP)的一个属性。
因为有些可用端口号是为已知的服务(例如 FTP 和 HTTP)保留的,所以应用程序选择端口时,必须特别小心。已知服务使用的端口是由IANA(Internet Assigned Numbers Authority,Internet 编号授权委员会)分配的,这些端口列举在网页 http://www.ianaorg/assignments/port-numbers 上。实质上,端口号分为下述3类:已知的、已注册的、动态和(或)专用端口。
- 0-1023:由ANA控制,为已知服务所保留。
- 1024~49151:由IANA 列出的已注册端口,中普通用户执行
的普通用户进程或程序可以使用这些端口。 - 49152~65 535:动态和(或)专用端口。普通用户应用程序应在1024-49151 范围内选用已注册端口,以避免可能使用其他应用程序或系统服务正在使用的端口。因为IANA 没有在49152-65 535 范围内的端口上注册服务,所以这些端口也可随意使用。
如果使用 API 函数 bind 时,应用程序绑定到了主机上另一个应用程序正在使用的端口上,则系统将返回 Winsock 出错信息 WSAEADDRINUSE。另外,即使没有显式地绑定到一个本地地址和端口上,客户机的发送或连接操作也会有效。在这种情况下,系统将把套接字隐式地绑定到1024~5000 范围内的一个本地端口上。如果应用程序显式地绑定套接字,但指定一个为0 的本地端口时,系统也会采取同样的行为。
SOCKADDR_IN 结构的 sin_addr 字段用于把 Ipv4 地址保存为4字节的以网络宁节顺序排列的数值,它是一个无符号长整数的数据类型。根据不同的用法,这个字段还可表示一个本地或远程 IP 地址。IP 地址一般是用“Internet 标准点分表示法”像“a.b.cd”一样指定的,其中每个字母代表一个字节数,并从左到右分配了一个4字节的无符号长整数。最后一个字段 sin_zero,只起到充当填充项的作用以使 SOCKADDR IN 结构和 SOCKADDR 结构的长度一样。
这个套接字及其他套接字地址的结构中,所有字段都得按照网络子节顺序排列。然而,如果应片程序使用本章稍后将讨论到的名称解析和分配的 API,则所需的排序转换将自动实行,当应用程序从结构成员中显式地分配或获取数值时,才需要进行字节排序转换。字节排序已在第 1 章中进行了介绍。
3.2 IPv6
随着 Internet 中计算机数量的爆炸式增长,IPy4 的局限性越来越明显。首先,可用 IPV4 地址的数月不断地被消耗掉,这就导致了NAT(network addrcss translators,网络地址转换器)的使用。NAT将多个专用地址映射到单个的公共 IP 地址上。对客户机一服务器应用程序而言,NAT 是有用的,不过九连接两个使用与用地址公间的组织时,NAT 就能会出现问题。另外,有时 NAT 必须注意到下层协议,以便执行适当的地址转换
其次,IPv4寻址并非完全分等级的,这意味着 Internet 中枢路由器必须保留大量的路由表,以便将IPy4 数据包正确地发送到Internet 中任何地点。
开发 IPv6 的另一个诱因,是为了提供更简单的配置过程。用IPv4 时,地址必须被静态地分配或通过配置协议如 DHCP 进行分配。比较想的情况是,主机不必依赖 DHCP 结构的管,而应该能基于其所在的网络段对自身进行自动配置。
Windows XP提供了 IPv6的 developer-release 版本。对于Windows 2000,可以从http://www.microsoft.com/ipv6 下载IPv6协议的技术预览版。对于Windows NT 4.0,也可从该网址获取“微软研究IPv6 协议”。
本节将讨论IPv6 地址的不同类型、IPY6 使用的支持协议,以及IPv6 地址在 Wsock 中是被如何处理的。尽管我们将讨论寻址和名称解析,但并不会涉及 PV6 的所有方面,如并没有讲述路由和建立IPv6网络。要了解更多的信息请查阅 Windows XP 联机帮助或Joseph Davies 的著作《UnderstandingIPv6》(微软出版社,2002)
3.2.1 寻址
IPv4 和IPv6之间最显著的区别是,IPv6 地址是128位的,其大小是IPy4 地址的四倍。使用这样大的地址空间,中·个原因是需要将可用地址细分为一个路由域体系。路由域反映了 Inlernet 的布局。表 3.2 列出了地址空间的部分分配方法,同时也给出了每个部分的地址前级。地址前缓指示了 IPV6地址的高阶位。IPv6 寻址在 RFC2373 有所叙述。
典型的 IPv6 地址由 16 字节分段表示,显示为冒号分隔的十六进制数。
下面是一个 IPV6 地址不例:
21DN:00D3:0000:2F3B:02AA:OUFF:FE28:9C5A
每个 16 位块内部前面的零都可以去掉,如下所示:
21DA:D3:0:2F3B:2AA:FF:FE28:9CSA
很多IPv6地址包含一长中零,可以压缩这些零块,用两个冒号代替。
例如,下述地址:
FE80:0:0:0:12:0:34:56
可以压缩为:
FE80::12:0:34:56
注意,只有单个邻近的一系列 16 位零块小可以压缩。
根据所在平台的不同,可以使用两种方江来获取分配到一个让算机上各个接口的 IPV6 地址列表从网上下载的“微软研究”及 Windows 2000 技术预览版,以及Windows XP 家庭版和 Wndows XP专业版,都使用IPV6 EXE 命令。
要列举IPv6 接口,在启动命令行提示符之后执行IPV6.EXE 即可对所有版本的 Windows 2000 和 Windows XP(包括未来的 Windows)版本,以使用 NETSHEXE令,这个命令的语法为:“NETSHEXE 接口 IPV6 显示接口”。要编程获取本地接的配置,可以使用I/0 控制命令 SIO ADDRESS LIST QUERY(第7章)及IP 助手API第 16 章)
IPv6 地址有3 种基本类型:
- 单播
- 任播
- 多播。
应注意到,IPv6 并未定义广播地址(而是使用多播)。
随后的儿个小节将讨论每种地址类型。
3.2.1.1 单播
单播地址标识单个接口。然而,对于 IPv6 而言,一个接口往往分配有多个单播地址。一般会遇到下列 4 种单播地址:
- 链接——本地地址(link-local)
- 站点——本地地址(site-local)
- 全球地址(global-address)
- 兼容地址
链接——本地地址(link-local)
每个接口始终分配有一个链接一个地址一每个物理网络接口都自动配置了,一个这样的地址链接一本地地址用来仅和在一个链接上的节点通信,并总是以 fe80::/64 为前级。另外,由于链接本地地址未持有路由信息,所以接口索引经常和地址一起显示。系统中的每个物理接口都分配到一个适配器索引号(也叫范围 ID),当链接一地地址破分配到某个口时,链接号会被附加到该地址后边。
下面的地址就是分配到物理适配器的一个链接——本地地址,该适配器的接口索引号为 5
fe80::250:8bff:fea0:92ed5
在 Winsock 中,如果使用链接——本地地址建立了一个连接,则必须给出接口索引,以指明从那个链接可以访问远程机。IPv6 链接——本地地址和本前面讨论的 IPV4 APIPA 地同义
站点——本地地址(地点——本地地址)(site-local)
例如,考虑具有链接一本地地址 fe80::250:tt:tea0:92ed35的主机A与具有链接——本地地址fe80::250:daff:fc3:9e3%4 的主机B如果主机A发出,一个到主机B 的连接,则它会将自己的能够到达主机 B 的范用ID 和B 的标地址一起使用。即要连接的址是: 80::250:daffec3:9e34%5
IPv6 地址中,只有在本地网络环境中才能抵达地点一本地地址,例如在一个特定地点的公司网中。因为不能从其他地点或 Internet 中访问这些地址,而且专用网络中的路器不会将通信转发到本地地点之外,所以它们和 Pv4 用地址空间相似。地点一本地地址使用 fec0.:/48 作为前,必须用IPv6 路由器或通过 DHCPv6 进行分配。目前,微软实的 IPv6 还支持 DHCPV6。支持IPV6的路由器将发送 RA(Router Advetisement,路由器告)信息,这些信息将公布该地址的网络部分(例如,地址的前 64 位由 48 位地点一前级与 16 网D成),然后机这些信息把地点——本地地址分配给接收到 RA 的接口。
全球地址
全球地址就足在 IPv6 互联网中都可访问的地址。该地址以 001 始,前 64 的其61 位用来建立路由体系,后 64 位组成接口的标识符,用以性一标识子网中的网络接口。今球地址也通过路占器广告或使用 DHCPv6 进行分配。
兼容地址
最后一种单播地址是兼容地址,用来支持从IP4 到IPv6 的转。
Windows 支持4 种兼容地址:
- ISATAP(Intrasite Automatic Tunnel Addressing Protocol,现场自动隧道寻址协议)地址
- 6到4地址
- 6跨4地址
- IPv4兼容地址。
ISATAP
ISATAP 地址可以由何 IPv6 单播地址(如链接一地地址、站点一本地地址及全球地址等)派生而得,通常况下,ISATAP地址由链接一本地地址派生而得。这种地址也包括嵌入式IPv4 地址,例如,ISATAP 地址 fe80::5ee:172.17.72
是一个链接一本地地址,它含了主机的IPv4 地址(172.17.7.2)当数据从这个接口送出时Pv6 数据包被封到一个IPv4报头中IPv4 目的地址从嵌入到IPV6 ISATAP 目标地址的4 地址中获取。4 地址必须是球可访问的,以便两个端点能够通过自动隧道进行通信。目前, ISATAP地址是·项TETF(Internet Engineering Task Force.Intemnet 工程任务组) 草案
6到4地址
第 2种兼容地址称为“6 到 4”, RFC3056 中所叙述。6 4 地址使用全球前缴2002:WWXX:YYZZ::/48,具中 WWXX:YYZZ 是wxy( 种公IPV4 地址)的十六进制一冒号小法。6到4地址允许 IPv6/1Pv4 主机之间通过IPV4 路由结构进行通信
WindowsXP 支持6到4服务。这项服务不仅允许机和使用6到4中继路由器的IP6 网络中的主机通信,还允许主机和同一站点的6到4 主机、连接到Intenet的6到4主机以及跨越 IP4 网络的其他站点的上机通信。在 Windows XP 中6到4服务被配置为自功运行,如某个接口分配有公共IPv4地址,则系统将创建一个6到 4隧道接口(接索引为 3),并把6到4地址分配给该隧道接口。
6跨4地址
第3 种兼容地址是“6跨4”,这是一种使用 1Py4 多播的道技术。它使得IPv4 和 IPV6节点可以在IPv4 下层结构之上使用 IP6 进行通。这种技术在 RFC2529 中有所叙述
IPv4兼容地址
最后一种兼容地址是 IPv4 兼容地址。这种地址表现形式为 0:0:0:0:0:0:wx.y.z(或::wxy.z),其中w.x.y.z 是公共 IPv4 地址的点分进制表示。当应用程序使用 IP4 兼容地作为目的地址时,IPv6的通信量将被自动封装到IPv4 报头中,并通过 IPV4 网络送到目的地。
3.2.1.2 任播
任播是标识多接口的地址。使用这些地址的目的,足将指向一个任播地址的数据包路由到最近的分配有该地址的接口。当网络中有在几个节点提供某种特定的服务时,使用任播地址比较有利。每台计算机都可以分配同一个任播地址,有意联系这种服务的客户机将被路由到近的提供服务的成员处。因为这种通信是一个对多个之中的一个,而不是-·个对多个,所以它和多播不同。不过,山前任播地址仅分配下路由器。
3.2.1.3多播
[Pv6 中的多播和 IPV4 中的类似。只要进程在某个特定接口加入多播组,就可收到发往该多播地址的数据。IPv6多播地址以1111 1111(FF)作为开始部分。IPv6 多播和IP6多播地址在第9 章中详细叙述。
3.2.2 IPv6管理协议
IPv6 只需要一个助手协议:
- ICMPv6(Internet Control Message Protocol for iPv6,IP6 Internet 控制信息协议)。
RFC2463 中有对这个协议的定义。ICMP6 提供和ICMP 同种类型的服务,如:目标地无法访问、回应及回应答复,但它还提供 MLD(Multicast Listener Discovery,多播监听者搜寻)机制和ND(Neighbor Discovery,近邻发现)机制。在ICMPV6 中,MLD代替了IGMP,ND代替了ARP。
3.2.3 Winsock 中的iPv6 寻址
Winsock 应用科序中指定IPv6 地址时使用下述结构:
struct sockaddr_in6
{
short sin6_family;
u_short sin6_port;
u_long sin6_flowinfo;
struct in6 addr sin6_addr;
u_long sin6_scope_id;
}
第1个字段仅标识了地址族,该地址族为 AF_INET6,
第2个字段是端口号。这个结构中的所有字段都必须按照网络学节顺序排列。应注意到,因为端口号是封装协议的一个属性,所以 IPV4 部分中讨论的所有有关端口号的知识同样适用厂IPV6,如TCP 和UDP 在IP6 中两者都可用。
第3个字段 sin6_flowinfo 用于为连接标记通信量,但未在微软IPV6 堆 中使用。
第4个字段是一个包含了进制IPv6地址的 16字结后一个段 sin6_scope_id 指地址所在的接口索引(或范围ID)。应记住,对于链接一本地地址,必须指定目的地所在的本地范围 ID,sin6_scope_id 就用于这个目的站点——本地地址可以引用地址号码作为范围ID。全球地址不包含范围 ID。
最后应该注意的一个问题是,SOCKADDR_IN6 结构的长度为 28 字节,而 SOCKADDR 结构和SOCKADDR_IN 结构的长度仅为16 字节。
3.3 地址及名称解析
本节将讲述怎样分配两种 IP 的文字串地址,以及将两种 IP 的名称解析为地址专用结构。
先讲述新的名称解析APl;
getaddrino 和 getnameinfo
,这两个API代替了IPv4 与用例程。
然后讲述用于文字地址和套接字地址结构之间转换的常规 Winsock API,即 WSAAddressToString 和WSAStringToAddress。注意,这些函数仅执行地址转换和分配,不执行名称解析。
接着将叙述传统IPv4 专用例程。其中包括传统 API的说明,以应付有必要保留原有代似的场合不过任何新的工程项都应该使用新的API 函数,使用新的函数后,编写个在IPv4和IPv6上都能够无缝运行的应用程序就比较容易了,这将是本章下一节的主题。
最后应注意到,本章叙述的所有名称解析函数仅仪解析名称,不能将名称注册为·个地址。这项功能出第8章中讨论的 Winsock RNR(Registration and Name Resolution,注册及名称解析)API来完成
3.3.1 名称解析例程
有几个随 IPv6·起引进的新的名称解析函数能够处理 IPy4 和 IPV6 地址。原有的函数如gethosbyname 机 inet addr 只能处理 IPV4 地址,代它们的雨数名为 getnameinfo 和 getaddrinfo.
这两个新的名称解析函数 WS2TCPIP.H 中定义,同时要注意到,尽管这些函数是 Windows XP中的新雨数,但它们也能在所有支持 Winsock 2 的台运行,这只要在包含头文件 WS2TCPIPH前,包含头文竹 WSPTAPLH 即可,编译过的二进制文件则可以在所有文持 Winsock 2 的台上运行刻 Windows 95、Windows 98、Windows MeWindows NT 4.0 及 Windows 2000.
getaddrinfo 雨散提供独立下协议的名称解析。
函数原型为:
int getaddrinio (
const char FAR *nodename.
Const char FAR *servname,
const struct addrinfo FAR *hintsr
struct addrinfo SAR *FAR *reg
)
参数 nodename 指定以空字符结束的主机名和文字地址,servname 参数是个包含口号或服务名(如 他 或 telnet)的以空字符结束的学符串。
第3个参数数 hints 是一个结,它能传递一个或多个选项,这些选项将影响到名称解析的执行方式。城后,参数 res 返回 addrlNF 结构的 个链表,该结构包含了由字符申名称解析而的地址。如果操作成功,则返回 0:否则返回 Winsock 错误信息。
addrINFO 结构的定义为:
struct addrinfo {
int ai_flags;
int ai_familyi;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
chal ai_canonname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
}
当要把 hints 结构传递到API 时,结构应预先置零,且结构的前4 个字段应相关联
-
ai_flags 字段长示下述3个值的 :APASSIVE、ALCANONNAME或AINUMERICHOST。Al PASSIVE 表nodename 是一个计算机名(如 wwwmicrosofcom)AI_NUMERICHOST 示 nodename 是一个文字字符地址(如“10.10.10.1”)、稍后将讨论AI_PASSIVE。
-
ai_family 子段表示AFINET、AF INET6或 AF UNSPEC,如果想析到个具体地,请键入 AF_INET AF INET6。则,如果给定 AF UNSPFC,则返回地址可能是 IPv4,也可能是 1Pv6,或者两个都返回。
-
ai_socktype 字段指定要求的套接字类型如SOCKDGRAM或SOCK STREAM当servname包含服务名称时,这个字段就会被使用。也就是说,根据使用的是 UDP,还是 TCP,有些服务具有不同的端4号。
-
ai_protocol字段指定要求的协议,如 IPPROTO TCP。跟上面一样当 servname 包含服务名称时,这个字段就有用。
如果没有 hints 结构传递给 getaddrinfo,函数将根据零 hints 结构执行,这时 ai family 取为AF UNSPEC.
如果函数解析名称成功,则解析后的地址会通过 res 返回。如果名称被解析为多个地址,则返回结果为一个由 ai.next 学段形成的链表。每个由名称解析而来的地址在 ai addr 中表示,其长度为ai addrlen 中给出的接字地址结构的长度。可以将这两个字段直接传递给 bind、connect、sendto等函数。
下面这一段代码示例展示了如何解析一个带有端口号的主机名,其中,解析操作是在与服务器建立一个TCP 连接之前进行的。
SOCKET s;
struct addrinfo hintsr,*result;
int rc;
memset(&hints,Osizeofihintsj);
hints.ai_lags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(”foobar",”50017,&hints,&result);
if (rc != 0){
//无法解析名称
}
s = socket(result->ai_family;result->ai_socktype,result->ai_protocol);
if(s == INVALID_SOCKET){
//套接字 API 失败
}
rc = connect(s,result->ai addr,result->ai addrlen);
if (IC == SOCKET ERROR)
//连接 API失败
}
freeaddrinfo(result);
在这个示例中,应用程序试图解析主机名“foobar”,并希望在5001号端口与某项服务建立一个TCP 连接。您还会注意到,代码并不在乎名称被解析为 IPv4 地址,还是 IPv6 地址。可能“foobar两种地址都注册了,在这种情况下,result 将包含由 ai next 字段链的额外的 addriNFO 结构。如果应用程序希望“foobar”仅注册了IPv4 地址,则应将 hintsai family 设为AF INET。最后应注意,过 res 返同的信息是动态分配的,–用程序使用完这些信息之后,便需要调用 fecaddrinfo APl释放这些信息所占用的空间。
应用程序实施的另一个常见动作,将一个文字字符串地址(如“172.17.7.1”或“fe80.:1234”)分配到适当类型的套接字地址结构中。getaddrinfo函数在 hints 结构中设置AI NUMERICHOST 标志从而做到这“点。下面的代码展示了这一做法:
struct addrinfo hints,*result;
int rc;
memset(shints,0,sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AI_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protoco1 = IPPROTO_TCP;
rc =getaddrinfo("172.17,7.1",“5001",&hints,&result);
if (rc !=0) {
//文字字符串地;无效
}
//使用 result
freeaddrinfo(result)
文字学符串地址“172.17.7.1”将被转换为必要的接字地址结构,并通过 result 返回。因为传递了AF UNSPEC,API将确定所需的正确套接宁地址结构(SOCKADDR_IN 或 SOCKADDR_IN6),并将地址作机应转换,如前所述,转换后生成的套接字地址结构的端口字段将被初始化为 5001。
注意,如果没有将任何标志作为 hints 结构的·部分进行传递,那么在解析文宁字符串地址之后被返回的含有转换后地址的结构 addrinfo 将设置AI NUMERICHOST标志。同样,如果解析了主机名而没有传递任何 hints 结构,所返回的结构addrinfo 将包AI CANONNAME标志。
getaddrinfo 可以使用的最后一个标志是 AL PASSIVE,该标志用于获取能够传递给 bind 函数的地址。对F IPv4 而言,这个地址可能是 INADDR ANY(0.0.0.0),而对下 IPv6 而言则可能是IN6ADDR ANY(: )。为获取绑定地址,hints 结构应该指明要为哪个地址族获取该无源地址(通过ai family),nodename 应设为 NULL,servname 应设为非 NULL–指明应用程序要绑定到哪个端号(可以是0)。如果AF_UNSPEC 被传递到 hints 结构中,则两个addrINFO 结构将被返回,一个带有IPv4绑定地址,另一个带有 IPv6 绑定地址。
在使用 getaddrinfo 解析了主机名之后,AI_PASSIVE 标志就可以派上用场了。一解析后的地址被返回,就可以在另一次调用 getaddrinfo 时使用初始结中的 ai family(地址族),以便获取该地址放合适的绑定地址。这让应用程序避免了接触内部变接字地址结构的字段,也排除了使用两个独立的代码路径(即根据地址将解析到哪个地址来绑定套接字的两个独立路径)的必要
另 个新的名称解析 API是 gelnameinlo,其功能和 getaddrinfo 函数相反。该函数接受已经初始化的套接字结构,并返回与地址及端口信息对应的土机和服务名。函数原型为:
int getnameinfo(
const struct sockdddr FAR *sd,
socklen t salen,
chdr FAR *hostr,
DNORD hostlen,
char EAR *serv,
DWORD servlen,
int flags
)
其中各个参数的含义是较为明显的。参数 a 是套接地址结构,名称信息将从这个参数获取而参数 salen 则是该地址结构的大小。参数 ost 是接收主机名称的字符缓冲区,默认状态下将返回FQDN(fully qualified domain name,完全合格的城名)参数 hostlen 表示机缓冲区的大小。参数 serv是接收服务(或端口)信息的字符缓冲区,而参数 scrvlen 则表示该缓冲区的长度。最后,ags 参数指明将如何解析套接字地址。
flags 可能的取值如下,
- NINOFQDN:表明仅返RDN(relative distinguishedname,相对特异名)。例如,设置这个标志时,名为“mist.microsoft.com”的卡机将只返回“mist”
- NT_NUMERICHOST:表将返回用字符串表示的地址,而不返回主机名。
- NI_NAMEREOD: 表明如果地址不能被解析为 FODN,则返回错误信息NI
- NUMERICSERV: 表明将端口信息作为个字符串返回,而不会将口信息解析为一个已知的服务名(如“ftp”)。注意,如果在提供 scrv 缓冲区时缺少这个标志,且11号不能被解析为某种已知服务,则 genameinfo 将失败,出错信息为 WSANO DATA(11004)
- NI_DGRAM: 用来将数据报服务从流服务中区分开来。对于少数几个为 UDP TCP 定义了不同端门号的服务来说,这种区分是很有必要的。
在补充材料中有一个名为 RESOLVERCPP
的文件,该文件县有执行名称解析的功能。主机名或地址随同服务或端口信息一起被传递给应用程序,并通过 gctaddrinfo 雨数进行解析。然后,针对每个返回的已解析地址,调用函数 getnameinfo,以获得计算机名,或获取学符串文字地址。
3.3.2 简单的地址转换
如果应用程序只是需要在字符串文字地址和套接字地址结构之间进行转换,可以使用WSAStringToAddress和WSAAddressIoString 这两个API。
使用WSAStringToAddress 时,必须指定字符串地址所属的地址族,所以它不如 getaddrinfo“灵活”。
这个API如下所示:
INT WSAStringToAddress(
LPTSTR AddrcssString,
INT AddressFamily,
LPWGAPROTOCOL_INFO lpProtocol_Infos,
LPSOCKADDR lpAddress,
LPINT lpAddressLength
)
第1个参数是需要转换的字符串,
第2 个参数指明该字符所属的地址族(如 AF INETAF_INET6或AF IPX)。
第3个参数lpProtocolinfo是个可选指针,它指WSAPROTOCOLINFO结构,这个结构定义了实施转换时所使用的协议提供程序。
如果同时有多个提供程序执行一种协议,则这个参数可以用来指定一个显式的提供程序。
第 4 个参数是个适当的套接字地址结构,可以将字符串地址转换为这种地址结构,并把值赋给它。
应注意到,这个 API 可以转换包含端口号的字符中地址。例如,IPv4 字符串的注释允许在地址末尾的冒号后边紧跟端口号。例如“157.54.126.42:1200”表示这个IPv4 地址使用 1200 端口在IPv6中,IPv6 地址串必须用方括号括起来,在方括号后边可以使用冒号和端口注释。例如,fe80.:250:8bf:fca0:92ed%51:80 表示·个链接一本地地址,其范围ID 后紧跟端号 80。注意,只有端日号小会被解析,服务名(如 fp)不会被解析。对这两个例子来说,如使用 WSAStringToAddress来转换这些字符串,则返回的套接字地址结构将被初始化,从而获得适当的二进制 IP 地址、端口号以及地址族。
对IPv6 而言,如果字符串地址的地址部分后边含有“%scope ID”,则范围ID字段也会被初始化。
WSAAddressToString 提供从套接字地址结构到该地址字符串表示的一个映射。
其原型为:
INT WSAAddressToString(
LPSOCKADDR lpsaAddress,
DWORD dwAddressLength,
LPWSAPROTOCOL_INFO pProtocolInfo,
LPTSTR lpszAddressstring,
PDWORD lpdwAddressstringLength
)
这个函数采用了一个 SOCKADDR 结构,并将二进制地址格式化为一个字符串,该字符串由lpszAddressString 缓冲区表示。如果一个给定协议有多个传输提供程序,那么将该协议的WSAPROTOCOLINFO 结构作为lpProtocolInfo 传递,便可选定一个具体的提供程序。注意,地址族是结构 SOCKADDR的一个字段,该结构为 lpsaAddress 传递。
3.3.3 传统名称解析例程
因为新的应用程序可以使用 getaddrinfo 和 getnameinfo,所以本节讨论传统名称解析的目的,只是为了方便代码维护。还应该注意到,这两个新的 API 调用能够完成8 个传统函数所具有的功能。
函数 inet addr 把一个点分IPv4 地址转换为–个 32 位无符号长整数,其定义为:
unsigned long inet_addr(
const char FAR *cp
);
中 字段是一个以空字符结束的字符串,它认可点分表示法的 IP 地址。意,这个雨数返回的 IPv4地址是一个按网络字节顺序排列的32位无符导长整数该长整数被分配给SOCKADDR IN的si add字段。网络字节顺序已在第1章中作了叙述
与inet_addr 相对的是inet_ntoa,它获取-个 IPV4 网络地址,并将其转换为一个字符串。该两数的声明为:
char FAR *inet ntoa(
struct in_addr in
);
下面的示例代码展示了如何使用 net addr 函数和 htons数创建一个SOCKADDR_IN结构
SOCKADDR_IN InternetAddr;
INT nPortId = 5150:
InternetAddr sin_family = AF_INET:
//将拟来用的点分Internet 地址 136.149.3,29 转换为字节整数,并把它分配给 sin_addr
InternetAddr sin_addr.s addr = inet_addr("136,149.3.29");
//nPortId变量按主机字节顺序存储,将 nPortId 转换为网络字节顺序,并分配给 sin_port
InternetAddr.sin_port = htons(nPortId);
gethostbyname、WSAAsyncGetHostByName、gethostbyaddr 和 WSAAsyncGetHostByAddt 这4个Winsock 函数从主机数据库中获取与主机名或主机地址相对应的主机信息。
前两个函数将士机名转换为其网络的 IPv4 地址,后两个函数则完成相反的操作一一将 P4 网络地址映射回主机名。这些函数均返回 HOSTENT 结构,该结构的定义为:
struct hostent
{
char FAR *h name/
char FAR *FAR *h_aliases;
short h_addrtype!
short h_length;
char FAR *FAR *h_addr list;
}
h_name字段是主机的正式名称。如果网络使用了 DNS,则FQDN 将促使名称服务器返回一条回复。如果网络使用一个本地“机”文件,则该文件名称就是 IP 地址后的第一个条目。
h_aliases 字段是一个以空字符结束的数组,表示主机的替代名称。
h_addrtype 字段表示返回的地址族。h_length字段定义h_addr_list 字段中每个地址的字节长度对IPv4 地址而言,地址长度为 4个字节h_addr_list。
字段是表示主机TP 地址的一个以空字符结束的数组(可以为一个主机分配多个 IP 地址),数组中的每个地址均以网络字节顺序返回。
一般情况下,应用程序使用数组中的第1个地址。然而,如果同时有多个地址被返回,则应用程序通常会随机选择 个可用的地址,而不会总是使用第 1个。
这些函数的定义如下:
struct hostent FAR *gethostbyname
{
const char FAR*name;
}
HANDLE WSAAsyncGetHostByName (
HWND hWnd,
unsigned int wMsg,
const char FAR *name,
char FAR *buf,
int buflen
);
struct HOSTENT FAR *gethostbyaddr(
const char FAR *addr;
int len;
int type;
);
HANDLE WSAAsyncGetHostByAddr
(
HWND hWnd,
unsigned int wMsg,
const char FAR *addrr,
int len,
int types,
char FAR *buf,
int buflen
);
对前两个函数而言,name参数显示用户正在寻找的主机的一个友好名称。
后两个函数将获取一个IPv4 网络地址并将它分配给参数 addr,同时将地址长度指定为 len。type 指明操作过程中所传递的网络地址的地址族,其取值应为 AF INET。这 4 个函数均通过一个 HOSTENT 结构返回结果。对两个同步函数(第1个和第3个)来说,HOSTENT 是一个系统分配的缓冲区,由于这个缓冲区是静态的所以应用程序不能依赖它。而两个异步函数(第 2个和第4个)将会把 HOSTENT 结构复制到由 buf参数表示的缓冲区中,这个缓冲区大小应该等于MAXGETHOSTSTRUCT
最后,这些函数以及其他异步的名称和服务解析函数均返回一个 HANDLE,并用它来标识已发动的操作。一旦操作完成,就会有·个由 wMsg 指明的窗口消总被发送到出 hWnd 给定的窗口。
如果在某一时刻应用程序希望取消异步请求,则应该使用 WSACancelAsyncRequest 函数,其声明为:
int WSACancelAsyncRequest(
HANDLE hAsyncraskHandle
);
应牢记,同步 APL调用只有在查询结束或超时的情况下,才会发生阻塞,这可能会花费几秒钟的时间。
下一种类型的传统名称解析函数能够找出已知服务的端口号,也能够根据端口号找出服务项目。API函数 getservbyname和 WSAAsyncGetServByName 获取一个知服务的名称(如 ftp),之后返回该服务所使用的端口号。函数 getservbyport 和 WSAAsyncGetServByPort 则执行相反的操作,接收端口号,返回使用该端口的服务名。
这些函数只简单地从一个文件名为“services”的文件中获取静念信息。在Windows 95、Windows 98 及Windows Me中,srvices 文件位于%WINDOWS%目录下:在WindowsNT中,该文件位于%WINDOWS%\System32Drivers\Etc下。
这4个函数用 SERVENT 结构返回服务信息。该结构的定义为:
struct servent{
char FAR *s_name;
Char FAR *FAR *s_aliases;
short s_port;
char FAR *s_proto;
}
宁段s name 是服务名,字段 s aliases 是表示字符指针的一个以空字符结束的数组,数组中每个元素分别包含了服务的一个别名。s port 是服务使用的端口号,s prto 是服务使用的协议,如字符串“tcp”和“udp”
这些函数的定义如下
struct servent FAR *getservbyname(
const char FAR*namer,
const char FAR *proto
);
HANDLE WSAAsyncGetServByName(
HWND hWnd,
unsigned int wMsg,
const char FARname,
const char FAR*proto,
char FAR *buf,
int buflen
);
struct servent FAR * getservbyport (
int port,
const char FAR *proto,
HANDLE WSAAsyneGet ServByPort,
HWND hWnd,
ungigned int wMsg,
int port,
const char FAR *proto,
cher FAR *buf
snt buflen
);
name 参数表示所寻找的服务的名称。proto参数有选择地指向一个字符串,该字符指 name参数表示的服务是在哪个协议下册的,如“tcp”和“udp”协议。后两个函数简单地接受口号并用它来和某服务名相匹配两个步的 API数将返回个 SERVENT 结构,该结构是系统分配的缓冲区。而两个并步的函数将采用一个由应用程序提供的缓冲区,其大小也应为MAXGETHOSTSTRUCT。
最后一组传统的名称解析 API函数在协议的字符名称(如 cp)与其协议号(tcp 被解析为IPPROTOTCP)之间执行转换操作。
这些函数有:getprotobyname、WSAAsyncGetProtoByName、getprotobynumber及WSAAsyncGetProtoByNumber。
前两个函数将字符串协议转换为协议号,而后两个则相反一将协议号映射回它的字符串名称。这些函数均返回一个 PROTOENT 结构,该结构的定义:
struct protoent {
char FAR * p_name;
char FAR *FAR *p_aliases;
short p_proto;
};
第1个字段 p_name 是协议的字符名称,paliases 是表符指的一个以空字符结束的数组,该数组包含了协议的其他已经为人所知的名称最后一个段p proto 是协议(如 IPPROTO_UDI成IPPROTO TCP)
这些函数的原型为
struct protoent FAR *getprotbyname(
const char FAR *name
);
HANDLE WSAAsyncGetProtoByName(
HWND hWnd,
unsigned int wMsgs,
const char FAR *name,
Char FAR *buf,
int buflen
);
struct protoent FAR *getprotobynumber(
int number
);
HANDLE WSAAsyncGetProtoByNumber (
HWND hWnd,
unsigned int wMsg,
int number,
char FAR *buf,
int buflen
);
在同步及异步方面,这些函数和前述传统名称解析函数表现一致
3.4 编写独立于IP版本的程序
如何在IP4 和1Pv6 上开发无缝运行的应用程序呢?本节将就此展开讨论。要做到这一点,首先要使用新的名称解析API函数etaddrinfo 和 getnameinfo,还需重新调整 Winsock 的调用方式
在介入具体内容之前,先要注意一些应该遵循的基本原则。首先,因为套接字地址结构的长度可能不相等,所以应用程序不能将这些结构具体分配到每种协议中(如将 SOCKADDR IN 和SOCKADDRIN6分别分配到IP4 和IPV6中)。针对这种情况,这里引进了一个新的套接字地址结构SOCKADDR STORAGE,它的大小和最大的协议专用地址结构相同。同时,这个结构还具有针对64位对齐问题的填充项。下列代码便使用了 SOCKADDR STORAGE 结构来储存目标IPv6 地址。
SOCKADDR_STORAGE saDestination;
SOCKET s;
int addrlen, rc;
s = socket (AF_INET6,SOCK_STREAM,IPPROTO_TCP);
if (s == INVALID SOCKET) {
//套接字失败
}
addrlen = sizeof(saDestination);
rc = WSAStringToAddress(
"3ffe:2900:d005:f28d;250:8bff:fea0:92ed", AF_INET6,NULL,
(SOCKADDR *)saDestination, &addzlen);
if(rc == SOCKET ERROR){
//转换失败
}
rc = connect(s,(SOCKADDR *)saDestination, sizeof(saDestination));
if(rc == SOCKET_ERROR){
//连接失败
}
其次,采用地址作为参数的函数应该传递整个套接学地址的结构,而不是传递协议专用的类型(如structin addr或 structin6 addr)。对下IP6而言,因为它需要范用ID 信息,以使进行成功的连接,这点就显得非常重要。所以,应该传递包含有地址的 SOCKADDR STORAGE 结构。
最后,避免使用硬编码(hardcode)地址,不管这些地址是 IP4,还是 Pv6。Winsock 头文件为所有硬编码地址(如用于绑定的环回地址和通配符地址)定义了常量。
弄清楚一些基本问题之斤,下面开始讨论怎样组织一个应用程序,才能使其独立于 IP。讨论分为两个部分:客户机和服务器。
3.4.1
对TCP及UDP 客户机来说,应用程序通常拥有服务器(或接受者)的IP 地址或机名。
至于解析到TPv4 地址,还是解析到IP6 地址,这一点并不重要,客户机应遵循下面的3 个步骤使用 getaddrinfo 数解析地址。
hints 结构不仅要包含套接字类型和协议,还应该包含AF_UNSPEC。这取决于客户机是使用 TCP 通信,还是使用UDP 通信使用步骤1返回的addrINFO 结构中ai faily、ai socktype 和ai protocol字段来创建套接字,
使用addrINFO结构中的成员ai_addr 来调用 connect 函数 sendto 函数。
下列示例代码展示了上述规则
SOCKET s;
struct addrinfo hints;
*res = NULL;
char *szRemoteAddress = NULL;
//分析命令行,以获取远程服务器的上机名、地址及端口号,它们应于// szRemoteAddress 利 szRemotePort Hmemset(khints,O,sizeofhintsl);
hints.ai_family = AF_UNSPEC;
hznts.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
//首先在假定字符串是一个文字宁符串址的情况下进行解析rC
getaddrinfo(szRemoteAddress,szRemotePortr
&hints
&res
);
if (rc == WSANO_DATA)
{
//无法解析名称一跳出
}
s = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
if (s == INVALID SOCKET)
{
//套接字失败
}
rc = connect(s,res->ai addr,res->ai addrlen);if (rC == SOCKET ERROR) {
//连接失败
freeaddrinfo(res);
首先,应注意到,这里没有显式地引用 AF INET 或 AF INET6。同时,也没有必要操作下层的SOCKADDR_IN SOCKADDR IN6 地址。getaddrino 调用将对返的套接地址结构进行完全初始化,使其获得所有必需的信息一一地址族、二进制地址等,这对于连接或发送数据报而言,是非常必要的。
如果客户机应用程序需要在套接字创建之后,并要在调用 connect 或 sendto 之前,将套接字显式地绑定到一个本地端口,则可再次调用 getaddrinfo。这个调用将指定从第一次调用中返回的地止族、套接字类型、协议、AI PASSIVE 标志以及所需的本地端口,并返回另一个套接字地址结构。这个返回的结构已被本次调用初始化,从而获得了必要的绑定地址(如 IPv4 的0.0.0.0,以及IPV6的3)。
3.4.2服务器
因为 Windows IPv6 堆栈是双堆栈,所以对服务器端进行编程比客户机端的编程牵涉的层面更多
IPv4 和IPv6 具有各白独立的堆栈,因此,如果一个服务器希望同能够接受 IP4 和 IP6 连接,它就必须为 IPv4 和IPv6 都创建接宁。要创建–个独十 的服务器,须遵循如下步骤:
- 用hints 结构调用 getaddrinfo 函数,该结构包含 AIPASSIVE、AF UNSPEC、所需的套接宁类型、协议及所需的本地端口(这个端口用来监听或接收数据)。这个调用将返回两个addrINFO 结构:个含用于 Pv4 的监听地址,另一个包含用于IPv6 的监听地址
- 针对每个返回的addrINFO 结构,创建一个包含ai family、ai socktype 和aiprotocol字段的套接字,接着使用ai_addr 和aiaddrlen 成员调用函数 bind。下述代码展示了具体的程序编写规则:
SOCKET slisten[16];
char *ezPort = "5150”;
struct addrinfo hints;
*tes = NULL;
"ptr = NULL;
int count = 0;
tejmemsetishints,0,sizeofinints);
hin=s.ai_family = AF_UNSPEC;
hints.a_socktype = SOCK_STREAM;
hinta.ai_frotoeol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
re = getaddrinfol(NULL,szPort,&hints, res);
if (re !=0) {
//某种原因而失败
}
ptr = res;
while(lptr){
slisten[count] = socket(ptr->ai_family.ptr->ai_socxtype,ptr->ai protocol);
}
if(isListenleount] == INVALID_SOCKET){
//套接字失败
}
re = bind(slisten[count],ptr->ai_addr,ptr->ai_addrlen);
if(rs == SOCKET ERROR){
//绑定失败
}
kc = listen(slisten[count],7);
if(rc == SOCKET_ERROR) {
//监听失败
}
count++;
ptr = ptr->ai_next;
}
创建并绑定套接字之后,应用程序需要等待与每个套接宁建立的连接,第 5 章讨论了 Winsock 中叫用的各种I/O 模型,并提供了些功能全面的客户机利服务器示例,这些示例都是依据本节叙述的规则编写的。
总结
本章讨论了 IP4 和IP6,从中不仪叙述了每种地址族所必需的 Winsock 数据结构,还叙述了址及名称解析。在介绍完新的名称解析函数之后,本章接着讲述传统的名称解析函数。最后,本章还叙述了如何编写能在Pv4 和IPv6 上缝运行的应用程序。第4 章将讨论其余从 Winsock 中可以访问的协议,包括IPX/SPX、AppleTalk、IrDA 和ATM。
参考:
Microsoft Windows网络编程 第三章 网际协议
私信回复:
windows网络编程pdf
可获得【Microsoft Windows网络编程】pdf版
关于博主
wx/qq:binary-monster/1113673178
wxgzh: 二进制怪兽
CSDN:https://blog.csdn.net/qq1113673178
码云:https://gitee.com/shiver
Github: https://github.com/ShiverZm
个人博客:www.shiver.fun