Linux网络编程【10】(广播和组播)

629 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

广播

1 相关概念

前面介绍的数据包发送方式只有一个接受方,称为单播

如果同时发给局域网中的所有主机,称为广播

只有用户数据报(使用UDP协议)套接字才能广播

2 广播地址

以192.168.70.0 (255.255.255.0) 网段为例,

最大的主机地址192.168.70.255代表该网段的广播地址

255.255.255.255在所有网段中都代表广播地址

为什么广播可以发送给局域网所有主机:

因为广播的mac地址和IP地址非常特殊,广播的mac地址全是ff,ff:ff:ff:ff:ff:ff,广播的ip地址是当前网段的最后一个ip地址

当数据发送给局域网所有主机的时候,广播数据包中的目的mac是广播mac地址,目的ip地址是广播的ip地址,数据包会发送给交换机,交换机时工作在链路层的,一看数据包中的目的mac地址是广播的mac地址,就会将这个数据包发送给当前网段所有主机,每一台主机接收到数据之后会进行解包,先对比链路层的目的mac地址,一看是广播的,则可以通过,然后对比网络层的目的IP地址,一看是同网段的广播的ip地址,则可以通过,在到达传输层,只要保证端口号一致,就可以正常接收到应用层的数据

广播的用途:

ARP协议获取对方mac地址的时候,发送的数据包中的目的mac地址要指定为广播地址,也就意味着,==arp协议就是通过广播获取对方mac地址==

3 广播流程

广播是依据UDP实现,所以整个流程类似于UDP网络编程

发送者:

  1. 创建套接字 socket()
  2. 设置为允许发送广播 setsockopt()
  3. 填充广播信息结构体 struct sockaddr_in
  4. 发送数据 sendto()

接收者:

  1. 创建套接字 socket()
  2. 填充广播信息结构体 struct sockaddr_in
  3. 将套接字与广播信息结构体绑定 bind()
  4. 接收数据 recvfrom()

4 设置允许发送广播

  1. 头文件:
    1. #include <sys/types.h>
    2. #include <sys/socket.h>
  2. 原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  3. 功能:设置一个套接字的选项
  4. 参数:
    1. sockfd:文件描述符
    2. level:协议层次
      1. SOL_SOCKET 套接字层次
    3. optname:选项的名称
      1. SO_BROADCAST 设置是否允许发送广播
    4. optval:要设置的值 1 允许 0 不允许
    5. optlen:optval的大小
  5. 返回值:
  6. 成功:0
  7. 失败:-1

5 代码

5.1 发送者

//广播之发送者

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in broadcastaddr;
    socklen_t addrlen = sizeof(broadcastaddr);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //设置允许发送广播权限
    int on = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
    {
        ERRLOG("setsockopt error");
    }

    //第二步:填充广播信息结构体
    broadcastaddr.sin_family = AF_INET;
    broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255  255.255.255.255
    broadcastaddr.sin_port = htons(atoi(argv[2]));

    //进行通信
    char buf[N] = {0};
    while(1)
    {
        fgets(buf, N, stdin);
        buf[strlen(buf) - 1] = '\0';

        if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&broadcastaddr, addrlen) == -1)
        {
            ERRLOG("sendto error");
        }
    }

    return 0;
}

5.2 接收者

//广播之接收者

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in broadcastaddr, sendaddr;
    socklen_t addrlen = sizeof(broadcastaddr);
    char buf[N] = {0};

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充广播信息结构体
    broadcastaddr.sin_family = AF_INET;
    broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255  255.255.255.255
    broadcastaddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与广播信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&broadcastaddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //进行通信
    while(1)
    {
        if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
        {
            ERRLOG("recvfrom error");
        }

        printf("%s-%d:%s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), buf);
    }

    return 0;
}

组播

1 相关概念

单播方式只能发给一个接收方。

广播方式发给所有的主机。

过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。

组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。

多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

2 组播地址

D类地址(组播地址)

不分网络地址和主机地址,第1字节的前4位固定为1110

224.0.0.1 – 239.255.255.255

3 组播流程

发送者:

  1. 创建套接字 socket()
  2. 填充组播信息结构体 struct sockaddr_in
  3. 发送数据 sendto()

接收者:

  1. 创建套接字 socket()
  2. 填充组播信息结构体 struct sockaddr_in
  3. 将套接字与组播信息结构体绑定 bind()
  4. 设置加入多播组 setsockopt()
  5. 接收数据 recvfrom()

4 设置加入多播组

#include <sys/types.h>          
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);
功能:设置一个套接字的选项
参数:
    sockfd:文件描述符
    level:协议层次
        IPPROTO_IP  IP层次
    optname:选项的名称
        IP_ADD_MEMBERSHIP  加入多播组
    optval:要设置的值
        struct ip_mreqn {
               struct in_addr imr_multiaddr; 组播地址
               struct in_addr imr_address;   本地IP地址
                   INADDR_ANY
               int            imr_ifindex;   接口号
                   0
         };
         或者
         struct ip_mreq
         {
            struct in_addr imr_multiaddr; 组播地址
            struct in_addr imr_interface; 本地主机地址
                INADDR_ANY
         };
    optlen:optval的大小
返回值:
    成功:0
    失败:-1  

5 代码

5.1 发送者

//组播之发送者

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in groupcastaddr;
    socklen_t addrlen = sizeof(groupcastaddr);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充组播信息结构体
    groupcastaddr.sin_family = AF_INET;
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //224.x.x.x - 239.x.x.x
    groupcastaddr.sin_port = htons(atoi(argv[2]));

    //进行通信
    char buf[N] = {0};
    while(1)
    {
        fgets(buf, N, stdin);
        buf[strlen(buf) - 1] = '\0';

        if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
        {
            ERRLOG("sendto error");
        }

    }

    return 0;
}

5.2 接收者

//组播之接收者

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <string.h>

#define N 128
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in groupcastaddr, sendaddr;
    socklen_t addrlen = sizeof(groupcastaddr);
    char buf[N] = {0};

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充组播信息结构体
    groupcastaddr.sin_family = AF_INET;
    groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255  255.255.255.255
    groupcastaddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与组播信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //设置加入多播组
    struct ip_mreqn mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
    mreq.imr_address.s_addr = htonl(INADDR_ANY);
    mreq.imr_ifindex = htonl(0);
    if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    {
        ERRLOG("setsockopt error");
    }

    //进行通信
    while(1)
    {
        if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
        {
            ERRLOG("recvfrom error");
        }

        printf("%s-%d:%s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), buf);
    }

    return 0;
}