📚 第一章 网络基础知识
📄 一句话概括

按接收端数量只有三类:一对一、一对全、一对多。 组播的语义是「订阅」而不是「投递」——发送方根本不知道有谁在听。

📝 本节和前面几节的关系

1.7.1 与 1.7.2 讲的是「怎么传」,这一节讲「传给谁」。 广播与组播的分界在路由器,这一点最容易搞混。

一、三类发法

一张表说清:

类型 发给谁 典型地址
单播 一个确定地址 192.168.3.1
广播 本网段全段 192.168.3.255
组播 一群订阅者 239.255.255.250

单播占了互联网流量的绝大多数:你打开一个网页、看一个视频,本质都是「一对一」。 视频网站对一万人直播,服务端发的是一万份单播——除非它用了组播或 CDN 分发。 第二行那个地址最后一位是 255,这就是「本网段广播地址」: 它的算法是「网络部分不变、主机部分全 1」。 所以 192.168.3.0/24 的广播地址是 192.168.3.255,这个不是随便定的,是算出来的。

二、组播是订阅,不是投递

这一点是组播最难理解也最关键的地方:

谁的动作 做什么 知道对方吗
发送方 往组地址发包 完全不知道有谁在听
接收方 主动加入组 知道组地址
路由器 按成员表转发 知道哪个口下有成员

第一行解释了组播的「无连接」性质:发送方只往一个地址发,不关心有没有人收。 没人收也不报错—— 这就是为什么「自发自收」的实验能成:本机加入了那个组,自己发的包自己也能收到。 第三行是组播能跨网段的原因:路由器要知道「哪个口下面有成员」,这靠 IGMP 协议维护。 没有 IGMP,路由器不知道该往哪转,组播就出不了本网段。 对比一下广播: 广播是「往本网段所有设备发」,路由器默认不转发——所以广播永远出不了本网段。 这也是「设备发现」这类功能偏爱组播的原因:它至少有机会跨过一个路由器。

三、为什么广播不能跨路由

不是技术上做不到,是「不敢」:

如果广播能跨路由 后果
全网泛洪 一台设备的广播打满所有链路
放大效应 一个包变成 N 个包
故障扩散 局部问题变成全局问题

第一行是最直接的: 广播的本质是「发给所有人」,一旦允许跨网段,任何一个设备的广播风暴都会扩散到全网。 互联网的规模决定了这条线不能松。 第二行是数学问题:一个广播包进路由器,要从每个口各发一份,N 个口就是 N 份。 层级一多,包的数量指数级上涨。 所以「广播域」是被刻意限制的:一个 VLAN 就是一个广播域,路由器是广播域的边界。 这也解释了为什么大网络要划 VLAN——不划,广播就互相干扰。

四、动手 Demo:单播、广播、组播,三种发法

列出三类地址,再真发一个组播包看本机能不能自己收到。 「自发自收」能成,正好说明组播是订阅语义。

import socket, struct

GROUP, PORT = '239.255.255.250', 3702

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', PORT))
mreq = struct.pack('4s4s', socket.inet_aton(GROUP),
                   socket.inet_aton('0.0.0.0'))
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
print('加入组播组 %s:成功' % GROUP)

out = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
out.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
out.sendto(b'hello-multicast', (GROUP, PORT))

s.settimeout(2)
data, addr = s.recvfrom(1024)
print('自发自收:收到 %d 字节,来自 %s' % (len(data), addr[0]))
out.close()
s.close()

实跑输出:

--------------------------------------------------------------------
单播、广播、组播,三种发法
--------------------------------------------------------------------
**按接收端数量分,只有三类:一对一(单播)、一对全(广播)、一对多(组播)。**

  类型     发给谁           典型地址              用在什么场合
  ---------------------------------------------------------------------
  单播     一个确定地址     `192.168.3.1`         **绝大多数通信**
  广播     本网段全段       `192.168.3.255`       **ARP、DHCP**
  组播     一群订阅者       `239.255.255.250`     **视频流、设备发现**

下面真发一个组播包,看本机能不能自己收到:

  加入组播组 239.255.255.250:成功
  自发自收:收到 15 字节

**「自己发、自己收」这件事能成,说明组播的语义是「订阅」而不是「投递」**:
**发送方不知道有谁在听,接收方主动加入组。**

**广播和组播的关键区别**:
**广播只有同一网段能收到,路由器默认不转发;组播可以跨网段,但要路由器支持 IGMP。**
**所以「设备发现」这类功能常用组播而不是广播**——**它至少有机会跨过一个路由器。**

收到 15 字节,正好是 hello-multicast 的长度: 本机发出的包,被本机自己加入的那个组收到了。 这件事能成,说明组播的语义是「订阅」而不是「投递」—— 发送方不知道有谁在听,接收方主动加入组。 IP_MULTICAST_TTL 设成 1 表示「只在本网段」:每过一个路由器减 1,减到 0 就丢。 这和单播的 TTL 是同一个机制,只是组播默认值更小。 bind(('', PORT)) 里的空字符串表示「所有网卡都听」: 如果写成 127.0.0.1,就收不到组播包了——因为组播包是从真实网卡进来的。 这个细节是初学者最常踩的坑。

30 秒实验

  1. 把 IP_MULTICAST_TTL 改成 0,看还能不能收到——0 表示不出本机。
  2. 把 GROUP 改成 224.0.0.1(所有主机组),再跑一遍。
  3. 算一下:192.168.3.152/24 的广播地址是多少?用「网络部分不变、主机部分全 1」自己算一遍。

常见误区

  1. 以为组播是「一对多投递」。 它是订阅:发送方不知道有谁在听,接收方主动加入组。
  2. 以为广播能跨路由器。 路由器默认不转发广播,广播域止于路由器——这是为了保护网络不被泛洪。
  3. 以为组播和广播一样出不了本网段。 组播可以跨网段,前提是路由器支持 IGMP 并维护成员表。
自测三问(点开对答案) 1. 单播、广播、组播分别发给谁? —— **单播给一个确定地址,广播给本网段全段,组播给一群订阅者。**互联网流量的绝大多数是单播。 2. 为什么「自发自收」能证明组播是订阅语义? —— **因为本机加入了那个组,自己发的包自己也能收到。**发送方不需要知道接收方是谁,只往组地址发。 3. 路由器为什么不转发广播? —— **怕泛洪**:广播本质是「发给所有人」,一旦跨网段,一个设备的广播风暴会扩散到全网, 且每个口都要复制一份。

下一节进入 1.8:地址——地址为什么必须唯一,又为什么必须分层。

相关笔记