UNP卷一chapter20 广播
1、单播、广播及多播等不同寻址方式
类型 | IPv4 | IPv6 | TCP | UDP | 所标识接口数 | 递送到接口数 |
单播 | Y | Y | Y | Y | 一个 | 一个 |
任播 | * | Y | 尚没有 | Y | 一组 | 一组中的一个 |
多播 | 可选 | Y | Y | 一组 | 一组中的全体 | |
广播 | Y | Y | 全体 | 全体 |
表中要点:
i、多播支持在IPv4中是可选的,在IPv6中却是必需的
ii、IPv6不支持广播。使用广播的任何IPv4应用程序一旦移植到IPv6就必须改用多播重新新编写
iii、广播和多播要求用于UDP或原始IP,不能用于TCP
广播的用途有:资源发现,多个客户主机与单个服务器主机通信的局域网环境中尽量减少分组流通。如下,
ARP:在子网中广播一个请求说“IP地址为a.b.c.d的系统亮明身份,请告诉我你的硬件地址”。ARP使用链路层广播而不是IP层广播。
DHCP:在子网有DHCP服务器下,DHCP客户主机向子网发送广播地址以请求获取自己的IP地址、子网掩码以及本子网的受限广播地址。
还有NTP、路由守护进程等等运用广播的例子。
2、广播地址
i、子网定向广播地址:{子网ID,-1}(此为IPv4地址表示方法,子网ID表示由子网掩码覆盖的连续位,主机ID表示以外的位,-1表示所有位均为1的字段)
ii、受限广播地址:{-1,-1}或255.255.255.255。路由器从不转发目的地址为255.255.255.255的IP数据报。
3、UDP数据报广播示例
目的IP地址是所在以太网的子网定向广播地址,于是把它映射成48位全为1的以太网地址:ff:ff:ff:ff:ff:ff。这个地址使得该子网上的每一个以太网接口都接收该帧。
右侧主机把该UDP数据报传递给绑定端口520的应用进程。一个应用进程无需就为接收广播UDP数据报而进行任何特殊处理:它只需要创建一个UDP套接字,并把应用的端口号摁绑到其上。(假设捆绑的IP地址是典型的INADDR_ANY。)
中间的主机没有任何应用进程绑定的UDP端口520。于是该主机UDP代码丢弃这个已收取的数据报。切记,该主机绝不能发送一个ICMP端口不可达消息,因为这么做可能产生广播风暴,导致网络拥塞。
4、竞争状态(当有多个进程访问共享的数据,而正确结果取决于进程的执行顺序)
三种方法解决在使用广播的dg_cli函数中竞争状态问题。
i、用pselect阻塞和解阻塞信号(需要了解pselect的用法,见书上6.9节P142)
#include "unp.h"
static void recvfrom_alarm(int);
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
fd_set rset;
sigset_t sigset_alrm, sigset_empty;//生成两个信号集
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));//开启套接字广播选项
FD_ZERO(&rset);
Sigemptyset(&sigset_empty);//声明信号集,初始化为空集
Sigemptyset(&sigset_alrm);//声明信号集,初始化为空集
Sigaddset(&sigset_alrm, SIGALRM);//再在信号集sigset_alarm,打开与SIGALRM对应的位
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
Sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);//调用sigprocmask函数,阻塞SIGALRM(即信号发生时,不会立即调用信号处理函数)
alarm(5);
for (; ; ) {
FD_SET(sockfd, &rset);
n = pselect(sockfd + 1, &rset, NULL, NULL, NULL, &sigset_empty);//最后一个参数表示信号集为空集,意味着在pselect调用期间
if (n < 0) { //SIGALRM不阻塞,当pselect返回时,又自动将SIGALRM设置为阻塞
if (errno == EINTR)
break;
else
err_sys("pselect error");
}
else if (n != 1)
err_sys("pselect error: returned %d", n);
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0; /* null terminate */
printf("from %s: %s",
Sock_ntop_host(preply_addr, len), recvline);
}
}
free(preply_addr);
}
static void
recvfrom_alarm(int signo)
{
return; /* just interrupt the recvfrom() */
}
ii、使用sigsetjump和siglongjmp#include "unp.h"
#include <setjmp.h>
static void recvfrom_alarm(int);
static sigjmp_buf jmpbuf;//分配一个将由本函数及其信号处理函数使用的跳转Buffer
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
Signal(SIGALRM, recvfrom_alarm);//捕获SIGALRM,并执行信号处理函数recvfrom_alarm
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
for (; ; ) {
if (sigsetjmp(jmpbuf, 1) != 0)//建立跳转缓冲区后返回0
break;
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0; /* null terminate */
printf("from %s: %s",
Sock_ntop_host(preply_addr, len), recvline);
}
}
free(preply_addr);
}
static void
recvfrom_alarm(int signo)
{
siglongjmp(jmpbuf, 1);//当SIGALRM信号被递交时,调用siglongjmp,跳转至sigsetjmp设置的缓冲区,并返回siglongjmp的第二个参数1,从而导致dg_cli的for循环结束
}
iii、使用从信号处理函数到主控函数的IPC
#include "unp.h"
static void recvfrom_alarm(int);
static int pipefd[2];
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n, maxfdp1;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
fd_set rset;
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
Pipe(pipefd);//创建一个普通Unix管道,返回两个描述符,pipefd[0](读入),pipefd[1](写出)
maxfdp1 = max(sockfd, pipefd[0]) + 1;
FD_ZERO(&rset);
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
for (; ; ) {
FD_SET(sockfd, &rset);
FD_SET(pipefd[0], &rset);
if ((n = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
if (errno == EINTR)
continue;
else
err_sys("select error");
}
if (FD_ISSET(sockfd, &rset)) {
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0; /* null terminate */
printf("from %s: %s",
Sock_ntop_host(preply_addr, len), recvline);
}
if (FD_ISSET(pipefd[0], &rset)) {
Read(pipefd[0], &n, 1); /* timer expired */
break;
}
}
}
free(preply_addr);
}
static void
recvfrom_alarm(int signo)
{
Write(pipefd[1], "", 1); /* write one null byte to pipe */
return;
}
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。上一篇: PHP实现无限分类的实现方法