[笔记] Microsoft Windows网络编程《三》网际协议(三)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: [笔记] Microsoft Windows网络编程《三》网际协议(三)

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;
}
• 1
• 2
• 3
• 4
• 5
• 6

宁段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 都创建接宁。要创建–个独十 的服务器,须遵循如下步骤:

  1. 用hints 结构调用 getaddrinfo 函数,该结构包含 AIPASSIVE、AF UNSPEC、所需的套接宁类型、协议及所需的本地端口(这个端口用来监听或接收数据)。这个调用将返回两个addrINFO 结构:个含用于 Pv4 的监听地址,另一个包含用于IPv6 的监听地址
  2. 针对每个返回的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版


相关文章
|
1月前
|
负载均衡 网络协议 算法
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
190 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
67 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
29天前
|
安全 Windows
【Azure Cloud Service】在Windows系统中抓取网络包 ( 不需要另外安全抓包工具)
通常,在生产环境中,为了保证系统环境的安全和纯粹,是不建议安装其它软件或排查工具(如果可以安装,也是需要走审批流程)。 本文将介绍一种,不用安装Wireshark / tcpdump 等工具,使用Windows系统自带的 netsh trace 命令来获取网络包的步骤
68 32
|
25天前
|
安全 搜索推荐 网络安全
HTTPS协议是**一种通过计算机网络进行安全通信的传输协议
HTTPS协议是**一种通过计算机网络进行安全通信的传输协议
53 11
|
24天前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
52 3
|
1月前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
87 3
|
1月前
|
网络虚拟化
生成树协议(STP)及其演进版本RSTP和MSTP,旨在解决网络中的环路问题,提高网络的可靠性和稳定性
生成树协议(STP)及其演进版本RSTP和MSTP,旨在解决网络中的环路问题,提高网络的可靠性和稳定性。本文介绍了这三种协议的原理、特点及区别,并提供了思科和华为设备的命令示例,帮助读者更好地理解和应用这些协议。
52 4
|
1月前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
51 13
|
2月前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
75 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
下一篇
DataWorks