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

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

作者: hylccmh | 来源:发表于2021-03-18 17:33 被阅读0次
    一、说明

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/qllecltx.html