BSD协议栈:多播
分用多播和广播数据报
与单播相比,多播与广播需要提交数据报给所有匹配的socket,其他的操作其实大同小异,读者可以参考下文的单播。但是,其中的难点就在于数据报的提交与回收。
实现逻辑:迭代遍历pcb,如果pcb不匹配,就使用continue关键字开启下一轮迭代,直到找到匹配的pcb或迭代到NULL。
如果pcb匹配,判断上一次是否匹配到pcb,如果是,那么调用sbappendaddr提交数据报到上一次的接收队列,并唤醒因为接收队列而阻塞的进程,之后使用last缓存指针保存pcb。
如果last指针为NULL,说明这是第一次匹配到pcb,那就用last保存该pcb,并进行一次是否结束迭代的判断。
但是在提交之前,我们需要拷贝一个副本,
sbappendaddr函数执行成功后会返回1,也就是说,提交数据报成功后就会唤醒阻塞进程。如果返回的是0,说明提交失败,这是因为缓冲区已满导致的问题,所以需要释放mbuf链。除此之外,该函数还会释放mbuf链,但是考虑到我们是多播或广播,可能不是最后一次提交,所以我们需要使用m_copy备份,并使用变量n保存地址,然后提交给接收队列。
这部分程序的精髓在于提交“上一次”,而非“这一次”。如果检查到匹配的pcb就提交数据报,那么我们不得不考虑最后一次可能出现的问题:我们会多留下一个数据报。但是使用“上一次”,当程序运行到这里时,表示前面的循环结束,可以判断是最后一次,因此无需备份,直接使用原始数据报即可。
(这段程序综合考虑了内存的回收释放,对不同情况处理得非常好)
if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif)) {
struct socket *last;
/*
* Deliver a multicast or broadcast datagram to *all* sockets
* for which the local and remote addresses and ports match
* those of the incoming datagram. This allows more than
* one process to receive multi/broadcasts on the same port.
* (This really ought to be done for unicast datagrams as
* well, but that would cause problems with existing
* applications that open both address-specific sockets and
* a wildcard socket listening to the same port -- they would
* end up receiving duplicates of every unicast datagram.
* Those applications open the multiple sockets to overcome an
* inadequacy of the UDP socket interface, but for backwards
* compatibility we avoid the problem here rather than
* fixing the interface. Maybe 4.5BSD will remedy this?)
*/
/*
* Construct sockaddr format source address.
*/
udp_in.sin_port = uh->uh_sport;
udp_in.sin_addr = ip->ip_src;
m->m_len -= sizeof (struct udpiphdr);
m->m_data += sizeof (struct udpiphdr);
/*
* Locate pcb(s) for datagram.
* (Algorithm copied from raw_intr().)
*/
last = NULL;
for (inp = udb.inp_next; inp != &udb; inp = inp->inp_next) {
if (inp->inp_lport != uh->uh_dport)
continue;
if (inp->inp_laddr.s_addr != INADDR_ANY) {
if (inp->inp_laddr.s_addr !=
ip->ip_dst.s_addr)
continue;
}
if (inp->inp_faddr.s_addr != INADDR_ANY) {
if (inp->inp_faddr.s_addr !=
ip->ip_src.s_addr ||
inp->inp_fport != uh->uh_sport)
continue;
}
if (last != NULL) {
struct mbuf *n;
if ((n = m_copy(m, 0, M_COPYALL)) != NULL) {
if (sbappendaddr(&last->so_rcv,
(struct sockaddr *)&udp_in,
n, (struct mbuf *)0) == 0) {
m_freem(n);
udpstat.udps_fullsock++;
} else
sorwakeup(last);
}
}
last = inp->inp_socket;
/*
* Don't look for additional matches if this one does
* not have either the SO_REUSEPORT or SO_REUSEADDR
* socket options set. This heuristic avoids searching
* through all pcbs in the common case of a non-shared
* port. It * assumes that an application will never
* clear these options after setting them.
*/
if ((last->so_options&(SO_REUSEPORT|SO_REUSEADDR) == 0))
break;
}
if (last == NULL) {
/*
* No matching pcb found; discard datagram.
* (No need to send an ICMP Port Unreachable
* for a broadcast or multicast datgram.)
*/
udpstat.udps_noportbcast++;
goto bad;
}
if (sbappendaddr(&last->so_rcv, (struct sockaddr *)&udp_in,
m, (struct mbuf *)0) == 0) {
udpstat.udps_fullsock++;
goto bad;
}
sorwakeup(last);
return;
}