TDI思维(1)
作者:陆麟
转载请征得作者同意.
2003.7.9
我试图解释TDI接口. 这套接口是我所接触的接口中十分离奇的接口. 网络数据传送可用的实现方法有比她简洁的, 但是微软却创建了TDI接口.
我迷惑. 只能用自己的理解来诠释这套接口. 我要揣摩创建接口的人是怎么想的, 为什么要如此定义.
TDI是WINNT网络应用和网络驱动的接口. 这套接口不同于普通的BSD SOCKET接口.
TDI有2部分. 一部分是SERVER, 另一部分是CLIENT. 在微软的文档中并没有这样的划分. 我为了清晰起见, 从逻辑上加了一层.
TDI的SERVER对下层的传输设备负责使用NDIS接口. 用NDIS提供的能力来传送数据, TDI SERVER对上层客户实现的是传输协议和CLIENT的接口,
而且是TDI规格的接口. 例如, IP, TCP, UDP提供给CLIENT的接口. 有时候比较难区分NDIS PROTOCOL DRIVER和TDI
DRIVER的功能. 但是, TDI的特性是暴露出TDI接口, 而不是NDIS接口, 并且,TDI是位于NDIS最上层. 虽然目前绝大部分的TDI
SERVER本身就是NDIS PROTOCOL DRIVER. 但是, 微软的文档中没有规定一定要这么做.
TDI SERVER的本意是提供给USER MODE的APP一个用CREATEFILE/READFILE/WRITEFILE/DEVICEIOCONTROL的接口可以访问.
很少有人直接使用CREATEFILE/READFILE/WRITEFILE/DEVICEIOCONTROL来使用TDI接口, 首先因为很少有人知道怎么去做.
:) 其次大家有WINSOCK, 干什么要自己去TRY那些NATIVE接口呢?
TDI的SERVER在现实中被用于模拟各种接口. 比如, APPLICATION使用的是BSD SOCKET接口.TCPIP TDI SERVER有一个USER
MODE的CLIENT, 模拟了BSD SOCKET的动作. 这样, APPLICATOIN可用大家已经习惯了的BSD SOCKET来工作, 而不需要学习一套新的网络API.
但是, 既然TDI是一套独立的接口, 自然有此接口本身的一些特殊属性. BSD SOCKET接口无法完全展现此接口的能力, 那只有2种方法, 增加BSD
SOCKET库的接口, 或者增加新的KERNEL MODE CLIENT, 同时提供配套的USER MODE库. 这2个方法WINNT都用到了. WINSOCK本身比BSD
SOCKET接口多出了很多函数, 而NETBIOS接口则是由NETBOIS的KERNEL MODE TDI CLIENT配合Netapi32.DLL来实现.
另外, 有个很重要的特性就是, BSD接口和其他接口, 都是由一套单向的接口. 只由下层的驱动保留一定时间的数据,
等上层的应用来取. 上层调用很多操作接口, 但是驱动无法为上层做点什么, 比如数据道路的通知. TDI对这点提供了增强. TDI能够为上层应用提供各种信号,
包括连接,数据到来,发送缓冲区空等. 这是窗口绑定SOCKET所依赖的机制. 也是一个很强大的机制.
为了能在网络上表示2个节点, 协议双方必须统一使用命名方式, 并且分配不同的ID来区分. 传输本身有下到上分了好多层, 学过网络的都知道. 我们仍然回到TDI的逻辑.
TDI希望支持N种传送方法和地址表达方法. 确实如此, TDI可以用来使2个红外线设备通信, 可以用来使2个TCPIP节点连接, 也可以使2个NETBIOS节点连接.
因此, TDI使用了
typedef UNALIGNED struct _TA_ADDRESS {
USHORT AddressLength; // length in bytes of Address[] in this
USHORT AddressType; // type of this address
UCHAR Address[1]; // actually AddressLength bytes long
} TA_ADDRESS, *PTA_ADDRESS;
TA_ADDRESS的表达方法来表示地址. 但是, TA_ADDRESS只是个占位符号. 通常真正的地址, 根据不同的TDI SERVER有不同的实际表达方法.
例如:
//
// NETBIOS Extended address
//
typedef struct _TDI_ADDRESS_NETBIOS_EX {
UCHAR EndpointName[16]; // the called name to be used
in NETBT session setup
TDI_ADDRESS_NETBIOS NetbiosAddress;
} TDI_ADDRESS_NETBIOS_EX, *PTDI_ADDRESS_NETBIOS_EX;
//
// Xns address for UB
//
typedef struct _TDI_ADDRESS_NETONE {
USHORT NetoneNameType;
UCHAR NetoneName[20];
} TDI_ADDRESS_NETONE, *PTDI_ADDRESS_NETONE;
但是, 一个网络对象可以有N个地址啊. 一个网卡当然可以同时使用IPX协议和TCP/IP协议. 这样, 就有2个不同的地址. 即使只使用TCP/IP协议,
我们可以绑定N个IP地址到同一张网卡啊. 因此, TDI SERVER用TRANSPORT_ADDRESS来表示N个可以绑定到同一对象的地址.
typedef struct _TRANSPORT_ADDRESS {
LONG TAAddressCount; // number of addresses following
TA_ADDRESS Address[1]; // actually TAAddressCount elements
long
} TRANSPORT_ADDRESS, *PTRANSPORT_ADDRESS;
这样, 在使用过程中, 不同的TDI SERVER只要识别自己的地址就可以了, 当用户想在一个操作中处理多个地址, 同样可以解决. 当然, 在实际实现中,
TDI CLIENT会选择正确的地址类型发送到对应的TDI SERVER. 比如, 不会把IPX地址扔给IP的TDI SERVER.
WOW, 算了. 看来这会是长篇. 今天不写了. 以后继续.