用C++Builder进入NetBIOS编程 NetBIOS是网络基本的输入/输出系统，是一个应用于程序接口，用于数据源与目的地之间的数据交换。即能够访问支持计算机应用程序和设备通信时要用到的各种服务，具有明确而简单的通信协议，必须用特殊的命令序列来调用NetBIOS服务。 首先我们来了解一下NetBIOS的基本知识。 一.NetBIOS 的基础 1。在网络层次中，NetBIOS是处于表示层和会话层之间，是参考模型的高层。因此其接口程序的应用在很大程度上并且从本质上与较低层次的各种活动隔离开。它支持IEEE802.2 的逻辑链路控制(LLC)协议。现在NetBIOS正迅速地成为不同操作系统环境下普遍使用的数据通信平台，这些操作系统包括PC DOS,OS/2,UNIX 和Windows。 2。NetBIOS的出现是在1984年8月IBM PC的网络适配器中，是由Sytek公司为IBM设计的。它采用宽带同轴电缆，提供每秒2MB的数据传输突发率，使用了流行的工业标准CSMA/CD（多访问载波侦听/载波检测）作为访问协议，这种协议首次出现在IEEE 802.3 以太网标准中。 3。NetBIOS的应用服务：NetBIOS提供四种应用服务，它们分别为：命名支持，数据报支持，会话支持和通用命令。 (1).命名支持：每个NetBIOS 网络积适配器在网络中都使用一个或多个网络名来标识自己，以区别于其他的网络适配器。网络名由16个字符组成。 (2).数据报支持：当一个适配器在网络中激活后，在计算机上的应用程序就可以用NetBIOS与驻留在同一个或不同计算机上的其他应用通信。它们之间的应用通信可以使用数据报来进行。 数据报是一条短信息，它的长度随NetBIOS实现方法的不同而不同，不能保证数据的正确传输。也不接收来自接收方的指示，如：在存在，未加电或不接收数据报，这样 在网络发生故障时，发出的数据报可能不会被任何计算机接收到。 数据报有两种类：广播型和定向型。广播型是完全不区分接收者的数据报，可以使用NetBIOS的Send Broadcast Datagram命令来发送数据，接收方可调用NetBIOS Receive Broadcaset Datagram命令来接收数据。定向型是指定数据报接收者的组名，任何一方都可以调用NetBIOS Send Datagram命令发送数据，接收方可以调用NetBIOS Receive Datagram命令来接收发送过来的数据。 (3).会话支持：在NetBIOS应用通信中的第二种形式是会话通信了，它支持可以在两个应用程序之间创建一个可靠的双向数据通信连接，并能保持较长时间。这种连接有时也称为虚电路。相互通信的应用可以驻留在同一台计算机上（本地会话）或驻留在不同的计算机上（远程会话）。 会话通信比数据报通信的优点有：对于每个被发送的信息，发送前能得知对方接收准备情况，而数据报通信只提供信息的发送状态。 可以使用NetBIOS的Listen命令来创建会话，Listen命令引用NetBIOS命令表中的一个名字。另一端则使NetBIOS Call 命令，Call命令要用到其NetBIOS命名表中的名字，这个名字必须与另一端的的应用程序所用的名字相匹配，这样两边的名字相匹配是两个应用创建会话的依据，在Listen 命令和 Call 命令执行完后，就建立会话了。注意的是：首先调用 Listen命令，然后再调用 Call 命令，这个顺序不能颠倒。 会话创建后，每个客户端都收到一个会话已创建的指示，得到一个字节的无符号返回值，表示会话与适配器之间的联。创建会话后，每个客户端都可以调用NetBIOS的Send和Receive命令来传输和接收数。 (4).通用命令：NetBIOS的通用命令提供了五项NetBIOS服务，分别是：Reset(复位适配器),Adapter Status(显示适配器状态),Cancel and Unlink(撤消及断开命令),Find Name(匹配名字)和 Trace(跟踪命令）。 1）.Reset 命令强迫适配器进入初始化状态，终止所有的会话，并删除NetBIOS 命名表中除永久节点名以外的所有名字。 2）.Adapter Status 命令用来查询NetBIOS适配器，以获取有关操作信息，如：发现LAN 错误数和适配器NetBIOS命名表，用于知道远程计算机和适配器是否都被挂起，还是只有计算机被挂起。这是对远程适配器的状态进行查询的一个很好的实现方法。 3）.Cancel and Unlink:Cancel 命令是使应用程序结束尚未执行完的命令。Unlink命令是允许RPL引导的PC Network LAN 适配器，从RPL服务器上脱开，它只对基本适配器有效，通常返回一个 0 值，表明请求成功。4）.Find Name 命令用于找出一个由Find Name 命令指定的符号名的适配器，在多个适配器共用一个组名时，它们都可以同时都应答，但是只有一个应答将返回给发出请求的应用端。 5）.Trace 命令将激活对所有发向NetBIOS 接口的命令进行跟踪，其基本用途是为诊断程序提供支持。 4.NetBIOS命令的调用： 在应用程序中调用 NetBIOS 命令之前，将内存的一个64 字节区域先清空为 0 ，这样防止内存原有的数据导致NetBIOS 命令结束时，NetBIOS 错误地进入其他内存区域。应用程序使用这个区域创建一个 NetBIOS 控制块（NCB）。调用 NetBIOS 命令时，要根据命令的要求，填充控制块的域，所以如果没有正确填充这些NCB域，会导致用户计算机运行的挂起，这样会破坏整个系统。 NetBIOS 中利用的一个函数就是 Netbios(),此函数用于解说和执行指定的信息控制块（NCB）。函数定义如下：UCHAR Netbios(PNCB pncb)；参数 pncb 用于描述网络控制块结构的指针。 NCB 结构的定义如下： typeddf struct_NCB｛ UCHAR ncb_command； //命令码 UCHAR ncb_retcode； //返回码 UCHAR ncb_lsn； //本地会话编码 UCHAR ncb_num； //数据报 ADD NAME （增加名）表入口 PUCHAR ncb_buffer； //信息缓冲区 WORD ncb_length； //信息缓冲区长度 UCHAR ncb_callname[NCBNAMSZ]； //CALL 的远程系统名 UCHAR neb_name[NCBNAMSZ]； //本地适配器网络名 UCHAR ncb_rto； //以 1/2s 为单位的接收超时UCHAR ncb_sto； //以 1/2s 为单位的发送超时void(*ncb_post)(struct_NCB*)； //POST 例程指针 UCHAR ncb_lana_num； //执行命令的网络适配器编号 UCHAR ncb_cmd_cplt； //0XFF 命令挂起，否则命令结束 UCHAR ncb_reserve[10]； //保留值 HANDLE ncb_event； //事件句柄 ｝NCB； NCB 命令域包含用于期望操作的 NetBIOS 的命令码,如果命令码的高价位是 0 ,NetBIOS 接收这个请求,并且当命令执行完成后,返回到应用程序,在这里这叫等待选择。每次只能有一个等待选择命令被挂起.虽然Reset,Cancel和Unlink 这些命令能保证执行完毕,但是其他一些命令仅仅在某些情况下才会结束。如果这样一个命令不结束,则NetBIOS 将不返回,并且计算机因为 NetBIOS 在等待结束的无限循环中而浪费资源。如要避免发生这样的情况，应用程序可以将除Reset,Cancel和Unlink 命令外所有命令的命令域的高价位设置为 1 ，这叫不等待选择。 NCB 返回码最终会包含命令的最终返回码值，如在命令结束后，其值为 0 ，则表示该命令成功地结束。否则，表明出现了错误。 NCB 的本地会话编码域包含着与一条命令相联系的本地会话编号。NCB 的名字编号域包含着与一条命令相联的 NetBIOS 命令表格中的名字编号。 NCB 的缓冲区域是一个指向信息缓冲区的指针。NCB 的缓冲区长度域记录了由NCB 的缓冲区域所指向的缓冲区的大小。NCB 的调用名域为16字节长，它包含着同该请求相关的一个远程名。 NCB 的本地名域包含着与请求相关的一个本地名。 NCB 的接受超时域与CALL 和 LISTEN 命令一起使用，它以 1/2s的时间间隔为单位。NCB 的发送超时域与CALL 和 LISTEN 命令一起使用，它以 1/2s的时间间隔为单位。 现把 Netbios() 函数的返回值介绍如下： 00h : 成功地完成，成功返回 01h : 无效的缓冲区 03h : 无效的命令 05h : 命令超时 06h : 不完整地接收消息 07h : 本地No-Ack命令失败 08h : 无效的本地会话 09h : 没有可使用的资源 0Ah : 会话已关闭 0Bh : 命令已撤消 0Dh : 本地NetBIOS命名表中名字重复 0Eh : NetBIOS命名表满 0Fh : 名字具有活动会话，现被撤消登记 11h : NetBIOS 本地会话表满了 12h : 没有挂起的Listen 命令，所有拒绝断开会话 13h : 非法名字编号 14h : 不能找到被调用名字或无回答 15h : 找不到命令，或不能把*号或00h指定ncb_name的首字节，或名字已被撤消而不能再使用 17h : 名字已被删除18h : 会话非正常结束 19h : 检测到名字冲突 1Ah : 不兼容的远程设备 21h : 接口忙 22h : 挂起的命令太多 23h : 在ncb_lana_num域中无效的编号 24h : 产生取消时，命令已完成 25h : 字节组名命令指定了保留名字 26h : 命令不能被撤消 30h : 被另一个进程定义了名字 34h : NetBIOS环境未被定义 35h : 所用的操作系统资源用尽 36h : 超出最大应用个数 37h : NetBIOS无可以使用的SAP 38h : 无可以使用的请求资源 40h : 系统错误 41h : 检测到远程适配器的热载波 42h : 检测到本地适配器的热载波 43h : 未检测到载波 4Eh : 状态位12、14、或15被置位的时间超过 1 min 4Fh : 状态位8--11中的一个或多个被置位 50h--F6h: 适配器发生故障 F7h : 隐式DIR-INITIALIZE错误 F8h : 隐式DIR-OPEN-ADAPTER 错误 F9h : IBM LAN支持程序内部错误 FAh : 适配器检查 FBh : NetBIOS 程序未被装入PC FCh : DIR-OPEN-ADAPTER 或 DIR-OPEN-SAP失败 FDh : 不期望关闭适配器 FFh : 命令挂起状态 为了更好地解说NetBIOS 的编程，现设计一个用 NetBIOS 来编写获取网络适配器信息的程序 二、创建程序例子 在C++Builder 5.0 中选择File / New ,打开“New Items”对话框，在对框中选择Consol Wizard.随后弹出“Consol Application Wizard”对话框，在Windows Type 中选择Consol,在ExecutionType中选择EXE，然后选择Finish,生成一个新的控制台应用程序。 //“Project1.cpp”源代码如下： #pragma hdrstop #include <；condefs.h>； #include <；windows.h>； #include <；stdio.h>；typedef struct _ASTAT ｛ ADAPTER_STATUS adapt； NAME_BUFFER NameBuffer[30]； ｝ ASTAT, *PASTAT；ASTAT Adapter；//自定义复位适配器函数 bool ClearAdapter(NCB ncb)； //--------------------------------------------------------------------------- //#pragma argsused int main() ｛ NCB ncb； UCHAR uRetCode；//先复位网络适配器ClearAdapter(ncb)；memset(&；ncb,0,sizeof(ncb))；//命令码为显示适配器状态 ncb.ncb_command=NCBASTAT； ncb.ncb_lana_num=0； strcpy((char *)ncb.ncb_callname,＂* ＂)； ncb.ncb_buffer=(unsigned char *) &；Adapter； ncb.ncb_length=sizeof(Adapter)； uRetCode=Netbios(&；ncb)； printf(＂The NCBASTAT returned code is OX%x\n＂,uRetCode)；if(uRetCode==0) ｛ printf( ＂The Ethernet Number is: %02x_%02x_%02x_%02x_%02x_%02x\n＂, Adapter.adapt.adapter_address[0], Adapter.adapt.adapter_address[1], Adapter.adapt.adapter_address[2], Adapter.adapt.adapter_address[3], Adapter.adapt.adapter_address[4], Adapter.adapt.adapter_address[5] )； if(Adapter.adapt.adapter_type==0xFF) printf(＂The adapter is Token Ring adapter.\n＂)； else if(Adapter.adapt.adapter_type==0xFE) printf(＂The adapter is Ethernet adapter.\n＂)；printf(＂The software-release level is %d.%d\n＂,Adapter.adapt.rev_major,Adapter.adapt.rev_minor)； printf(＂The number of names in the local names table is %d\n＂,Adapter.adapt.name_count)； for(int i=0；i<；Adapter.adapt.name_count；i++) printf(＂%s\n＂,Adapter.NameBuffer[i].name)； ｝//注意下面程序代码故意出错误 char Remote[16]=＂202.112.87.221＂；//先复位网络适配器 ClearAdapter(ncb)； memset(&；ncb,0,sizeof(ncb))；//命令码为发送数据 ncb.ncb_command=NCBSEND； ncb.ncb_lana_num=0； ncb.ncb_lsn=4； ncb.ncb_sto=2； strcpy(ncb.ncb_callname,Remote)； //strcpy((char *)ncb.ncb_callname,＂* ＂)； ncb.ncb_buffer=(unsigned char *) &；Adapter； ncb.ncb_length=sizeof(Adapter)； uRetCode=Netbios(&；ncb)； printf(＂The NCBASTAT returned code is OX%x\n＂,uRetCode)； printf(＂The number of FRMR frames received is %d\n＂,Adapter.adapt.frmr_recv)； printf(＂The number of FRMR frames transmitted is %d\n＂,Adapter.adapt.frmr_xmit)；getchar()； return 0； ｝ //--------------------------------------------------------------------------- //复位网络适配器 bool ClearAdapter(NCB ncb) ｛ memset(&；ncb,0,sizeof(ncb))； ncb.ncb_command=NCBRESET； ncb.ncb_lana_num=0； Netbios(&；ncb)； return true； ｝ 以上实例利用网络基本输入/输出系统NetBIOS创建了一个能获取主机 MAC （网络适配器）信息及其他一些信息的应用程序。由于作者水平有限，有不当之处请凉解。本文希望能起到抛砖引玉的作用。