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

多播/组播

从基础知识 ip 地址一节知识,可以知道多播使用 D 类 IP 地址。
在这里插入图片描述
在这里插入图片描述
下面就通过程序来完成一个组播的实现。

实现逻辑如下:发送方应该创建一个多播组,并且发送组间消息。接收方应该加入多播组,并且接收组间消息。因此,这个组就必须成为大家都应该知道的事情。因此应该在双方约定的协议中进行定义。

在 proto.h 中,

//定义一个多播组,它的组号(ip地址)为“224.2.2.2”
#define MTGROUP "224.2.2.2"

在 snder.c 中,
发送的目标地址就应该是

inet_pton(AF_INET,MTGROUP,&raddr.sin_addr.s_addr);

多播也是不能随时发送的,它也有它自己所谓的开关或旋钮。

使用命令 “man 7 ip” 查看 ip 层的手册,然后使用命令 “/socket opt” 来查看,

Socket options

IP supports some protocol-specific socket options that can be set with setsockopt(2) and read with getsockopt(2). The socket option level for IP  is  IPPROTO_IP. A boolean integer flag is zero when it is false,otherwise true.

翻译如下:
IP支持一些特定于协议的套接字选项,这些选项可以用setsockopt(2)设置,也可以用getsockopt(2)读取。IP的套接字选项级别是IPPROTO_IP

如果想要加入多播组,看下面。

IP_ADD_MEMBERSHIP (since Linux 1.2)
Join a multicast group.  Argument is an ip_mreqn structure.

struct ip_mreqn {
    struct in_addr imr_multiaddr; /* IP multicast group
                                     address 多播组地址*/
    struct in_addr imr_address;   /* IP address of local
                                     interface 当前自己的Ip地址*/
    int            imr_ifindex;   /* interface index 网络索引号*/
};

//struct in_addr 实际就是一个uint32_t的大整数,只不过封装了一下。
//这里使用inet_pton来进行转换

//对于网络索引号应该怎么查看呢?
//我们可以使用命令 ip ad sh来查看

在这里插入图片描述
在程序里面,我们可以使用一个函数来获取,

SYNOPSIS
#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);

而如果想要离开多播组,看下面。

IP_DROP_MEMBERSHIP (since Linux 1.2)
Leave  a  multicast  group.   Argument is an ip_mreqn or ip_mreq structure similar to IP_ADD_MEMBERSHIP.

而如果想要创建一个多播组,看下面。

IP_MULTICAST_IF (since Linux 1.2)
Set the local device for a multicast socket.  The  argument  for setsockopt(2) is an ip_mreqn or (since Linux 3.5) ip_mreq structure similar to  IP_ADD_MEMBERSHIP,  or  an  in_addr  structure. (The  kernel determines which structure is being passed based on the size passed in optlen.)  For getsockopt(2), the argument  is an in_addr structure.

配置本设备为组播套接字。setsockopt(2)的参数是一个ip_mreqn或ip_mreq结构体(从Linux 3.5开始),类似于IP_ADD_MEMBERSHIP,或in_addr结构。(内核根据optlen中传递的大小确定传递的是哪个结构。)对于getsockopt(2),参数是一个in_addr结构。

因此,作为发送方,应该创建多播组,而作为接收方,则应该加入多播组。

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    -- 在Socket options处提到IP的套接字选项级别是IPPROTO_IP。
optname  -- 属性名,这里设置为IP_MULTICAST_IF,表明创建一个多播组
optval   -- 应该是一个ip_mreqn或ip_mreq结构体(从Linux 3.5开始),类似于IP_ADD_MEMBERSHIP,或in_addr结构。
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.

回顾下,inet_pton

NAME
       inet_pton - convert IPv4 and IPv6 addresses from text to binary form
       将IPv4 and IPv6 addresse转换成二进制形式

SYNOPSIS
       #include <arpa/inet.h>

       int inet_pton(int af, const char *src, void *dst);
RETURN VALUE
       inet_pton() returns 1 on success (network address was successfully converted).  0 is returned if src does not contain a character string  representing a valid network address in the specified address family.  If af does not contain a valid address family, -1 is returned and errno is set to EAFNOSUPPORT.
       
第一个参数af,因为转换的是ipv4和ipv6的地址,所以使用的协议族要么是 AF_INET 要么是AF_INET6.

第二个参数const char *src,待转换的ip地址。

第三个参数void *dst,表示转换完成后,放到哪块空间去。

snder.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>

#include "proto.h"

int main(int argc,char* argv[])
{
    //修改1:添加 name 字段 
    if(argc < 2)
    {
        fprintf(stderr,"%s ipaddress message.\n",argv[0]);
        exit(1);
    }

    if(strlen(argv[1]) > NAMEMAX)
    {
        fprintf(stderr,"message is too long.\n");
		exit(1);
    }

    // 1、使用SOCKET来创建套接字
    int sd;
    int size;
    //修改2:将要发送的信息的地址需要修改成指针
    struct msg_st* psbuf;
    struct sockaddr_in raddr;

    //给要发送的信息申请空间
    //申请多大空间呢
    size = sizeof(struct msg_st)+strlen(argv[1]);
    psbuf = malloc(size);
    if(psbuf == NULL)
    {
        fprintf(stderr,"malloc fail.\n");
        exit(1);
    }

    sd = socket(AF_INET,SOCK_DGRAM,0);//这里相当于是用IPv4协议族中的IPPROTO_UDP>来实现SOCK_DGRAM的操作
	if(sd < 0)
    {
        perror("socket()");
        exit(1);
    }

    struct ip_mreqn mreq;
    inet_pton(AF_INET,MTGROUP,&mreq.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
    mreq.imr_ifindex = if_nametoindex("eth0");

    // 创建多播组
    if(setsockopt(sd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq))<0)
    {
        perror("setsockopt()");
        exit(1);
    }

    // 2、给SOCKET取得地址(可省略)

    // 3、收/发消息
	// 字符串,不能直接使用"=",应该使用strcpy函数。
    strcpy(psbuf->name, argv[1]);
    psbuf->math = htonl(rand()%100);
    psbuf->chinese = htonl(rand()%100);

    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RCVPORT));//对端的端口
    inet_pton(AF_INET,MTGROUP,&raddr.sin_addr.s_addr);

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

    puts("OK!\n");

    // 4、关闭SOCKET
    close(sd);

    return 0;
}

proto.h

//定义一个多播组,它的组号(ip地址)为“224.2.2.2”
#define MTGROUP "224.2.2.2"
//将来使用到时用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

下面再来看看接收方,接收方应该加入多播组。因此也需要设置setsockopt。加入多播组的属性为IP_ADD_MEMBERSHIP (看前面介绍)

rcver.c

#include <stdio.h>
...
#include <net/if.h>
#include "proto.h"

...
    struct ip_mreqn mreq;
    inet_pton(AF_INET,MTGROUP,&mreq.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);
    mreq.imr_ifindex = if_nametoindex("eth0");

    if(setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
    {
        perror("setsockopt() error!!!\n");
        exit(1);
    }
...

编译代码后运行,
在这里插入图片描述
这里就是 snder 方负责创建多播组,而 rcver 方则负责加入多播组。这样就完成了组间通信。

此外,因为多播中有一个特殊的 ip 地址(224.0.0.1),它表示,所有支持多播的地址默认都存在这个组当中,并且无法离开。如果snder 方向这个 ip 地址发送信息,就相当于向 255.255.255.255 上发消息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xuechanba

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

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

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

打赏作者

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

抵扣说明:

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

余额充值