- 多播支持在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。
示例图
- 广播分组去往子网上的所有主机,包括发送主机自身。
- 子网上未参加相应广播应用的所有主机也不得不沿协议栈一路向上完整地处理收取的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上