利用NetBIOS进行Windons网络编程

2008-3-6 10:47:28   Count:

    本文介绍了NetBIOS编程的一些基本概念,并通过一个异步事件服务器和一个异步事件客户机的例子,详细说明了NetBIOS进行Windows编程的基本方法。
    “网络基本输入/输出系统”(Network Basic Input/Output System,NetBIOS)是1983年由Sytex公司为IBM公司开发的一种标准应用程序编程接口,并被微软采用。1985年,IBM改进了NetBIOS,推出了NetBIOS扩展用户接口(NetBIOS Extended User Interface,NetBEUI)通信协议,它占用内存少,配置简单,适用于小型局域网不同计算机之间的通信,但不具有跨网段工作的能力,不支持路由机制。NetBIOS是一种与“协议无关”的编程接口,它使应用程序不用理解网络细节,应用程序可通过TCP/IP、NetBEUI、SPX/IPX运行。下面我们介绍以下NetBIOS编程用到的一些重要概念及其实现方法。

    一、理解NetBIOS

    1、 LANA编号

    理解LAN适配器(LAN Adapter,LANA)编号是NetBIOS进行网络编程的关键所在。网络的传输协议是通过LANA编号同NetBIOS对应起来,每个LANA编号对应于网卡及传输协议的唯一组合。因此,我们在编程时要注意,两台要进行通信计算机必须至少安装有同一种协议,并且这两台计算机通信所依赖的LANA编号对应的网络协议要相同,否则即使这两台计算机安装相同的协议也无法进行通信。LANA编号范围在0到9之间,其中,LANA 0代表默认的LANA。

    2、 NetBIOS名字

    NetBIOS名字可分为两种类型:唯一名字(Unique Name)和组名(Group Name)。顾名思义,唯一名字只允许一台计算机注册该名字,一旦唯一名字注册成功,其他计算机如果再注册该名字,就会出现:“名字重复”的错误,微软网络中的机器名采用的就是NetBIOS唯一名字。组名则是一组计算机的总称,可以用来接收发给这一组计算机的数据。值得注意的是:组名可以和唯一名字同名,这会引起发送或接收数据的目的出现错误!NetBIOS名字长度为16个字符,其中第16个字符用于区分不同的网络服务。关于计算机注册NetBIOS名字的信息可以利用Nbtstat命令查看。

    3、 NetBIOS提供的服务

    NetBIOS提供两种服务:面向连接的服务和数据报服务(无连接)。面向连接的服务为两台需要进行通信的计算机建立一个连接,并利用错误探测和恢复机制保证数据在通信的两端准确无误的传输,它适于传输比较长的消息。对于NetBIOS,服务器在对想通过它建立通信的LANA编号上注册,而对于位于其他计算机上的客户机会搜索服务器注册的名字,并将它解析为机器名,然后发出进行通信的请求。

    数据报服务是无连接的,因而它不能保证数据有序、正确的传输,但它可以节省建立连接的开销,它适合短消息的传输。在NetBIOS中,客户机只是将发送数据的目的地定义为服务器注册的进程名,而不进行任何连接。

    二、NetBIOS编程的实现

    NetBIOS的所有函数声明、常数都在头文件“Nb30.h”中定义,在编程时还须与Netapi32.lib库进行链接。NetBIOS接口通过一个函数实现:
    UCHAR Netbios (PNCB pNCB);
    其中,参数pNCB指向一个网络控制块(Net Control Block,NCB)指针,NCB结构如下:
    typedef struct _NCB {
    UCHAR   ncb_command;    // NetBIOS命令
    UCHAR   ncb_retcode;     // 指定操作的返回代码         
    UCHAR   ncb_lsn;         // 本地会话编号       
    UCHAR   ncb_num;         // 本地名字编号        
    PUCHAR  ncb_buffer;      // 数据缓冲区地址        
    WORD    ncb_length;      // 缓冲区长度       
    UCHAR   ncb_callname[NCBNAMSZ]; // 远程应用程序名
    UCHAR   ncb_name[NCBNAMSZ];  // 本地应用程序名   
    UCHAR   ncb_rto;         // 接收操作延时
    UCHAR   ncb_sto;         // 发送操作延时
    void (CALLBACK *ncb_post)( struct _NCB * );
    // 异步命令完成后需调用的后例程地址
    UCHAR   ncb_lana_num;    // LANA 编号
    UCHAR   ncb_cmd_cplt;    // 指定操作的返回代码      
    UCHAR   ncb_reserve[10]; // 保留字段    
    HANDLE  ncb_event;       // Win32事件句柄
    } NCB, *PNCB;

    此外,编程时应注意调用NetBIOS函数的同步和异步问题。NetBIOS命令调用本身均为同步,即在完成指定命令之前,会一直调用NetBIOS模块。而在实际编程时,我们通常需要进行异步调用,即希望允许多个客户机同时与服务器进行连接,这就需要让NetBIOS命令与异步标志逻辑或(OR)操作,但必须在ncb_post字段中指定一个后例程,或在ncb_event字段中指定一个事件句柄。

    下面,我以实现一个异步事件服务器和一个异步事件客户机为例,具体说明NetBIOS的编程实现,其中,服务器接收由客户机发送的数据。

    1、 异步事件服务器的实现

    首先,我们进行初始化工作,列举可用的LANA编号,并重设:
    if (LanaEnum(&lenum) != NRC_GOODRET)
        return 1;
    if (ResetAll(&lenum, (UCHAR)MAX_SESSIONS, (UCHAR)MAX_NAMES,FALSE) != NRC_GOODRET)
        return 1;
    &lenum是一个LANA_ENUM结构变量,其定义如下:
    typedef  struct  LANA_ENUM
    {
      UCHAR      length ;
      UCHAR      lana[MAX_LANA+1] ;
    }  LANA_ENUM, *PLANA_ENUM;

    其中,length指出本地计算机可用的LANA的数量,lana表示由这些LANA编号组成的一个数组。

    然后为每个LANA编号分配NCB结构,并添加服务器名字,执行异步侦听。如果有客户机与服务器连接成功,则服务器接收由客户机发送的数据。程序代码如下:

 


    // 为异步事件分配一组句柄
    EventArray = (HANDLE*)GlobalAlloc(GMEM_FIXED,sizeof(HANDLE) * lenum.length);
    // 为每个LANA编号分配一个NCB结构
    GlobalClients = (PNCB)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeof(NCB) * lenum.length);
    // 产生事件,为LANA编号添加服务器名,并执行异步侦听
    for(i = 0; i < lenum.length; i++)
    {
        EventArray[i] = GlobalClients[i].ncb_event = CreateEvent(NULL, TRUE, FALSE, NULL);
        AddName(lenum.lana[i], SERVER_NAME, &num);
        Listen(&GlobalClients[i], lenum.lana[i], SERVER_NAME);
    }
   // 此时若在windows下运行Nbtstat–n 服务器名,会看到一个NetBIOS
   // 名字表。
  // 产生事件,为LANA编号添加服务器名,并执行异步侦听
    while (1)
    {
        // 等待,直到有一个连接建立
        RetEvent = WaitForMultipleObjects(lenum.length, EventArray, FALSE, INFINITE);
        if (RetEvent == WAIT_FAILED)
        {
            printf("等待连接失败!\n");
           break;
        }
        // 遍历每个NCB结构,是否有多个连接建立,
        // 如果ncb_cmb_plt的值不是NRC_PENDING,即连接成功,
        // 则产生接收数据的是线程,并为它创建一个新的NCB结构。
        for(i = 0; i < lenum.length; i++)
        {
           if (GlobalClients[i].ncb_cmd_cplt != NRC_PENDING)
            {
                pncb = (PNCB)GlobalAlloc(GMEM_FIXED, sizeof(NCB));
                memcpy(pncb, &GlobalClients[i], sizeof(NCB));
                pncb->ncb_event = 0;
                hThread = CreateThread(NULL, 0, ClientThread,(LPVOID)pncb, 0, &ThreadId);
                CloseHandle(hThread);
                // 重设事件句柄,进行另一个侦听
                ResetEvent(EventArray[i]);
                Listen(&GlobalClients[i], lenum.lana[i], SERVER_NAME);
            }
       }
    }
    最后,关闭所有句柄,释放内存空间。
    for(i = 0; i < lenum.length; i++)
    {
        DelName(lenum.lana[i], SERVER_NAME);
        CloseHandle(EventArray[i]);
    }
    GlobalFree(GlobalClients);
    2、 异步事件客户机的实现

    同实现服务器一样先进行初始化。在给LANA编号添加客户机名字之后,进行异步连接,若连接成功,则向服务器发送数据,最后,关闭句柄,释放内存空间。这里我只给出异步连接和发送数据的部分程序代码:
    // 产生一个事件,并把它分配给一个相应的NCB结构,并执行异步连接
    for(i = 0; i < lenum.length; i++)
    {
        EventArray[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
        pncb[i].ncb_event = EventArray[i];
        AddName(lenum.lana[i], ClientName, &Num);
        Connect(&pncb[i], lenum.lana[i], ServerName, ClientName);
    } 
    // 等待,直到至少有一个连接成功
    RetEvent = WaitForMultipleObjects(lenum.length, EventArray, FALSE,
        INFINITE);
    if (RetEvent == WAIT_FAILED)
    {
        ErrorCode.Format("等待连接失败!");
        AfxMessageBox(ErrorCode);
    }
    else
    {
        // 如果有多个连接成功,关闭多余的连接,我们只用由
        // WaitForMultipleObjects函数返回的连接;如果没有连接成功,
        // 则取消。
        for(i = 0; i < lenum.length; i++)
        {
            if (i != RetEvent)
            {
                if (pncb[i].ncb_cmd_cplt == NRC_PENDING)
                    Cancel(&pncb[i]);
                else
                    Hangup(pncb[i].ncb_lana_num, pncb[i].ncb_lsn);
            }
        }        
        // 发送消息
        sprintf(SendBuffer, m_sendmsg);
        RetValue = Send(pncb[RetEvent].ncb_lana_num, pncb[RetEvent].ncb_lsn, SendBuffer, strlen(SendBuffer));
        if (RetValue != NRC_GOODRET)
            AfxMessageBox("无法建立连接!");
        Hangup(pncb[RetEvent].ncb_lana_num, pncb[RetEvent].ncb_lsn);
    }  
    本程序在Windows98环境下,由VC++6.0编译通过。

 


浏览该文章的用户为您推荐了该信息: 
       
   
   
 
站内检索:
本月授课安排
栏目导航
阅读排行