本书第一部分讲述的是传统的网络接口N e t B I O S、重定向器以及通过重定向器进行的各类
网络通信。尽管本书大部分内容均围绕Wi n s o c k编程这一主题展开,但是, A P I比起Wi n s o c k
来,仍然具有某些独到之处。其中,第1章探讨的是N e t B I O S接口,它和Wi n s o c k类似,也是
一种与协议无关的网络A P I。N e t B I O S提供了异步调用,同时兼容于较老的操作系统,如O S / 2
和D O S等等。第2章讨论了重定向器的问题,它是接下去的两个新主题—邮槽(第3章)和
命名管道(第4章)的基础。重定向器提供了与传输无关的文件输入/输出方式。邮槽是一种
简单的接口,可在Wi n d o w s机器之间实现广播和单向数据通信。最后,命名管道可建立一种
双向信道,这种信道提供了对Wi n d o w s安全通信的支持。
第1章NetBIOS
“网络基本输入/输出系统”(Network Basic Input/Output System, NetBIOS)是一种标准
的应用程序编程接口( A P I),1 9 8 3年由S y t e k公司专为I B M开发成功。N e t B I O S为网络通信定
义了一种编程接口,但却没有详细定义物理性的“帧”如何在网上传输。1 9 8 5年,I B M创制
了N e t B I O S扩展用户接口(NetBIOS Extended User Interface, NetBEUI),它同N e t B I O S接口集
成在一起,终于构成了一套完整的协议。由于N e t B I O S接口变得愈来愈流行,所以各大厂商
也开始在其他如T C P / I P和I P X / S P X的协议上实施N e t B I O S编程接口。到目前为止,全球已有许
多平台和应用程序需要依赖于N e t B I O S,其中包括Windows NT、Windows 2000、Windows 95
和Windows 98的许多组件。
注意Windows CE并不支持NetBIOS API,只是用TCP/IP作为其传送协议,并同时支持
NetBIOS的名字与名字解析。
Win32 NetBIOS接口向后兼容于早期的应用程序。本章要讨论的是N e t B I O S编程基础。首
先向大家介绍的是N e t B I O S的一些基本知识,从N e t B I O S的名字及L A N A编号开始,接着,我
们围绕N e t B I O S提供的基本服务展开讨论,比如面向会话和“无连接”通信等等。在每一节,
都展示了一个简单的客户机和服务器示例。在本章最后,我们陈列了程序员需留意的一系列
陷阱以及易犯的错误。在本书的附录A中,大家可找到一份命令索引,其中对每个N e t B I O S命
令都进行了总结,包括必要的参数,以及对其行为的简单说明。
OSI 网络模型
“开放系统互连”(O S I)模型从一个很高的层次对网络系统进行了描述。O S I模型总
共包含了七层。从最顶部的“应用层”开始,一直到最底部的“物理层”,这七个层完整
阐述了最基本的网络概念。图1 - 1展示的正是O S I模型的样子。
第一部分传统网络API
图1-1 OSI网络模型
对应O S I模型,N e t B I O S主要在会话和传输层发挥作用。
1.1 Microsoft NetBIOS
如前所述,NetBIOS API实施方案适用于为数众多的网络协议,使得编程接口“与协议无
关”。换言之,假如根据N e t B I O S规范设计了一个应用程序,它就能在T C P / I P、N e t B I O S甚至
I P X / S P X上运行。这是一项非常有用的特性,因为对一个设计得当的N e t B I O S应用程序来说,
它几乎能在任何机器上运行,无论机器连接的物理网络是什么。然而,我们也必须留意几个
方面的问题。要想使两个N e t B I O S应用(程序)通过网络进行正常通信,那么对它们各自运
行的机器来说,至少必须安装一种两者通用的协议。举个例子来说,假定小张的机器只安装
了T C P / I P,而小马的机器只安装了N e t B E U I,那么对小张机器上的N e t B I O S应用来说,便无法
同小马机器上的应用进行通信。
除此以外,只有部分协议实施了N e t B I O S接口。Microsoft TCP/IP和N e t B E U I在默认情况
下已提供了一个N e t B I O S接口;然而, I P X / S P X却并非如此。为此,微软专门提供了一个
I P X / S P X版本,在其中实现了该接口。在设计网络时,这个问题必须注意。安装协议时,具
有N e t B I O S能力的I P X / S P X协议通常会自动提醒你注意这方面的问题。例如, Windows 2000
提供的协议本身就叫作“ NWLink IPX/SPX/NetBIOS兼容传送协议”。而在Windows 95和
Windows 98中,请留意I P X / S P X协议属性对话框,其中有一个特殊的复选框,名为“希望在
I P X / S P X上启用N e t B I O S”。
另外要注意的一个重要问题是N e t B E U I并非是一种“可路由”协议。假定在客户机和服
务器之间存在一个路由器,那么这种协议在两部机器上的应用便无法沟通。收到数据包后,
路由器便会将其“无情地”地抛弃。T C P / I P和I P X / S P X则不同,它们均属“可路由”协议,
不会出现这方面的问题。要注意的是,假如你需要在很大程度上依靠N e t B I O S,那么在配置
网络时,至少应安装一种可路由的传送协议。要想深入了解各种协议的特征以及相应的注意
事项,请参阅第6章。
1.1.1 LANA编号
从编程角度思考,大家或许会觉得奇怪,传送协议与N e t B I O S如何对应起来呢?答案便在
于L A N适配器( LAN adapter, LANA )编号,它是我们理解N e t B I O S的关键。在最初的
N e t B I O S实施方案中,每张物理网卡都会分配到一个独一无二的值:即L A N A编号。但到
Wi n 3 2下,这种做法便显得有些问题。因为对一个工作站来说,它完全可能同时安装了多种网
络协议,也可能安装了多张网卡。
2计计第一部分附传统网络API
下载
应用层
层描述
为用户提供相应的界面,以便使用提供的连网功能
完成数据的格式化
控制两个主机间的通信链路(开放、操作和关闭)
提供数据传输服务(可靠或不可靠)
在两个主机之间提供一套定址/寻址机制,同时负责数据包的路由选择
控制两个主机间的物理通信链路:同时还要负责对数据进行整形,以便在物理媒体上传输
物理媒体负责以一系列电子信号的形式,传出数据
表示层
会话层
传输层
网络层
数据链路层
物理层
每个L A N A编号对应于网卡及传输协议的唯一组合。例如,假定某工作站安装了两张网卡,
以及两种具有N e t B I O S能力的传输协议(如T C P / I P和N e t B E U I),那么总共就有四个L A N A编
号。下面是一种对应关系的例子:
0 . T C P / I P—网卡1
1 . N e t B E U I—网卡1
2 . T C P / I P—网卡2
3 . N e t B E U I—网卡2
通常,L A N A编号的范围在0到9之间,除LANA 0之外,操作系统并不按某种固定的顺序来分
配这些编号。那么,LANA 0有什么特殊含义呢?LANA 0代表的是“默认”L A N A!N e t B I O S问
世早期,许多应用都采用硬编码的形式,只依赖LANA 0进行工作。在那时,大多数操作系统也
只支持一个L A N A编号。考虑到向后兼容的目的,我们可将LANA 0人工分配给一种特定的协议。
在Windows 95和Windows 98中,通过选择控制面板中的“网络”图标,可访问一种网络
协议的“属性”对话框。在“网络”对话框中选择“配置”选项卡,再从网络组件列表中选
择一种网络协议,按下“属性”按钮即可。对具有N e t B I O S能力的每一种协议来说,其属性
对话框的“高级”选项卡都有一个“设成默认的通信协议”复选框。若选中这个复选框,会
重新安排协议的绑定,使默认协议能够分配到LANA 0。注意在任何时候,只能有一种协议才
能选中这个复选框。由于Windows 95和Windows 98具有所谓的“即插即用”功能,所以我们
没有其他办法可对协议的编号顺序进行更改。
Windows NT 4则允许用户在设置N e t B I O S时拥有更大的灵活性。在“网络”对话框的
“服务”选项卡中,可从“网络服务”列表框内选择N e t B I O S接口,然后点按“属性”按钮。
随后便会出现“ N e t B I O S配置”对话框,在这里可针对每一对网卡/传输协议的组合,分配
各自的L A N A编号。在这个对话框中,每张网卡都以其驱动程序的名字加以标识;但协议名
称却显得有些暧昧。在图1 - 2中,我们展示了N e t B I O S配置对话框的样子。单击其中的“ E d i t”
(编辑)按钮,便可为每种协议单独分配L A N A编号。Windows 2000也允许我们单独分配
L A N A编号。在控制面板中,双击“网络和拨号连接”图标。随后,从“高级”菜单中选择
“高级设置”,然后在高级设置对话框中选择“ L A N A编号”选项卡。
图1-2 NetBIOS配置对话框。这是一部多宿主机器,安装了两张网卡和三种传输协议:
TCP/IP(NetBT)、NetBEUI(Nbf)以及IPX/SPX(NwlnkNb)
第1章计NetBIOS计计3
下载
要想设计出一个“健壮”的N e t B I O S应用,必然需要让自己的代码能对任意L A N A编号上
的连接进行控制。例如,假定小马编写了一个N e t B I O S服务器应用,对LANA 2上的客户机进
行监听。在小马的机器(即服务器)上, LANA 2正好对应于T C P / I P。后来,小张需要编写一
个客户端应用,同小马的服务器通信,所以他决定让自己的程序通过工作站的LANA 2建立连
接。然而,小张工作站上的LANA 2对应于N e t B E U I。这样一来,两个应用相互间均无法通信
—尽管两者都安装了T C P / I P和N e t B E U I。为纠正协议的这种差异,小马的服务器应用程序
必须对小马工作站上每个可能的L A N A编号上的客户机连接进行“监听”。类似地,小张的客
户机应用程序需要针对本机每个可能的L A N A编号,尝试在其上面的连接。只有这样,小马
和小张才能保证自己的应用尽最大可能成功通信。当然,尽管我们需要在代码中对任何
L A N A编号上的连接进行控制,但并不表示能够百分之百地成功。假如两台机器根本就没有
安装一种共通的协议,那么无论如何都是不能成功的!
1.1.2 NetBIOS名字
现在,我们知道了L A N A编号是什么,接着再来讨论N e t B I O S名字(名称)的问题。对一
个进程(或“应用”、“应用程序”)来说,它会注册自己希望与其通信的每个L A N A编号。一
个N e t B I O S名字长度为1 6个字符,其中第1 6个字符是为特殊用途保留的。在名字表内添加一
个名字时,应将名字缓冲区初始化成空白。在Wi n 3 2环境中,针对每个可用的L A N A编号,每
个进程都会为其维持一张N e t B I O S名字表。若为LANA 0增添一个名字,意味着你的应用程序
只能在LANA 0上同客户机建立连接。对每个L A N A来说,能够添加的名字的最大数量是2 5 4,
编号从1到2 5 4(0和2 5 5由系统保留)。然而,每种操作系统都设置了一个低于2 5 4的最大默认
值。重设每个L A N A编号时,我们可对此默认值进行修改。
另外,N e t B I O S名字共有两种类型:唯一名字和组名。“唯一名字”意味着它是独一无二
的:网络上不能再有其他任何进程来注册这个名字。如果一台机器已注册了某名字,那么在你
注册该名字时,便会收到一条“重复名字”出错提示。大家或许已经知道,微软网络中的机器
名采用的便是N e t B I O S名字。机器启动时,会将自己的名字注册到本地的“ Wi n d o w s互联网命
名服务器”(W I N S)。如果事前已有另一台机器注册了同样的名字, W I N S服务器便会报错。
W I N S服务器维护着已注册的所有N e t B I O S名字的一个列表。除此以外,随名字一道,还可保
存协议特有的一些信息。比如在T C P / I P网络中, W I N S同时维护着N e t B I O S名字以及注册那个
名字的I P地址(亦即相应的机器)。假如配置网络时未为其分配一个W I N S服务器,那么如何检
查名字是否重复呢?这时便要采用在整个网络内“发广播”的形式。当一名发送者向全网络发
出一条特殊的广播消息时,如果没有其他机器回应这条消息,便允许发送者使用该名字。
而在另一方面,“组名”的作用是将数据同时发给多个接收者;或者相反,接收发给多个
接收者的数据。组名并非一定要“独一无二”,它主要用于多播(多点发送)数据通信。
在N e t B I O S名字中,第1 6个字符用于区分不同的微软网络服务。各种网络服务和组名需
要用一个W I N S服务器完成注册。要么由配置了W I N S功能的计算机进行名字的直接注册,要
么由那些尚未配置W I N S功能的计算机,通过在本地子网内进行广播注册。N b t s t a t命令是一个
非常有用的工具,可用它获取与本地(或远程)计算机上注册的N e t B I O S名字有关的信息。
在表1 - 1展示的例子中, N b t s t a t - n命令可针对用户“ D a v e m a c”,生成这个已注册的N e t B I O S名
字的列表。D a v e m a c登录进入的那部计算机已被配置成一个主域控制器,而且运行的是
4计计第一部分附传统网络API
下载
Windows NT Server操作系统,且已安装了I n t e r n e t信息服务器(I I S)。
表1-1 NetBIOS名字表
名字第1 6个字节名字类型服务
D AV E M A C 1 < 0 0 > 唯一工作站服务名
D AV E M A C 1 < 2 0 > 唯一服务器服务名
D AV E M A C D < 0 0 > 成组域名
D AV E M A C D < 1 C > 成组域控制器名
D AV E M A C D < 1 B > 唯一主控浏览器名
D AV E M A C 1 < 0 3 > 唯一发信者名
I n e t ~ S e r v i c e s < 1 C > 成组I n t e r n e t信息服务器组名
I S ~ D AV E M A C 1 < 0 0 > 唯一I n t e r n e t信息服务器唯一名
D AV E M A C 1 + + + + + + + < B F > 唯一网络监视器名字
只有在安装了T C P / I P协议的前提下,才会安装N b t s t a t命令。该工具亦可用来查询远程机
器的名字表,方法是在远程机器的名字后面,接上一个- a参数;或在远程机器的I P地址后,接
上一个- A参数。
在表1 - 2中,我们总结了各种不同的M i c r o s o f t网络服务为唯一N e t B I O S计算机名追加的默
认第1 6个字节值。
表1-2 唯一名字标识符
第1 6个字节含义
< 0 0 > 工作站服务名。通常,它对应于N e t B I O S计算机名
< 0 3 > 收发消息时采用的信使服务名。W I N S服务器会将这个名字注册成W I N S客户机上
的信使服务,并通常追加到计算机名后面,以及当前登录到计算机的用户名的后面
< 1 B > 域主控浏览器名。这个名字用于标识主域控制器,并指出用什么客户机和其他浏
览器同域主控浏览器取得联系
< 0 6 > 远程访问服务(R A S)服务器服务
< 1 F > 网络动态数据交换( N e t D D E)服务
< 2 0 > 用于为文件共享提供“共享点”的服务器服务名
< 2 1 > R A S客户机
< B E > 网络监视器代理
< B F > 网络监视器工具
表1 - 3则列出了在常用的一系列N e t B I O S组名后,追加的默认第1 6个字节字符。
如此多的标识符很易使人产生混淆,很难真正记住。所以,请考虑把它作为一个“速查
表”或“索引”使用。大家或许不应在自己的N e t B I O S名字中使用它们。为防止偶然同你的
N e t B I O S名字发生冲突,最好避免使用唯一名字标识符。对于组名,恐怕更要引起高度注意
—假如你的名字同一个已有的组名相同,那么不会产生任何错误提示。若发生这种情况,
结果就是会收到原本发给其他人的数据。
表1-3 组名标识符
第1 6个字节含义
< 1 C > 一个域组名,在这个组内包含了已注册域名的一系列计算机的特定地址。由域控
制器来注册这个名字。W I N S将它当作一个域组看待:组内每个成员必须单独更新
自己的名字。域组最多只能包容2 5个名字。若复制的一个静态1 C名字同另一个
W I N S服务器上的某个动态1 C名字发生冲突,便会增加成员的一个“联合”,同时将
记录标定为“静态”。假如记录是静态的,组内成员便不必定时刷新自己的I P地址
第1章计NetBIOS计计5
下载
(续)
第1 6个字节含义
< 1 D > 指定一个主控浏览器的名字,客户机通过它访问主控浏览器。在一个子网上,只
能有一个主控浏览器。W I N S服务器会对域名注册作出“正”(肯定)响应,但却
不会将域名保存在自己的数据库中。假如一台计算机向W I N S服务器送出一个域名
查询,则W I N S服务器会返回一个“负”(否定)响应。若送出域名查询的那台计
算机已被配置成h节点或m节点,便会随之广播那个查询,以解析出正确的名字。
客户机解析名字的方法是由节点的类型决定的。如客户机配置成b节点解析,便会
送出广播包,以便广告并解析出N e t B I O S名字。p节点解析采用与W I N S服务器的点
到点通信方式。而m节点属于b及p节点的一种混合形式:首先使用的是b节点;如
有必要,再接着使用p节点。最后一种解析方式是h节点,亦称“混合模式”。它无
论如何都会先尝试使用p节点注册和解析,然后只有在解析失败的前提下,才会换
用b节点。Wi n d o w s操作系统默认为h节点
< 1 E > 一个普通组名。浏览器可向这个名字发送广播数据,并通过对它的监听来挑选一
个主控浏览器。这些广播面向的是本地子网,绝对不应通过路由器传输
< 2 0 > 一个I n t e r n e t组名。这种类型的名字由W I N S服务器进行注册,以便为了管理方面
的目的来标定特定的计算机组。例如,“p r i n t e r s g”可以是一个注册的组名,用于
标定由打印服务器构成的一个管理性组
_ M S B R O W S E 不再是单独一个追加的第1 6位字符,“_ M S B R O W S E ”需要追加到一个域名后面,
并在本地子网上进行广播,向其他主控浏览器通告这个新增的域
1.1.3 NetBIOS特性
N e t B I O S同时提供了“面向连接”服务以及“无连接”服务。面向连接的服务,是指它允
许两个客户机相互间建立一个会话,或者说建立一个“虚拟回路”。这种“会话”实际是一种
双向的通信数据流,通信的每一方都可向另一方发送消息。面向连接的服务可担保在两个端
点之间,任何数据都能准确无误地传送。在这种服务中,服务器通常将自己注册到一个已知
的名字下。客户机会搜寻这个名字,以便建立与服务器的通信。就拿N e t B I O S的情况来说,
服务器进程会针对想通过它建立通信的每一个L A N A编号,将自己的名字加入与其对应的名
字表。而对位于其他机器上的客户来说,就可将一个服务名解析成机器名,然后要求同服务
器进程建立连接。大家可以看到,为建立这种虚拟回路,必须采取一些适当的步骤。而且在
初次建立连接的时候,还会牵涉到一些额外的开销。“面向连接”或“面向会话”的通信可保
证通信具有极高的可靠性,而且数据包的收发顺序亦能确保正确无误。然而,它仍然是一种
“以消息为基础”的服务。也就是说,假如已连接好的某个客户机执行一个“读”命令,那么
服务器在流中仍然只会返回一个数据包—尽管客户机此时提供了一个足够大的缓冲区,可
同时容下几个包!
“无连接”或数据报服务中,服务器并不将自己注册到一个特定的名下,而只是由客户机
收集数据,然后将其送入网络,事前不必先建好任何连接(即无连接)。对于数据的目的地址,
客户机会将其定义成服务器相应进程对应的N e t B I O S名字。这种类型的服务不提供任何保障,
但同面向连接的服务相比,却可有更好的性能,如在使用数据报服务(无连接服务)时,省
下了建立连接所需的开销。例如,客户机可能向服务器兴冲冲地一下子发出数千字节的数据,
但那台服务器早在一两天前便已当机了。除非依赖自服务器传来的响应,否则客户机永远都
收不到任何错误提示(在这种情况下,假如在一个特定的时间段内,没有收到任何响应,便
6计计第一部分附传统网络API
下载
可认为服务器出了故障)。数据报服务既不能保证数据传输的可靠性,也不能保证数据包的传
送顺序正确无误。
1.2 NetBIOS编程基础
现在,我们已理解了N e t B I O S的一些基本概念,接下来要讨论的是NetBIOS API的设置,
这其实非常简单,因为只有一个函数:
UCHAR Netbios(PNCB pNCB);
用于N e t B I O S的所有函数声明、常数等等均是在头文件N b 3 0 . h内定义的。若想连接
N e t B I O S应用,唯一需要的库是N e t a p i 3 2 . l i b。该函数最重要的特征便是p N C B这个参数,它对
应于指向某个网络控制块( N C B)的一个指针。在那个N C B结构中,包含了为执行一个
N e t B I O S命令,相应的N e t b i o s函数需要用到的全部信息。该结构的定义如下:
注意,并不是在对N e t B I O S的每次调用中都需要用到该结构内的全部成员;有些数据字段对
应的是输出参数(换言之,自N e t b i o s调用返回之后才能设置)。在此提醒大家重要的一点:进行
任何N e t b i o s调用之前,不要一开始就填写结构内的各个成员,而应先将这个N C B结构清零!请
看看表1 - 4的总结,其中解释了每个字段的用法。此外,本书附录A的命令索引对每个N e t B I O S
命令都进行了详尽总结,并解释了它需要用到NCB结构中的哪些字段,以及哪些字段可选。
表1-4 NCB结构成员
字段定义
n c b c o m m a n d 指定要执行的N e t B I O S命令。许多命令都可同步或异步与A S Y N C H ( 0 X 8 0 )标志以
及命令进行按位O R(或)运算
n c b r e t c o d e f 指定操作的返回代码。在一个异步操作进行期间,函数会将该值设为
N R C P E N D I N G
n c b l s n 对应一个本地会话编号,与当前环境内的一次会话有着唯一对应的关系。成功执
行了一次N C B C A L L或N C B L I S T E N命令后,函数会返回一个新的会话编号
n c b n u m 指定本地名字的编号。伴随N C B A D D N A M E或N C B A D D G R N A M E命令的每一次
调用,都会返回一个新编号。针对所有数据报命令,都必须使用一个有效的编号
n c b b u ff e r 指向数据缓冲区。对那些需要发送数据的命令,该缓冲区包含了要送出的实际数
据;而对那些需要接收数据的命令,则包含了要从N e t b i o s函数返回的数据。对其
第1章计NetBIOS计计7
下载
(续)
字段定义
他命令来说,如N C B E N U M,缓冲区便是预定义的结构L A N A E N U M
n c b l e n g t h 以字节数为单位,指定缓冲区的长度。对于接收命令来说, N e t b i o s会将该值设为
收到的字节数。若指定的缓冲区不够大, N e t b i o s就会返回N R C B U F L E N错误
n c b c a l l n a m e 指定远程应用的名字
n c b n a m e 指定应用程序已知的名字
n c b r t o 设定接收操作的超时期限。该值应设为5 0 0毫秒的一个整数倍数。若为1,表示没
有超时限制。该值是为N C B C A L L和N C B L I S T E N命令设置的,它们会影响后续的
N C B R E C V命令
n c b s t o 设定发送操作的超时期限。该值应设为5 0 0毫秒的一个整数倍数。若为1,表示不
存在超时限制。该值是为N C B C A L L和N C B L I S T E N命令设置的,它们会影响后续
的N C B S E N D和N C B C H A I N S E N D命令
n c b p o s t 指定异步命令完成后需要调用的后例程的地址。函数定义为:
void CALLBACK PostRoutine(PNCB pncb);
其中,p n c b指向已完成命令的网络控制块
n c b l a n a n u m 指定要在上面执行命令的L A N A编号
n c b c m d c p l 指定操作的返回代码。异步操作进行期间,Netbios会将这个值设为NRC_PENDING
n c b r e s e r v e 保留;必须为0
n c b e v e n t 指定设置为“未传信”(N o n s i g n a l e d)状态的一个Wi n d o w s事件对象的句柄。完
成一个异步命令后,事件便会设置成它的“传信”(S i g n a l e d)状态。只应使用人
工重设事件。假若n c b c o m m a n d未设置A S Y N C H标志,或者n c b p o s t不为0,那么
该字段必须为0。否则, N e t b i o s会返回N R C I L L C M D错误
同步与异步
调用N e t b i o s函数时,可选择进行同步调用,还是进行异步调用。所有N e t B I O S命令本身
均是同步的。换言之,完成命令以前,会一直调用N e t b i o s块。而对一个N C B L I S T E N命令来
说,当有一个客户机建立了连接,或发生某种类型的错误时,对N e t b i o s的调用才会返回。要
想异步调用一个命令,需要让N e t B I O S命令同A S Y N C H标志进行一次逻辑O R(或)运算。如
指定了A S Y N C H标志,那么必须在n c b p o s t字段中指定一个后例程(Post Routine),或必须在n c b e v e n t字段中指定一个事件句柄。执行一个异步命令时,从N e t b i o s返回的值是N R C G O O D R E T
( 0 x 0 0 ),但n c b c m d c p l t字段会设为N R C P E N D I N G ( 0 x F F )。除此以外, N e t b i o s函数还会将
N C B结构的n c b c m d c p l t字段设为N R C P E N D I N G(待决),直到命令完成为止。命令完成后,
n c b c m d c p l t字段会设为该命令的返回值。N e t b i o s也会在完成后将n c b r e t c o d e字段设为命令
的返回值。
1.3 常规NetBIOS例程
本节将讨论一个基本的N e t B I O S服务器应用程序。之所以首先拿服务器开刀,是由于服务
器的设计决定了客户机的行为。由于大多数服务器都要求同时为多个客户提供服务,所以异
步N e t B I O S模型是最适合的。展示这个服务器应用程序例子时,我们同时用到了异步回调
(C a l l B a c k)例程以及事件模型。但在我们首先展示的源码中,必须实现大多数N e t B I O S应用
程序、都要用到的一些常规函数。程序清单1 - 1取自文件N b c o m m o n . c,它可在本书配套光盘
8计计第一部分附传统网络API
下载
的\ E x a m p l e s \ C h a p t e r 0 1 \ C o m m o n目录下找到。贯穿全书的示范代码都会用到来自本文件的一
系列基本函数。
程序清单1-1 常规N e t B I O S例程( N b c o m m o n . c )
第1章计NetBIOS计计9
下载
10计计第一部分附传统网络API
下载
第1章计NetBIOS计计11
下载
12计计第一部分附传统网络API
下载
在N b c o m m o n . c中,出现的第一个常规例程是L a n a E n u m。这是几乎所有N e t B I O S应用都会
用到的一个最基本的例程。该函数可列举一个指定系统上可用的所有L A N A编号。函数会将
一个N C B结构初始化成0,将n c b c o m m a n d字段设为N C B E N U M,为n c b b u ff e r字段分配一个
L A N A E N U M结构,并将n c b l e n g t h字段设为L A N A E N U M结构的长度。在N C B结构正确初
始化之后,为执行N C B E N U M命令,L a n a E n u m函数需要采取的唯一行动便是调用N e t b i o s函数。
如大家所见,一个N e t B I O S命令的执行异常简单。对同步命令来说,自N e t b i o s返回的值可告
诉我们命令是否成功执行。注意常数N R C G O O D R E T肯定意味着“成功”。
使用当前机器上可用的L A N A编号数量,以及各个实际的L A N A编号,一次成功的
N e t B I O S调用会填充完善指定的L A N A E N U M结构。L A N A E N U M结构的定义如下:
其中, l e n g t h成员指出本地机器共有多少个L A N A编号。l a n a字段代表由实际的L A N A编
号构成的一个数组。而l e n g t h值指出l a n a数组内有多少个元素会被填充L A N A编号。
接下去的一个函数是R e s e t A l l(全部重设)。同样,该函数会在所有N e t B I O S应用中用到。
对一个编写风格良好的N e t B I O S程序来说,必须重设计划使用的每个L A N A编号。一旦拥有一
个L A N A E N U M结构,并有来自L a n a E n u m的L A N A编号,便可针对结构中的每个L A N A编号,
调用N C B R E S E T命令来重设它们。这正是R e s e t A l l要帮我们达到的目的;函数的第一个参数
是L A N A E N U M结构。重设只要求函数将n c b c o m m a n d设为N C B R E S E T,并将n c b l a n a n u m
设为它需要重设的L A N A。注意尽管某些平台(比如Windows 95)并不要求我们对打算使用
的每个L A N A编号进行重设,但最好还是那样做。Windows NT要求我们在正式使用前对每个
L A N A编号进行重设;否则,对N e t b i o s的其他调用就会返回错误代码5 2(亦即N R C _
E N V N O T D E F)。
除此以外,重设一个L A N A编号时,可通过n c b _ c a l l n a m e的字符字段,设置特定的
N e t B I O S环境参数。R e s e t A l l的其他参数与这些环境设定是对应的。函数用u c M a x S e s s i o n参数
来设置n c b c a l l n a m e的字符0,它用于指定可同时进行的最大会话数量。通常,操作系统会强
第1章计NetBIOS计计13
下载
制使用一个比最大值小的默认值。举个例子来说, Windows NT 4的最大默认值为6 4个并发会
话。R e s e t A l l将n c b c a l l n a m e的字符2(用于指定可为每个L A N A增加的最大N e t B I O S名字数量)
设为u c M a x N a m e参数的值。同样,操作系统也会强加一个默认的最大值。最后, R e s e t A l l会
将字符3(用于N e t B I O S客户机)设为它的b F i r s t N a m e参数的值。通过将此参数设为T R U E,
一个客户机便能将机器名作为自己的N e t B I O S进程名使用。因此,那个客户机可与一个服务
器建立连接,并在不允许任何进入连接的前提下,向其发送数据。这一选项有效缩短了初始
化时间。而假若将一个N e t B I O S名字加入本地名字表,那么必须为此付出相应的代价。
要想将名字加入本地名字表,必须用到另一个常规函数: A d d N a m e。需要的参数就是想
添加的名字,以及将其加到哪个L A N A编号。请记住,每个L A N A编号永远对应一个名字表。
如果你的应用程序需要与每个可用的L A N A通信,便需为每个L A N A增加进程名。用于增加一
个唯一名字的命令是N C B A D D N A M E。必须使用的其他字段包括要为其增加名字的那个
L A N A编号,以及要实际增加的名字,后者必须复制到n c b _ n a m e中。A d d N a m e首先会将
n c b n a m e缓冲区初始化成空白,然后假定n a m e参数指向一个空中止字串。成功添加一个名字
后,N e t b i o s会在n c b n u m字段中,返回同新增名字对应的N e t B I O S名字编号。可随数据报一
起使用该值,以标定始发的N e t B I O S进程。有关数据报更深入的情况,我们会在本章的后面
进行详细讨论。增加一个独一无二的名字时,经常遇到的一个错误是N R C D U P N A M E。若网
络中的另一个进程已使用了要增加的名字,便会出现此类错误。
A d d G r o u p N a m e的工作原理同A d d N a m e大致相同,只是它执行的命令是N C B A D D G R N A M E,
而且永远不会出现什么N R C D U P N A M E错误。
D e l N a m e是另一个有紧密联系的函数,用于从名字表中删除一个N e t B I O S名字。它只要求
指定打算删除的名字,以及从哪个L A N A编号上删除那个名字。
在程序清单1 - 1中,接下去的两个函数是S e n d和R e c v,用于在一个已经建立的会话中,进
行数据的收发。这两个函数在工作方式上几乎完全相同,除了n c b c o m m a n d字段的设置以外。
这个命令字段可设为N C B S E N D或N C B R E C V。当然,用于收发数据的L A N A编号以及相应的
会话编号也是必需的参数。若成功执行了一个N C B C A L L或N C B L I S T E N命令,便会返回相应
的会话编号。客户机利用N C B C A L L命令同一个已知的服务建立连接;而服务器使用
N C B L I S T E N“等候”进入的客户机连接。若两个命令中有一个成功, N e t B I O S接口便会建立
一个会话,并为其赋予独一无二的整数标识符。S e n d和R e c v还需要用到映射到n c b _ b u ff e r和
n c b l e n g t h的参数。发送数据时, n c b b u ff e r指向那个包含了要送出的数据的缓冲区。在长度
(l e n g t h)字段中,指定了缓冲区中应当送出的字符数量。而在接收数据时,缓冲区( b u ff e r)
字段指向在向其中复制数据的一个内存块。而长度字段指定了该内存块的大小。N e t b i o s函数
返回之后,它会用成功接收到的字节数来更新长度字段。在一个面向会话的连接中,对数据
的发送来说,要注意的一个重要的问题是在调用S e n d函数时,接收方执行一个R e c v函数之前,
S e n d函数会一直等待下去。这意味着假如发送方送出大量数据,但接收方还没有读取它,便
会耗用大量本地资源对数据进行缓冲。因此,一个良好的编程习惯是同时只执行少数几个
N C B S E N D 或N C B C H A I N S E N D 命令。为进一步解决这个问题,请换用N e t b i o s 命令
N C B S E N D N A和N C B C H A I N S E N D N A命令。通过这两个命令,便不必从接收方那里等待收到
确认消息,而是直接送出数据了事。
在程序清单1 - 1中,最后的两个函数是H a n g u p和C a n c e l,分别用于关闭已经建立的会话,
14计计第一部分附传统网络API
下载
或者取消一个尚未进行(待决)的命令。我们可调用N e t B I O S命令N C B H A N G U P来从容关闭
一个建好的会话。执行该命令时,对于指定的会话来说,所有尚未进行的接收调用都会中止,
并返回一个“会话关闭”错误: N R C S C L O S E D ( 0 x 0 A )。如果还存在任何没有执行的发送命
令,则H a n g u p命令会暂时停止封锁执行,等那些命令完成了再说。无论命令正在传输数据,
还是正在等待远程端执行一个接收命令,这种延迟都会发生。
1.3.1 会话服务器:异步回调模型
现在,我们已掌握了进行后续工作所需的基本N e t B I O S函数。接下来,且让我们看看用于
监听“进入”客户机连接的服务器。这只是一个简单的回应反射服务器;从客户机那里接收
到的任何数据都会立即返还回去。在程序清单1 - 2中,我们展示了具体的服务器代码,其中利
用的是异步回调函数。在本书配套光盘上,亦可找到这个C b n b s v r. c程序,位于/ E x a m p l e s /
C h a p t e r 0 1 / S e r v e r文件夹下。注意一下函数m a i n,便会发现我们首先用L a n a E n u m来列举所有
可用的L A N A编号,然后用R e s e t A l l来重设每个L A N A。记住在几乎所有N e t B I O S应用中,都
要先采取这两个步骤。
程序清单1-2 异步回调服务器(C b n b s v r. c)
第1章计NetBIOS计计15
下载
16计计第一部分附传统网络API
下载
第1章计NetBIOS计计17
下载
m a i n接下来要做的事情是将进程名增加到打算用来接收连接的每个L A N A编号。通过一
次循环,服务器会将自己的进程名T E S T- S E RV E R - 1增加到每个L A N A编号。通过这个名字,
客户机才能连接我们的服务器(当然要用空格填充)。试图建立或接受一个连接时, N e t B I O S
名字中的每个字符都必须明确指定。对于这个问题,大家必须特别留意。编写N e t B I O S客户
机和服务器代码时,最常见的问题便是名字的错误匹配。一定要注意用空格或其他字符来填
充名字。空格是最常用的填充字符,因为可以列举出来或打印出来,总之,人眼能够辨别它
的存在。
对服务器来说,最后一个也是最关键的一个步骤是执行大量N C B L I S T E N命令。L i s t e n函
数首先会分配一个N C B结构。使用异步N e t B I O S调用时,我们递交的N C B结构必须自执行调
用之时开始,一直持续到调用结束为止。这便要求我们在执行命令前动态分配每一个N C B结
构,或者维持一个全局性的N C B结构池,以便在异步调用中使用。对N C B L I S T E N来说,应设
置希望通过它进行调用的那个L A N A编号。注意在程序清单1 - 1列出的源代码清单中,
18计计第一部分附传统网络API
下载
N C B L I S T E N命令需要同A S Y N C H命令进行逻辑或( O R)运算。指定A S Y N C H命令时,对
n c b p o s t和n c b e v e n t这两个字段来说,其中任意一个必须设为非零值。否则, N e t b i o s调用就
会出错,报告N R C I L L C M D(非法命令)错误。在程序清单1 - 2中,L i s t e n函数会将n c b p o s t
字段设成我们的回调函数: L i s t e n C a l l b a c k。接下来,L i s t e n函数将把n c b n a m e字段设为服务
器进程的名字。这正是客户机需要与之建立连接的那个名字。函数也会将n c b c a l l n a m e字段的
第一个字符设为一个星号(*),指出服务器可从任何客户机接受连接请求。如果不这样做,
亦可在n c b c a l l n a m e字段中设置一个特定的名字,只允许注册了那个特定名字的客户机建立与
服务器的连接。最后, L i s t e n会发出对N e t b i o s的一个调用。调用立即便会完成, N e t b i o s函数
将已提交的N C B结构的n c b c m d c p l t字段设为N R C P E N D I N G ( 0 x F F )—表示“待决”,直到
命令执行完毕为止。
一旦m a i n完成了重设,并为每个L A N A编号都投放了一个N C B L I S T E N命令,主线程会进
入一个连续的循环中。
注意由于这个服务器仅是一个简单的例子,所以在设计上非常简陋。在你编写自己的
N e t B I O S服务器应用时,还可在主循环中进行其他处理;或者在主循环中,为某个
LANA编号投放一个同步NCBLISTEN命令。
只有在一个L A N A编号上接受了一个进入的连接时,回调函数才会执行。N C B L I S T E N命
令接受了一个连接后,会调用由n c b _ p o s t指定的函数,并将最初的N C B结构作为参数使用。
随后, n c b r e t c o d e会设为返回代码。请务必留意对这个值的检查,了解客户机连接是否成功
建立。若连接成功,会在n c b r e t c o d e字段中返回一个N R C G O O D R E T ( 0 x 0 0 )值。
连接成功后,需针对同一个L A N A编号执行另一个N C B L I S T E N命令。之所以要这样做,
是由于一旦原始的监听操作成功,则服务器会停止在那个L A N A编号上对客户机的连接进行
监听,直到递交了另一个N C B L I S T E N为止。因此,假如服务器需要频繁地为客户提供服务,
便需在同一个L A N A上投放多个N C B L I S T E N命令,以便能够同时接受多个客户机发出的连接
请求。最后,回调函数会创建一个特殊的线程,为客户机提供服务。在我们的这个例子中,
线程只是简单地循环,并调用一个成块读入命令( N C B R E C V),紧接着调用一个成块发送命
令(N C B S E N D)。所以,我们在此实现的是一个简单的回应服务器,用于从建立连接的客户
机那里读入消息,再将其原封不动地“反射”回去。除非客户机中断连接,否则客户机线程
会一直循环下去。连接中断时,客户机线程会执行一个N C B H A N G U P命令,以便在自己的这
一端关闭当前连接。随后,客户机线程释放N C B结构占用的空间,并正常退出。
对面向连接的会话来说,数据是由最基层的协议加以缓冲的,所以并非一定要发出“待
决”的调用。发出一个接收命令后, N e t b i o s函数会将可用的数据立即传给现成的缓冲区,而
且调用会立即返回。而假若没有数据可用,接收调用便会暂停,直到有数据可用,或者会话
断开为止。同样的道理也适用于发送命令:若网络堆栈能通过线缆立即送出数据,或者能将
数据缓存在堆栈中,调用便会立即返回。而假若系统没有足够的缓冲区空间来立即送出数据,
发送调用便会暂停,直到有可用的空间为止。要想避免这种形式的数据延误,可对数据的收
发使用A S Y N C H(异步)命令。若执行的是异步发送或接收命令,那么相应的缓冲区必须有
一个较大的容量,超出调用进程本身的范围之外。避免收发延误的另一个办法是使用n c b s t o
和n c b r t o这两个字段。其中, n c b s t o字段用于设置发送延时。若为其指定一个非零值,便相
当于为命令的执行规定了一个超时时限。注意时间长度是以5 0 0毫秒为单位指定的。如命令超
第1章计NetBIOS计计19
下载
时,便需要立即返回,数据则不会送出。接收超时的设置( n c b r t o)道理是一样的:如在预
先规定好的时间内没有数据到达,则调用返回,没有数据输入缓冲区。
1.3.2 会话服务器:异步事件模型
程序清单1 - 3展示了与程序清单1 - 2类似的一个回应服务器程序,只是采用了Wi n 3 2事件作
为传信机制。事件模型与回调模型相似,唯一的区别在于对回调模型来说,系统会在异步操
作完成后执行你的代码;而对事件模型来说,程序必须通过对事件状态的检查,来核实操作
是否完成。由于这些属于标准的Wi n 3 2 事件,所以可在此选用任何同步例程,比如
Wa i t F o r S i n g l e E v e n t和Wa i t F o r M u l t i p l e E v e n t s等等。事件模型显得更有效率,因为程序员必须
为程序规定一个恰当的结构,有意检查完成与否。
就我们的这个事件模型服务器程序来说,它最开头的几步与回调服务器是完全一致的:
1) 列举L A N A编号。
2) 重设每个L A N A。
3) 为每个L A N A增加服务器的名字。
4) 为每个L A N A都执行(投放)一个监听命令。
唯一区别在于我们需要跟踪每一个待决的(即尚未返回的)监听命令,因为必须将事件
的完成同初始化某个特定命令的各个N C B块对应起来。程序清单1 - 3分配了一个由系列N C B结
构构成的数组, N C B结构的数量则等同于L A N A编号的数量(因为我们希望针对每个编号都
执行一个N C B L I S T E N监听命令。除此以外,代码还为每个N C B结构都创建了一个事件,对应
于命令的“完成”。L i s t e n函数会从数组中取得某个N C B结构,作为自己的参数使用。
程序清单1-3 异步事件服务器(E v n b s v r. c)
20计计第一部分附传统网络API
下载
第1章计NetBIOS计计21
下载
22计计第一部分附传统网络API
下载
第1章计NetBIOS计计23
下载
其中,m a i n函数的首次循环需要遍历每一个可用的L A N A编号,为其增加服务器名字,执
行N C B L I S T E N 命令, 并同时构建由事件句柄构成的一个数组。接下来调用
Wa i t F o r M u l t i p l e O b j e c t s。在其中一个句柄收到信号之前(即进入传信状态之前),这个调用会
一直等待下去。一旦事件控制数组中的一个或多个句柄进入传信状态, Wa i t F o r M u l t i p l e O b j e c t s
调用便会完毕,代码会构建一个线程,用它读取进入的消息,并将其原封不动回送给客户机。
随后,代码会为传信状态的N C B结构创建一个副本,将其传递进入客户机线程。之所以要这
样做,是由于我们希望沿用最初的N C B,以执行另一个N C B L I S T E N监听命令。要达到这个目
的,可重设事件,并针对那个结构再次调用L i s t e n。注意此时没有必要将整个结构都复制下来。
在实际应用中,只需用到本地会话编号( n c b l s n)和L A N A编号(n c b l a n a n u m)就可以了。
然而,要想同时容纳两个值,打算将其都传递给同一个线程参数,那么N C B结构是一个非常
不错的容器。事件模型使用的客户机线程与回调模型使用的那个几乎完全相同,只是这里采
用了G l o b a l F r e e语句。
异步服务器策略
注意对前述的两种服务器工作模式来说,都可能存在拒绝为客户机提供服务的情况。
N C B L I S T E N完成后,在调用回调函数之前,或在事件收到信号之前,都存在少许的延时。只有
在经历了几个语句之后,服务器才会执行另一个N C B L I S T E N命令。例如,假定服务器在L A N A
2上接受了一个客户机的连接。随后,在服务器针对同一个L A N A编号执行另一个N C B L I S T E N
之前,假如又有一个客户机试图建立连接,便会收到一个名为N R C N O C A L L ( 0 x 1 4 )的错误信息。
这意味着,对指定的名字来说,目前尚未在它上面执行N C B L I S T E N。要防止此类情况的出现,
服务器必须为每个L A N A都执行多个N C B L I S T E N命令。
从前述两个服务器应用的例子可以看出,异步命令的执行其实是非常容易的。A S Y N C H
标志可应用于几乎所有N e t B I O S命令。只是要记住,传递给N e t b i o s的N C B结构有一个全局性
的范围。
1.3.3 NetBIOS会话客户机
N e t B I O S客户机在设计上类似于异步事件服务器。在程序清单1 - 4中,我们展示了客户机
程序使用的源代码。按照名字,客户机首先采取大家或已熟知的一系列初始化步骤。它为每
24计计第一部分附传统网络API
下载
个L A N A编号的名字表增添自己的名字,然后执行一个异步连接命令。主循环会等候某个事
件进入传信状态。随后,代码遍历与执行的那个连接命令对应的所有N C B结构,每个L A N A
编号都对应一个结构。它会检查n c b c m d c p l t的状态。如发现它设为N R C P E N D I N G(待决),
代码就会取消异步命令;若命令完成(亦即建立了连接),但N C B并不与传信的那个N C B相符
(由来自Wa i t F o r M u l t i p l e O b j e c t s调用的返回值指定),代码便会断开连接。假如服务器正在自
己那一端对每个L A N A进行监听扫描,同时客户机正在尝试在其每个L A N A上建立连接,那么
就可能出现成功建立多个连接的情况。此时,代码会用N C B H A N G U P命令关掉多余的连接
—它只需通过一个信道进行通信。由于允许双方都同时尝试建立一个连接,所以连接成功
的机率大增。
程序清单1-4 异步事件客户机(N b c l i e n t . c)
第1章计NetBIOS计计25
下载
26计计第一部分附传统网络API
下载
第1章计NetBIOS计计27
下载
1.4 数据报的工作原理
“数据报”(D a t a g r a m)属于一种“无连接”的通信方法。作为发送方,只需用目标
N e t B I O S名字为发出的每个包定址,然后简单地送出了事。此时,不会执行任何检查,以确
保数据的完整性、抵达顺序或者传输的可靠性等等。
发出一个数据报共有三种方式。第一种是指挥数据报抵达一个特定的(或唯一的)组名。
这意味着只能有一个进程负责数据报的接收—亦即注册了目标名字的那个进程。第二种是
将数据报发给一个组名。只有注册了指定组名的那些进程才有权接收消息。最后,第三种方
式是将数据报广播到整个网络。局域网内任何一个工作站上的任何进程均有权接收这种数据
报消息。请用N C B D G S E N D命令将数据报发给一个唯一的名字,或者发给一个组名;要想进
行广播通信,请用N C B D G S E N D B C命令。
任何数据报发送命令的使用都非常简单。首先,将n c b n u m字段设为自一个N C B A D D N A M E
或N C B A D D G R N A M E命令返回的名字编号。这个编号对应着消息的发送端。然后,请将
n c b _ b u ff e r设为一个缓冲区的地址,那个缓冲内应包含了需要实际发出的数据。接下来,将
n c b _ l a n a _ n u m字段设为一个特定的L A N A编号,数据报将通过该L A N A发送出去。最后,将
n c b c a l l n a m e设为目标N e t B I O S名字。这既可是一个独一无二的名字,亦可是个组名。若想发
送广播数据报,仍请执行上述所有步骤,只是剔除最后一步:因为所有工作站均需接收消息,
不必设定n c b c a l l n a m e的值。
当然,在前述的每种情况下,都必须有一个对应的数据报接收命令,以便实际地接收数
据。数据报是“无连接”的;如数据报抵达了一个客户机,但对方却没有一个已处在“待决”
状态的接收命令,数据便会被无情地抛弃,客户机也没有办法恢复那个数据(除非服务器重
新发出数据)。这正是数据报通信最大的一个缺点。然而,这个缺点也换来了速度快的优点。
同面向连接的方法相比,数据报的传送速度要快得多,因为完全省去了错误检查、连接设置
之类的开销。
至于数据报的接收,也有三种方法可供选择。头两种方法要用到N C B D G R E C V命令。首
先,为目标设一个特定名字(唯一名字或组名)的消息执行一个数据报接收命令。其次,针
对目标定为进程N e t B I O S名字表内任何一个名字的任何一个数据报,为其执行一个数据报接
收命令。第三,可用N C B D G R E C V B C命令为一个广播数据报执行一个接收命令。
注意若数据报的目标设为由一个不同的进程注册的名字,那么除非两个进程都注册了
一个组名,否则不可能为其投放一个接收命令。在后一种情况下,两个进程都会接收
28计计第一部分附传统网络API
下载
到相同的消息。
为执行一个接收命令,请将n c b n u m字段设为自一次成功N C B A D D N A M E或N C B A D D G R N A M E
调用返回的名字编号。这个编号指出我们打算在哪个名字上“监听”进入的数据报。若将该
字段设为0 x F F,表示可接收发给该进程之N e t B I O S名字表内的任何名字的数据报。此外,还
要创建一个用来接收数据的缓冲区,并将n c b b u ff e r设为该缓冲区的地址,将n c b l e n g t h设为
该缓冲区的大小。最后,将n c b l a n a n u m设为要想在上面等候数据报的L A N A编号。若通过
N C B D G R E C V命令(或N C B D G R E C V B C命令)对N e t b i o s的调用成功返回,那么在n c b l e n g t h
字段中,就会包含接收到的实际字节数,而n c b c a l l n a m e会包含发送进程的N e t B I O S名字。
程序清单1 - 5的代码包含了基本的数据报函数。所有发送命令均属于封锁调用—一旦命
令执行,而且数据进入线缆传输,函数便会返回,而且不会由于数据过载而造成执行暂停即
锁定。接收调用属于异步事件,因为我们不知道数据在那个L A N A编号上抵达。使用的代码
类似于事件模型中的面向会话的服务器。针对每个L A N A,代码都会投放一个异步的
N C B D G R E C V(或N C B D G R E C V B C)命令。除非某个命令成功,否则会一直等候下去。成
功后,它会检查投放的每个命令,为那些成功的打印出消息,并取消仍处在“待决”状态中
的那些命令。这个例子同时为定向/广播式发送与接收提供了相应的函数。可将该程序编译
成一个示范性应用,将其配置成负责数据报的发送或接收。这个程序可接收几个命令行参数,
允许用户指定要发送或接收的数据报数量、连续两次发送之间的延迟、是否用广播数据报来
取代定向数据报、用于任何名字的数据报收据等等。
程序清单1-5 NetBIOS数据报示例(N b d g r a m . c)
第1章计NetBIOS计计29
下载
30计计第一部分附传统网络API
下载
第1章计NetBIOS计计31
下载
32计计第一部分附传统网络API
下载
第1章计NetBIOS计计33
下载
34计计第一部分附传统网络API
下载
第1章计NetBIOS计计35
下载
36计计第一部分附传统网络API
下载
第1章计NetBIOS计计37
下载
38计计第一部分附传统网络API
下载
编译好这个例子后,请进行下述测试,以便对数据报的工作原理心中有数。考虑到学习
研究的目的,应运行该应用的两份“实例”,不过要在不同的机器上运行。如在同一台机器上
运行,尽管仍然可行,但却无法体会一些重要的概念。在同一台机器上运行时,每一方的
L A N A编号都对应于相同的协议。但我们更感兴趣的是对应不同协议的情况。在表1 - 5中,我
们总结了应进行试验的命令。除此以外,后面的表1 - 6还列出了对于这个示范程序来说,可考
虑选用的命令行参数。
第1章计NetBIOS计计39
下载
表1-5 Nbdgram.c的命令
客户机命令服务器命令
Nbdgram /n:CLIENT01 Nbdgram /s /n:SERVER01 /r:CLIENT01
Nbdgram /n:CLIENT01 /b Nbdgram /s /n:SERVER01 /b
Nbdgram /g:CLIENTGROUP Nbdgram /s /r:CLIENTGROUP
表1-6 Nbdgram.c的命令参数
标志含义
/ n : m y - n a m e 注册唯一名字m y - n a m e
/ g : g r o u p - n a m e 注册组名g r o u p - n a m e
/ s 发送数据报(默认情况下,示例接收数据报)
/ c : n 发送或接收n个数据报
/ r : r e c e i v e r 指定数据报的目标N e t B I O S名字
/ b 使用广播数据报
/ a 为任何N e t B I O S名字投放接收命令(将n c b n u m设为0 x F F)
/ l : n 只在LANA n 上执行所有操作(默认情况下,为每个L A N A编号都会投放发送和接收命令)
/ d : n 在连续两次发送之间,等待n毫秒的时间
针对第三个命令,请在不同的机器上运行多个客户机。这样可营造一个服务器将同一条
消息发给一个组的环境,正在等候数据的每位组员都会接收到消息。此外,请用/ l : x命令行参
数试验所列命令的不同组合。其中, x代表一个有效的L A N A编号。这个参数的作用是将程序
模式从在所有L A N A上执行命令,切换为只在列出的L A N A上执行命令。举个例子来说,命令
Nbdgram /n:CLIENT01 /l:0的作用是让应用程序只监听LANA 0上的进入数据报,忽略抵达其
他任何L A N A上的任何数据。除此以外,参数/ a只对客户机才有意义。这个标志意味着接收命
令需要取得发给任何N e t B I O S名字的数据报,只要那些名字已得到了进程的注册。在我们的
例子中,这个参数意义不大,因为客户机仅注册了一个名字。尽管如此,大家至少可从中看
出如何进行编程。大家可试着对代码进行修改,以便为命令行的每个“ / n :名字”选项注册一
个名字。服务器启动时,接收标志只设为已由客户机注册的一个名字。尽管N C B D G R E C V命
令没有指出一个特定的名字,客户机仍会接收到数据。
1.5 其他NetBIOS命令
迄今为止,我们讨论的所有命令都在某种程度上涉及一个会话的建立;通过会话或数据
报进行数据的发送或接收;以及与之相关的一些主题。另外,还有少数几个命令专门负责信
息的获取。其中包括适配器状态命令( N C B A S TAT)和查找名字命令( N C B F I N D N A M E)。
在后续的两个小节中,我们打算分别对这两个命令进行解释。最后一节则准备通过一种利于
编程的形式,将L A N A编号同它们的协议对应起来(实际并非一个N e t B I O S函数;之所以要讨
论它,是由于可藉着它收集到大量有用的N e t B I O S信息)。
1.5.1 适配器状态
利用N C B A S TAT命令,可取得与本地计算机及其L A N A编号有关的信息。要想通过程序,
从Windows 95及Windows NT 4中查知机器的M A C地址,这个命令也是唯一可行的途径。但随
着Windows 2000和Windows 98的问世,由于它们引入了新的IP Helper(I P助手)函数,所以
40计计第一部分附传统网络API
下载
使得M A C地址的查找变得更加容易。然而,对其他Wi n 3 2平台来说,适配器(通常是“网卡”)
状态命令仍是我们的唯一选择。
命令及其语法是很易理解的,但通过两种不同的方式来调用函数时,会对数据的返回产生
影响。适配器状态命令会返回一个A D A P T E R S TAT U S结构,紧接着是大量N A M E B U F F E R
结构。对结构的定义如下:
其中,特别值得注意的字段当属M A C地址( a d a p t e r _ a d d r e s s)、数据报最大长度( m a x _
d g r a m s i z e)以及最大会话数( m a x s e s s)。此外, n a m e c o u n t字段告诉我们总共返回了多少
个N A M E B U F F E R结构。对每个L A N A来说,最多能支持的N e t B I O S名字为2 5 4个,所以我们
可选择提供一个足够大的缓冲区,容下所有名字;或将n c b l e n g t h设为0,仅调用一次适配器
状态命令。N e t b i o s函数返回之后,它会提供必要的缓冲区大小设置。
要想调用N C B A S TAT,需要设置的字段包括n c b _ c o m m a n d,n c b _ b u ff e r,n c b _ l e n g t h,
n c b l a n a n u m以及n c b c a l l n a m e。若n c b c a l l n a m e的第一个字符是一个星号( *),那么尽管会
执行一个状态命令,但只有那些由调用进程增添的N e t B I O S名字才会返回。然而,如果用一
个适配器状态命令调用N e t b i o s,在当前进程的名字表内增添一个“唯一”名字,然后在
n c b c a l l n a m e字段中使用那个名字,那么所有N e t B I O S名字都会在本地进程的名字表内注册,
第1章计NetBIOS计计41
下载
另外还要加上由系统注册的任何名字。除命令在其上面执行的那台机器以外,还可针对另一
台机器,执行一镒适配器状态查询。要想做到这一点,请将n c b c a l l n a m e字段设为远程工作站
的机器名。
注意记住所有Microsoft机器名字都将其第16个字节设为0,应该用空格来替代它。
示范程序A s t a t . c是一个简单的适配器状态程序,可针对所有L A N A适配器运行指定的查询。
此外,通过/ l : L O C A L N A M E标志,亦可在本地机器上执行命令,只是要求提供完整的名字表。
/ r : R E M O T E N A M E标志则针对某个指定的机器名,执行一次远程查询。
使用适配器状态命令时,有几方面的问题需要注意。首先,对一台设置成“多主机”或
“多宿主”的机器来说,拥有的M A C地址不止一个。由于N e t B I O S没办法调查一个L A N A到底
与哪个适配器以及协议绑定到一起,所以此时需要由我们负责,对返回的值进行过滤。此外,
假如安装了远程访问服务( R A S),系统也会为那些连接分配对应的L A N A编号。若R A S连接
已经断开(即未建立连接),那些L A N A上的适配器状态会为M A C地址返回全零值。若R A S连
接已经建立, M A C地址便与R A S分配给其所有虚拟网络设备的M A C地址相同。最后,在执行
一次远程适配器状态查询时,必须通过两台机器均已正确安装的一种传送协议进行。举个例
子来说,系统命令N b t s t a t(亦即N C B A S TAT的一个命令行版本)只能通过T C P / I P传送协议执
行它的查询。若远程机器尚未安装T C P / I P协议,命令便会失败。
1.5.2 查找名字
N C B F I N D N A M E命令只能在Windows NT及Windows 2000操作系统上使用,用于调查一
个指定的N e t B I O S名字是由谁注册的。要想进行一次成功的查找名字查询,进程必须在名字
表内添加其唯一名字。该命令要求设置的字段包括命令本身、L A N A编号、缓冲区以及缓冲
区的长度。查询返回的是一个F I N D N A M E H E A D E R结构,以及数量不定的F I N D N A M E B U F F E R结构。结构定义如下:
和适配器状态命令一样,假如N C B F I N D N A M E命令在执行时将缓冲区的长度设为0,那
么N e t b i o s函数会返回所需的长度,同时返回一个名为N R C B U F L E N的错误。
对一次成功的查询来说,参照返回的F I N D N A M E H E A D E R结构,便可知道一个名字是
注册成唯一名字,还是注册成组名。假如u n i q u e g r o u p字段的值为0,表示它是一个“唯一”名
字;值为1,则表示是个组名。n o d e c o u n t字段指出总共返回了多少个F I N D N A M E B U F F E R结
42计计第一部分附传统网络API
下载
构。通过F I N D N A M E B U F F E R结构,我们可获知大量信息。在这些信息中,大多数只有在
协议这一级才有用处。目前,我们最感兴趣的是d e s t i n a t i o n a d d r(目标地址)和s o u r c e a d d r
(源地址)两个字段。其中, s o u r c e a d d r字段包含了注册指定名字的那个网络适配器的M A C
地址;而d e s t i n a t i o n a d d r包含了执行查询的那个适配器的M A C地址。
针对本地机器的任何L A N A编号,均可执行一次查找名字查询。对于本地网络的任何有效
L A N A编号来说,返回的数据应当是完全相同的。例如,我们可针对一个R A S连接执行该命
令,以判断一个名字是否在远程网络上进行了注册。在Windows NT 4.0中,存在着一个不该
存在的错误:若通过T C P / I P执行一次查找名字查询,那么N e t b i o s会返回虚假的信息。因此,
若计划在Windows NT 4.0下使用这种查询,请务必选择与另一种传送协议对应的L A N A编号,
不要依赖T C P / I P。
1.5.3 将传送协议同LANA编号对应起来
本节打算讨论如何将T C P / I P和N e t B E U I这样的传送协议同它们的L A N A编号对应起来。由
于应用程序打算采用的传送协议不同,我们也需要解决不同系列的一些潜在问题,所以最好
能够先以程序化的方式。来查找这些传送协议。用固有的N e t B I O S调用是无法做到这一点的,
但在Windows NT 4和Windows 2000下却可通过Winsock 2做到。一个名为W S A E n u m P r o t o c o l s
的Winsock 2函数可返回与可用的传送协议有关的信息(第5和第6章对W S A E n u m P r o t o c o l s进行
了更详细的解释)。尽管Winsock 2可加到Windows 95中,而且也是Windows 98的默认安装选
项,但令人遗憾的是,在这些平台保存的协议资料中,并不包括我们真正需要的任何
N e t B I O S信息。
对于Winsock 2,现在不打算作深入探讨,那属于本书第二部分的主题。牵涉到的基本步
骤包括先用W S A S t a r t u p函数装载Winsock 2 ,调用W S A E n u m P r o t o c o l s,再对调用返回的
W S A P R O TO C O L I N F O结构进行检查核实。在本书配套C D提供的N b p r o t o . c中,包含了用于
执行这种查询的代码。
W S A E n u m P r o t o c o l s函数需要用到一个缓冲区地址参数,对应一个数据块;另外还需用到一
个缓冲区长度参数。首先用一个空缓冲区地址和长度0来调用该函数。当然,调用会失败,但随
后在缓冲区长度参数字段,会返回所需缓冲区的真实长度。拿到了正确的长度之后,再次调用这
个函数即可。W S A E n u m P r o t o c o l s替返回它发现的协议数量。同时,W S A P R O TO C O L I N F O会成
为一个很大的结构,其中包含了大量字段,但我们在此感兴趣的只有s z P r o t o c o l,i A d d r e s s F a m i l y
和i P r o t o c o l。假如i A d d r e s s F a m i l y等于A F N E T B I O S,则i P r o t o c o l的绝对值便对应于由字串
s z P r o t o c o l指定的那种协议的数量。除此以外, ProvidedId GUID可用于将返回的协议同协议的
预定义G U I D对应起来。
采用这种方法,仅存在着一个方面的不足。在Windows NT和Windows 2000下,对于
LANA 0上安装的任何协议来说, i P r o t o c o l字段的值都为0 x 8 0 0 0 0 0 0 0。这是由于协议0是为特
殊用途而保留的;分配了LANA 0的任何协议都有一个0 x 8 0 0 0 0 0 0 0的值,所以只需注意对这个
值的检查就可以了。
1.6 平台问题
在下述平台上实施N e t B I O S时,请留意一些特殊的限制。
第1章计NetBIOS计计43
下载
1.6.1 Windows CE
N e t B I O S接口未在Windows CE中实现。尽管重定向器提供了对N e t B I O S名字和名字解析
的支持,但却没有提供相应的编程接口的支持。
1.6.2 Windows 9x
就普通用户广泛采用的Windows 95和Windows 98(含第二版)这两种操作系统来说,要
注意几处特别的系统错误。在这些平台上,若要为任何L A N A增加任何N e t B I O S名字,务必首
先重设所有L A N A编号。这是由于重设任何一个L A N A,都会完全破坏其他L A N A的名字表。
所以,应避免写出像下面这样的代码:
此外,对Windows 95来说,它根本不会在对应于T C P / I P协议的L A N A上尝试执行一个异
步N C B R E S E T命令。从最开始,便不应以异步形式来执行该命令,因为在通过L A N A进行任
何后续的操作之前,首先必须完成一次重设。如试图强行以异步形式执行一个N C B R E S E T命
令,程序便会在NetBIOS TCP/IP虚拟设备驱动程序( V X D)中,导致一个严重错误,不得不
重新启动计算机。
1.6.3 常规问题
进行面向会话的通信时(与“无连接”通信相对),其中一方可送出自己希望的、任意多
的数据。但实际上,若非接收方投放一条接收命令,确认数据已经收到,否则作为发送方,
会将数据缓存起来。对发送命令来说, N C B S E N D N A和N C B C H A I N S E N D N A这两个N e t B I O S
命令是它们的“毋需确认”版本。如果不想让自己的发送命令等候来自接收方的确认信息,
那么可考虑使用这两个命令。由于T C P / I P在最基层的协议中提供了自己的收到确认机制,所
以发送命令的这两个版本(毋需接收方确认)在其行为上,类似于要求确认的版本。
1.7 小结
尽管N e t B I O S接口功能非常强大,但可惜的是,由于问世较早,它在功能上存在着诸多限
制,已不适合现在的需要。它的优点之一是“与协议无关”—应用程序可通过T C P / I P、
N e t B E U I以及S P X / I P X运行。N e t B I O S同时提供了面向连接和无连接的通信模式。与Wi n s o c k
接口相比, N e t B I O S接口的一项重要优点在于:它提供了一种统一的名字解析及注册方法。
换言之,对一个N e t B I O S应用来说,它只需一个N e t B I O S名字便可工作。Wi n s o c k应用则不同,
假如它利用了不同的协议,那么每种协议的定址方案都要考虑到(详见本书第二部分)。在第
2章中,我们向大家引入“重定向器”的概念。重定向器实际是“邮槽”和“命名管道”密不
可分的一部分。在第3和第4章将深入讨论“邮槽”和“命名管道”。
44计计第一部分附传统网络API
下载
阅读(1610) | 评论(0) | 转发(0) |