已刊登在《无线电》2月刊
一 实例背景
最近一个做智能家居的朋友面临这样的一个烦恼,他想让用户通过智能手机在家里方便地控制家居设备,又想让用户免除下载安装App的麻烦,通过浏览器直接打开设备内嵌的网页便可实现控制。但是设备的IP地址都是通过家里的路由器自动获得的,设备上又没有屏幕来显示其IP地址。问我有没有办法不输入IP地址来实现浏览器访问该设备网页的办法,就是类似DNS之类,但是无需连外网,只在家庭网络内能访问即可。
这使我想起一个古老的协议,NetBIOS(Network Basic Input/Output System)。这个在上世纪80年代由IBM开发的协议,主要用于数十台左右计算机组成的小型局域网,该协议的主要用途之一就是把计算机名称解析为相应IP地址。如果每个设备有一个固定名字,在实现了NetBIOS的前提下,用户在浏览器里输入该设备的名字,然后通过NetBIOS解析,便可实现访问该设备网页的这个功能了。而且NetBIOS占用系统资源少,在单片机上运行不成问题。于是推荐这个朋友在他的设备上实现了NetBIOS协议,解决了他的烦恼。
除了智能家居,在当下物联网时代,想必还有其他应用也会遇到类似问题,就拿手头的WIZnet-W5500评估板实现了一下NetBIOS,希望能对做网络设备开发的朋友有所帮助。在用W5500实现之前,我们还是先在PC上看一下NetBIOS到底是一个什么东西。
二 NetBIOS协议
我们知道在DOS 命令下可以通过PING主机名获得另外一台电脑的IP地址,实际上就是通过 NETBIOS进行的。在Windows操作系统中,默认情况下在安装TCP/IP协议后会自动安装NetBIOS。查看方法如下:本地连接属性的中“高级TCP/IP设置”窗口中选择“WINS”选项卡,在“NetBIOS设置”区域中就可以设置相应的NetBIOS,如图1:
图1 WINS下的NetBIOS设置
Ping主机名的第一个数据包就是NBNS(NetBIOS Name Server),协议包,它是 TCP/IP 上的 NetBIOS (NetBT) 协议族的一部分,它在基于 NetBIOS 名称访问的网络上提供主机名和地址映射方法。NBNS是动态DNS的一种,Microsoft的NBNS实现称为WINS。NetBIOS的报文类型较多、结构复杂,不同的网络环境及不同的用途中,会使用不同报文,可用端口进行区分,WINS协议中,NetBIOS名字报文、数据报报文及会话报文分别使用TCP 137、138和139端口。
NetBIOS 数据报有很多不同格式,主要取决于服务和信息类型,以及用以传送 NetBIOS 数据报的传输协议。 NetBIOS 协议架构可见图2,其中包含三种基本服务: NAME、SESSION 和 DATAGRAM ,其中NAME所用协议就是NBNS协议。
图2:NetBIOS协议架构
下面看一下WINS协议使用的报文NETBIOS的名字报文(NAME)的总体格式如表1:
表1 NetBIOS名字报文格式
事物ID(2bytes) | 通用标志(2bytes) |
问题记录个数(2bytes) | 回答记录个数(2bytes) |
权威记录个数(2bytes) | 附加记录个数(2bytes) |
问题记录(若干字节) | |
回答记录(若干字节) | |
权威记录(若干字节) | |
附加记录(若干字节) |
报文的前12字节总称为NETBIOS名字报文的首部,通过首部我们可以判断出是否为名字查询的报文。
NETBIOS名字报文中最常见的是携带问题记录的报文,问题记录的格式如表2:
表2 NetBIOS名字报文中问题记录格式
问题名称(若干字节) |
|
问题类型(2 bytes) |
问题类别(2bytes) |
通过携带问题记录的报文,我们可以得到要查询的名字字符,如果和本机名相符,就发送报文响应,响应中带有IP地址,发送广播的主机就会得到该IP地址。
三 W5500EVB实现NETBIOS名字报文解析
了解了NETBIOS协议之后,下面就让我们通过W5500EVB做一个嵌入NetBIOS的简单实验。
图3:主程序流程图
本文主要讨论如何在单片机上实现NETBIOS名字解析服务,DHCP和TCP Server相关部分子程序在此不再详细介绍,根据NETBIOS名字解析服务子程序流程图(如图4示),我们可以得知当查询到137端口收到网络的UDP数据包时,读取数据包并进行判断是否为NETBIOS名字报文,如果是就将解析出的名字与本机名比较,如果一致就回复报文。
图4:NBNS程序流程图
在此贴出NETBIOS部分代码,要获取完整代码,请到http://pan.baidu.com/s/1nt9MQKh上进行下载。
void do_netbios(void)
{
unsigned char state;
unsigned int len;
1 state = getSn_SR(NETBIOS_SOCK);
switch(state)
{
case SOCK_UDP:
2 if((len=getSn_RX_RSR(NETBIOS_SOCK))>0)
{
unsigned char rem_ip_addr[4];
uint16 rem_udp_port;
3 char netbios_name[NETBIOS_NAME_LEN+1];
4 NETBIOS_HDR* netbios_hdr;
5 NETBIOS_NAME_HDR* netbios_name_hdr;
6 len=recvfrom(NETBIOS_SOCK,(unsignedchar*)&netbios_rx_buf,len,rem_ip_addr,&rem_udp_port);
printf(“rem_ip_addr=%d.%d.%d.%d:%d\r\n”,rem_ip_addr[0],rem_ip_addr[1],rem_ip_addr[2],rem_ip_addr[3],rem_udp_port);
7 netbios_hdr = (NETBIOS_HDR*)netbios_rx_buf;
8 netbios_name_hdr = (NETBIOS_NAME_HDR*)(netbios_hdr+1);
/* if the packet is a NetBIOS name query question */
9 if(((netbios_hdr->flags& ntohs(NETB_HFLAG_OPCODE)) == ntohs(NETB_HFLAG_OPCODE_NAME_QUERY)) &&
((netbios_hdr->flags & ntohs(NETB_HFLAG_RESPONSE)) == 0) &&
(netbios_hdr->questions == ntohs(1)))
{
printf(“netbios name query question\r\n”);
/* decode the NetBIOS name */
10 netbios_name_decoding( (char*)(netbios_name_hdr->encname), netbios_name, sizeof(netbios_name));
printf(“name is %s\r\n”,netbios_name);
/* if the packet is for us */
11 if (strcmp(netbios_name, NETBIOS_W5500_NAME) == 0)
{
uint8 ip_addr[4];
NETBIOS_RESP *resp = (NETBIOS_RESP*)netbios_tx_buf;
/* prepare NetBIOS header response */
12 resp->resp_hdr.trans_id = netbios_hdr->trans_id;
resp->resp_hdr.flags = htons(NETB_HFLAG_RESPONSE | NETB_HFLAG_OPCODE_NAME_QUERY |
NETB_HFLAG_AUTHORATIVE |
NETB_HFLAG_RECURS_DESIRED);
继续阅读:http://blog.csdn.net/wiznet2012/article/details/43451805
文章评论(0条评论)
登录后参与讨论