第一章 NetBIOS

NetBIOS(Network Basic Input/Output System, 网络基本输入/输出系统)是一种与协议无关的网络API,但使用时必须注意:
1、通信双方必须使用一种通用的协议。Microsof TCP/IP默认情况下实现了NetBIOS接口,而IPX/SPX协议没有。Microsoft提供了一个实现了 NetBIOS接口的IPX/SPX版本。
2、NetBIOS的扩展版本NetBEUI(NetBIOS Extended User Interface,NetBIOS扩展用户接口)不是一种可路由的协议,如果通信双方之间隔了 个路由器,则不能实现通信,因为路由器会将收到的数据包丢弃。TCP/IP和IPX/SPX则属于可路由的协议,如果程序在很大程度上依赖于 NetBIOS,则必须至少在通信双方上安装一种可路由的协议。

LANA(LAN Adapter)编号:
每个LANA编号对应于网卡及传输协议的唯一组合。例如,某工作站安装了两张网卡,以及两种具有NetBIOS能力的传输协议(如TCP/IP和NetBEUI),那么总共有四个LANA编号:

0. TCP/IP - 网卡1
1. NetBEUI - 网卡1
2. TCP/IP - 网卡2
3. NetBEUI - 网卡2
通常LANA编号在0到9之间,除了LANA0之外,操作系统并不按某种固定顺序来分配这些编号,LANA0是默认的LANA,为了兼容以前的程序,可以将LANA0人工分配给一种特定协议。

NetBIOS名字:
一个NetBIOS名字长度为16个字符,其中第16个字符是为特殊用途保留的(用于区分不同的微软网络服务),在NetBIOS名字表内添加一个名字时,应将名字缓冲区初始化为空白。在Win32环境中,针对每个可用的LANA编号,每个进程都会为其维护一张NetBIOS名字表。若为LANA0添加一个名字,意味着你的应用程序只能在LANA0上同客户机建立连接。对每个LANA来说,能够添加的名字的最大数量是 254,编号从1到254(0和255由系统保留)。然而,每种操作系统都设置了一个低于 254的最大默认值。重设每个LANA编号时,我们可对此默认值进行修改。


NetBIOS名字共有两种类型:唯一名字和组名。“唯一名字”意味着它是独一无二的:网络上不能再有其他任何进程来注册这个名字。如果一台机器已注册了某名字,那么在你注册该名字时,便会收到一条“重复名字”出错提示。微软网络中的机器名采用的便是NetBIOS名字。机器启动时,会将自己的名字注册到本地的 Windows互联网命名服务器(WINS)。如果事前已有另一台机器注册了同样的名字,WINS服务器便会报错。WINS服务器维护着已注册的所有NetBIOS名字的一个列表,还可保存协议特有的一些信息。比如在TCP/IP网络中,WINS同时维护着NetBIOS名字以及注册那个名字的IP地址(亦即相应的机器)。假如配置网络时未为其分配一个WINS服务器,那么如何检查名字是否重复呢?这时便要采用在整个网络内“发广播”的形式。当一名发送者向全网络发出一条特殊的广播消息时,如果没有其他机器回应这条消息,便允许发送者使用该名字。而在另一方面,“组名”的作用是将数据同时发给多个接收者。组名并非一定要“独一无二”,它主要用于多播(多点发送)数据通信。

 

NetBIOS特性
NetBIOS同时提供了“面向连接”服务以及“无连接”服务。面向连接的服务,是指它允许两个客户机相互间建立一个会话,或者说建立一个“虚拟回路”。这种“会话”实际是一种双向的通信数据流,通信的每一方都可向另一方发送消息。面向连接的服务可担保在两个端点之间,任何数据都能准确无误地传送。在这种服务中,服务器通常将自己注册到一个已知的名字下。客户机会搜寻这个名字,以便建立与服务器的通信。就拿NetBIOS的情况来说,服务器进程会针对想通过它建立通信的每一个LANA编号,将自己的名字加入与其对应的名字表。而对位于其他机器上的客户来说,就可将一个服务名解析成机器名,然后要求同服务器进程建立连接。
“无连接”或数据报服务中,服务器并不将自己注册到一个特定的名下,事前不必先建好任何连接(即无连接)。对于数据的目的地址,客户机会将其定义成服务器相应进程对应的NetBIOS名字。这种类型的服务不提供任何保障,但同面向连接的服务相比,却可有更好的性能,如在使用数据报服务(无连接服务)时,省下了建立连接所需的开销。

 

NetBIOS编程基础:
用于NetBIOS的所有函数声明、常数等都是在头文件 Nb30.h内定义的。若想连接NetBIOS应用,唯一需要的库是Netapi32.lib。
NetBIOS API只有一个函数:
UCHAR Netbios(PNCB pNCB);
pNCB参数是一个指向网络控制块(Network Control Block,NCB)的指针,NCB结构包含了为执行一个NetBIOS命令,Netbios()函数需要用到的
全部信息。NCB结构定义如下:
typedef struct _NCB
{
 UCHAR  ncb_command; //要执行的NetBIOS命令。许多命令都可以同步或异步与ASYNCH(0X80)标志以及命令进行按位或(OR)运算。
 UCHAR  ncb_retcode; //操作的返回代码。在一个异步操作进行期间,函数会将该值设为NRC_PENDING。
 UCHAR  ncb_lsn; //对应一个本地会话编号,与当前环境内的一次会话有着唯一对应的关系。成功执行了一次NCBCALL或NCBLISTEN命令后,函数会返回一个新的会话编号。
 UCHAR  ncb_num; //指定本地名字的编号。每一次调用NCBADDNAME或NCBADDGRNAME命令,都会返回一个新的编号。针对所有数据报的命令,都必须使用一个有效的编号。
 PUCHAR  ncb_buffer; //指向数据缓冲区。对于需要发送数据的命令,该缓冲区包含了要送出的实际数据;对于需要接收数据的命令,则包含了要从Netbios()返回的数据。对于其它命令来说,如NCBENUM,缓冲区便是预定义的结构LANA_ENUM。
 WORD ncb_length; //缓冲区的长度,单位为字节数。对于需要接收数据的命令,Netbios()会将该值设为收到的字节数。若指定的缓冲区不够大,Netbios()会返回NRC_BUFLEN错误。
 UCHAR ncb_callname[NCBNAMSZ]; //用于重设NetBIOS环境的参数,或用于保存客户端的NetBIOS名字。
 UCHAR ncb_name[NCBNAMSZ]; //用于添加NetBIOS名字。
 UCHAR ncb_rto; //设定接收操作的超时期限。该值应设为500毫秒的整数倍。若为1,表示没有超时限制。该值是为NCBCALL和NCBLISTEN命令设置的,它们会影响后续的NCBRECV命令。
 UCHAR ncb_sto; //设定发送操作的超时期限。该值应设为500毫秒的整数倍。若为1,表示没有超时限制。该值是为NCBCALL和NCBLISTEN命令设置的,它们会影响后续的NCBSEND和NCBCHAINSEND命令。
 void (*ncb_post) (struct _NCB *); //指定异步命令完成后需要调用的函数的地址。这个函数的定义为:void CALLBACK PostRoutine(PNCB pncb); 其中pncb参数是指向已完成命令的网络控制块。
 UCHAR ncb_lana_num; //指定要在上面执行命令的LANA编号。
 UCHAR ncb_cmd_cplt; //指定操作的返回代码。异步操作进行期间,Netbios()会将这个值设为NRC_PENDING。
 UCHAR ncb_reserve[10]; //保留,必须为0
 UCHAR ncb_event; //指定设置为Nonsignaled状态的一个Windows事件对象的句柄。完成一个异步命令后,事件对象便会设置成Signaled状态。只应使用人工重设事件。假如ncb_command未设置ASYNCH标志,或者ncb_post不为0,那么该字段必须为0。否则,Netbios()会返回NRC_ILLCMD错误。
}* PNCB, NCB;


注意,并不是每次调用时都会用到该结构的全部成员,有些成员对应的是输出参数,在Netbios()返回之后才能读取。
调用Netbios()之前,应该先清零NCB结构,再填写必要的成员。
调用Netbios()时,可选择进行同步调用还是异步调用。所有NetBIOS命令本身都是同步的。而对于一个NCBLISTEN命令来说,当有一个客户端
建立了连接,或发生某种类型的错误时,对Netbios()的调用才会返回。要想异步调用一个命令,需要让NetBIOS命令同ASYNCH标志进行一次逻辑或运算。如果指定了ASYNCH标志,那么必须在ncb_post字段中指定一个回调用函数,或必须在ncb_event字段中指定一个事件句柄。执行一个异步命令时,从Netbios()返回的值是NRC_GOODRET(0X00),但ncb_cmd_cplt字段会设为NRC_PENDING(0xFF,待决),直到命令完成为止。
命令完成后,ncb_cmd_cplt字段会设为该命令的返回值。Netbios()也会在完成后将ncb_retcode字段设为命令的返回值。
以下是常规NetBIOS例程:
//
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Nb30.h>

//
// Enumerate all LANA numbers
//
int LanaEnum(LANA_ENUM *lenum)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBENUM;
 ncb.ncb_buffer=(PUCHAR)lenum;
 ncb.ncb_length=sizeof(LANA_ENUM);
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  printf("Error: Netbios: NCBENUM: %d\n",ncb.ncb_retcode);
  return ncb.ncb_retcode;
 }
 return NRC_GOODRET;
}

//
// Reset each LANA listed in the LANA_ENUM structure. Also, set the NetBIOS environment (max sessions, max name table size),
// and use the first NetBIOS name.
//
int ResetAll(LANA_ENUM *lenum, UCHAR ucMaxSession, UCHAR ucMaxName, BOOL bFirstName)
{
 NCB ncb;
 int i;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBRESET;
 ncb.ncb_callname[0]=ucMaxSession;
 ncb.ncb_callname[2]=ucMaxName;
 ncb.ncb_callname[3]=(UCHAR)bFirstName;
 for(i=0;i<lenum->length;++i)
 {
  ncb.ncb_lana_num=lenum->lana[i];
  if(Netbios(&ncb)!=NRC_GOODRET)
  {
   printf("Error: Netbios: NCBRESET[%d]: %d\n",ncb.ncb_lana_num,ncb.ncb_retcode);
   return ncb.ncb_retcode;
  }
 }
 return NRC_GOODRET;
}

//
// Add the given name to the given LANA number. return the name number of the registered name.
//
int AddName(int lana, char *name, int *num)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBADDNAME;
 ncb.ncb_lana_num=lana;
 memset(ncb.ncb_name,' ',NCBNAMSZ);
 strncpy((char*)ncb.ncb_name,name,strlen(name));
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  printf("Error: Netbios: NCBADDNAME[lana=%d;name=%s]: %d\n",lana,name,ncb.ncb_retcode);
  return ncb.ncb_retcode;
 }
 *num=ncb.ncb_num;
 return NRC_GOODRET;
}

//
// Add the given NetBIOS group name to the given LANA number, return the name number of the added name.
//
int AddGroupName(int lana, char * name, int *num)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBADDGRNAME;
 ncb.ncb_lana_num=lana;
 memset(ncb.ncb_name,' ',NCBNAMSZ);
 strncpy((char*)ncb.ncb_name,name,strlen(name));
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  printf("Error: Netbios: NCBADDGRNAME[lana=%d;name=%s]: %d\n",lana,name,ncb.ncb_retcode);
  return ncb.ncb_retcode;
 }
 *num=ncb.ncb_num;
 return NRC_GOODRET;
}

//
// Delete the given NetBIOS name from the name table associated with the LANA number
//
int DelName(int lana, char *name)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBDELNAME;
 ncb.ncb_lana_num=lana;
 memset(ncb.ncb_name,' ',NCBNAMSZ);
 strncpy((char*)ncb.ncb_name,name,strlen(name));
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  printf("Error: Netbios: NCBDELNAME[lana=%d;name=%s]: %d\n",lana,name,ncb.ncb_retcode);
  return ncb.ncb_retcode;
 }
 return NRC_GOODRET;
}

//
// Send len bytes from the data buffer on the given session (lsn) and lana number
//
int Send(int lana, int lsn, char *data, DWORD len)
{
 NCB ncb;
 int  retcode;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBSEND;
 ncb.ncb_buffer=(PUCHAR)data;
 ncb.ncb_length=(unsigned short)len;
 ncb.ncb_lana_num=lana;
 ncb.ncb_lsn=lsn;
 retcode=Netbios(&ncb);
 return retcode;
}

//
// Receive up to len bytes into the data buffer on the given session (lsn) and number
//
int Recv(int lana, int lsn, char *buffer, DWORD *len)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBRECV;
 ncb.ncb_buffer=(PUCHAR)buffer;
 ncb.ncb_length=(unsigned short)*len;
 ncb.ncb_lana_num=lana;
 ncb.ncb_lsn=lsn;
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  *len=-1;
  return ncb.ncb_retcode;
 }
 *len=ncb.ncb_length;
 return NRC_GOODRET;
}

//
//Disconnect the given session on the given lana number
//
int Hangup(int lana, int lsn)
{
 NCB ncb;
 int retcode;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBHANGUP;
 ncb.ncb_lsn=lsn;
 ncb.ncb_lana_num=lana;
 retcode=Netbios(&ncb);
 return retcode;
}

//
// Cancel the given asynchronous command denoted in the NCB structure parameter
//
int Cancel(PNCB pncb)
{
 NCB ncb;
 ZeroMemory(&ncb,sizeof(NCB));
 ncb.ncb_command=NCBCANCEL;
 ncb.ncb_buffer=(PUCHAR)pncb;
 ncb.ncb_lana_num=pncb->ncb_lana_num;
 if(Netbios(&ncb)!=NRC_GOODRET)
 {
  printf("Error: Netbios: NCBCANCEL: %d\n",ncb.ncb_retcode);
  return ncb.ncb_retcode;
 }
 return NRC_GOODRET;
}

//
// Format the given NetBIOS name so that it is printable. Any unprintable characters are replaced by a period.
// The out name buffer is the returned string, witch is assumed to be at least NCBNAMSZ+1 characters in length.
//
int FormatNetbiosName(char *nbname, char *outname)
{
 int i;
 strncpy(outname,nbname,NCBNAMSZ);
 outname[NCBNAMSZ-1]='\0';
 for(i=0;i<NCBNAMSZ-1;++i)
 {
  //If the character isn't printable, replace it with a '.'
  if(!((outname[i]>=32) && (outname[i]<=126)))
   outname[i]='.';
 }
 return NRC_GOODRET;
}
///
说明:
1、LANA_ENUM结构的定义如下:
typedef struct _LANA_ENUM
{
 UCHAR length;  //本地机器上共有多少个LANA编号,也指示了lana数组有多少个元素被填充LANA编号
 UCHAR lana[MAX_LANA+1]; //由实际的LANA编号构成的数组。
}LANA_ENUM, *PLANA_ENUM;


2、对一个NetBIOS程序来说,必须重设计划使用的每个LANA编号。一旦拥有一个LANA_ENUM结构,并有来自LanaEnum的LANA编号,便可针对结构中的每个LANA编号,调用NCBRESET命令来重设它们。尽管某些平台(比如 Windows 95)并不要求我们对打算使用的每个LANA编号进行重设,但最好还是那样做。 Windows NT要求我们在正式使用前对每个LANA编号进行重设;否则,对Netbios的其他调用就会返回错误代码52(亦即NRC_ENVNOTDEF)。除此以外,重设一个LANA编号时,可通过ncb_callname的字符字段,设置特定的NetBIOS环境参数。ResetAll的其他参数与这些环境设定是对应的。函数用ucMaxSession参数来设置ncb_callname的字符0,它用于指定可同时进行的最大会话数量。通常,操作系统会强制使用一个比最大值小的默认值。举个例子来说, Windows NT 4的最大默认值为64个并发会话。ResetAll将ncb_callname的字符2(用于指定可为每个LANA增加的最大NetBIOS名字数量)设为ucMaxName参数的值。同样,操作系统也会强加一个默认的最大值。最后,ResetAll会将字符3(用于NetBIOS客户机)设为bFirstName参数的值。通过将此参数设为TRUE,一个客户机便能将机器名作为自己的NetBIOS进程名使用。
因此,那个客户机可与一个服务器建立连接,并在不允许任何进入连接的前提下,向其发送数据。这一选项有效缩短了初始化时间。而假若将一个NetBIOS名字加入本地名字表,那么必须为此付出相应的代价。


3、每个LANA编号永远对应一个名字表。如果你的应用程序需要与每个可用的LANA通信,便需为每个LANA增加进程名。成功添加一个名字后,Netbios会在ncb_num字段中,返回同新增名字对应的NetBIOS名字编号。可随数据报一起使用该值,以标定始发的NetBIOS进程。增加一个唯一名字时(不是组名,组名不存在这种情况),如果网络中的另一个进程已经使用了要增加的名字,就会出现NRC_DUPNAME错误。


4、NCBRECV和NCBSEND命令用来收发数据,必须提供LANA编号和相应的会话编号,若成功执行了一个NCBCALL或NCBLISTEN命令,就会返回相应的会话编号。客户端用NCBCALL命令同一个已知的服务建立连接,而服务器端使用NCBLISTEN命令等待进入的客户端连接。若两个命令中有一个成功,NetBIOS接口便会建立一个会话,并为其赋予独一无二的整数标识符。在一个面向会话的连接中,对数据的发送来说,要注意的一个重要的问题是在调用Send函数时,接收方执行一个Recv函数之前,Send函数会一直等待下去。这意味着假如发送方送出大量数据,但接收方还没有读取它,便会耗用大量本地资源对数据进行缓冲。因此,一个良好的编程习惯是同时只执行少数几个NCBSEND或NCBCHAINSEND命令。为进一步解决这个问题,请换用Netbios命令NCBSENDNA和NCBCHAINSENDNA命令。通过这两个命令,便不必从接收方那里等待收到确认消息,而是直接送出数据了事。


5、可调用NetBIOS命令NCBHANGUP来从容关闭一个建好的会话。执行该命令时,对于指定的会话来说,所有尚未进行的接收调用都会中止,并返回一个“会话关闭”错误:NRC_SCLOSED(0x0A)。如果还存在任何没有执行的发送命令,则Hangup命令会暂时停止封锁执行,等那些命令完成了再说。

///
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "netbios_common.h"  //这个头文件包含了所有上面的通用例程

DWORD WINAPI ClientThread(PVOID lpParam);
int Listen(int lana,char *name);

//
//Description:
// This function is called when an asynchronous listen completes.
// If no error occured, create a thread to handle the client.
// Also, post another listen for other client connections on the same lana numer.
//
void CALLBACK ListenCallback(PNCB pncb)
{
 HANDLE hThread;
 DWORD dwThreadId;
 if(pncb->ncb_retcode!=NRC_GOODRET)
 {
  printf("ERROR: ListenCallback: %d\n", pncb->ncb_retcode);
  return;
 }
 Listen(pncb->ncb_lana_num,SERVER_NAME);

 hThread=CreateThread(NULL,0,ClientThread,(PVOID)pncb,0,&dwThreadId);
 if(hThread==NULL)
 {
  printf("ERROR: CreateThread: %d\n",GetLastError());
  return;
 }
 CloseHandle(hThread);

 return;
}

//
//Description:
// The client thread blocks for data sent from clients and simply sends it back to them.
// This is a continuous loop until the session is closed or an error occurs. If the read
// or write fails with NRC_SCLOSED, the session has closed gracefully--so exit the loop.
//
DWORD WINAPI ClientThread(PVOID lpParam)
{
 PNCB pncb=(PNCB)lpParam;
 NCB ncb;
 char szRecvBuff[MAX_BUFFER];
 DWORD dwBufferLen=MAX_BUFFER,dwRetVal=NRC_GOODRET;
 char szClientName[NCBNAMSZ+1];
 
 FormatNetbiosName((char *)pncb->ncb_callname,szClientName);

 while(1)
 {
  dwBufferLen=MAX_BUFFER;
  dwRetVal=Recv(pncb->ncb_lana_num,pncb->ncb_lsn,szRecvBuff,&dwBufferLen);
  if(dwRetVal!=NRC_GOODRET)
   break;
  szRecvBuff[dwBufferLen]=0;
  printf("READ [LANA=%d]: '%s'\n",pncb->ncb_lana_num,szRecvBuff);
  dwRetVal=Send(pncb->ncb_lana_num,pncb->ncb_lsn,szRecvBuff,dwBufferLen);
  if(dwRetVal!=NRC_GOODRET)
   break;
 }
 printf("Client '%s' on LANA %d disconnected\n",szClientName,pncb->ncb_lana_num);
 if(dwRetVal!=NRC_SCLOSED)
 {
  ZeroMemory(&ncb,sizeof(NCB));
  ncb.ncb_command=NCBHANGUP;
  ncb.ncb_lsn=pncb->ncb_lsn;
  ncb.ncb_lana_num=pncb->ncb_lana_num;
  if(Netbios(&ncb)!=NRC_GOODRET)
  {
   printf("ERROR: Netbios: NCBHANGUP: %d\n",ncb.ncb_retcode);
   dwRetVal=ncb.ncb_retcode;
  }
  GlobalFree(pncb);
  return dwRetVal;
 }
 GlobalFree(pncb);
 return NRC_GOODRET;
}

//
//Description:
// Post an asynchronous listen with a callback function. Create an NCB structure for use by the callback (since
// it needs a global scope).
//
int Listen(int lana,char *name)
{
 PNCB pncb=NULL;
 pncb=(PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeof(NCB));
 pncb->ncb_command=NCBLISTEN | ASYNCH;
 pncb->ncb_lana_num=lana;
 pncb->ncb_post=ListenCallback;
 memset(pncb->ncb_name,' ',NCBNAMSZ);
 strncpy((char*)pncb->ncb_name,name,strlen(name));
 memset(pncb->ncb_callname,' ',NCBNAMSZ);
 //
 //将ncb_callname字段的第一个字符设为一个星号(*),指出服务器可从任何客户机接受连接请求。如果不这样做,
 //也可在ncb_callname字段中设置一个特定的名字,只允许注册了那个特定名字的客户机建立与服务器的连接。
 //
 pncb->ncb_callname[0]='*';
 if(Netbios(pncb)!=NRC_GOODRET)
 {
  printf("ERROR: Netbios: NCBLISTEN: %d\n",pncb->ncb_retcode);
  return pncb->ncb_retcode;
 }
 return NRC_GOODRET;
}

//
//Description:
// Initialize the NetBIOS interface, allocate some resources, add the server name to each LANA, and post an
// asynch NCBLISTEN on each LANA with the appropriate callback. The wait for incoming client connections, at
// which time, spawn a worker thread to handle them. The main thread simply waits while the server threads are
// handling client requests. You wouldn't do this in a real application, but this sample is for illustrative
// purposes only.
//
int main(int argc, char* argv[])
{
 LANA_ENUM lenum;
 int i,num;
 if(LanaEnum(&lenum)!=NRC_GOODRET)
  return 1;
 if(ResetAll(&lenum,254,254,FALSE)!=NRC_GOODRET)
  return 1;
 for(i=0;i<lenum.length;++i)
 {
  AddName(lenum.lana[i],SERVER_NAME,&num); // add the service name to each LANA, so that the client can connect to the server by this service name.
  Listen(lenum.lana[i],SERVER_NAME);
 }

 while(1){
  Sleep(5000);
 }
 return 0;
}
//

对面向连接的会话来说,数据是由最基层的协议加以缓冲的,所以并非一定要发出异步调用。发出一个接收命令后,Netbios函数会将可用的
数据立即传给现成的缓冲区,而且调用会立即返回。而假若没有数据可用,接收调用便会暂停,直到有数据可用,或者会话断开为止。同样的道理也适用于发送命令:若网络堆栈能通过线缆立即送出数据,或者能将数据缓存在堆栈中,调用便会立即返回。而假若系统没有足够的缓冲区空间来立即送出数据,发送调用便会暂停,直到有可用的空间为止。要想避免这种形式的数据延误,可对数据的收发使用ASYNCH(异步)命令。若执行的是异步发送或接收命令,那么相应的缓冲区必须有一个较大的容量,超出调用进程本身的范围之外。避免收发延误的另一个办法是使用ncb_sto和ncb_rto这两个字段。其中,ncb_sto字段用于设置发送延时。若为其指定一个非零值,便相当于为命令的执行规定了一个超时时限。注意时间长度是以500毫秒为单位指定的。如命令超时,便需要立即返回,数据则不会送出。接收超时的设置(ncb_rto)道理是一样的:如在预先规定好的时间内没有数据到达,则调用返回,没有数据输入缓冲区。上面的例子是一个回调模型,下面例子是一个事件模型,事件模型与回调模型相似,唯一的区别在于对回调模型来说,系统会在异步操作完成后执行回调代码;而对事件模型来说,程序必须通过对事件状态的检查,来核实操作是否完成(这一步主要是用来阻塞程序流程,让程序执行流程暂停,起到同步作用)。这些属于标准的Win32事件,可在此选用任何同步例程,比如WaitForSingleEvent和WaitForMultipleEvents等等。事件模型显得更有效率。

//
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "netbios_common.h"  //这个头文件包含了所有上面的通用例程

#define MAX_SESSIONS 254
#define MAX_NAMES 254
#define MAX_BUFFER 2048
#define SERVER_NAME "TEST-SERVER-1"

NCB *g_Clients=NULL;

DWORD WINAPI ClientThread(PVOID lpParam)
{
 PNCB pncb=(PNCB)lpParam;
 NCB ncb;
 char szRecvBuff[MAX_BUFFER],
  szClientName[NCBNAMSZ+1];
 DWORD dwBufferLen=MAX_BUFFER,
  dwRetVal=NRC_GOODRET;
 FormatNetbiosName((char *)pncb->ncb_callname,szClientName);
 while(1)
 {
  dwBufferLen=MAX_BUFFER;
  dwRetVal=Recv(pncb->ncb_lana_num,pncb->ncb_lsn,szRecvBuff,&dwBufferLen);
  if(dwRetVal!=NRC_GOODRET) break;
  szRecvBuff[dwBufferLen]=0;
  printf("READ [LANA=%d]: '%s'\n",pncb->ncb_lana_num,szRecvBuff);
  dwRetVal=Send(pncb->ncb_lana_num,pncb->ncb_lsn,szRecvBuff,dwBufferLen);
  if(dwRetVal!=NRC_GOODRET) break;
 }
 printf("Client '%s'  on LANA %d disconnected\n",szClientName,pncb->ncb_lana_num);
 if(dwRetVal!=NRC_SCLOSED)
 {
  ZeroMemory(&ncb,sizeof(NCB));
  ncb.ncb_command=NCBHANGUP;
  ncb.ncb_lsn=pncb->ncb_lsn;
  ncb.ncb_lana_num=pncb->ncb_lana_num;
  if(Netbios(&ncb)!=NRC_GOODRET)
  {
   printf("ERROR: Netbios: NCBHANGUP: %d\n",ncb.ncb_retcode);
   GlobalFree(pncb);
   dwRetVal=ncb.ncb_retcode;
  }
 }
 GlobalFree(pncb);
 return NRC_GOODRET;
}

int Listen(PNCB pncb,int lana,char *name)
{
 pncb->ncb_command=NCBLISTEN | ASYNCH;
 pncb->ncb_lana_num=lana;
 memset(pncb->ncb_name,' ',NCBNAMSZ);
 strncpy((char*)pncb->ncb_name,name,strlen(name));
 memset(pncb->ncb_callname,' ',NCBNAMSZ);
 pncb->ncb_callname[0]='*';
 if(Netbios(pncb)!=NRC_GOODRET)
 {
  printf("ERROR: Netbios: NCBLISTEN: %d\n",pncb->ncb_retcode);
  return pncb->ncb_retcode;
 }
 return NRC_GOODRET;
}

//
// Function: main
// Description:
// Initialize the NetBIOS interface, allocate some resources, and post asynchronous listens on each LANA using
// events. Wait for an event to be triggered, and then handle the client's connection.
//
int main(int argc, char* argv[])
{
 PNCB pncb=NULL;
 HANDLE hArray[64],
  hThread;
 DWORD dwHandleCount=0,
  dwRet,
  dwThreadId;
 int i,
  num;
 LANA_ENUM lenum;

 if(LanaEnum(&lenum)!=NRC_GOODRET) return 1;
 if(ResetAll(&lenum,(UCHAR)MAX_SESSIONS,(UCHAR)MAX_NAMES,FALSE)!=NRC_GOODRET) return 1;

 g_Clients=(PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeof(NCB)*lenum.length);
 for(i=0;i<lenum.length;++i)
 {
  hArray[i]=g_Clients[i].ncb_event=CreateEvent(NULL,TRUE,FALSE,NULL);
  AddName(lenum.lana[i],SERVER_NAME,&num);
  Listen(&g_Clients[i],lenum.lana[i],SERVER_NAME);
 }
 printf("Server started.\n");
 while(1)
 {
  //Wait until a client connects,调用WaitForMultipleObjects。在其中一个句柄收到信号之前(即进入传信状态之前),
  //这个调用会一直等待下去。一旦事件控制数组中的一个或多个句柄进入传信状态,WaitForMultipleObjects调用便会完毕
  dwRet=WaitForMultipleObjects(lenum.length,hArray,FALSE,INFINITE);
  if(dwRet==WAIT_FAILED)
  {
   printf("ERROR: WaitForMultipleObjects: %d\n",GetLastError());
   break;
  }

  //Go through all the NCB structures to see whether more than one succeeded. If ncb_cmd_cplt is not NRC_PENDING,
  //there is a client; create a thread, and hand off a new NCB structure to the thread. We need to reuse the original
  //NCB for other client's connections.
  for(i=0;i<lenum.length;++i)
  {
   if(g_Clients[i].ncb_cmd_cplt!=NRC_PENDING)
   {
    printf("There is a client connection come in, create a client thread to handle it\n");
    pncb=(PNCB)GlobalAlloc(GMEM_FIXED,sizeof(NCB));
    memcpy(pncb,&g_Clients[i],sizeof(NCB));
    pncb->ncb_event=0;
    hThread=CreateThread(NULL,0,ClientThread,(LPVOID)pncb,0,&dwThreadId);
    CloseHandle(hThread);
    ResetEvent(hArray[i]);
    Listen(&g_Clients[i],lenum.lana[i],SERVER_NAME);
   }
  }
 }

 for(i=0;i<lenum.length;++i)
 {
  DelName(lenum.lana[i],SERVER_NAME);
  CloseHandle(hArray[i]);
 }
 GlobalFree(g_Clients);

 return 0;
}
//

注意:
对前述的两种服务器工作模式来说,都可能存在拒绝为客户机提供服务的情况。NCBLISTEN完成后,在调用回调函数之前,或在事件收到信号之前,都存在少许的延时。只有在经历了几个语句之后,服务器才会执行另一个NCBLISTEN命令。例如,假定服务器在LANA2上接受了一个客户机的连接。随后,在服务器针对同一个 LANA编号执行另一个NCBLISTEN之前,假如又有一个客户机试图建立连接,便会收到一个名为NRC_NOCALL(0x14)的错误信息。这意味着,对指定的名字来说,目前尚未在它上面执行NCBLISTEN。要防止此类情况的出现,服务器必须为每个LANA都执行多个NCBLISTEN命令。

 

下面是NetBIOS客户端例子,它与异步事件服务器类似,客户机首先采取大家已熟知的一系列初始化步骤。它为每个LANA编号的名字表增添自己的名字并执行一个异步连接命令。主循环会等候某个事件进入传信状态(即至少有一个连接成功)。随后,代码遍历所有NCB结构,每个LANA编号都对应一个结构。它会检查ncb_cmd_cplt的状态。如发现它设为NRC_PENDING(待决),代码就会取消异步命令;若命令完成(亦即建立了连接),但NCB并不与传信状态的那个NCB相符(由来自WaitForMultipleObjects调用的返回值指定),代码便会断开连接。假如服务器正在自己那一端对每个LANA进行监听扫描,同时客户机正在尝试在其每个LANA上建立连接,那么就可能出现成功建立多个连接的情况。此时,代码会用NCBHANGUP命令关掉多余的连接—它只需通过一个信道进行通信。由于允许双方都同时尝试建立一个连接,所以连接成功的机率大增。
//
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "netbios_common.h"  //这个头文件包含了所有上面的通用例程

#define MAX_SESSIONS 254
#define MAX_NAMES 254
#define MAX_BUFFER 1024

char szServerName[NCBNAMSZ];

int Connect(PNCB pncb,int lana,char *server,char *client)
{
 pncb->ncb_command=NCBCALL | ASYNCH;
 pncb->ncb_lana_num=lana;

 memset(pncb->ncb_name,' ',NCBNAMSZ);
 strncpy((char*)pncb->ncb_name,client,strlen(client));
 memset(pncb->ncb_callname,' ',NCBNAMSZ);
 strncpy((char*)pncb->ncb_callname,server,strlen(server));

 if(Netbios(pncb)!=NRC_GOODRET)
 {
  printf("ERROR: Netbios: NCBCALL: %d\n",pncb->ncb_retcode);
  return pncb->ncb_retcode;
 }
 return NRC_GOODRET;
}

int main(int argc, char* argv[])
{
 HANDLE *hArray;
 NCB *pncb;
 char szSendBuff[MAX_BUFFER];
 DWORD dwBufferLen,
  dwRet,
  dwIndex;
 LANA_ENUM lenum;
 int dwNum,i;

 if(argc!=3)
 {
  printf("usage: NetBIOS_App2 CLIENT-NAME SERVER-NAME\n");
  return 1;
 }
 if(LanaEnum(&lenum)!=NRC_GOODRET) return 2;
 if(ResetAll(&lenum,(UCHAR)MAX_SESSIONS,(UCHAR)MAX_NAMES,FALSE)!=NRC_GOODRET) return 2;
 
 strcpy(szServerName,argv[2]);
 hArray=(HANDLE *)GlobalAlloc(GMEM_FIXED, sizeof(HANDLE)*lenum.length);
 pncb=(NCB *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeof(NCB)*lenum.length);
 printf("number of lana: %d\n",lenum.length);
 for(i=0;i<lenum.length;++i)
 {
  hArray[i]=CreateEvent(NULL,TRUE,FALSE,NULL);
  pncb[i].ncb_event=hArray[i];
  AddName(lenum.lana[i],argv[1],&dwNum);
  Connect(&pncb[i],lenum.lana[i],szServerName,argv[1]);
 }

 dwIndex=WaitForMultipleObjects(lenum.length,hArray,FALSE,INFINITE);
 if(dwIndex==WAIT_FAILED)
 {
  printf("ERROR: WaitForMultipleObjects: %d\n",GetLastError());
 }
 else
 {
  //If more than one connection succeeds, hang up the extra connection. We'll use the connection that was returned by
  //WaitForMultipleObjects. Otherwise, if it's still pending, cancel it.
  for(i=0;i<lenum.length;++i)
  {
   if((DWORD)i!=dwIndex)
   {
    if(pncb[i].ncb_cmd_cplt==NRC_PENDING)
     Cancel(&pncb[i]);
    else
     Hangup(pncb[i].ncb_lana_num,pncb[i].ncb_lsn);
   }
  }
  printf("Connected on LANA: %d\n",pncb[dwIndex].ncb_lana_num);

  for(i=0;i<20;++i)
  {
   wsprintf(szSendBuff,"Test message %03d",i);
   dwRet=Send(pncb[dwIndex].ncb_lana_num,pncb[dwIndex].ncb_lsn,szSendBuff,strlen(szSendBuff));
   if(dwRet!=NRC_GOODRET)
   {
    printf("ERROR: Send: %d\n",dwRet);
    break;
   }
   dwBufferLen=MAX_BUFFER;
   dwRet=Recv(pncb[dwIndex].ncb_lana_num,pncb[dwIndex].ncb_lsn,szSendBuff,&dwBufferLen);
   if(dwRet!=NRC_GOODRET)
   {
    printf("ERROR: Recv: %d\n",dwRet);
    break;
   }
   szSendBuff[dwBufferLen]=0;
   printf("Read: '%s'\n",szSendBuff);
  }
  Hangup(pncb[dwIndex].ncb_lana_num,pncb[dwIndex].ncb_lsn);
 }

 for(i=0;i<lenum.length;++i)
 {
  DelName(lenum.lana[i],argv[1]);
  CloseHandle(hArray[i]);
 }
 GlobalFree(hArray);
 GlobalFree(pncb);

 return 0;
}
//

数据报(Datagram)的工作原理:
数据报是一种无连接的通信方法,只需要用目标NetBIOS名字为发出的每个包指定目标地址,然后简单的发出包即可。有三种发送方式:
第一种是指定数据报发给一个唯一名,这种情况下,只有注册了目标名字的那个进程(接收端)才能接收数据;第二种是指定数据报发给
一个组名,这样只有注册了目标组名的那些进程(接收端)才能接收到数据;第三种是将数据报广播到整个网络,局域网内任何一个工作
站上的任何进程都可以接收到数据。使用命令NCBDGSEND发送数据报到唯一名或组名,使用NCBDGSENDBC命令广播数据报到整个网络。

 

发送数据报的过程如下:
首先,将ncb_num字段设为一个由NCBADDNAME或NCBADDGRNAME命令返回的名字编号,这个编号是发送端的;然后,将ncb_buffer设置为一个缓冲区的地址,该缓冲区中存放将要发送的数据;接着,将ncb_lana_num设置为一个特定的LANA编号,数据报会通过这个LANA编号发送出去;最后,将ncb_callname设置为接收端的NetBIOS名字,这可以是一个唯一名,也可以是一个组名,或者不要设置这个字段,这三种情况分别对应于数据报的三种发送方式。

如果数据报到达接收端,但接收端没有一个处于“待决(pending)”状态的接收命令,则数据报会被无情地抛弃,接收到无法恢复这个被抛弃
了的数据包(除非发送端重新发送它)。

相应的,数据报的接收也有三种方式,接收那种目标地址为唯一名或组名的数据报要使用NCBDGRECV命令。接收广播数据报要使用NCBDGRECVBC命令。对于前两种接收方式,接收端要先注册数据报的目标NetBIOS名(唯一名或组名)。接收过程如下:首先,将ncb_num设置为由NCBADDNAME或NCBADDGRNAME命令返回的名字编号(接收端的),这个编号指出接收端要在哪个名字上监听进入的数据报,若将ncb_num设置成0xFF,则表示可以接收发给该进程的NetBIOS名字表中任何名字的数据报;然后创建一个用来接收数据的缓冲区,将ncb_buffer设置为该缓冲区的地址,将ncb_length设置成缓冲区的大小;接着,将ncb_lana_num设置为要在上面等候数据报的LANA编号;最后,通过Netbios()函数调用NCBDGRECV或NCBDGRECVBC命令接收数据。如果函数调用成功,则ncb_length字段包含了接收到的实际字节数,而ncb_callname则包含了发送端进程的NetBIOS名字。

 

其它NetBIOS命令:
1、查询适配器(Adapter,一般是指网卡)状态的命令:
NCBASTAT命令可获得本地或远程机器上相应LANA编号有关的适配器信息,该命令返回一个ADAPTER_STATUS结构和大量的NAME_BUFFER结构。这两
个结构的定义如下:
typedef struct _ADAPTER_STATUS{
 UCHAR adapter_address[6]; //适配器的MAC地址
 UCHAR rev_major;
 UCHAR reserved0;
 UCHAR adapter_type;
 UCHAR rev_minor;
 WORD duration;
 WORD frmr_recv;
 WORD frmr_xmit;
 WORD iframe_recv_err;
 WORD xmit_aborts;
 DWORD xmit_success;
 DWORD recv_success;
 WORD iframe_xmit_err;
 WORD recv_buff_unavail;
 WORD tl_timeouts;
 WORD ti_timeouts;
 DWORD reserved1;
 WORD free_ncbs;
 WORD max_cfg_ncbs;
 WORD max_ncbs;
 WORD xmit_buf_unavail;
 WORD max_dgram_size; //数据报最大长度
 WORD pending_sess;
 WORD max_cfg_sess;
 WORD max_sess; //最大会话数
 WORD max_sess_pkt_size;
 WORD name_count; //NCBASTAT命令总共返回了多少个NAME_BUFFER结构。对每个LANA来说,最多能支持的NetBIOS名字为254个,所以可选择提供一个足够大的缓冲区,容下所有名字;或将ncb_length设为0,调用一次适配器状态命令,Netbios函数返回之后,它会提供必要的缓冲区大小设置。
}ADAPTER_STATUS, *PADAPTER_STATUS;
typedef struct _NAME_BUFFER{
 UCHAR name[NCBNAMSZ];
 UCHAR name_num;
 UCHAR name_flags;
}NAME_BUFFER, *PNAME_BUFFER;

要想调用NCBASTAT,需要设置的字段包括ncb_command,ncb_buffer,ncb_length,ncb_lana_num以及ncb_callname。若ncb_callname的第一个字符是一个星号(*),那么只有那些由调用进程增添的NetBIOS名字才会返回。然而,如果用一个适配器状态命令调用Netbios,在当前进程的名字表内增添一个“唯一”名字,然后在ncb_callname字段中使用那个名字,那么所有NetBIOS名字都会在本地进程的名字表内注册,另外还要加上由系统注册的任何名字。还可针对远程机器,执行适配器状态查询命令,需要将ncb_callname字段设为远程工作站的机器名。

 

注意:
1)、所有Microsoft机器名字都将其第16个字节设为0,应该用空格来替代它。
2)、如果一台机器上有多个适配器(即具有多个MAC地址),由于NetBIOS没办法调查一个LANA到底与哪个适配器以及协议绑定到一起,所以此时需要对返回的值进行过滤。
3)、假如安装了远程访问服务(RAS),系统也会为那些连接分配对应的LANA编号。若RAS连接已经断开(即未建立连接),那些LANA上的适配器状态会为MAC地址返回全零值。若RAS连接已经建立,MAC地址便与RAS分配给其所有虚拟网络设备的MAC地址相同。
4)、在执行一次远程适配器状态查询时,必须通过两台机器均已正确安装的一种传送协议进行。
例子:
//
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "netbios_common.h"  //这个头文件包含了所有上面的通用例程

int main(int argc, char* argv[])
{
 LANA_ENUM lenum;
 if(LanaEnum(&lenum)!=NRC_GOODRET) return 1;
 if(ResetAll(&lenum,254,254,FALSE)!=NRC_GOODRET) return 1;
 printf("num of LANA: %d\n",lenum.length);
 for(int i=0;i<lenum.length;++i)
 {
  printf("LANA %d\n",lenum.lana[i]);
  PVOID buff=NULL;
  buff=(PVOID)GlobalAlloc(GMEM_FIXED,sizeof(ADAPTER_STATUS)+sizeof(NAME_BUFFER)*254);
  PNCB pncb=NULL;
  pncb=(PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(NCB));
  pncb->ncb_command=NCBASTAT;
  pncb->ncb_lana_num=lenum.lana[i];
  pncb->ncb_callname[0]='*';
  pncb->ncb_buffer=(PUCHAR)buff;
  pncb->ncb_length=sizeof(ADAPTER_STATUS)+sizeof(NAME_BUFFER)*254;
  if(Netbios(pncb)!=NRC_GOODRET)
  {
   printf("ERROR: Netbios: NCBASTAT %d\n",pncb->ncb_retcode);
   return 1;
  }
  printf("Adapter MAC address: %d\n",((PADAPTER_STATUS)buff)->adapter_address);
  printf("Adapter type: %d\n",((PADAPTER_STATUS)buff)->adapter_type);
  printf("Max length of datagram: %d Bytes\n",((PADAPTER_STATUS)buff)->max_dgram_size);
  printf("Max sessions: %d\n",((PADAPTER_STATUS)buff)->max_sess);
  printf("count of name buffers: %d\n",((PADAPTER_STATUS)buff)->name_count);
 }
 return 0;
}
//

2、查找名字:
NCBFINDNAME命令只能在Windows NT及Windows 2000操作系统上使用,用于调查一个指定的NetBIOS名字是由谁注册的。要想进行一次成功的查找名字查询,进程必须在名字表内添加其唯一名字。该命令要求设置的字段包括ncb_command、ncb_lana_num、ncb_buffer和ncb_length。
查询返回的是一个FIND_NAME_HEADER结构,以及数量不定的FIND_NAME_BUFFER结构。结构定义如下:
typedef struct _FIND_NAME_HEADER{
 WORD node_count; //指出总共返回了多少个FIND_NAME_BUFFER结构
 UCHAR reserved;
 UCHAR unique_group; //指定的NetBIOS名字是唯一名还是组名,0表示唯一名;1表示组名。
}FIND_NAME_HEADER, *PFIND_NAME_HEADER;
typedef struct _FIND_NAME_BUFFER{
 UCHAR length;
 UCHAR access_control;
 UCHAR frame_control;
 UCHAR destination_addr[6]; //含了执行查询的那个适配器的MAC地址
 UCHAR source_addr[6]; //包含了注册指定名字的那个网络适配器的MAC地址
 UCHAR routing_info[18];
}FIND_NAME_BUFFER, *PFIND_NAME_BUFFER;


注意:
1)、和NCBASTAT命令一样,假如NCBFINDNAME命令在执行时将缓冲区的长度设为 0,那么Netbios函数会返回所需的长度,同时返回一个名为NRC_BUFLEN的错误。
2)、针对本地机器的任何LANA编号,均可执行一次查找名字查询。对于本地网络的任何有效LANA编号来说,返回的数据应当是完全相同的。
例如,我们可针对一个RAS连接执行该命令,以判断一个名字是否在远程网络上进行了注册。在 Windows NT 4.0中,存在着一个错误:若通过TCP/IP执行一次查找名字查询,那么Netbios会返回虚假的信息。因此,若计划在Windows NT 4.0下使用这种查询,请务必选择与另一种传送协议对应的LANA编号,不要依赖TCP/IP。

3、将传送协议同LANA编号对应起来:
如何将TCP/IP和NetBEUI这样的传送协议同它们的LANA编号对应起来呢?由于Netbios()函数无法实现这种功能,所以必须使用到insock2,
一个名为WSAEnumProtocols的Winsock 2函数可返回与可用的传送协议有关的信息。基本步骤是:先用WSAStartup函数装载Winsock 2,调用WSAEnumProtocols,再对调用返回的WSAPROTOCOL_INFO结构进行检查核实。WSAEnumProtocols函数需要用到一个缓冲区地址参数和缓冲区长度参数。首先用一个空缓冲区地址和长度0来调用该函数。当然,调用会失败,但随后在缓冲区长度参数字段,会返回所需缓冲区的真实长度。
拿到了正确的长度之后,再次调用这个函数即可。WSAEnumProtocols返回它发现的协议数量,同时,WSAPROTOCOL_INFO会成为一个很大的结构,其中包含了大量字段,但我们在此感兴趣的只有szProtocol,iAddressFamily和iProtocol。假如iAddressFamily等于AF_NETBIOS,则iProtocol的绝对值便对应于由字串szProtocol指定的那种协议的数量。除此以外,ProvidedId GUID可用于将返回的协议同协议的预定义GUID对应起来。采用这种方法,仅存在着一个方面的不足。在 Windows NT和Windows 2000下,对于LANA 0上安装的任何协议来说,iProtocol字段的值都为0x80000000。这是由于协议0是为特殊用途而保留的;分配了LANA 0的任何协议都有一个0x80000000的值,所以只需注意对这个值的检查就可以了。

NetBIOS的平台限制:
Windows CE不支持NetBIOS编程接口,Windows 9x平台上,若要为任何LANA增加任何NetBIOS名字,务必首先重设所有LANA编号,再逐个增加名字,这是由于重设任何一个LANA,都会完全破坏其他LANA的名字表。所以,应避免写出像下面这样的代码:
LANA_ENUM lenum;
//Enumerate the LANAs
for(i=0;i<lenum.length;++i)
{
 Reset(lenum.lana[i]);
 AddName(lenum.lana[i],MY_NETBIOS_NAME);
}
对Windows 95来说,它根本不会在对应于TCP/IP协议的LANA上尝试执行一个异步NCBRESET命令。如试图强行以异步形式执行一个NCBRESET命令,程序便会在NetBIOS TCP/IP虚拟设备驱动程序(VXD)中导致一个严重错误,不得不重新启动计算机。

NetBIOS的常规问题:
进行面向会话的通信时(与“无连接”通信相对),其中一方可送出自己希望的、任意多的数据。但实际上,若非接收方投放一条接收命令,确认数据已经收到,否则作为发送方,会将数据缓存起来。对发送命令来说,NCBSENDNA和NCBCHAINSENDNA这两个NetBIOS命令是它们的“毋需确认”版本。如果不想让自己的发送命令等候来自接收方的确认信息,那么可考虑使用这两个命令。由于TCP/IP在最基层的协议中提供了自己的收到确认机制,所以发送命令的这两个版本(毋需接收方确认)在其行为上,类似于要求确认的版本。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值