如何发现网络中的设备(收集)
如何发现网络中的设备(收集)
好吧,每次要SSH到树莓派都很麻烦,我是没找什么办法,每次都得连上显示器鼠标键盘。才能知道IP地址。
看了点python,写了个脚本来发出 ip 地址。这样就省事多了。
缺陷:发现启动时有可能早于树莓派网络初始化,会导致UDP服务初始化出现问题。脚本里面延时 30秒执行。
在树莓派4 测试通过。
=================配置树莓派
SSH登录树莓派
sudo nano /etc/rc/local
输入python脚本
重启树莓派
这个时候怎么看我们的程序是否自启动了呢。登录树莓派 执行 sudo systemctl status rc-local 看看 FindIPUDPServer 是否已经起来了。
=================Python 脚本
链接:https://pan.baidu.com/s/1DJ0JY71MutlB7IFl_HD2Fw 密码:qexh
=================Android客户端:
APP 截图
源码FindIPUDPServer.py
11
# from time import ctime
import socket
# from socket import *
import uuid
import time
import os
import time
import logging
from logging import handlers
# 获取MAC地址
def get_mac_address():
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
return ":".join([mac[e:e + 2] for e in range(0, 11, 2)])
# 获取IP地址
def get_host_ip():
try:
my = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
my.connect(('8.8.8.8', 80))
# ip = my.getsockname()[0]
ipList = my.getsockname()
finally:
my.close()
return ipList
def _logging(**kwargs):
level = kwargs.pop('level', None)
filename = kwargs.pop('filename', None)
datefmt = kwargs.pop('datefmt', None)
format = kwargs.pop('format', None)
if level is None:
level = logging.DEBUG
if filename is None:
filename = 'default.log'
if datefmt is None:
datefmt = '%Y-%m-%d %H:%M:%S'
if format is None:
format = '%(asctime)s [%(module)s] %(levelname)s [%(lineno)d] %(message)s'
log = logging.getLogger(filename)
format_str = logging.Formatter(format, datefmt)
# backupCount 保存日志的数量,过期自动删除
# when 按什么日期格式切分(这里方便测试使用的秒)
th = handlers.TimedRotatingFileHandler(filename=filename, when='H', backupCount=3, encoding='utf-8')
th.setFormatter(format_str)
th.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# ch.setFormatter(format)
log.addHandler(ch)
log.addHandler(th)
log.setLevel(level)
return log
os.makedirs("logs", exist_ok=True)
mylog = _logging(filename='logs/udpserver.log')
print("等待30秒")
mylog.debug("等待30秒")
time.sleep(30)
print("等待结束")
mylog.debug("等待结束")
HOST = ''
PORT = 9999
BUFSIZ = 1024
ADDRESS = (HOST, PORT)
udpServerSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udpServerSocket.bind(ADDRESS) # 绑定客户端口和地址
myname = socket.gethostname()
print("myname:" + myname)
mylog.debug("myname:" + myname)
# myIP = get_host_ip()
# print("myIP:"+myIP)
myIPList = get_host_ip()
# ipListSize = len(myIPList)
# for ip in myIPList:
# print("myIP:"+str(ip))
print("myIPList:" + str(myIPList))
mylog.debug("myIPList:" + str(myIPList))
macAddress = get_mac_address()
print("macAddress:" + macAddress)
mylog.debug("macAddress:" + macAddress)
while True:
print("waiting for message...")
mylog.debug("waiting for message...")
data, addr = udpServerSocket.recvfrom(BUFSIZ)
currCode = data.decode('utf-8')
print("接收到数据:" +currCode)
mylog.debug("接收到数据:"+currCode)
# content = '[%s] %s' % (bytes(ctime(), 'utf-8'), data.decode('utf-8'))
# 发送服务器时间
if currCode == "TIME":
content = "Time:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
udpServerSocket.sendto(content.encode('utf-8'), addr)
# 发送IP地址
elif currCode == "IP":
content = "IP:" + str(myIPList)
udpServerSocket.sendto(content.encode('utf-8'), addr)
# 发送mac地址
elif currCode == "MAC":
content = "MAC:" + macAddress
udpServerSocket.sendto(content.encode('utf-8'), addr)
# 发送ip mac地址
elif currCode == "IP_MAC":
content = "IP:" + str(myIPList) + "|MAC:" + macAddress
udpServerSocket.sendto(content.encode('utf-8'), addr)
# 退出UDP服务端
elif currCode == "EXIT":
content = "服务端退出"
udpServerSocket.sendto(content.encode('utf-8'), addr)
# print(content)
break
# 重启
elif currCode == "REBOOT":
content = "服务端重启"
udpServerSocket.sendto(content.encode('utf-8'), addr)
print("服务端开始重启")
mylog.debug("服务端开始重启")
os.system('shutdown -r now')
break
# 关机
elif currCode == "SHUTDOWN":
content = "服务端关机"
udpServerSocket.sendto(content.encode('utf-8'), addr)
print("服务端开始关机")
mylog.debug("服务端开始关机")
os.system('sudo shutdown -h now')
break
else:
udpServerSocket.sendto("Bad Key".encode('utf-8'), addr)
# content = '[%s] %s %s' % (bytes(ctime(), 'utf-8'), str(myIPList), macAddress)
# udpServerSocket.sendto(content.encode('utf-8'), addr)
print('...received from and returned to:', addr)
mylog.debug('...received from and returned to:', addr)
udpServerSocket.close()
print("服务端退出")
mylog.debug('服务端退出')
22
设备发现协议
网络环境下设备发现是一种比较常见的应用,比如查找打印机与WiFi。那么我们应该如何通过编程实现对网络中的特定设备进行查找呢?
常用的方式有:IP广播与多播,以及基于这两种方式所实现的第三方协议,较著名的有Onvif协议。
我的第二个猜测是使用有限的IP地址范围主动扫描网络并等待正确的响应.不幸的是,这意味着网络使用DHCP来寻址IP地址.
1
除了AirKiss、SmartConfig此类网络应用技巧,我们需要尽可能利用手头开源硬件来测试传统的TCP/UDP连接。其中UDP的组播在物联网应用中有一定的重要意义。主要的UDP组播应用协议有:
mDNS
SSDP/uPnP
Apple AirPlay
DLNA,各类媒体播放器
IP组播与IGMP
IGMP(Internet Group Management Protocol)协议告诉路由器,在所在子网内有客户端对发送到某一个组播组的数据感兴趣,这样当该组的数据到达后,路由器会转送给所有感兴趣的客户端。
Windows 失败原因
之间发现,凡是Windows主机参与的IP组播都是失败的。首先怀疑WiFi路由器禁止了IGMP和uPnP服务,测试下来,同一WiFi局域网内,电视和手机的媒体推送没有任何问题。
但即便开启了Windows的Bonjour服务,关闭了防火墙,依然失败。推测Windows的网路配置上依然存在问题,这是Windows 10以及Ubuntu子系统无法侦听和推送多播报文的主要原因。Windows 10自带的Ubuntu子系统受控于操作系统防火墙,无法收发组播报文。抛出一个错误:
allankliu@allankliu-HP:/mnt/c/Users/allankliu$ iperf -s -u -B 224.0.67.67 -i 1
bind failed: Cannot assign requested address
而运行于Windows中的虚拟机,因为其网络配置是桥接模式,Ubuntu虚拟机可以直接从路由器获得IP地址,Windows防火墙对其没有作用。是可以收发组播报文的。
今早拿Ubuntu 12.04(Windows中VirtualBox中虚拟机)和树莓派Raspbian做了测试,Linux之间组播没有任何问题。测试工具使用了iperf,下一步将采用Python socket做测试。
iperf for Linux
pi@raspberrypi ~ $ iperf -c 224.0.67.67 -u --ttl 5 -t 10
Client connecting to 224.0.67.67, UDP port 5001
Sending 1470 byte datagrams
Setting multicast TTL to 5
UDP buffer size: 160 KByte (default)
[ 3] local 192.168.1.25 port 35294 connected with 224.0.67.67 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec
[ 3] Sent 893 datagrams
allankliu@ubuntu-server-vm:~$ iperf -s -u -B 224.0.67.67 -i 1
Server listening on UDP port 5001
Binding to local address 224.0.67.67
Joining multicast group 224.0.67.67
Receiving 1470 byte datagrams
UDP buffer size: 160 KByte (default)
[ 3] local 224.0.67.67 port 5001 connected with 192.168.1.25 port 35294
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 3] 0.0- 1.0 sec 128 KBytes 1.05 Mbits/sec 1.017 ms 0/ 89 (0%)
[ 3] 1.0- 2.0 sec 129 KBytes 1.06 Mbits/sec 0.972 ms 0/ 90 (0%)
[ 3] 2.0- 3.0 sec 128 KBytes 1.05 Mbits/sec 0.723 ms 0/ 89 (0%)
[ 3] 3.0- 4.0 sec 128 KBytes 1.05 Mbits/sec 6.029 ms 0/ 89 (0%)
[ 3] 4.0- 5.0 sec 128 KBytes 1.05 Mbits/sec 1.236 ms 0/ 89 (0%)
[ 3] 5.0- 6.0 sec 128 KBytes 1.05 Mbits/sec 2.518 ms 0/ 89 (0%)
[ 3] 6.0- 7.0 sec 129 KBytes 1.06 Mbits/sec 1.097 ms 0/ 90 (0%)
[ 3] 7.0- 8.0 sec 128 KBytes 1.05 Mbits/sec 1.024 ms 0/ 89 (0%)
[ 3] 8.0- 9.0 sec 128 KBytes 1.05 Mbits/sec 0.697 ms 0/ 89 (0%)
[ 3] 9.0-10.0 sec 128 KBytes 1.05 Mbits/sec 1.586 ms 0/ 89 (0%)
[ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 1.577 ms 0/ 893 (0%)
以上是树莓派发,Ubuntu收,反之亦然。
ESP8266
以组播为基础的mDNS/SSDP作为IoT最大的好处就是可以通过这两项服务构建起一个本地的IoT设备生态,无论是办公司、家居、工业、农业、军事等。均可以通过IPv4/v6组播实现自动接入、然后利用TCP/UDP的单播进行后续认证和协同。
ESP8266自带mDNS/SSDP例子。之所以对组播感兴趣也就是因为自己要做些产品而做得实验。
iperf的其他版本
iperf是法国人写的,除了最初的Linux,还支持32bit/64bit Windows,以及Android,iOS,Mac等,主流平台都得到了支持。如果Chrome/Android/iOS移动端的JavaScript runtime支持组播,那么甚至可以不需要Java/C/C++/Python的参与了。
21
udp 单播、组播、广播都可以实现,单为什么我使用udp组播,
请参考我的上一篇 局域网发现之UDP组播
本篇讲解的是如何使用代码来实现局域网发现功能;
我的需求背景:
使用场景,手机上安装有app A,同一局域网内的电视 上安装有app B,要求当app B 这个版本支持来自A的某个互动功能(比如投屏、游戏控制)时,A就应该能搜到到B所在的设备提示给用户,然后用户才进行互动,局域网搜索设备则是互动的第一步也是前提;
局域网发现之UDP组播
https://blog.csdn.net/lixin88/article/details/55209630
局域网发现的意义
局域网发现设备是通信的第一步,通信需要先知道对方的ip地址,因为一般使用 DHCP 动态分配 ip 地址的局域网内,各个主机的 IP 地址是由 DHCP 服务器来帮你分配 IP 地址的。所以在很多情况下,你要知道对方的 IP 地址是比较麻烦的。
因此,局域网发现,我们要解决的事情就是:如何找到局域网内其他设备,并获取到设备的ip;
查询资料之后,发现使用udp单播、组播、广播来实现的方式都有,并且亲测确实都可行,那么哪种更合适呢,这也是我写这篇的目的,让大家对这些有个基本概念和对比;
使用哪种协议实现
udp 不用保证数据可靠性,传输速度快;并且一般tcp是不用于多播场景的;那使用udp如何实现呢?
使用udp 单播、组播还是广播
先来了解什么是单播和组播、广播
单播
只有一个源点网络和一个终点网络。源点网络和终点网络的关系是一对一的。数据报途径的每一个路由器都要将这个分组仅从一个接口转发出去。
图例:
多播
在多播系统中,有一个源点一组终点。这是一对多的关系。在这种类型的通信中,源地址是一个单播地址,而目的地址则是一个组地址。
图例:
单播和组播、广播的区别
多播的优点
q 具有同种业务的主机加入同一数据流,共享同一通道,节省了带宽和服务器的优点,具有广播的优点而又没有广播所需要的带宽。
q 服务器的总带宽不受客户端带宽的限制。由于组播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。
q 与单播一样,多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行。
广播的缺点
q 多播与单播相比没有纠错机制,当发生错误的时候难以弥补,但是可以在应用层来实现此种功能。
q 多播的网络支持存在缺陷,需要路由器及网络协议栈的支持。
多播的应用主要有网上视频、网上会议等。
组播与广播
广播数据报的接收是被动的。
连接到子网上的所有主机都要接收广播数据报,这会增加网络流量,并且子网上的主机增加额外的负担。
UDP广播只能在内网(同一网段)有效,而组播可以较好实现跨网段群发数据。
UDP广播:消耗更多网络带宽,路由器向子网内的每个终端都投递一份数据包,不论这些终端是否乐于接收该数据包;
UDP组播:有了很大优化,只有终端加入到了一个广播组,UDP组播的数据才能被他接收到;
多播数据报的接收是主动的。主机主动加入指定的多播组,才会接收该组的多播数据报。
不同子网内的A,B进行组播通信,依靠IGMP协议;局域网组播,不考虑跨网段的组播实现,因此组播路由协议IGMP与本文要介绍的内容无关;
局域网的多播
多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
q 局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
q 预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
q 管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。
初步结论
局域网发现可以使用:
1.udp单播,获取源主机的ip和子网掩码,得到该局域网的ip地址范围,然后使用udp单播轮询 找到对应的目标主机;
2.udp组播,让源主机和目标主机都加到同一个局部多播地址;源主机给该多播地址发送组播消息即可;
3.udp广播,使用广播地址255.255.255.255 来广播定制好的消息;
综合考虑:udp单播轮询比较耗时,而且如果局域网内设备较多,UDP发送过快的话,会导致本地发送缓冲区丢包;接收过慢的话,也会导致接收缓冲丢包;单播和广播一样,对于不需要关心该消息的主机是一种打扰;
因此,使用udp组播来实现局域网发现比使用udp广播更合适;并且我之后会学习mdns和dns-sd,而这两种都是基于udp组播,所以用组播来实现对于后面的深入研究更有意义;
现在,我们来实现一个最简单的局域网发现的demo;具体实现请看下一篇
设备发现协议
网络环境下设备发现是一种比较常见的应用,比如查找打印机与WiFi。那么我们应该如何通过编程实现对网络中的特定设备进行查找呢?
常用的方式有:IP广播与多播,以及基于这两种方式所实现的第三方协议,较著名的有Onvif协议。
1局域网广播
1.1 定义
广播是一种一对所有的通信模式。有线电视网就是典型的广播型网络,我们的电视机实际上是接受到所有频道的信号,但只将一个频道的信号还原成画面。
广播不用进行网络路径选择,不能穿越路由器。这是为了防止广播数据影响大面积的主机,引起广播灾难。
1.2 优缺点
1.2.1 优点
网络设备简单,维护简单,布网成本低廉。
由于服务器不用向每个客户机单独发送数据,所以服务器流量负载极低。
1.2.2 缺点
无法针对每个客户的要求和时间及时提供个性化服务。
网络允许服务器提供数据的带宽有限,客户端的最大带宽=服务总带宽。例如有线电视的客户端的线路支持100个频道(如果采用数字压缩技术,理论上可以提供 500个频道),即使服务商有更大的财力配置更多的发送设备、改成光纤主干,也无法超过此极限。
不能在广域网上传播,这是为了防止广播风暴。
1.3 广播地址
每一个网段都有一个广播地址,其格式为 xxx.xxx.xxx.255 的形式。计算方式如下:
网络地址 = IP地址 & 子网掩码
广播地址 = 网络地址 | (~子网掩码)
avahi
mDNS
/etc/avahi/services/sftp-ssh.service
sspd
网络发现客户端,实现sspd协议。