广播与多播

本文深入探讨了广播与多播在IPv4和IPv6中的区别与应用场景。广播主要用于本地子网资源发现,但IPv6不支持广播,推荐使用多播。文章通过多个示例详细介绍了如何在UDP中实现广播和多播,包括广播可能导致的阻塞问题及其改进方法,以及多播地址的分配和使用。此外,还讨论了多播套接字选项、范围字段和多播示例程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1

  • 多播支持在IPv4中是可选的,在IPv6中却是必需的。
  • IPv6不支持广播。使用广播的任何IPv4应用程序一旦移植到IPv6就必须改用多播重新编写。
  • 广播和多播要求用于UDP或原始IP,它们不能用于TCP。

广播

广播的用途之一是在本地子网定位一个服务器主机,前提是已知或认定这个服务器主机位于本地子网,但是不知道它的单播IP地址。这种操作也称为资源发现
源自Berkeley的内核不允许对广播数据报执行分片。对于目的地址是广播地址的IP数据报,如果其大小超过外出接口的MTU,发送它的系统调用将返回EMSGSIZE错误。(这样的理由感觉上是由于既然广播已经施加给网络相当大的负担,再因分片而造成这个负担倍乘片段的数量就更不应该。)

地址

使用记法{子网ID,主机ID}表示一个IPv4地址,其中子网ID表示由子网掩码覆盖的连续位,主机ID表示以外的位。例如IP为192.168.1.99/22的主机,其广播地址为192.168.3.255。

示例图

2

  • 广播分组去往子网上的所有主机,包括发送主机自身。
  • 子网上未参加相应广播应用的所有主机也不得不沿协议栈一路向上完整地处理收取的UDP广播数据报,直到该数据报历经UDP层时被丢弃为止。另外,子网上所有非IP的主机也不得不在数据链路层接收完整的帧(对于IPv4,分组就是0x0800),然后再丢弃它。

广播示例:程序可能永远阻塞

将文章UDP客户/服务器中的回射服务的客户端中的dg_cli函数改写为使用广播,完整的客户端例子如下:

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

#define SERV_PORT 8755
#define MAXLINE 32
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define error_exit(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

void* Signal(int signo, void (*func)(int)) {
    struct sigaction act, oact;

    act.sa_handler = func; 
    sigemptyset(&act.sa_mask); 
    act.sa_flags = 0;
#ifdef SA_INTERRUPT 
    if (signo == SIGALRM) act.sa_flags |= SA_INTERRUPT;
#endif
#ifdef SA_RESTART 
    if (signo != SIGALRM) act.sa_flags |= SA_RESTART; 
#endif
    if (sigaction(signo, &act, &oact) < 0) 
        return SIG_ERR;
    return oact.sa_handler; // 返回信号的旧行为
}

static void sig_alrm(int signo) {
    return; /* just interrupt the recvfrom() */
}       

void dg_cli(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
    int n;
    const int on = 1;
    char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
    socklen_t len;
    struct sockaddr *preply_addr;

    /* 设置套接字选项、分配服务器地址空间、安装SIGALRM信号处理函数 */
    setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // POSIX规范要求发送广播数据报必须设置该选项
    preply_addr = malloc(servlen);
    Signal(SIGALRM, sig_alrm);

    /* 发送广播数据报 */
    while (fgets(sendline, MAXLINE, fp) != NULL) {
        sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        /* 5s内接收所有广播应答 
         * (如果某一时刻,程序刚好成功执行完recvfrom后,此时alarm触发SIGALRM信号,会导致程序永远阻塞在recvfrom上) */
        alarm(5);
        for ( ; ; ) {
            len = servlen;
            n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
            if (n < 0) {
                if (errno == EINTR)
                    break;  /* waited long enough for replies */
                else
                    error_exit("recvfrom error");
            } else {
                recvline[n] = 0;
                printf("reply from %s:%d : %s", 
                    inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
                    ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
                    recvline);  
            }
        }
    }
    free(preply_addr);
}       

int main(int argc, char **argv) {
    int sockfd;
    struct sockaddr_in servaddr;

    if (argc != 2)
        error_exit("usage: udpcli <IPaddress>");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    exit(0);
}

结果:

test@test:~$ ./udpcli 192.168.3.255
123456
reply from 192.168.1.96:8755 : 123456
reply from 192.168.1.98:8755 : 123456
abcdefg
reply from 192.168.1.96:8755 : abcdefg
reply from 192.168.1.98:8755 : abcdefg

当涉及信号处理时,往往会出现另一种类型的竞争状态。发生问题的原因在于信号会在程序执行过程中由内核随时随地递交。例如上述程序中,如果某一时刻,程序刚好成功执行完recvfrom后,此时alarm触发SIGALRM信号,会导致程序永远阻塞在recvfrom上

改进dg_cli函数(不正确):阻塞和解阻塞信号

<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值