Windows Sockets 规范及应用
发布: 2008-4-30 22:53 | 作者: 施炜 李铮 秦颍 | 来源: 网络转载 | 查看: 483次
5.3.10 WSACleanup()
简述:
中止Windows Sockets DLL的使用.
#include <winsock.h>
int PASCAL FAR WSACleanup ( void );
注释:
应用程序或DLL在使用Windows Sockets服务之前必须要进行一次成功的WSAStartup()调用.当它完成了Windows Sockets的使用后,应用程序或DLL必须调用WSACleanup()将其从Windows Sockets的实现中注销,并且该实现释放为应用程序或DLL分配的任何资源.任何打开的并已建立连接的SOCK_STREAM类型套接口在调用WSACleanup()时会重置; 而已经由closesocket()关闭却仍有要发送的悬而未决数据的套接口则不会受影响- 该数据仍要发送.
对应于一个任务进行的每一次WSAStartup()调用,必须有一个WSACleanup()调用.只有最后的WSACleanup()做实际的清除工作;前面的调用仅仅将Windows Sockets DLL中的内置引用计数递减.一个简单的应用程序为确保WSACleanup()调用了足够的次数,可以在一个循环中不断调用WSACleanup()直至返回WSANOTINITIALISED.
返回值:
0 操作成功.
SOCKET_ERROR 否则.同时可以调用WSAGetLastError()获得错误代码.
评价:
一个常见的Windows Sockets编程错误是:试图在一个阻塞钩子函数中调用WSACleanup()并且检测返回值失败.如果在一次阻塞调用正在进行时应用程序需要退出,应用程序必须首先通过调用WSACancelBlockingCall()使该阻塞操作作废, 然后一旦控制返回给应用程序时就启动WSACleanup().
关于Windows Sockets提供者的说明:
良好的Windows Sockets应用程序会通过调用WSACleanup()指出它从Windows Sockets实现中注销.本函数因此可以用来释放分配给指定应用程序的资源.
Windows Sockets的实现必须能处理应用程序在调用WSACleanup()函数之前就中止的情况.-例如,返回一个错误.
在一个多线程的环境下,WSACleanup()中止了Windows Sockets在所有线程上的操作.
Windows Sockets的实现必须确认WSACleanup()调用后,应用程序能调用WSAStartup()函数来重新建立Windows Sockets的应用.
错误代码:
WSANOTINITIALISED 使用本API前必须要进行一次成功的WSAStartup()调用.
WSAENETDOWN Windows Sockets的实现已经检测到网络子系统故障.
WSAEINPROGRESS 一个阻塞的Windows Sockets操作正在进行.
参见:
WSAStartup()
5.3.11 WSAGetLastError()
简述:
获得上次失败操作的错误状态.
#include <winsock.h>
int PASCAL FAR WSAGetLastError ( void );
注释:
本函数返回上次发生的网络错误.当一特定的Windows Sockets API函数指出一个错误已经发生,本函数就应调用来获得对应的错误代码.
返回值:
返回值指出了本线程进行的上一次Windows Sockets API函数调用时的错误代码.
关于Windows Sockets提供者的说明:
这里使用WSAGetLastError()函数来获得上一次的错误代码,而不是依靠全局错误变量, 是为了提供和将来的多线程环境相兼容.
注意在一个非占先的Windows环境下,WSAGetLastError()只用来获得Windows Sockets API错误.在占先环境下,WSAGetLastError()将调用GetLastError(), 来获得所有在每线程基础上的Win32 API函数的错误状态.为提高可移植性,应用程序应在调用失败后立即使用WSAGetLastError().
参见:
WSASetLastError()
5.3.12 WSAIsBlocking()
简述:
判断是否有阻塞调用正在进行.
#include <winsock.h>
BOOL PASCAL FAR WSAIsBlocking ( void );
注释:
本函数允许任务判断它是否在等待前一次阻塞调用完成时执行.
返回值:
TRUE 如果存在一个尚未完成的阻塞函数在等待完成.
FALSE 否则.
评价:
尽管在阻塞套接口上进行的调用对于应用程序来说似乎"阻塞"着,Windows Sockets DLL必须放弃处理机以使其它应用程序可以使用.这意味着对于启动该阻塞调用的应用程序来说可能会重入-这依赖于它接收的消息.在这种情况下,WSAIsBlocking()函数可用来确定在等待一个未完成的阻塞调用完成时,本任务是否重入.注意Windows Sockets禁止对每一线程多于一个未完成的调用.
关于Windows Sockets提供者的说明:
Windows Sockets的实现必须禁止在每个线程上多于一次的未完成阻塞调用.
5.3.13 WSASetBlockingHook()
简述:
建立一个应用程序指定的阻塞钩子函数.
#include <winsock.h>
FARPROC PASCAL FAR WSASetBlockingHook ( FARPROC lpBlockFunc );
lpBlockFunc 指向要安装的阻塞函数的函数指针.
注释:
本函数安装了一个新的函数,由Windows Sockets的实现用来实现阻塞套接口函数调用.
Windows Sockets的实现中包含了一种缺省的机制,通过它可以实现阻塞套接口函数. 函数WSASetBlockingHook()为应用程序提供了在"阻塞"时执行自己的程序,来代替缺省的函数.
当一个应用程序调用了一个阻塞的Windows Sockets API操作时,Windows Sockets的实现启动该操作,然后进入了和下列伪代码相似的循环:
for(;;) {
/* flush messages for good user response */
while(BlockingHook())
;
/* check for WSACancelBlockingCall() */
if(operation_cancelled())
break;
/* check to see if operation completed */
if(operation_complete())
break; /* normal completion */
}
注意Windows Sockets的实现可能以不同的次序运行上述代码,例如,对操作完成的检查可能发生在调用阻塞钩子函数之前.缺省的BlockingHook()函数如下:
BOOL DefaultBlockingHook(void) {
MSG msg;
BOOL ret;
/* get the next message if any */
ret = (BOOL)PeekMessage(&msg,NULL,0,0,PM_REMOVE);
/* if we got one, process it */
if (ret) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* TRUE if we got a message */
return ret;
}
WSASetBlockingHook()函数用来支持需要更复杂消息处理的应用程序-例如,使用了MDI(多文本界面)的程序.它并不是为运行通常应用程序函数的.特别的,唯一可以由客户阻塞钩子函数调用的唯一Windows Sockets API函数是WSACancelBlockingCall()-它将引起阻塞循环中止.
本函数必须为Windows的非多线程版本和多线程版本(如Windows NT)提供每线程基础上的实现.这样, 它为特殊的任务或线程提供了不影响其它任务或线程的基础上替换阻塞机制的能力.
在Windows的多线程版本中没有缺省的阻塞钩子函数-阻塞调用阻塞了进行该调用的线程.然而, 应用程序可以通过调用WSASetBlockingHook()安装一个特定的阻塞钩子.这为依赖于阻塞钩子的应用程序提供了简单的可移植性.
返回值:
返回值是一个指向前面安装的阻塞函数例程的指针.调用WSASetBlockingHook()函数的应用程序或库应该保留返回值,以使它在需要时能恢复.(若"嵌套"并不重要,应用程序可以简单地放弃WSASetBlockingHook()返回值,并且最终使用WSAUnhookBlockingHook()来恢复缺省的机制.)如果操作失败, 返回一个NULL指针,并且可通过调用WSAGetLastError()获得特定的错误代码.
错误代码:
WSANOTINITIALISED 使用本API前必须要进行一次成功的WSAStartup()调用.
WSAENETDOWN Windows Sockets的实现已经检测到网络子系统故障.
WSAEINPROGRESS 一个阻塞的Windows Sockets操作正在进行.
参见:
WSAUnhookBlockingHook()
5.3.14 WSASetLastError()
简述:
设置可以被WSAGetLastError()接收的错误代码.
#include <winsock.h>
void PASCAL FAR WSASetLastError ( int iError );
iError 指明将被后续的WSAGetLastError()调用返回的错误代码.
注释:
本函数允许应用程序为当前线程设置错误代码,并可由后来的WSAGetLastError()调用返回. 注意任何由应用程序调用的后续Windows Sockets函数都将覆盖本函数设置的错误代码.
关于Windows Sockets提供者的说明:
在Win32环境中,本函数将调用SetLastError().
返回值:
无.
错误代码:
WSANOTINITIALISED 使用本API前必须要进行一次成功的WSAStartup()调用.
参见:
WSAGetLastError()
5.3.15 WSAStartup()
简述:
#include <winsock.h>
int PASCAL FAR WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );
wVersionRequested Windows Sockets API提供的调用方可使用的最高版本号.高位字节指出副版本(修正)号,低位字节指明主版本号.
lpWSAData 指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节.
注释:
本函数必须是应用程序或DLL调用的第一个Windows Sockets函数.它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节.应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数.
为支持日后可能和Windows Sockets 1.1有功能上差异的Windows Sockets实现及应用程序,在WSAStartup()中规定了一个协议.WSAStartup()的调用方和Windows Sockets DLL互相通知对方它们可以支持的最高版本,并且互相确认对方的最高版本是可接受的. 在WSAStartup()函数的入口,Windows Sockets DLL检查了应用程序所需的版本.如果版本高于DLL支持的最低版本,则调用成功并且DLL在wHighVersion中返回它所支持的最高版本,在wVersion中返回它的高版本和wVersionRequested中的较小者.然后Windows Sockets DLL就会假设应用程序将使用wVersion.如果WSDATA结构中的wVersion域对调用方来说不可接收, 它就应调用WSACleanup()函数并且要么去另一个Windows Sockets DLL中搜索,要么初始化失败.
本协议允许Windows Sockets DLL和Windows Sockets应用程序共同支持一定范围的Windows Sockets版本.如果版本范围有重叠,应用程序就可以成功地使用Windows Sockets DLL.下列的图表给出了WSAStartup()在不同的应用程序和Windows Sockets DLL版本中是如何工作的:
应用程序版本 DLL版本 wVersionRequested wVersion wHighVersion 最终结果
1.1 1.1 1.1 1.1 1.1 use 1.1
1.0 1.1 1.0 1.1 1.0 1.0 use 1.0
1.0 1.0 1.1 1.0 1.0 1.1 use 1.0
1.1 1.0 1.1 1.1 1.1 1.1 use 1.1
1.1 1.0 1.1 1.0 1.0 失败
1.0 1.1 1.0 -- -- WSAVERNOTSUPPORTED
1.0 1.1 1.0 1.1 1.1 1.1 1.1 use 1.1
1.1 2.0 1.1 2.0 1.1 1.1 use 1.1
2.0 1.1 2.0 1.1 1.1 失败
下列代码段给出了只支持Windows Sockets 1.1版本的应用程序是如何进行WSAStartup()调用的:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
/* Tell the user that we couldn't find a useable */
/* winsock.dll. */
return;
}
/* Confirm that the Windows Sockets DLL supports 1.1.*/
/* Note that if the DLL supports versions greater */
/* than 1.1 in addition to 1.1, it will still return */
/* 1.1 in wVersion since that is the version we */
/* requested. */
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
/* Tell the user that we couldn't find a useable */
/* winsock.dll. */
WSACleanup( );
return;
}
/* The Windows Sockets DLL is acceptable. Proceed. */
下面的代码段示例了只支持1.1版的Windows Sockets DLL是如何进行WSAStartup()协商的:
/* Make sure that the version requested is >= 1.1. */
/* The low byte is the major version and the high */
/* byte is the minor version. */
if ( LOBYTE( wVersionRequested ) < 1 ||
( LOBYTE( wVersionRequested ) == 1 &&
HIBYTE( wVersionRequested ) < 1 ) {
return WSAVERNOTSUPPORTED;
}
/* Since we only support 1.1, set both wVersion and */
/* wHighVersion to 1.1. */
lpWsaData->wVersion = MAKEWORD( 1, 1 );
lpWsaData->wHighVersion = MAKEWORD( 1, 1 );
一旦应用程序或DLL进行了一次成功的WSAStartup()调用,它就可以继续进行其它所需的Windows Sockets API调用.当它完成了使用该Windows Sockets DLL的服务后,应用程序或DLL必须调用WSACleanup()以允许Windows Sockets DLL释放任何该应用程序的资源.
实际的Windows Sockets实现细节在WSAData结构中描述如下:
struct WSAData {
WORD wVersion;
WORD wHighVersion;
char
szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYSSTATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
};
该结构的成员为:
成员 用法
wVersion Windows Sockets DLL期待调用方使用的Windows Sockets规范的版本.
wHighVersion DLL可支持的Windows Sockets规范的最高版本.通常它和wVersion相同.
szDescription 一个null结尾的ASCII字串,Windows Sockets DLL将Windows Sockets实现的说明及厂商描述拷至该串.这段文本(长度最多256个字符)可能包含任何字符, 但厂家注意到不把控制和格式字符包含进去:该字串的最可能用法就是在状态消息中显示.
szSystemStatus 一个null结尾的ASCII字串,Windows Sockets DLL将相关的状态和配置信息拷至该串.Windows Sockets DLL只有在该信息对用户或支撑人员有用时才会使用该域:它不应该被认为是szDescription域的扩展.
iMaxSockets 一个进程可以打开的最大套接口数目.Windows Sockets的实现可以提供一个全局的套接口池给任何进程分配;也可以为每个进程分配套接口资源.该数字可反映出Windows Sockets DLL或网络软件是如何配置的.应用程序员可以使用该数字作为该Windows Sockets实现是否可以被应用程序使用的原始依据.例如,一个X Windows服务器可能在它启动时检查iMaxSockets:若它小于8,应用程序应显示一条错误信息, 让用户重新配置网络软件.(这是szSystemStatus可能使用到的一种情况.)显然,并不保证一个特定的应用程序可以实际分配到iMaxSockets个套接口,因为可能有其它的Windows Sockets应用程序在使用.
iMaxUdpDg 以字节表示的可由Windows Sockets应用程序发送或接收的最大UDP数据报的大小.如果应用程序没有给出限制,iMaxUdpDg为0.在Berkeley套接口的许多实现中,对于UDP数据报的导向有一个隐含的限制8192字节.Windows Sockets的实现可以在分配碎片重组缓冲区的基础上给出界限.对于一般的Windows Sockets实现iMaxUdpDg的最小值为512.注意不考虑iMaxUdpDg的值,而试图在网络上发送一个大于最大传输单元(MTU)的广播数据报是不明智的.(Windows Sockets API没有提供发现MTU的机制,但它必须不小于512字节.)
lpVendorInfo 指向厂商规定数据结构的远指针.该结构的定义(如果提供)超出了本规范的范围.
应用程序或DLL若需要多次得到WSAData结构信息,就必须多次调用WSAStartup().然而,wVersionRequired参数假设在所有调用WSAStartup()中都相同;也就是,应用程序或DLL不能在第一次调用WSAStartup()后改变Windows Sockets的版本号.
对应于每一次WSAStartup()调用必须有一个WSACleanup()调用,以使第三级(third-party)DLL可以利用和应用程序相关的Windows Sockets DLL.这意味着,例如,如果应用程序调用了WSAStartup()三次,它就必须调用WSACleanup()三次.对WSACleanup()的前两次调用除了减少内置计数器以外不做任何事, 对任务的最后一次WSACleanup()调用为任务释放了所有所需的资源.
返回值:
0 成功.
否则返回下列的错误代码之一.注意通常依靠应用程序调用WSAGetLastError()机制获得的错误代码是不能使用的,因为Windows Sockets DLL可能没有建立"上一错误"信息储存的客户数据区域.
关于Windows Sockets提供者的说明:
每一个Windows Sockets应用程序必须在进行其它Windows Sockets API调用前进行WSAStartup()调用.这样,本函数就可以用于初始化的目的.
进一步的说明在WSACleanup()的说明中有讨论.
错误代码:
WSASYSNOTREADY 指出网络通信依赖的网络子系统还没有准备好.
WSAVERNOTSUPPORTED 所需的Windows Sockets API的版本未由特定的Windows Sockets实现提供.
WSAEINVAL 应用程序指出的Windows Sockets版本不被该DLL支持.
参见:
send(), sendto(), WSACleanup()
5.3.16 WSAUnhookBlockingHook()
简述:
恢复缺省的阻塞钩子函数.
#include <winsock.h>
int PASCAL FAR WSAUnhookBlockingHook ( void );
注释:
本函数除去了任何先前安装的阻塞钩子函数,并且重新安装缺省的阻塞钩子函数.
WSAUnhookBlockingHook()将肯定安装缺省的钩子函数,而非上一个.如果应用程序希望嵌套钩子函数-也就是,建立一个临时的钩子函数,然后返回前一个钩子函数(不论是缺省的还是由前面的WSASetBlockingHook()建立的)-它必须储存和恢复WSASetBlockingHook()的返回值;不能使用WSAUnhookBlockingHook().
在Windows的多线程版本(如Windows NT)中没有缺省的阻塞钩子函数.调用WSAUnhookBlockingHook()去除了应用程序和任何阻塞调用(阻塞了进行该调用的线程本身)安装的所有阻塞钩子函数.
返回值:
0 操作成功.
SOCKET_ERROR 否则.同时可以调用WSAGetLastError()获得错误代码.
错误代码:
WSANOTINITIALISED 使用本API前必须要进行一次成功的WSAStartup()调用.
参见:
WSASetBlockingHook()
第六章 Windows Socket 2的扩展特性
这一章将讨论从Windows Sockets 1.1到Windows Socket 2的主要变动。
6.1 同时使用多个传输协议
为了用户能够同时使用多个传输协议,在Windows Socket 2中,结构有所改变。在Windows Sockets 1.1中,软件开发商所提供的DLL实现了Windows Sockets的API和TCP/IP协议栈。Windows Sockets DLL和底层协议栈的接口是唯一而且独占的。Windows Socket 2改变了这种模型:它定义了一个Windows Sockets DLL和底层协议栈间的标准服务提供接口(SPI)。这使得一个Windows Sockets DLL能够同时访问不同软件开发商的多个底层协议栈。此外,Windows Sockets 2并不象Windows Sockets 1.1仅支持TCP/IP协议栈。与Windows开放系统结构(WOSA)兼容的Windows Sockets 2的结构如下图:
图6-1:Windows Socket 2开放系统结构图
注意:16位的Windows Sockets 2应用程序应使用WS2-16.DLL,而32位的Windows Sockets 2应用程序应使用WS2-32.DLL。但今后,为了简单起见,它们将都使用WINSOCK.DLL。这并不会造成任何问题,因为在它们之间并没有任何语法上的区别。
由于以上的结构,现在已没有必要每个协议栈开发商都提供它们自己的WINSOCK.DLL(甚至这样做也不是期望的)。因为任何一个WINSOCK.DLL能够在所有协议栈上工作。因此,WINSOCK.DLL可以被看作是一个操作系统组件。Microsoft将在Windows 95和Windows NT上提供一个32位的WINSOCK.DLL。Intel公司目前正在打算提供Windows 3.1和Windows 3.11上的Windows Sockets 2兼容的16位WINSOCK.DLL。
6.2 与Windows Socket 1.1应用程序的向后兼容性
Windows Socket 2与Windows Sockets 1.1在两个基础上向后兼容:源码和二进制代码。这就实现了Windows Sockets应用程序和任何版本的Windows Sockets实现之间的最大的互操作性,而且也减少了Windows Sockets应用程序使用者,网络协议栈提供者和服务提供者的许多痛苦。现有的Windows Sockets 1.1兼容的应用程序可以在Windows Sockets 2实现上不加修改的运行,只要有一个TCP/IP协议栈被安装。
6.2.1 源码的兼容性
Windows Sockets 2中的源码兼容性意味着所有的Windows Sockets 1.1版的API在Windows Sockets 2中都被保留了下来。这意味着现有的Windows Sockets 1.1应用程序的原程序可以被简单的移植到Windows Sockets 2系统上运行。程序员需要做的只是包含新的头文件-WINSOCK2.H和简单的与合适的Windows Sockets 2函数库的连接。应用程序开发者应该把这种工作看作是完全转向Windows Sockets 2的第一步,因为有许多方式可以使用Windows Sockets 2中的新函数来提高原来的Windows Sockets 1.1应用程序的运行性能。
6.2.2 二进制兼容性
在设计Windows Sockets 2时的一个主要目标就是使得现有的Windows Sockets 1.1应用程序在二进制级别上能够不加修改的应用于Windows Sockets 2之上。由于Windows Sockets 1.1是基于TCP/IP上的,二进制兼容性就要求Windows Sockets 2系统提供基于TCP/IP上的Windows Sockets 2的传输和名字解析服务。为了Windows Sockets 1.1应用程序能在这种意义上运行,Windows Sockets 2系统提供了一个附加的组件-Windows Sockets 1.1的DLL。Windows Sockets 2安装时的提示保证了在终端机用户引进Windows Sockets 2系统时不会对已有的Windows Sockets软件环境有任何影响。
图6-2:与Windows Sockets 1.1二进制兼容性结构图
一个完全的Windows Sockets 1.1二进制兼容的必要的前提是在系统上已经安装了至少一个TCP/IP协议栈并且在Windows Sockets 2中做了注册。Windows Sockets 1.1目前通过WSAData结构中的某些元素来得到关于底层TCP/IP协议栈的信息(例如通过WSAStartup()函数调用),这些信息包括iMaxSockets,iMaxUdpDg和IPVendorInfo。但是在Windows Sockets 2中,应用程序应该知道忽略这些信息,因为这些值不能统一地适用于所有协议栈。不过DLL必须仍然提供这些值以免破坏Windows Sockets 1.1的应用程序。这些信息只能从(因此也只能应用于)缺省的TCP/IP服务提供者得到。缺省的TCP/IP服务提供者是由WSAEnumProtocols()调用返回的PROTOCOL_INFO结构缓冲区的第一条TCP/IP协议栈。
6.3 在Windows Sockets中注册传输协议
要使Windows Sockets能够利用一个传输协议,该传输协议必须在系统上安装并且在Windows Sockets中注册。Windows Sockets 2的DLL包含了一组API来完成这个注册过程。这个注册过程包括建立一个新的注册和取消一个已有的注册。在建立新的注册时,调用者(假设是协议栈开发商的安装程序)必须提供一组或多组完整的关于协议的信息,这些信息将被用来填充PROTOCOL_INFO结构。
6.3.1 使用多个协议
一个应用程序可以通过WSAEnumProtocols()功能调用来得到目前有多少个传输协议可以使用,并且得到与每个传输协议相关的信息,这些信息包含在PROTOCOL_INFO结构中。然而,某些传输协议可能表现出多种行为。例如SPX是基于消息的(发送者发送的消息的边界在网络上被保留了),但是接收的一方可以选择忽略这些边界并把套接口作为一个字节流来对待。这样就很合理地导致了SPX有两个不同的PROTOCOL_INFO结构条目,每一个条目对应了一种行为。
在Windows Sockets 1中仅有一个地址族(AF_INET),它包含了数量不多的一些众所周知的套接口类型和协议标识符。这在Windows Sockets 2中已经有所改变。除了现有的地址族,套接口类型和协议标识符为了兼容性原因被保留以外,Windows Sockets 2加入了许多唯一的但是可能并不为大家所知的地址族,套接口类型和协议标识符。不为大家所知并不意味着会对应用程序开发造成问题,因为一个企图做成协议无关的应用程序应该在对自身合适的基础上选择协议而不应该依赖于某个分配给它的特定的套接口类型或协议类型值。PROTOCOL_INFO结构中包含的通讯性质指明了协议的合适性(例如:基于消息的对应于基于字节流的,可靠的对应于不可靠的,等等)。基于合适性原则选取协议而不使用某个特定的协议名和套接口类型。
对于客户机/服务器模型,服务器一端的应用程序最好能够在所有合适的传输协议上建立监听套接口。这样,客户机一端的应用程序就可以通过任何合适的传输协议来与服务器一端的应用程序建立连接。这样做可以使得一个客户机应用程序易于移植。例如一台运行于LAN上的台式机的客户机应用程序在转到运行于无线网上的笔记本计算机时就不用作任何改变。
6.3.2 select()函数应用中关于多个服务提供者的限制
在Windows Sockets 2中,函数select()使用FD_SET仅能应用于和单个服务提供者相连的套接口。但是这并不限制一个应用程序使用多个服务提供者打开多个套接口。如果应用程序开发者喜欢使用非阻塞方式编程,那么可以使用WSAAsyncSelect()函数。由于该函数需要一个套接口描述字作为输入参数,那么与该套接口相连的服务提供者是很重要的。如果一个应用程序需要在一组跨越多个服务提供者的套接口上使用带有阻塞语法的函数,那么应该使用WSAWaitForMultipleEvents()函数。应用程序也可以使用WSAEventSelect()函数。该函数允许应用程序把FD_XXX网络事件和一个事件对象相连接,并且在该事件对象中处理网络事件(这一模式将在下文讨论)。
6.4 协议无关的名字解析
Windows Sockets 2包含了应用程序可以使用的多种标准化的网络名字服务。Windows Sockets 2应用程序并不需要理解与名字服务相关的许多迥异的接口(例如DNS,NIS,X.5000,SAP等等)。本书的4.2介绍了这一主题,并对API做了详细介绍。
6.5 重叠I/O和事件对象
Windows Sockets 2引入了重叠I/O的概念并且要求所有的传输协议提供者都支持这一功能。重叠I/O仅能在由WSASocket()函数打开的套接口上使用(使用WSA_FLAG_OVERLAPPED标记)。这种方式的使用将采用Win32建立的模型。
对于接收,应用程序使用WSARecv()函数或WSARecvFrom()函数来提供存放接收数据的缓冲区。如果数据在网络接收以前,应用程序已经提供了一个或多个数据缓冲区,那么接收的数据就可以立即被存放进用户缓冲区。这样可以省去使用recv()函数和recvfrom()函数时需要进行的拷贝工作。如果在应用程序提供数据缓冲区时已经有数据到来,那么接收的数据将被立即拷贝进用户缓冲区。如果数据到来时,应用程序没有提供接收缓冲区,那么网络将回到我们熟悉的同步操作方式-传送来的数据将被存放进内部缓冲区,直到应用程序发出了接收调用并且提供了接收缓冲区,这时接收的数据就被拷贝进接收缓冲区。这种做法会有一个例外:就是当应用程序使用setsockopt()函数把接收缓冲区长度置为了0。在这种情况下,对于可靠传输协议,只有在应用程序提供了接收数据缓冲区后,数据才会被接收;而对于不可靠传输协议,数据将会丢失。
对于发送的一方,应用程序使用WSASend()函数或WSASendTo()函数提供一个指向已填充的数据缓冲区的指针。应用程序不应在网络使用完该缓冲区的数据以前以任何方式破坏该缓冲区的数据。
重叠发送和接收调用会立即返回。如果返回值是0,那么表明了I/O操作已经完成,对应的完成指示也已经可以得到。如果返回值是SOCKET_ERROR,并且错误代码是WSA_IO_PENDING,那么表明重叠操作已经被成功地初始化,今后发送缓冲区被用完或者接收缓冲区被填满时,将会有完成指示。任何其他的错误代码表明了初始化没有成功,今后也不会有什么完成指示。
发送操作和接收操作都可以被重叠使用。接收函数可以被多次调用,发出接收缓冲区,准备接收到来的数据。发送函数也可以被多次调用,组成一个发送缓冲区队列。要注意的是,应用程序可以通过按顺序提供发送缓冲区来确保一系列重叠发送操作的顺序,但是对应的完成指示有可能是按照另外的顺序排列的。同样的,在接收数据的一方,缓冲区是按照被提供的顺序填充的,但是完成指示也可能按照另外的顺序排列。
WSAIoctl()函数(ioctolsocket()函数的增强版本)还可以使用重叠I/O操作的延迟完成特性。
6.5.1 事件对象
重叠I/O概念的引入需要建立一个机制使得应用程序能够正确的把发送和接收事件与今后它们完成时的指示相连接。在Windows Sockets 2中,这一点是通过事件对象实现的,它采用了Win32事件的模型。Windows Sockets事件对象是一个相当简单的结构,它可以被创建,关闭,设置,清除,等待和检查。它们的主要用处是使得应用程序能够阻塞并等待直到一个或多个事件对象被设置。
应用程序可以使用WSACreateEvent()函数来得到一个事件对象句柄,这个句柄可以作为以后的重叠发送和接收函数的输入参数(WSASend(),WSASendTo(),WSARecv(),WSARecvFrom())。事件对象在创建时被清除,在相关的重叠I/O操作完成时由传输协议提供者设置(或者成功,或者出错)。每个被WSACreateEvent()函数创建的事件对象都必须有对应的WSACloseEvent()函数释放它。
WSAEventSelect()函数把一个或多个FD_XXX网络事件与一个事件对象连接。这将在2.6中讨论。
在32位环境中,与事件对象相关的函数,包括WSACreateEvent(),WSACloseEvent(),WSASetEvent(),WSAResetEvent(),WSAWaitForMultipleEvent()和WSAGetOverlappedResult(),都被直接映射到对应的Win32函数,例如没有WSA前缀的同名函数。
6.5.2 接收操作完成指示
为了提供给应用程序适当的灵活性,Windows Sockets 2为接收操作完成指示提供了多个选项。它们包括:等待(阻塞)事件对象,检查事件对象和套接口I/O完成例程。
6.5.2.1 阻塞并且等待完成指示。
应用程序可以使用WSAWaitForMultipleEvents()函数来选择阻塞程序直到一个或多个事件对象被设置。在Win16实现中,这种方式将使用一个阻塞钩子,就象在标准的阻塞套接口操作时一样。在Win32实现中,进程或线程会被真正地阻塞。因为Windows Sockets 2事件对象被实现成Win32事件,所以Win32函数WaitForMultipleObjects()也可以使用。这在线程需要阻塞套接口和非套接口事件时将会非常有用处。
6.5.2.2 检查完成指示
应用程序如果不希望使用阻塞方式,它可以使用WSAGetOverlappedResults()函数来检查与某个特定的事件对象相连的完成状态。该函数检查重叠操作是否完成,如果完成的话,处理重叠操作的出错信息,使得该信息在WSAGetLastError()函数调用时可以得到。
6.5.2.3 使用套接口I/O操作完成例程
所有的用来初始化重叠I/O操作的函数(WSASend(),WSASentTo(),WSARecv(),WSARecvFrom())都把lpCompletionRoutine作为输入参数。这是一个应用程序定义的函数指针,在重叠I/O操作完成时可以被调用。
在Win16环境中,回调函数有可能在VMM环境(有时也被称作中断环境)下被激活。传送对时间要求较高的数据(例如视频或音频数据)在这种低延时,占先方式下接受这种指示会很方便。但是应用程序必须知道,在这种特殊情况下,只要很少一部分运行时和Windows库函数可以被调用。作为一条规则,应用程序必须把自己限制在一套Windows文挡说明的在一个多媒体定时回调函数中可以被安全调用的运行时函数库。
在Windows 95和Windows NT中,完成例程与Win32文件I/O完成例程遵循着同样的规则。在所有的环境中,传输协议允许应用程序以完成例程的方式唤起发送和接收操作,而且保证对于给定的一个套接口,I/O完成例程不会嵌套。这就允许了在一个占先的环境中进行对时间敏感的数据的传送。
6.5.3 WSAOVERLAPPED的细节
WSAOVERLAPPED结构提供了一个重叠I/O操作的初始化和它将来的如何被完成之间的通讯媒体。WSAOVERLAPPED结构被设计成与Win32中的OVERLAPPED结构兼容:
typedef struct WSAOVERLAPPED {
DWORD Internal; // reserved
DWORD InternalHigh; // reserved
DWORD Offset; // ignored
DWORD OffsetHigh; // ignored
WSAEVENT hEvent;
} WSAOVERLAPPED, LPWSAOVERLAPPED;
Internal 这一个保留的域是由重叠I/O实现的实体内部使用的。对于使用类文件方式创建套接口的传输服务提供者,这一域是被底层的操作系统使用的;对于其他的传输服务提供者(那些创建伪句柄的),可以视需要使用这个域。
InternalHigh 这一个保留的域是由重叠I/O实现的实体内部使用的。对于使用类文件方式创建套接口的传输服务提供者,这一域是被底层的操作系统使用的;对于其他的传输服务提供者(那些创建伪句柄的),可以视需要使用这个域。
Offset 由于套接口没有文件偏移量的概念,应用程序可以视需要使用这个域。
OffsetHigh 由于套接口没有文件偏移量的概念,应用程序可以视需要使用这个域。
hEvent 如果一个重叠的I/O操作在被调用时没有使用I/O操作完成例程(lpCompletionRoutine为空指针),那么这个域必须包含一个有效的WSAEVENT对象的句柄,否则(lpCompletionRoutine不为空指针),应用程序可以视需要使用这个域。
6.6 使用事件对象异步通知
为了适应一些应用程序例如精灵程序或者某些没有用户界面的服务程序(因此不使用窗口句柄),Windows Socket 2提供了WSAEventSelect()函数和WSAEnumNetworkEvents()函数。WSAEventSelect()函数和WSAAyncSelect()函数很类似,区别仅在于当一个FD_XXX网络事件发生时,WSAEventSelect()函数将导致一个应用程序指定的事件对象被设置,而WSAAyncSelect()将导致一条Windows消息被发送(例如FD_READ,FD_WRITE等等)。
此外,传输服务提供者会记住每个特定的FD_XXX网络事件的发生。应用程序可以调用WSAEnumNetworkEvents()函数把目前的网络事件记忆拷贝到应用程序提供的缓冲区中,并且自动清除网络事件记忆。如果需要,应用程序还可以把某个特定的事件对象和网络事件记忆一起清除。
6.7 服务的质量(QOS)
Windows Sockets 2中的QOS机制是从Craig Partridge在RFC 1363中描述的流规格引入的。这一概念可以大致描述如下:
流规格描述了一个网络上单向数据流的性质的集合。应用程序可以在调用WSAConnect()函数发出连接请求或者使用WSAIoctl()函数等其他QOS命令时,把一对流规格和一个套接口连接(一个规范对应了一个方向)。流规格以参数方式声明了应用程序所要求的服务的级别,并且为应用程序适应不同的网络条件提供了一套反馈机制-如果应用程序要求的服务级别不能达到,应用程序是否愿意松动它的要求。
Windows Sockets 2中QOS的使用模型如下:
对于基于连接的传输服务,应用程序可以很方便的在使用WSAConnect()函数提出连接请求时规定它所要求的服务质量(QOS)。要注意的是:如果应用程序在调用WSAConnect()时QOS参数不为空,那么对于基于连接的套接口,任何预先设置的QOS都会被覆盖。如果WSAConnect()函数成功返回,应用程序就会知道它所要求的QOS已经被网络接受,那么应用程序就可以随意的使用这个套接口进行数据交换。如果连接操作由于资源有限而失败,应用程序应该适当地降低它所要求的服务质量或者干脆就放弃操作。
在每次连接企图之后(不论成功与否),传输服务提供者都会更新flow_spec结构,以便尽可能地指明目前的网络条件。如果应用程序所要求的服务质量仅仅包含了一些传输服务提供者必须满足的缺省值,那么这种更新会是很有用处的。应用程序可以利用这些关于当前网络条件的信息来指导自己使用网络,例如今后的QOS要求。然而应用程序应该注意的是,传输服务提供者在不断更新的flow_spec结构中提供的信息仅仅是一个参考,它们只不过是粗略的估计。应用程序应该很小心的解释这些数据。
无连接的套接口也可以使用WSAConnect()函数为一个指定的通讯规定特定的QOS级别。WSAIoctl()函数也可以用来规定初始的QOS要求,或者用来今后的QOS协商
