Bootstrap

组播与广播详解及Python实现

1. 广播(Broadcast)

广播的定义

广播是一种数据传输方式,数据包从一个源发送到网络中所有节点。广播是网络中的一对多通信方式,所有处于同一网络段的设备都能接收到广播数据包。

广播的特点
  • 范围限制:广播数据包只在本地网络(LAN)传播,不会穿越路由器到达其他网络段。
  • 地址格式:IPv4中,广播地址通常为子网的最高地址。例如,192.168.1.0/24网络的广播地址为192.168.1.255。
广播的Python实现

注:在下列示例代码中,先执行接收端代码再执行发送端代码。

发送端(broadcast_sender.py)
import socket
import struct


def get_local_ip():
    """
    获取本地计算机的 IP 地址。
    :return: 本地 IP 地址
    """
    # 创建一个 UDP 套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # 尝试连接一个外部的地址(不会实际发送数据)
        s.connect(('8.8.8.8', 80))
        local_ip = s.getsockname()[0]
    except Exception:
        local_ip = '127.0.0.1'
    finally:
        s.close()
    return local_ip


def calculate_broadcast_address(ip, subnet_mask):
    """
    计算给定 IP 地址和子网掩码的广播地址。
    :param ip: IP 地址
    :param subnet_mask: 子网掩码
    :return: 广播地址
    """
    ip_addr = struct.unpack('!I', socket.inet_aton(ip))[0]
    mask = struct.unpack('!I', socket.inet_aton(subnet_mask))[0]
    broadcast = ip_addr | ~mask
    return socket.inet_ntoa(struct.pack('!I', broadcast & 0xFFFFFFFF))


def send_broadcast(message, port):
    local_ip = get_local_ip()
    print(f'本机IP地址为:{local_ip}')
    subnet_mask = '255.255.255.0'  # 假设子网掩码为 255.255.255.0

    # 计算广播地址
    broadcast_address = calculate_broadcast_address(local_ip, subnet_mask)
    print(f"广播地址为: {broadcast_address}")
    # 创建 UDP 套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    # 创建 UDP 套接字用于广播
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    try:
        # 发送广播消息
        sock.sendto(message, (broadcast_address, port))
    finally:
        sock.close()
    print(f'已将广播消息 [{message}] 发送到 [{broadcast_address}:{port}]')

if __name__ == "__main__":
    send_broadcast(b'Hello, World!', 5000)

在这个代码示例中,创建了一个UDP套接字,并设置了SO_BROADCAST选项,以允许套接字发送广播消息。然后,将消息发送到广播地址。

接收端(broadcast_receiver.py)
import socket

def receive_broadcast(port):
    # 创建 UDP 套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    # 绑定到任意地址和指定端口
    sock.bind(('', port))
    print("接收广播消息中...")
    while True:
        data, addr = sock.recvfrom(1024)
        print(f'收到来自{addr}的广播消息: {data.decode()}')

if __name__ == "__main__":
    receive_broadcast(5000)

在这个代码示例中,创建了一个UDP套接字,并将其绑定到本地地址和端口5000,以接收广播消息。

2. 组播(Multicast)

组播的定义

组播是一种数据传输方式,数据包从一个源发送到网络中一组特定的节点(即一个组),而不是所有节点。组播是一对多的通信方式,但仅限于加入该组播组的接收者。

组播的特点
  • 范围灵活:组播数据包可以在局域网(LAN)或广域网(WAN)中传播,取决于网络配置。
  • 地址格式:IPv4组播地址范围是224.0.0.0到239.255.255.255。
组播的Python实现

注:在下列示例代码中,先执行接收端代码再执行发送端代码。

发送端(multicast_sender.py)
import socket
import struct

def multicast_message(message, group, port):
    # 创建 UDP 套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    # 设置TTL,控制数据包在网络中可以经过的最大跳数
    ttl = struct.pack('b', 1)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
    try:
        # 发送数据到组播地址
        sock.sendto(message, (group, port))
    finally:
        sock.close()
    print(f'已将组播消息 [{message}] 发送到 [{group}:{port}]')

if __name__ == "__main__":
    multicast_message(b'Hello, World!', '224.0.0.2', 5000)

在这个代码示例中,创建了一个UDP套接字,并设置了IP_MULTICAST_TTL选项,以控制组播数据包的生存时间(TTL)。然后,将消息发送到指定组播地址及端口号。

接收端(multicast_receiver.py)
import socket
import struct

def receive_multicast(group, port):
    # 创建 UDP 套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    # 绑定到组播地址和端口
    sock.bind(('', port))

    # 加入组播组
    mreq = struct.pack('4sl', socket.inet_aton(group), socket.INADDR_ANY)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    print('接收组播消息中...')

    while True:
        data, addr = sock.recvfrom(1024)
        print(f'接收到来自{addr}的组播消息: {data.decode()}')

if __name__ == "__main__":
    receive_multicast('224.0.0.2', 5000)

在这个代码示例中,创建了一个UDP套接字,并将其绑定到本地地址和端口5000。然后,通过设置IP_ADD_MEMBERSHIP选项来加入组播组,以接收组播消息。

3. setsockopt方法详解

sock.setsockopt 方法在 Python 的 socket 模块中用于设置套接字的选项。这个方法非常灵活,可以控制许多与套接字相关的行为。下面是 sock.setsockopt 可以设置的常见参数及其含义的详细解释:

level 参数

level 参数指定了套接字选项的协议层级。常见的层级有:

  • socket.SOL_SOCKET:套接字级别的选项。
  • socket.IPPROTO_IP:IPv4 协议级别的选项。
  • socket.IPPROTO_IPV6:IPv6 协议级别的选项。
  • socket.IPPROTO_TCP:TCP 协议级别的选项。
  • socket.IPPROTO_UDP:UDP 协议级别的选项。
optname 参数

optname 参数指定了具体的选项名称。以下是一些常见选项及其含义:

套接字级别选项 (socket.SOL_SOCKET)
  • socket.SO_BROADCAST:允许套接字发送广播消息。
  • socket.SO_REUSEADDR:允许重用本地地址和端口。
  • socket.SO_KEEPALIVE:启用TCP保持连接机制。
  • socket.SO_LINGER:控制套接字关闭时的行为。
  • socket.SO_RCVBUF:设置接收缓冲区大小。
  • socket.SO_SNDBUF:设置发送缓冲区大小。
  • socket.SO_RCVTIMEO:设置接收超时时间。
  • socket.SO_SNDTIMEO:设置发送超时时间。
IPv4 选项 (socket.IPPROTO_IP)
  • socket.IP_MULTICAST_TTL:设置组播数据包的生存时间(TTL)。
  • socket.IP_MULTICAST_LOOP:控制组播数据包是否回送到本地。
  • socket.IP_ADD_MEMBERSHIP:加入组播组。
  • socket.IP_DROP_MEMBERSHIP:退出组播组。
TCP 选项 (socket.IPPROTO_TCP)
  • socket.TCP_NODELAY:禁用Nagle算法,降低延迟。
value 参数

value 参数指定了选项的值,可以是整数、布尔值或特定的结构体。以下是一些常见的值及其格式:

布尔值选项

一些选项可以使用布尔值(0或1)来开启或关闭。例如:

sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

socket.SO_BROADCAST选项被设置为1,启用了广播功能,使套接字可以发送广播消息。

整数选项

一些选项需要整数值,例如设置缓冲区大小。整数值通常用于调整套接字的性能参数。

sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4096)

socket.SO_RCVBUF选项被设置为4096,指定接收缓冲区的大小为4096字节。这有助于控制数据接收时的缓冲能力。

结构体选项

某些选项需要特定的结构体,这通常涉及复杂的配置,如组播组的加入。这类选项通常通过struct模块来构造所需的结构体。

import struct
# 构造组播请求结构体
mreq = struct.pack('4sl', socket.inet_aton('224.0.0.2'), socket.INADDR_ANY)
# 加入组播组
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

使用struct.pack方法将组播地址和本地接口(socket.INADDR_ANY表示本地所有接口)打包成一个结构体。然后,通过socket.IP_ADD_MEMBERSHIP选项将套接字加入到指定的组播组。

常见示例

以下是一些常见的setsockopt用法示例,展示如何使用不同类型的值来配置套接字。

设置广播
# 启用广播
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
设置地址重用
# 启用地址重用
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
加入组播组
import struct
# 构造组播请求结构体
mreq = struct.pack('4sl', socket.inet_aton('224.0.0.2'), socket.INADDR_ANY)
# 加入组播组
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
设置组播TTL
# 设置组播TTL为2
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
设置发送和接收超时
import struct
# 设置接收超时为5秒
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack('ll', 5, 0))
# 设置发送超时为5秒
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, struct.pack('ll', 5, 0))

在设置发送和接收超时的示例中,使用struct.pack方法将超时时间(秒和微秒)打包成一个结构体,然后通过socket.SO_RCVTIMEO和socket.SO_SNDTIMEO选项分别设置接收和发送的超时时间。

;