一、说明
最近在负责基于人工智能相关的项目,从产品的立项,以及项目的实施和技术难点的调研,着实是花费了不少的时间和经历,这期间遇到了很多的问题,好在经过深入的研究都一一解决了。今天给大家分享一个基于局域的广播技术,具体的就是,你在公司内部搭建了一个本地服务器(只允许某些小伙伴通过特定的网络访问,比如说同一个WIFI,但不是基于web的),其他小伙伴电脑上安装了访问客户端,但是一般情况下,小伙伴要访问你的服务,需要知道你的IP 地址和端口号才能访问你的服务。由于内网的IP根据你链接的网络不同,以及接入的终端的数量不同,随时会发生变化。这样一来,客户端小伙伴每次登录都要确认服务端的IP 和端口。怎么做到让客户端小伙伴不用关心服务端的IP 和端口,直接登录显得很重要。
二、概念以及原理
要想达到我们的目的,知道 局域网广播 的概念显得非常重要,对这个概念不是很熟悉的小伙伴,我们一起来复习下。
1、定义:网络上的一个主机向网络上的所有其他主机发送数据
2、广播地址:局域网都定义的一个特殊的用于广播的保留地址称为广播地址,当信息头中目的地址域的内容为广播地址时, 该帧被局域网上所有计算机接收
3、广播地址分类
(1)受限的广播:受限的广播地址是255.255.255.255。该地址用于主机配置过程中IP数据报的目的地址,此时,主机可能还不知道它所在网络的网络掩码,甚至连它的IP地址也不知道。在任何情况下,路由器都不转发目的地址为受限的广播地址的数据报,这样的数据报仅出现在本地网络中。
(2)指向网络的广播:指向网络的广播地址是主机号为全1的地址。A类网络广播地址为netid.255.255.255,其中netid为A类网络的网络号。一个路由器必须转发指向网络的广播,但它也必须有一个不进行转发的选择。
(3)指向子网的广播:指向子网的广播地址为主机号为全1且有特定子网号的地址。作为子网直接广播地址的IP地址需要了解子网的掩码。例如,如果路由器收到发往128.1.2.255的数据报,当B类网络128.1的子网掩码为255.255.255.0时,该地址就是指向子网的广播地址;但如果该子网的掩码为255.255.254.0,该地址就不是指向子网的广播地址。
(4)指向所有子网的广播:指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开。指向所有子网的广播地址的子网号及主机号为全1。例如,如果目的子网掩码为255.255.255.0,那么IP地址128.1.255.255是一个指向所有子网的广播地址。然而,如果网络没有划分子网,这就是一个指向网络的广播。
4.广播注意事项
(1)TCP/IP协议栈中, 传输层只有UDP可以广播.
(2)只能对同一子网内部广播, 广播数据包不经过路由器.
(3)UDP的广播地址为255.255.255.255
(4)在winsock实现中, 有一个选项对应是否允许广播,必须调用setsockopt打开该选项.
(5)打开后, 用sendto向255.255.255.255发送的数据包全部广播.
三、完整案例
1、服务端 :我们定义的是,服务端启动后,一直发广播消息,直到客户端接收到消息
"""
服务端给客户端广播消息
"""
import socket
import threading
import Tools.BroadcastAddress as broadcastAddre
import time
class UDPBroadcastServer:
def __init__(self):
self.PORT = 20441
self.broadcastNetwork = broadcastAddre.getBroadcastAddress()
#广播消息
def sendBroadcastInfo(self,str):
self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.thread = threading.Thread(target=self.sendMsg,args=(str,))
self.thread.setDaemon(1)
self.thread.start()
def sendMsg(self,str):
while True:
# 发送数据:
self.server.sendto(str.encode("utf-8"), (self.broadcastNetwork, self.PORT))
time.sleep(0.5)
def close(self):
self.server.close()
#======================================== test ===================================
broadcastServer = UDPBroadcastServer()
thread = threading.Thread(target=broadcastServer.sendBroadcastInfo,args=('1111111',))
thread.setDaemon(1)
thread.start()
time.sleep(10000)
2.客户端:我们定义的是,客户端启动后,一直监听广播消息,直到收到服务端的消息
"""
监听服务端发来的广播消息
"""
import ctypes
import socket,sys
import threading
import time
class UDPBroadcastClient:
def __init__(self):
self.isReceive = False
self.BUF_SIZE = 4096
self.PORT = 20441
self.client = None
"""
开始监听消息
"""
def startListenMsgThread(self):
if self.client is not None:
self.client.close()
self.client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 绑定 客户端口和地址:
self.client.bind(('', self.PORT))
self.isReceive = True
self.thread = threading.Thread(target=self.startListenMsg)
self.thread.setDaemon(1)
self.thread.start()
def startListenMsg(self):
while True:
print('while -----', self.isReceive)
try:
# 接收数据 自动阻塞 等待客户端请求:
data, addr = self.client.recvfrom(self.BUF_SIZE)
message = 'Received from %s:%s.' % (addr, data)
print(message)
if self.isReceive == False:
self.client.close()
break
except:
self.client.close()
break
def close(self):
print('close')
self.isReceive = False
# self.client.close()
#======================================== test ===================================
broadCast = UDPBroadcastClient()
thread = threading.Thread(target=broadCast.startListenMsgThread)
thread.setDaemon(1)
thread.start()
time.sleep(5000)
3.获取广播地址:这里最主要的如何正确的获取广播地址,广播地址的正确与否决定了是否可以正常通讯(这里划重点,很多人说按照其他人写的代码测试了,依然无法正常通讯,原因就在这里),执行下面的代码之前,请先导入库 psutil ,这里用的本机IP 和 子网掩码来计算的 广播地址,有兴趣的小伙伴可以研究下。
#coding=utf-8
import psutil
import NetworkCommunication.Tools.SocketTools as tool
def get_broad_addr(ipstr, maskstr):
ipstrArr = ipstr.split(".")
print(ipstr)
iptokens = list(map(int, ipstrArr))
maskstrArr = maskstr.split(".")
masktokens = list(map(int, maskstrArr))
broadlist = []
for i in range(len(iptokens)):
ip = iptokens[i]
mask = masktokens[i]
broad = ip & mask | (~mask & 255)
broadlist.append(broad)
return '.'.join(map(str, broadlist))
#获取网卡名称和其ip地址,不包括回环
def get_netcard():
netcard_info = []
info = psutil.net_if_addrs()
for k,v in info.items():
# print('k == ',k ,'v == ',v)
for item in v:
ip_netmask= {}
if item[0] == 2 and not item[1]=='127.0.0.1':
ip_netmask['ip'] = item[1]
ip_netmask['netmask'] = item[2]
netcard_info.append(ip_netmask)
return netcard_info
#获取广播地址
def getBroadcastAddress():
info = get_netcard()
# print ('info =====',info)
ip = tool.get_host_ip()
broadcastAddress = '255.255.255.0' #默认值,没有太多用处
for item in info:
if item['ip'] == ip:
broadcastAddress = get_broad_addr(item['ip'], item['netmask'])
break
print('broadcastIp == ',broadcastAddress)
return broadcastAddress
SocketTools 文件
import socket
import requests
import re
import collections
# 获取本机IP
def get_host_ip():
ip = ''
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
except Exception as e:
code = e.args[0]
if code == 51:
print('获取IP失败=============没有网络=============')
finally:
s.close()
return ip
# 网络检测
def isConnected():
try:
html = requests.get("http://www.baidu.com",timeout=2)
except:
return False
return True
四、总结
在研究这块技术的时,参考了网上的一些博客,但是大家都是本机测试广播都可以收到消息,但是多台电脑测试时,发的广播就不能收到消息,也没有人说明为什么,大家的文章都是相互copy,并没有解决实际的问题,经过仔细的研究后,发现大家忽略了一个问题,就是 获取广播地址 的方式不正确,导致不能在局域网网络广播。分享这篇文章的目的,是希望有这方面需求的小伙伴 少走弯路。
网友评论