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