《Windows网络编程 》NetBIOS 异步回调模型

//  Module Name: Cbnbsvr.c
//
//  Description:
//     This NetBIOS sample implements a server using the asynchronous
//     callback functions as opposed to asynch events.  The server
//     will add its name to each LANA number on the machine and post
//     a listen (NCBLISTEN) on each LANA in order to service client
//     requests.
//
//  Compile:
//     cl -o cbnbsvr.exe cbnbsvr.c ..\Common\nbcommon.obj netapi32.lib
//
//  Command Line Options:
//     NONE - The server automatically uses the NetBIOS name as
//            defined by the constant SERVER_NAME
//


/**/ /*要补上连接库netapi32.lib,在工程设置地里*/
#include 
< windows.h >
#include 
< stdio.h >
#include 
< stdlib.h >

#include 
" nbcommon.h "

#define  MAX_BUFFER      2048
#define  SERVER_NAME     "TEST-SERVER-1"

DWORD WINAPI ClientThread(PVOID lpParam);

int  Listen( int  lana,  char   * name) ;

void  CALLBACK ListenCallback(PNCB pncb) ;


//
//  Function: main
//
//  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. Then 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 ;
    
int         num;

    
// Enumerate all LANAs and reset each one
    
//

    printf(
"The application run : \n");  //添加上来看看

    
if (LanaEnum(&lenum) != NRC_GOODRET)
    
{
        printf(
"The LanaEnum Function failed\n") ;
        
return 1;
    }

    
if (ResetAll(&lenum, 254254, FALSE) != NRC_GOODRET)
    
{
        printf(
"The ResetAll Function failed\n") ;
        
return 1;
    }

    
//
    
// Add the server name to each LANA and issue a listen on each
    
//
    for(i = 0; i < lenum.length; i++)
    
{
        printf(
"第%d次加名\n",i+1) ;
        AddName(lenum.lana[i], SERVER_NAME, 
&num);  //每一个端口都加上这一个服务的名字,
        Listen(lenum.lana[i], SERVER_NAME);       //顺便监听一下
    }


    
while (1)
    
{    
        printf(
"Sleep,wait for the client\n") ;
        Sleep(
5000);
    }


    
return 0 ;
}


//
//  Function: Listen
//
//  Description:
//     Post an asynchronous listen with a callback function. Create
//     an NCB structure for use by the callback (since it needs a
//     global scope).
//
// 说明:通过回调函数产生异步监听,创建NCB 结构.

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;   //指定LANA号

    pncb
->ncb_post = ListenCallback; //异步回调函数,ListenCallback调用了么?
    
//
    
// This is the name clients will connect to
    
//
    memset(pncb->ncb_name, ' ', NCBNAMSZ);
    strncpy(pncb
->ncb_name, name, strlen(name));

    printf(
"Listen函数开始执行!\n");
    
//
    
// An '*' means we'll take a client connection from anyone.  By
    
// specifying an actual name here you restrict connections to
    
// clients with that name only.
    
//
    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;
    }


    printf(
"Listen函数开始执行完成!\n");
    
return NRC_GOODRET;
}



//
//  Function: ListenCallback
//
//  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.
//
//  函数: ListenCallback
//
//  说明:该函数当异步监听完成时调用,如果创建处理客户端的线程没有发生错误,同时还创
// 建对其它
//  客户端连接的监听
void  CALLBACK ListenCallback(PNCB pncb)
{
    HANDLE      hThread;
    DWORD       dwThreadId;

    printf(
"异步函数开始执行!\n"); 

    
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);

    printf(
"异步函数执行完成!\n");

    
return;
}




//
//  Function: ClientThread
//
//  Description:
//     The client thread blocks for data sent from clients and 
//     simply sends it back to them. This is a continuous loop
//     until the sessions is closed or an error occurs.  If
//     the read or write fails with NRC_SCLOSED then the session
//     has closed gracefully so exit the loop.
//

//  函数: ClientThread
//  说明:客户端线程阻塞从客户端发送的数据,并且将它们返回客户端。该循环直到会话关闭
// 或发生 错误,如果读或写因为NRC_SCLOSED 失败而退出会话,会退出循环
//

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(pncb
->ncb_callname, szClientName);

    printf(
"Client函数开始执行!\n");

    
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)
    
{
        
// Some other error occured, hang up the connection
        
//
        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);

    printf(
"Client函数开始执行完成!\n");
    
return NRC_GOODRET;
}






main 接下来要做的事情是将进程名增加到打算用来接收连接的每个LANA 编号。通过一次循环,
服务器会将自己的进程名TEST-SERVER-1 增加到每个LANA 编号。通过这个名字,客户端才能连接服务
器(当然要用空格填充)。试图建立或接受一个连接时,NetBIOS 名字中的每个字符都必须明确指定。
对于这个问题,我们必须特别留意。编写NetBIOS 客户端和服务器代码时,最常见的问题便是名字的
错误匹配。注意应使用空格或其他字符来填充名字,例如可以用空格来填充。
对服务器来说,最后一个也是最关键的一个步骤是执行大量NCBLISTEN 命令。Listen 函数首先会
分配一个NCB 结构。使用异步NetBIOS 调用时,我们递交的NCB 结构必须自执行调用之时开始,一直
持续到调用结束为止。这便要求我们在执行命令前动态分配每一个NCB 结构,或者维持一个全局性的
NCB 结构池,以便在异步调用中使用。对NCBLISTEN 来说,应设置希望通过它进行调用的那个LANA 编
号。注意在程序清单2-1 列出的源代码清单中,NCBLISTEN 命令需要同ASYNCH 命令进行逻辑或(OR)
运算。指定ASYNCH 命令时,对ncb_post 和ncb_event 这两个字段来说,其中任意一个必须设为非零
值。否则,NetBIOS 调用就会出错,报告NRC_ILLCMD(非法命令)错误。在程序清单2-2 中,Listen
函数会将ncb_post 字段设成回调函数ListenCallback。接下来,Listen 函数将把ncb_name 字段设为
服务器进程的名字。这正是客户端需要与之建立连接的那个名字。函数也会将ncb_callname 字段的第
一个字符设为一个星号(*),指出服务器可从任何客户端接受连接请求。如果不这样做,亦可在
ncb_callname 字段中设置一个特定的名字,只允许注册了那个特定名字的客户端建立与服务器的连
接。最后,Listen 会发出对NetBIOS 的一个调用。调用立即便会完成,NetBIOS 函数将已提交的NCB
结构的ncb_cmd_cplt 字段设为NRC_PENDING ( 0xFF)—表示“待决”,直到命令执行完毕为止。
一旦main 完成了重设,并为每个LANA 编号都投放了一个NCBLISTEN 命令,主线程会进入一个连
续的循环中。
注意由于这个服务器仅是一个简单的例子,所以在设计上非常简单。用户在编写自己的NetBIOS
服务器应用时,还可在主循环中进行其他处理;或者在主循环中,为某个LANA 编号投放一个同步
NCBLISTEN 命令。
只有在一个LANA 编号上接受了一个进入的连接时,回调函数才会执行。NCBLISTEN 命令接受了一
个连接后,会调用由ncb_post 指定的函数,并将最初的NCB 结构作为参数使用。随后,ncb_retcode
会设为返回代码。请务必留意对这个值的检查,了解客户端连接是否成功建立。若连接成功,会在
ncb_retcode 字段中返回一个NRC_GOODRET ( 0x00)值。连接成功后,需针对同一个LANA 编号执行另
一个NCBLISTEN 命令。之所以要这样做,是由于一旦原始的监听操作成功,则服务器会停止在那个LANA
编号上对客户端的连接进行监听,直到递交了另一个NCBLISTEN 为止。因此,假如服务器需要频繁地
为客户提供服务,便需在同一个LANA 上投放多个NCBLISTEN 命令,以便能够同时接受多个客户端发出
的连接请求。最后,回调函数会创建一个特殊的线程,为客户端提供服务。在我们的这个例子中,线
程只是简单地循环,并调用一个成块读入命令(NCBRECV),紧接着调用一个成块发送命令(NCBSEND)。
所以,我们在此实现的是一个简单的回应服务器,用于从建立连接的客户端处读入消息,再将其原封
不动地“反射”回去。除非客户端中断连接,否则客户端线程会一直循环下去。连接中断时,客户端
第2 章 NetBIOS 编程
·43·
线程会执行一个NCBHANGUP 命令,以便在自己的这一端关闭当前连接。随后,客户端线程释放NCB 结
构占用的空间,并正常退出。
对面向连接的会话来说,数据是由最基层的协议加以缓冲的,所以并非一定要发出“待决”的调
用。发出一个接收命令后,NetBIOS 函数会将可用的数据立即传给现成的缓冲区,而且调用会立即返
回。而假若没有数据可用,接收调用便会暂停,直到有数据可用,或者会话断开为止。同样的道理也
适用于发送命令:若网络堆栈能通过线缆立即送出数据,或者能将数据缓存在堆栈中,调用便会立即
返回。而假若系统没有足够的缓冲区空间来立即送出数据,发送调用便会暂停,直到有可用的空间为
止。要想避免这种形式的数据延误,可对数据的收发使用ASYNCH(异步)命令。若执行的是异步发送
或接收命令,那么相应的缓冲区必须有一个较大的容量,超出调用进程本身的范围之外。避免收发延
误的另一个办法是使用ncb_sto 和ncb_rto 这两个字段。其中,ncb_sto 字段用于设置发送延时。若
为其指定一个非零值,便相当于为命令的执行规定了一个超时时限。注意时间长度是以500ms 为单位
指定的。如命令超时,便需要立即返回,数据则不会送出。接收超时的设置(ncb_rto)道理是一样的:
如在预先规定好的时间内没有数据到达,则调用返回,没有数据输入缓冲区。

不知道是我水平差还是这本书问题,真心觉得这本书写得很差,很恶心。。让人感觉很不爽。无奈Windows平台下没有一本可以系统地讲一讲网络编程的书。。。等我看完之后,马上跳到Linux

你可能感兴趣的:(《Windows网络编程 》NetBIOS 异步回调模型)