组播 笔记

文章目录

    • 简述
    • 单播
    • 组播发送Python
    • 组播接收Python
    • C组播接收
    • C组播发送
    • 参考

简述

SOME/IP用到了组播, 如华为MDC300F系统101版本的SOME/IP组播域地址为239.192.255.251, 端口号 30491. 本篇不讲SOME/IP, 只看下组播的概念.

组播(Multicast, 又称多播)是UDP专有的, 关于UDP的单播(Unicast), 组播, 广播(Broadcast)的区别, 网络上有张图有些形象:

组播 笔记_第1张图片

D类地址空间分配给了IP组播, 范围 224.0.0.0~239.255.255.255, 二进制表示前四位均为1110, 细分下来又有如下区别:

  • 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用。
  • 224.0.1.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
  • 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

这里用如下系统测试组播:

  • Win10, Ethernet adapter VMware Network Adapter VMnet8, 192.168.65.1
  • Ubuntu18(虚拟机中), ens33, 192.168.65.156
  • Ubuntu20(虚拟机中), ens33, 192.168.65.128

Win10能ping通U18和U20, U18和U20彼此可以ping通

单播

先来看下单播, 用Ubuntu18发送数据给Win10和Ubuntu20, python3的发送程序 tx.py

import socket
import time

win10_addr = ("192.168.65.1", 34567)
u20_addr   = ("192.168.65.128", 34567)
# local_addr = ("192.168.65.156", 34568)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# sock.bind(local_addr)

cnt = 0
while True:
    MSG = bytes(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}' + \
        ", it's unicast, " + str(cnt), encoding="utf8")
    print(MSG)    
    sock.sendto(MSG, win10_addr)
    sock.sendto(MSG, u20_addr)
    cnt = cnt + 1
    time.sleep(1)


# $ python3 tx.py 
# b"2021-10-12 14:48:24, it's unicast, 0"
# b"2021-10-12 14:48:25, it's unicast, 1"

Win10这边python3的接收程序, Ubuntu20把addr改为addr = ("192.168.65.128", 34567)

import socket

addr = ("192.168.65.1", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(addr)

while True:
    r_data, r_addr = sock.recvfrom(1024)
    print(r_addr, r_data)
    
# ('192.168.65.156', 37053) b"2021-10-12 14:59:36, it's unicast, 120"
# ('192.168.65.156', 37053) b"2021-10-12 14:59:37, it's unicast, 121"

这样Win10和Ubuntu20都能接收到Ubuntu18发出的消息, 但注意发送程序中的两个 sock.sendto, 如果再增加一个设备接收消息, 那个发送程序还是要修改, 这是很不方便且耗费网络资源的(如果直播按这种方式多一个用户观看要多一份视频带宽, 这将是不能忍受的), 由Wireshark也能看出来, 一份消息被投递了两次

组播 笔记_第2张图片

有没有方法只发送一次, 就能让两个设备都接收到, 并且即便再增加设备接收消息, 也不需要修改发送程序? 来, 组播上场

组播发送Python

先来看Ubuntu18的发送程序:

import socket
import time

multicast_addr = ("239.255.255.252", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

cnt = 0
while True:
    MSG = bytes(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}' + \
        ", it's unicast, " + str(cnt), encoding="utf8")
    print(MSG)    
    sock.sendto(MSG, multicast_addr)
    cnt = cnt + 1
    time.sleep(1)

组播接收Python

Win10的接收程序

import socket

win10_addr = ("192.168.65.1", 34567)
multicast_addr = ("239.255.255.252", 34567)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# allow multiple sockets to use the same PORT number
# sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sock.bind(win10_addr)
sock.setsockopt(socket.IPPROTO_IP,\
        socket.IP_ADD_MEMBERSHIP,\
        socket.inet_aton(multicast_addr[0]) + socket.inet_aton(win10_addr[0]))
#sock.setblocking(0)

while True:
    r_data, r_addr = sock.recvfrom(1024)
    print(r_addr, r_data)
    # try:
    #     r_data, r_addr = sock.recvfrom(1024)
    # except socket.error:
    #         pass
    # else:
    #     print(r_addr, r_data)
    
# ('192.168.65.156', 43223) b"2021-10-12 15:43:06, it's unicast, 1515"
# ('192.168.65.156', 43223) b"2021-10-12 15:43:07, it's unicast, 1516"

Ubuntu20的接收程序(与Win10不同)

import socket
import struct

MCAST_GRP = '239.255.255.252'
MCAST_PORT = 34567
IS_ALL_GROUPS = True

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
    # on this port, receives ALL multicast groups
    sock.bind(('', MCAST_PORT))
else:
    # on this port, listen ONLY to MCAST_GRP
    sock.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)

sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

while True:
    print(sock.recv(10240))
    
# b"2021-10-12 16:18:09, it's unicast, 3614"
# b"2021-10-12 16:18:10, it's unicast, 3615"
# b"2021-10-12 16:18:11, it's unicast, 3616"

# 复制一份程序继续运行, 测试发现组播还能被同一个设备的两个进程同时使用

Ubuntu下也有一种是直接绑定网卡的方式

# 一种是直接绑定网卡的
import struct
import socket

nic_name = 'ens33'	#网卡名
# ubuntu20_addr = ("192.168.65.128", 34567)
multicast_addr = ("239.255.255.252", 34567)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# 25是linux上的socket.SO_BINDTODEVICE的宏定义
sock.setsockopt(socket.SOL_SOCKET,25,nic_name.encode())
sock.bind(multicast_addr)
# 加入组播组
mreq = struct.pack("=4sl", socket.inet_aton(multicast_addr[0]), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq)

while True:
    r_data, r_addr = sock.recvfrom(1024)
    print(r_addr, r_data)

用wireshark查看, 确实是Ubuntu18只投递了一次数据, 两个设备都能接收到, 即便再增加设备收消息, 发送程序也无需改动

组播 笔记_第3张图片

C组播接收

直接搬参考代码

/* Receiver/client multicast Datagram example. */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 

#define PORT 34567
#define LOCAL_IP "192.168.65.128"
#define MCAST_IP "239.255.255.252"

struct sockaddr_in localSock;
struct ip_mreq group;
int sd;
int datalen;
char databuf[1024];

int main(int argc, char *argv[])
{
    /* Create a datagram socket on which to receive. */
    sd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sd < 0)
    {
        perror("Opening datagram socket error");
        exit(1);
    }
    else
    {
        printf("Opening datagram socket....OK.\n");
    }

    /* Enable SO_REUSEADDR to allow multiple instances of this */
    /* application to receive copies of the multicast datagrams. */
    {
        int reuse = 1;
        if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0)
        {
            perror("Setting SO_REUSEADDR error");
            close(sd);
            exit(1);
        }
        else
        {
            printf("Setting SO_REUSEADDR...OK.\n");
        }
    }

    /* Bind to the proper port number with the IP address */
    /* specified as INADDR_ANY. */
    memset((char *)&localSock, 0, sizeof(localSock));
    localSock.sin_family = AF_INET;
    localSock.sin_port = htons(PORT);
    localSock.sin_addr.s_addr = INADDR_ANY;

    if (bind(sd, (struct sockaddr *)&localSock, sizeof(localSock)))
    {
        perror("Binding datagram socket error");
        close(sd);
        exit(1);
    }
    else
    {
        printf("Binding datagram socket...OK.\n");
    }

    /* Join the multicast group 226.1.1.1 on the local 203.106.93.94 */
    /* interface. Note that this IP_ADD_MEMBERSHIP option must be */
    /* called for each local interface over which the multicast */
    /* datagrams are to be received. */
    group.imr_multiaddr.s_addr = inet_addr(MCAST_IP);
    group.imr_interface.s_addr = inet_addr(LOCAL_IP);

    if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0)
    {
        perror("Adding multicast group error");
        close(sd);
        exit(1);
    }
    else
    {
        printf("Adding multicast group...OK.\n");
    }

    /* Read from the socket. */
    datalen = sizeof(databuf);

    while(1)
    {
        if (read(sd, databuf, datalen) < 0)
        {
            perror("Reading datagram message error");
            close(sd);
            exit(1);
        }
        else
        {
            printf("Reading datagram message...OK.\n");
            printf("The message from multicast server is: \"%s\"\n", databuf);
        }
    }

    return 0;
}

C组播发送

/* Send Multicast Datagram code example. */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 34567
#define LOCAL_IP "192.168.65.128"
#define MCAST_IP "239.255.255.252"

struct in_addr localInterface;
struct sockaddr_in groupSock;
int sd;
char databuf[1024] = "Multicast test message lol!";
int datalen = sizeof(databuf);

int main(int argc, char *argv[])
{
    /* Create a datagram socket on which to send. */
    sd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sd < 0)
    {
        perror("Opening datagram socket error");
        exit(1);
    }
    else
    {
        printf("Opening the datagram socket...OK.\n");
    }

    /* Initialize the group sockaddr structure with a */
    /* group address of 225.1.1.1 and port 5555. */

    memset((char *)&groupSock, 0, sizeof(groupSock));
    groupSock.sin_family = AF_INET;
    groupSock.sin_addr.s_addr = inet_addr(MCAST_IP);
    groupSock.sin_port = htons(PORT);
    /* Disable loopback so you do not receive your own datagrams.
    {
        char loopch = 0;
        if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch)) < 0)
        {
            perror("Setting IP_MULTICAST_LOOP error");
            close(sd);
            exit(1);
        }
        else
        {
            printf("Disabling the loopback...OK.\n");
        }
    }
    */

    /* Set local interface for outbound multicast datagrams. */
    /* The IP address specified must be associated with a local, */
    /* multicast capable interface. */

    localInterface.s_addr = inet_addr(LOCAL_IP);
    if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface)) < 0)
    {
        perror("Setting local interface error");
        exit(1);
    }
    else
    {
        printf("Setting the local interface...OK\n");
    }

    /* Send a message to the multicast group specified by the*/
    /* groupSock sockaddr structure. */
    /*int datalen = 1024;*/
    while(1)
    {
        if (sendto(sd, databuf, datalen, 0, (struct sockaddr *)&groupSock, sizeof(groupSock)) < 0)
        {
            perror("Sending datagram message error");
        }
        else
        {
            printf("Sending datagram message...OK\n");
        }
        sleep(1);
    }

    /* Try the re-read from the socket if the loopback is not disable
    if(read(sd, databuf, datalen) < 0)
    {
        perror("Reading datagram message error\n");
        close(sd);
        exit(1);
    }
    else
    {
        printf("Reading datagram message from client...OK\n");
        printf("The message is: %s\n", databuf);
    }
    */

    return 0;
}

在Ubuntu20运行, Wireshark可以看到, 两个组播发送都可以发出来, 在Win10上也可以接收到所有的组播数据

组播 笔记_第4张图片

组播 笔记_第5张图片

参考

How do you UDP multicast in Python? - Stack Overflow

The Linux socket and network programming on multicasting client-server with C program example (tenouk.com)

https://web.cs.wpi.edu/~claypool/courses/4514-B99/samples/multicast.c

Python3组播通信编程实现教程(发送者+接收者) - 诸子流 - 博客园 (cnblogs.com)

通过python下的socket实现组播数据的发送和接收_ztb3214的专栏-CSDN博客_python 组播

组播的时候到底该如何绑定网卡_holly的专栏-CSDN博客

组播_百度百科 (baidu.com)

UDP 用户数据报格式(单播+组播)_u010018619的博客-CSDN博客

欢迎扫描二维码关注本人微信公众号, 及时获取最新文章:
在这里插入图片描述

你可能感兴趣的:(CS,udp,pyhton,c,组播,多播)