背景

有些时候我们在网络通信中也需要用到 组播(多播)、广播。现在我们来介绍如何实现。

建议:在此之前,关闭防火墙。

ubuntu: service ufw stop

windows: 控制面板关闭

有关知识

基本概念

  1. 单播:两个主机间单对单的通信
  2. 广播:一个主机对整个局域网上所有主机上的数据通信(网络地址全1)

单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网的主机进行通信

  1. 组播:实际情况下,经常需要对一组特定的主机进行通信,而不是所有局域网上的主机
  • IP组播(也称多址广播或多播),是一种允许一台或多台主机发送数据包到多台主机的TCP/IP网路技术。

  • 多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特殊例子。

多播组只能用UDP 或者原始套接字实现,不能用TCP。

广播地址

在使用TCP/IP 协议的网络中,主机标识段host ID 为全1 的IP 地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。

传输层只有UDP可以广播 。

组播地址

IP 组播通信必须依赖于 IP 多播地址,在 IPv4 中它是一个 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址3类:

  • 局部链接多播地址范围在 224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包;

  • 预留多播地址为 224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议;

  • 管理权限多播地址为 239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围。

组播地址与MAC地址的映射

使用同一个 IP 多播地址接收多播数据包的所有主机构成了一个主机组,也称为多播组。一个多播组的成员是随时变动的,一台主机可以随时加入或离开多播组,多播组成员的数目和所在的地理位置也不受限制,一台主机也可以属于几个多播组。

这个我们可以这样理解,多播地址就类似于 QQ 群号,多播组相当于 QQ 群,一个个的主机就相当于群里面的成员。

设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤,这个过滤过程是网络驱动或IP层自动完成。(设备驱动程序会对多播数据进行过滤,将其发到相应的位置)

组播应用

  1. 单点对多点应用

点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。

  1. 多点对单点应用

多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。

  1. 多点对多点应用

多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等。

组播编程

多播程序框架主要包含套接字初始化、设置多播超时时间、加入多播组、发送数据、接收数据以及从多播组中离开几个方面。其步骤如下:

1)建立一个socket。

2)然后设置接收方多播的参数,例如超时时间TTL、本地回环许可LOOP等。

3)设置接收方加入多播组。

4)发送和接收数据。

5)从多播组离开。

我们需要用到 setsocket 函数 ,使用这些参数:

int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen); struct ip_mreq
{ struct in_addr imn_multiaddr; // 多播组 IP,类似于 群号 struct in_addr imr_interface; // 将要添加到多播组的 IP,类似于 成员号 }; struct in_addr
{
in_addr_t s_addr;
} // 当imr_interface 为 INADDR_ANY 时,选择的是默认组播接口。

level :

  • IPPROTO_IP

optname:

  • IP_MULTICAST_LOOP 支持多播数据回送
  • IP_ADD_MEMBERSHIP 加入多播组
  • IP_DROP_MEMBERSHIP 离开多播组

默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。

使用IP_ADD_MEMBERSHIP选项每次只能加入一个网络接口的IP地址到多播组,但并不是一个多播组仅允许一个主机IP地址加入,可以多次调用IP_ADD_MEMBERSHIP选项来实现多个IP地址加入同一个广播组,或者同一个IP地址加入多个广播组。

optval:

  • IP_MULTICAST_LOOP 选项对应传入 unsigned int 来确认是否支持多播数据回送
  • IP_ADD_MEMBERSHIP 传入 ip_mreq
  • IP_DROP_MEMBERSHIP 传入 ip_mreq

组播例程

group_client.c

/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: group_client.c
# Created : Mon 23 Mar 2020 04:00:49 PM CST
*/ #include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> #define IP_FOUND "IP_FOUND"
#define IP_FOUND_ACK "IP_FOUND_ACK" /*
广播与多播只支持UDP协议,因为TCP协议是端到端,这与广播与多播的理念相冲突
广播是局域网中一个主机对所有主机的数据通信,而多播是一个主机对一组特定的主机进行通信.多播可以是因特网,而广播只能是局域网。多播常用于视频电话,网上会议等。 setsockopt设置套接字选项可以设置多播的一些相关信息 IP_MULTICAST_TTL //设置多播的跳数值
IP_ADD_MEMBERSHIP //将主机的指定接口加入多播组,以后就从这个指定的接口发送与接收数据
IP_DROP_MEMBERSHIP //主机退出多播组
IP_MULTICAST_IF //获取默认的接口或设置多播接口
IP_MULTICAST_LOOP //设置或禁止多播数据回送,即多播的数据是否回送到本地回环接口 例子:
int ttl=255;
setsockopt(socket,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));//设置跳数 socket -套接字描述符
PROTO_IP -选项所在的协议层
IP_MULTICAST_TTL -选项名
&ttl -设置的内存缓冲区
sizeof(ttl) -设置的内存缓冲区长度 struct in_addr in; setsockopt(socket,IPPROTO_IP,IP_MUTLICAST_IF,&in,sizeof(in));//设置组播接口 int yes=1;
setsockopt(socket,IPPROTO_IP,IP_MULTICAST_LOOP,&yes,sizeof(yes));//设置数据回送到本地回环接口 struct ip_mreq addreq;
setsockopt(socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&req,sizeof(req));//加入组播组 struct ip_mreq dropreq;
setsockopt(socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&dropreq,sizeof(dropreq));//离开组播组 */ #define MCAST_ADDR "224.0.0.88" int main(int argc ,char **argv)
{
int ret,count;
int sock_fd;
char send_buf[20];
char recv_buf[20]; struct sockaddr_in server_addr; //多播地址
struct sockaddr_in our_addr;
struct sockaddr_in recvaddr;
int so_broadcast=1; socklen_t socklen; sock_fd = socket(AF_INET, SOCK_DGRAM, 0); memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET; //server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_addr.s_addr=inet_addr(MCAST_ADDR); //多播地址
server_addr.sin_port = htons(6666); //客户端绑定通信端口,否则系统自动分配
memset(&our_addr,0,sizeof(our_addr));
our_addr.sin_family = AF_INET;
our_addr.sin_port = htons(7777);
our_addr.sin_addr.s_addr = htonl(INADDR_ANY); //MCAST_ADDR
//自定义地址如果为有效地址
//则协议栈将自定义地址与端口信息发送到接收方
//否则协议栈将使用默认的回环地址与自动端口
//our_addr.sin_addr.s_addr = inet_addr("127.0.0.10"); ret = bind(sock_fd, (struct sockaddr *)&our_addr, sizeof(our_addr) );
if(ret == -1)
{
perror("bind !");
} socklen = sizeof(struct sockaddr);
strncpy(send_buf,IP_FOUND,strlen(IP_FOUND)+1); for(count=0;count<1;count++)
{
ret = sendto(sock_fd, send_buf, strlen(send_buf)+1, 0,(struct sockaddr *)&server_addr, socklen);
if(ret != strlen(send_buf)+1)
{
perror(" send to !");
} ret = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&recvaddr, &socklen);
if(ret < 0 )
{
perror(" recv! ");
} printf(" recv server addr : %s \n", (char *)inet_ntoa(recvaddr.sin_addr));
printf(" recv server port : %d \n", ntohs(recvaddr.sin_port) );
printf(" recv server msg :%s \n", recv_buf); } close(sock_fd); return 0;
}

group_server.c

/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: group_server.c
# Created : Mon 23 Mar 2020 04:02:12 PM CST
*/ #include <stdio.h>
#include <string.h>
#include <unistd.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> #define IP_FOUND "IP_FOUND"
#define IP_FOUND_ACK "IP_FOUND_ACK"
#define MCAST "224.0.0.88" //说明:设置主机的TTL值,是否允许本地回环,加入多播组,然后服务器向加入多播组的主机发送数据,主机接收数据,并响应服务器。 int main(int argc,char **argv)
{ int sock_fd,client_fd;
int ret;
struct sockaddr_in localaddr;
struct sockaddr_in recvaddr;
socklen_t socklen;
char recv_buf[20];
char send_buf[20];
int ttl = 10;//如果转发的次数等于10,则不再转发
int loop=0; sock_fd = socket(AF_INET, SOCK_DGRAM , 0);
if(sock_fd == -1)
{
perror(" socket !");
} memset(&localaddr,0,sizeof(localaddr));
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(6666);
localaddr.sin_addr.s_addr = htonl(INADDR_ANY); ret = bind(sock_fd, (struct sockaddr *)&localaddr,sizeof(localaddr));
if(ret == -1)
{
perror("bind !");
} socklen = sizeof(struct sockaddr); //设置多播的TTL值
if(setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))<0){
perror("IP_MULTICAST_TTL");
return -1;
}
//设置数据是否发送到本地回环接口
if(setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop))<0){
perror("IP_MULTICAST_LOOP");
return -1;
}
//加入多播组
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr=inet_addr(MCAST);//多播组的IP
mreq.imr_interface.s_addr=htonl(INADDR_ANY);//本机的默认接口IP,本机的随机IP
if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("IP_ADD_MEMBERSHIP");
return -1;
} while(1)
{
ret = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&recvaddr, &socklen);
if(ret < 0 )
{
perror(" recv! ");
} printf(" recv client addr : %s \n", (char *)inet_ntoa(recvaddr.sin_addr));
printf(" recv client port : %d \n",ntohs(recvaddr.sin_port));
printf(" recv msg :%s \n", recv_buf); if(strstr(recv_buf,IP_FOUND))
{
//响应客户端请求
strncpy(send_buf, IP_FOUND_ACK, strlen(IP_FOUND_ACK) + 1);
ret = sendto(sock_fd, send_buf, strlen(IP_FOUND_ACK) + 1, 0, (struct sockaddr*)&recvaddr, socklen);//将数据发送给客户端
if(ret < 0 )
{
perror(" sendto! ");
}
printf(" send ack msg to client !\n");
}
} // 离开多播组
ret = setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
if(ret < 0){
perror("IP_DROP_MEMBERSHIP");
return -1;
} close(sock_fd); return 0;
}

广播编程

广播的实现比较简单,只需要设置socket允许广播(想广播的终端设置即可),然后在发送数据前指定为广播地址即可。我们直接看例程吧。

boardcast_client.c

/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: boardcast_client.c
# Created : Mon 23 Mar 2020 04:29:48 PM CST
*/ #include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h> int main()
{
char send_buf[100] = "GET Msg";
char recv_buf[100]; // 1 创建一个套接字,用于网络通信
int sk_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (sk_fd == -1)
{
perror("socket");
return -1;
} // 2 绑定服务的IP与端口
struct sockaddr_in ser_addr;
ser_addr.sin_family = PF_INET;
ser_addr.sin_port = htons (6666) ;
ser_addr.sin_addr.s_addr = inet_addr("192.168.1.88");
int ret = bind(sk_fd, (struct sockaddr *)&ser_addr,sizeof(ser_addr)); if (ret == -1)
{
perror("bind");
return -1;
}
// 3 等待 服务器广播
struct sockaddr_in src_addr;
socklen_t size = sizeof(ser_addr);
ret = recvfrom(sk_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&src_addr, &size);
if (ret == -1)
{
perror("reveform");
return -1;
}
printf("recv :%s\n",recv_buf); // 4 关闭套接字
close(sk_fd); return 0;
}

boardcast_server.c

/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: boardcast_server.c
# Created : Mon 23 Mar 2020 04:29:48 PM CST
*/ // UDP 服务端
#include <stdio.h>
#include <unistd.h>
#include <string.h> #include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> int main()
{
char send_buf[100] = "hello client 6666";
char recv_buf[100]; // 1 创建一个套接字,用于网络通信
int sk_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (sk_fd == -1)
{
perror("socket");
return -1;
} //设置广播使能
int on = 1;
setsockopt(sk_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // 2 绑定服务的IP与端口
struct sockaddr_in ser_addr;
ser_addr.sin_family = PF_INET;
ser_addr.sin_port = htons (6666) ;
ser_addr.sin_addr.s_addr = inet_addr("192.168.1.88");
int ret = bind(sk_fd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));
if (ret == -1)
{
perror("bind");
return -1;
} // 3 广播数据
struct sockaddr_in client_addr;
client_addr.sin_family = PF_INET;
client_addr.sin_port = htons (6666) ;
client_addr.sin_addr.s_addr = inet_addr("192.168.1.255");
socklen_t size = sizeof(struct sockaddr_in); while(1)
{ ret = sendto(sk_fd, send_buf, sizeof(send_buf), 0,(struct sockaddr *)&client_addr, size);
if (ret == -1)
{
perror("sendto");
return -1;
} } // 4 关闭套接字
close(sk_fd); return 0;
}

基于 UDP 的 组播、广播详解的更多相关文章

  1. 线程组ThreadGroup分析详解 多线程中篇(三)

    线程组,顾名思义,就是线程的组,逻辑类似项目组,用于管理项目成员,线程组就是用来管理线程. 每个线程都会有一个线程组,如果没有设置将会有些默认的初始化设置 而在java中线程组则是使用类ThreadG ...

  2. TCP/IP、UDP、HTTP、SOCKET详解

    文章大纲 网络OSI七层及各层作用 TCP与UDP基本介绍 TCP连接过程详解 SOCKET原理与连接详解     一.网络OSI七层及各层作用   应用层:文件传输,电子邮件,文件服务,虚拟终端 T ...

  3. 基于H5的微信支付开发详解

    这次总结一下用户在微信内打开网页时,可以调用微信支付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能.当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现代码可 ...

  4. ****基于H5的微信支付开发详解[转]

    这次总结一下用户在微信内打开网页时,可以调用微信支付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能.当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现代码可 ...

  5. UDP及其组播,接收发送封装

    1.Receiver public class Receiver { public delegate void HeartBeat(byte[] data); public event HeartBe ...

  6. ***PHP基于H5的微信支付开发详解(CI框架)

    这次总结一下用户在微信内打开网页时,可以调用微信支付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能.当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现代码可 ...

  7. 基于JavaSE阶段的IO流详解

    1.IO流基本概述 在Java语言中定义了许多针对不同的传输方式,最基本的就是输入输出流(俗称IO流),IO流是属于java.io包下的内容,在JavaSE阶段主要学下图所示的: 其中从图中可知,所有 ...

  8. [蓝桥杯]2017蓝桥省赛B组题目及详解

    /*——————————————————————————————————————————————————————————— [结果填空题]T1 (分值:5) 题目:购物单 小明刚刚找到工作,老板人很好 ...

  9. [蓝桥杯]2016蓝桥省赛B组题目及详解

    /*——————————————————————————————————————————————————————————— [结果填空题]T1 (分值:3) 题目:煤球数目 有一堆煤球,堆成三角棱锥形 ...

  10. [蓝桥杯]2014蓝桥省赛B组题目及详解

    /*——————————————————————————————————————————————————————————— [结果填空题]T1 题目:啤酒和饮料 啤酒每罐2.3元,饮料每罐1.9元.小 ...

随机推荐

  1. 使用 @NoRepositoryBean 简化数据库访问

    在 Spring Data JPA 应用程序中管理跨多个存储库接口的数据库访问逻辑可能会变得乏味且容易出错.开发人员经常发现自己为常见查询和方法重复代码,从而导致维护挑战和代码冗余.幸运的是,Spri ...

  2. python+requests爬取B站视频保存到本地

    import os import datetime from django.test import TestCase # Create your tests here. import requests ...

  3. go http请求如果参数中带有"等特殊字符,参数传输可能会出现问题

    编码完整的URL url.QueryEscape(urlStr) 编码完整的URL 如果我们要对完整的 URL 进行编码呢? 就是PHP中 urlencode() 函数的功能. 在 GO 语言下可以直 ...

  4. ansible(18)--ansible的selinux模块

    1. selinux模块 功能:管理远端主机的 SELINUX 防火墙: 主要参数如下: 参数 说明 state Selinux模式:enforcing.permissive.disabled pol ...

  5. fastposter 2.1.1 紧急版本发布 电商级海报生成器

    fastposter 2.1.1 紧急版本发布 电商级海报生成器 fastposter低代码海报生成器,一分钟完成海报开发.支持Java Python PHP Go JavaScript等多种语言. ...

  6. 远程桌面使用Pr剪视频

    要远程访问高性能计算机并使用 Pr(Adobe Premiere Pro)进行视频编辑,您可以考虑使用流畅且响应迅速的远程桌面软件.您可以考虑以下选项. Splashtop Business Acce ...

  7. JDK源码阅读-------自学笔记(二十)(java.util.List初探)

    List简介 List是有序.可重复的容器. 有序:List中每个元素都有索引标记.可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素. 可重复:List允许加入重复的元素.更 ...

  8. containerd 源码分析:kubelet 和 containerd 交互

    0. 前言 Kubernetes:kubelet 源码分析之创建 pod 流程 介绍了 kubelet 创建 pod 的流程,其中介绍了 kubelet 调用 runtime cri 接口创建 pod ...

  9. 【超详细】宝塔面板安装WordPress程序图文教程

    宝塔面板是目前广受用户喜爱的服务器控制面板之一,自己也在用,确实很方便,很多用户的网站都是基于宝塔面板搭建,今天简单介绍下宝塔面板是如何安装WordPress的,方便新手用户快速部署.也方便自己以后查 ...

  10. layui-框架学习小总结

    主要6点: 1.导航栏变成了类似tab的页签,支持关闭,点击刷新. 2.左侧菜单树可隐藏. 3.树的搜索. 4.表格的新增行,并保存到后台. 5.表格 加载 下拉框,并赋值,选择了值后把值同步到表格对 ...