嵌入式网络编程 -- 报式套接字 -- 多点通讯(一)

多点通讯

流式套接字的特点是点对点的,是没有办法实现多点通讯的。
报式套接字的多点通讯分为广播和多播。其中,广播又可分为全网广播和子网广播。而多播通常又被称为组播。

什么是广播

广播:由一台主机向该主机所在的子网内的所有主机发送数据的方式。

广播的实现方式

广播只能用 UDP 或原始 IP 实现,不能用 TCP 实现。

广播的用途

单个服务器与多个客户端主机通信时减少分组流通。
(比如老师上课没有必要一个个一对一去讲课,而是同时向对多个人去讲解。)
以下几个协议都用到广播:

1、地址解析协议(ARP)(作用:通过 ip 来获取对方的 MAC 地址)

2、动态主机配置协议(DHCP)(作用:自动获取主机 IP ,当你的电脑通过网线或者无线网接入网络,DHCP服务器会自动给你分配一个动态 IP 地址)
在这里插入图片描述
3、网络时间协议(NTP)

UDP广播的特点

1、处于同一个子网的所有主机都必须处理数据。

2、UDP 数据包会沿协议栈向上一直到 UDP 层。

3、运行音视频等较高速率工作的应用,会带来大量的负担。

4、局限于局域网内使用。

广播地址

在这里插入图片描述
定向广播地址:主机号全部为1。

1、例:对于 192.168.220.0/24 ,其定向广播地址为:192.168.220.255

2、通常路由器不转发该广播。

受限广播地址:255.255.255.255

对本机来说,这个地址指本网段内(同一广播域)的所有主机。可以这样理解:" 这个房间里的所有人都注意了!"

路由器从不转发此广播。

全网广播的实现

程序代码查看CSDN资料库中的 net/03broadcast/AllNetBroadcast

全网广播实现上就是向一个大家周知的地址(255.255.255.255)上发送一条消息。

在实现上,从前面的代码上进行修改。

通信协议文件 proto.h 不用修改。

//将来使用到时用atoi进行转换
#define RCVPORT "1995"

#define NAMEMAX (512-8-8)
//512是udp包的推荐大小
//第一个8是udp包头的大小
//第二个8是uint32_t math;和uint32_t chinese;所占的8个字节大小

struct msg_st
{
    uint32_t math;
    uint32_t chinese;
    // 尽管使用 name[0] 来做占位符操作,但是还是有必要定义以name为起始地址空间的
最大值
    uint8_t name[0];
    // 需要注意在数组中写0是C99的标准,因此如果使用C99以前的GCC编译器来编译代码>是会出错的
    // 因此,有时候为了避免出错,可以将0写成1.
}__attribute__((packed));

#endif

再看发送方文件 snder.c。

之前,我们的发送方 sendto 的时候,

if(sendto(sd,psbuf,size,0,(void *)&raddr,sizeof(raddr))<0)
{   
    perror("sendto()");
    exit(1);
}   

即要把 psbuf 中的内容发送到 raddr 这个地址上去,而 raddr 这个地址是通过 argv[1] 进行指定的,见下面代码。

inet_pton(AF_INET,argv[1],&raddr.sin_addr.s_addr);

因为,我们要实现全网广播,所以就要将信息发送到地址为 255.255.255.255 这个ip地址上去,因此,这里需要进行修改,需要特别注意,要加引号(字符串形式)。

inet_pton(AF_INET,"255.255.255.255",&raddr.sin_addr.s_addr);

接下来,还存在一个问题。

我们当前的广播默认是不可以被发送的,但是有一个开关是可以来控制的。

我们来 man 7 socket 查看。

在打开的帮助文档中去查找这样一个条目:Socket options,然后找到 SO_BROADCAST 这个属性。
在这里插入图片描述
上图翻译过来就是:下面列出的套接字选项可以通过使用setsockopt(2)进行设置,并使用 getsockopt(2)进行读取,所有套接字的套接字级别设置 SOL_SOCKET。除非另有说明,否则 optval 是一个 int 型指针。

在传输层(TCP/UDP)、网络层(IP)都有 Socket options,比如,我们想要查看 网络层 上的 Socket options,就可以使用 man 7 socket 先打开帮助文档,然后使用命令 /Socket options 来搜寻。

下面来介绍下 SO_BROADCAST 这个属性,
在这里插入图片描述
上图翻译过来就是:设置或获取 broadcast flag。启用后,允许数据报套接字向广播地址发送数据包。此选项对流式套接字没有影响。

在写代码之前,我们来看下 setsockopt 这个函数,使用命令 man setsockopt 来查看。

NAME
       getsockopt, setsockopt - get and set options on sockets

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

sockfd   -- socket 文件描述符
level    -- 所有套接字的套接字级别设置为 SOL_SOCKET,除非另有说明。
optname  -- 属性名,这里设置为SO_BROADCAST 
optval   -- 设置或获取 broadcast flag,可以理解为一个整型数。
optlen   -- 传入这个数据类型的大小。

我要对某一个 socket 描述符的level 层 及 optname 进行指定 ,由于是不同的level ,不同的optname ,所以就会有不同的 optval 参数,optval的类型可能是int、bool、结构体等等,所以 optlen 也就不一样。

RETURN VALUE
       On success, zero is returned for the standard options.  On error, -1 is
       returned, and errno is set appropriately.

下面,在代码中来设置这一项。一般属性的设置在创建 socket 之后。

int val=1;
if(setsockopt(sd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val))<0)
{
    perror("setsockopt()");
    exit(1);
}

需要注意的是,除了 SO_BROADCAST 这个属性之外,SO_BINDTODEVICE 这个属性也是比较经常使用的。通过这个属性,你可以

 SO_BINDTODEVICE
              Bind  this  socket to a particular device like “eth0”, as speci‐
              fied in the passed interface name.  If  the  name  is  an  empty
              string  or  the option length is zero, the socket device binding
              is removed.  The passed option is a variable-length  null-termi‐
              nated  interface  name string with the maximum size of IFNAMSIZ.
              If a socket is bound to an interface, only packets received from
              that  particular  interface  are  processed by the socket.  Note
              that this works only for some socket types, particularly AF_INET
              sockets.   It  is  not  supported for packet sockets (use normal
              bind(2) there).

翻译如下,将这个套接字绑定到一个特定的设备(指定网卡),如“eth0”,在传递的接口名称中指定。如果名称为空字符串或选项长度为零,则删除套接字设备绑定。传递的选项是一个可变长度的null- termini - nated接口名称字符串,其最大大小为IFNAMSIZ。如果套接字与某个接口绑定,则该套接字只处理从该接口接收到的报文。注意,这只适用于某些套接字类型,特别是AF_INET套接字。它不支持包套接字(使用正常的bind(2))。

用到的时候再来详细说一说吧。

下面来运行下代码。

要先运行接收方,再来运行发送方。
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
在这里插入图片描述
可以正常运行。

但是,如果只是上述写法的话,如果接收方不止一个用户的话,就会存在有的用户能够接收到信息,而有的用户则接收不到信息。这是什么原因呢?

我们需要注意帮助文档中有这么一条信息。

在这里插入图片描述
上面的笔记已经将该段内容给翻译出来了,内容提到使能之后,可以接收和发送,但是没有提到如果不使能的情况,所以存在不确定性。

因此,接收端也是需要进行修改的,修改的方法也是与发送方一样。

此外,如果做这个实验的时候,还是接收不到消息,有可能是防火墙的原因。

使用抓包器来抓取

打开 Wireshark 软件,
在这里插入图片描述
一般选择有心脏图的那个,
在这里插入图片描述
双击点进去。
在这里插入图片描述
然后要先运行接收方,再来运行发送方。
在这里插入图片描述
在这里插入图片描述
再点击暂停,停止抓包。
在这里插入图片描述
当抓取大量包的时候,怎么才能最快找到要抓取的包呢?使用条件筛选器。

因为我知道我发送包的目的地址是什么,所以就可以在条件筛选框中去这样写,最后,得到的结果如下:
在这里插入图片描述

子网广播的实现

对于子网广播的实现,我的思路是直接将全网广播地址替换为子网广播地址(看当前网段属于哪类网段,然后是 C 类网段,就修改为 xxx.xxx.xxx.255 ,如果是 B 类网段,就修改为 xxx.xxx.255.255 )

不知道为啥就是接收不到,是不是思路错了。将防火墙给关闭之后,也不行。

不整了,如果要实现,看视频链接:https://www.bilibili.com/video/BV1V5411G7WK?p=7&spm_id_from=333.880.my_history.page.click.
中的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuechanba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值