spi

spi

随着WOSA模型的出现,在Ws2_32.dll和协议堆栈之间多了一层开放的接口,就是SPI。Winsock2 SPI和Winsock2 API在性质上是一样的,只是他们的服务对象不同,API提供的接口工作在应用层的上层,为应用程序提供接口,在Winsock之上,而SPI提供的接口工作在应用层的最底层,为核心的网络服务提供接口,在Winsock之下。如果按照OSI分层标准来划分,SPI应该是工作在会话层,API工作在应用层。如果有人非要象我一开始这样,非要分出个一二三四的话,这样的理解或许能让你得到一点满足,当然这种理解不是完全正确的,但是至少在层次的先后上,这样的理解应该是正确的。我们的工作逐渐进入了OSI的下层,要学习,就要多费一些口舌了。

SPI的上沿是Ws2_32.dll,而它的下沿是协议堆栈和名字空间,当然分层服务可以多层叠放,扩展上下沿。这里讲述向协议堆栈的服务。SPI包含两种服务,一种是基础服务,一种是分层服务。

SPI实例实际上就是一个动态库,开放了一个初始化函数:WSPStartup 或者 NSPStartup。其他函数的指针通过一个函数分配表在此初始化函数中指定给Ws2_32.dll。Ws2_32.dll在需要的时候将相应的服务/协议实现模块装载入内存,在不需要的时候卸载。

一般来说,当一个应用程序调用 Winsock2 API 函数的时候,Winsock2 都会调用相应的 Winsock2 SPI 函数。如 WSPAccept, WSPConnect, etc. 下述特例不经过SPI:

htonl,htons,ntohs,ntohl,inet_addr,inet_ntoa,gethostname,getXbyY,WSAAsnyGetXByY,wsacANCELaSYNCrEQUEST,WSAEnumProtocols,WSAIsBlocking,WSASetBlocking,WSAUnhookBlocking,WSAGetLastError,WSASetLastError,WSACreateEvent,WSACloseEvent,WSASetEvent,WSAResetEvent,WSAWaitForMultipleEvents.SPI的函数原型定义在ws2spi.h中,前缀都有WSP,NSP,WPU,WSC,有兴趣可以浏览一下。相应的对应函数有30个。都会在WSPStartup的实现中得到这些函数指针。

要注意的是:WSPStartup并不是在WSAStartup调用时被调用的,一旦要WSPStartup的时候也就是说明需要用到服务提供者了,那么Ws2_32什么时候加载服务提供者呢?应用程序创建了套接字的时候,Ws2_32就根据套接字的地址家族,类型,协议信息等加载相应的提供者,这时候,WSPStartup就会被调用,服务提供者就开始调度它的传输函数等等内容了。

现在有个问题,既然我们知道Windows Sockets动态库和协议堆栈之间没有直接联系了,那么,如果我的应用程序想要调用我的SPI实例的扩展函数的时候那该怎么办呢?Ws2_32.dll不可能再扩展专门的函数调用,让他按照规范再去调用协议堆栈提供者的扩展函数。其实,Ws2_32.dll提供了一个函数来解决这个额外的扩展问题,那就是WSAIoctl函数,通过命令码 SIO_GET_EXTENSION_FUNCTION_POINTER,输入缓冲区是扩展函数的标识符,输出参数就是该函数的指针了。那么,应用程序就可以直接跳过Ws2_32.dll而直接调用扩展函数。

在动手编写SPI实例之前,我们需要了解一下基础服务和分层服务的区别以及系统如何标志这两种服务,然后讲述如何在系统中安装一个自己的SPI实例,来扩展你的网络传输功能。以及如何卸载自己的SPI实例,来恢复系统原来默认的设置。最后再来讲述如何编写SPI的实例。

基础服务执行核心的网络传输协议功能,而分层服务在基础服务的基础上执行自定义的通讯控制,所有的真正的数据交换是通过基础服务提供者来实现的,分层服务提供者无需再去实现网络协议的功能。简单的网络封包的截取和管理可以在此进行。

需要提及套接字句柄。当调用下层SPI的时候,如果调用WSPSocket,WSPAccept,WSPJoinLeaf时,服务提供者必须返回一个套接字句柄,Winsock2允许在此句柄上直接调用 ReadFile/WriteFile来读写数据。这个句柄有两种类型:IFS(可安装文件系统句柄)和 Non IFS(不可安装的文件系统句柄)。微软的基础服务提供者都是IFS句柄,但是分层服务提供者可以是IFS,也可以是Non IFS。但是如果分层提供者是IFS提供者,就必须将下一级IFS提供者的句柄上传到上一级提供者或者WS2_32.dll,此时对套接字的ReadFile/WriteFile调用会跳过分层服务提供者的WSPSend/WSPRecv函数而直接通过基础服务提供者的功能进行数据读写,而且分层服务提供者将不能处理一个完成端口的重叠I/0操作,这些操作也将绕过分层提供者,而直接通过基础提供者完成数据的读写。所以,最好将分层服务提供者定义为Non IFS句柄的服务提供者,这样就可以对网络数据进行完整的监控。具体做法是在协议信息结构中将dwServiceFlags1标志的XP1_IFS_HANDLES标志去掉。这样,你的分层提供这就是一个Non IFS分层服务提供者,Non IFS服务提供者使用WSPCreateSocketHandle上调函数建立套接字句柄,Winsock2会把ReadFile/WriteFile的调用重定向到WSPSend和WSPReceive上去,但这无疑会对系统性能产生负面影响。

Windows系统为服务提供者维护了一个目录,这些信息保存在注册表中,要更改/安装服务提供者,就必须对此目录信息进行维护。系统提供了一些函数简化对此信息的访问,他们都是以WSC开头。

对于基础服务提供者的安装相当简单,只要准备一个WSAPROTOCOL_INFOW结构,用来代表基础服务提供者信息,正确填充合适的值,然后调用WSCInstallProvider函数就可以完成基础服务提供者的安装。但是这种方法只对新增加的基础服务提供者来讲是很方便的,但是我们往往要利用系统的基础服务提供者来实现基本的协议,比如说TCP协议的实现,所以在这种情况下,方便的方法是不通过WSCInstallProvider函数,而是我们自己修改目录条目信息,对于基础提供者,只要把基础提供者的动态库路径信息改成我们自己虚拟的基础服务提供者路径就可以了。这样安装的后续工作必须是把所有调用传给原来的基础服务提供者,所以在我们虚拟的服务提供者程序中,必须能够检索到原来的服务提供者路径信息。记住,安装之前千万要备份原来的目录信息。否则,一旦发生错误会引起网络无法访问。

你可能感兴趣的:(spi)