// 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, 254, 254, 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