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

一、说明

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容