Python基于局域网自动建立通讯服务之IP地址广播(一)

2021-03-18  本文已影响0人  hylccmh
一、说明

最近在负责基于人工智能相关的项目,从产品的立项,以及项目的实施和技术难点的调研,着实是花费了不少的时间和经历,这期间遇到了很多的问题,好在经过深入的研究都一一解决了。今天给大家分享一个基于局域的广播技术,具体的就是,你在公司内部搭建了一个本地服务器(只允许某些小伙伴通过特定的网络访问,比如说同一个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,并没有解决实际的问题,经过仔细的研究后,发现大家忽略了一个问题,就是 获取广播地址 的方式不正确,导致不能在局域网网络广播。分享这篇文章的目的,是希望有这方面需求的小伙伴 少走弯路。

上一篇下一篇

猜你喜欢

热点阅读