You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
13 KiB
Markdown

2 years ago
# 07 | 性能好,效率高的一对多通讯该如何实现?
你好,我是陶辉。从这一讲开始,我们将从单机进入网络层面的性能优化。
我们接触过的绝大多数通讯方式无论是面向连接的HTTP协议还是无连接的DNS协议都是一对一收发消息的。其实除了一对一还有一对多的通讯方式它在网络资源的利用上效率要比一对一高得多。这种一对多的通讯方式在局域网中有很广泛的应用常见的ARP欺骗、泛洪攻击等都是通过一对多通讯进行的。
当应用场景中用一对多代替一对一通讯时发送方性能会获得很大的提升整个局域网的效率也会提高。比如源主机的带宽只有1Gbps如果采用一对一的方式向100个客户端发送流媒体这100台主机的带宽之和不会超过1Gbps。但采用一对多传输时总带宽就可以达到100Gbps。
除了能提升性能以外,由于一对多通讯可同时向所有主机发送消息,这就在功能层面上可以替换许多人工操作。比如分布式系统的服务发现,使用人工配置既容易出错,速度也慢,而用广播就可以轻松实现自动化服务发现。
一对多通讯协议一直在发展在运营商的IPTV网络的视频直播中它就得到了广泛的应用。即使你暂时不会用到一对多这种方式也应当了解下它是怎么工作的熟悉它的工作原理后还能更深入地理解一对一通讯协议。
这一讲,我们就来学习如何实现一对多通讯。
## 广播是怎么实现的?
一对多通讯分为两种:对局域网内所有主机发送消息的叫做**广播**,而对部分主机发送消息的,则叫做**组播**。我们先来看一下广播是怎么实现的。
使用广播要改用UDP协议。可能你会问为什么不能使用最熟悉的TCP协议呢这要从TCP协议的分层说起。
1978年在TCP协议迭代了3个版本后才被Jon PostelIANA创始人提出违反了网络分层原则网络层和传输层耦合在一起很难扩展。于是在TCP的第4个迭代版本中把协议一分为二包括网络层IP协议和传输层TCP协议这也是今天的IP协议被称为IPv4的原因
![](https://static001.geekbang.org/resource/image/95/02/95d8fe24b6343fef34579f240696dd02.jpg)
当你访问Internet站点时IP协议会将数据通过网络设备穿越多个卫星、光纤等网络才能送到服务器。而网络设备天然就拥有广播能力当它在一个网络端口上收到主机发来的报文时可以向其他端口上的所有主机重发一遍这就是广播如下图所示
![](https://static001.geekbang.org/resource/image/ce/f0/ce45d52f5c87bcae9c4adae8056c21f0.jpg)
**虽然IP协议已经具有广播功能但实际编程中并不会直接使用IP协议发送广播因为它很难与进程关联起来。**
根据网络分层模型上层协议可以使用下层协议的功能所以传输层协议拥有IP协议的广播能力。同时传输层通过端口号把网络报文和进程关联在了一起就像TCP的80端口把HTTP消息与Nginx等Web Server关联在一起。
![](https://static001.geekbang.org/resource/image/13/ac/13d968cbacd229f242764557957d3bac.jpg)
然而传输层的TCP协议为了保证可靠性建立了逻辑上的连接概念由于一个连接上只能有两方所以TCP无法进行一对多通讯。而传输层的UDP协议无需建立连接所以我们常用UDP协议发送广播。
广播的性能高有两个原因:首先,交换机直接转发给接收方,要比从发送方到接收方的传输路径更短。其次,原本需要发送方复制多份报文再逐一发送至各个接受者的工作,被交换机完成了,这既分担了发送方的负载,也充分使用了整个网络的带宽。
那么交换机收到消息后怎么知道这是广播报文并转发给整个网络呢我们知道以太网中的数据链路层通过硬件的MAC地址来传播消息交换机就通过报文的MAC地址来确定是否需要广播。**当交换机收到目标MAC地址是ff:ff:ff:ff:ff:ff的报文时便知道这是一个广播报文**才会将它转发给局域网中的所有主机否则只会转发给MAC地址对应端口上的主机。
不过我们写代码时无法控制底层的MAC地址只能填写目标IP地址。什么样的目标IP地址会生成广播MAC地址呢**如果只是对所在子网进行广播那么使用受限广播地址255.255.255.255就可以了;如果局域网划分了多个子网,主机需要向其他子网广播,则需要正确地设置直接广播地址(路由器需要打开直接广播功能)。**
## 如何正确地设置直接广播IP地址
怎么设置直接广播的IP地址呢我们首先得了解IP地址的构成。
由于IP协议需要跨越多个网络工作所以IP地址被一分为二包括前边的网络ID和后边的主机ID其中**网络ID用于不同网络间的寻址而主机ID则用于在本地局域网内通讯。**
举个例子如果你的局域网IP地址是192.168.0.101那么网络ID就是192.168.0而主机ID则是101这里假定网络管理员没有继续划分C类子网
这是因为以192.168打头的IP地址被称为C类地址而C类地址中最后1个十进制数字代表主机ID。如果IP地址是172.16.20.227这就是B类地址此时172.16是网络ID而20.227才是主机ID。
所以IP地址的前缀数字不同主机ID的划分位置也不同。事实上IP地址一共被划分为A、B、C、D、E5个类别它的划分依据正是IP地址转换为二进制后用前4个比特位作为依据的。如果第1个比特位为0这就是A类地址它的网络ID是IP地址的第1到第8比特位而主机ID则是随后的24个比特位。如果局域网IP地址第1个数字是10这就是A类私有地址局域网中的地址不能在公网中使用统称为私有地址或者内网地址
![](https://static001.geekbang.org/resource/image/4f/e1/4f9c58b60404e2f874802b6a2ec207e1.jpg)
类似的若前2个比特位是10则是B类地址它的主机ID是后16位如果前3个比特位为110这就是C类地址它的主机ID是后8位。显然以192打头的地址前3位正是110所以这是C类地址。
![](https://static001.geekbang.org/resource/image/d6/5e/d67b7a52601f80027576c866cf80e25e.jpg)
此外还有D类组播地址和E类预留实验地址。
清楚了划分方法后再来看如何通过修改主机ID将IP地址改为直接广播地址。如果你留心观察IP地址会发现主机ID不会出现全0和全1这两种情况这是因为全0和全1有特殊用途其中全0特指它自己所以0.0.0.0可以指代本机IP而全1表示全部主机。
**所以主机ID的比特位全部设为1后就是广播地址。**比如192.168.0.101是C类地址把主机ID从101改为255后就可以用192.168.0.255发送广播了。
然而事情到这并没有完。一个A类网络可以容纳千万台主机B类网络则只能容纳6万多台主机C类网络则最多容纳254台主机。仅有的三类网络中主机数量差距太大而世界上存在各种规模的企业它们所需网络中的主机规模千差万别上述划分方式太过单一无法满足各类企业的需求**于是诞生了CIDR这种新的划分方式它通过子网掩码或者叫Netmask可以在任意的位置将IP地址拆分为网络ID和主机ID扩展了A、B、C三类网络的用法。**
当你查看主机的IP地址时就会看到其后跟着一个类似IP地址的子网掩码。子网掩码必须把它展开成二进制才能使用这样掩码前N位为1时就表示IP地址的前N位是网络ID而掩码后面剩余的位全是0表示IP地址对应的位是主机ID。
比如若192.168.0.101的子网掩码是255.255.255.192就表示IP地址的前26位是网络ID后6位是主机ID将主机ID置为全1后就得到了它的广播地址192.168.0.127,如下图所示:
![](https://static001.geekbang.org/resource/image/65/08/6586d4ec875f63b19993b78c7a11e808.jpg)
到这里我们设置好IP地址后再把socket句柄设置SO\_BROADCAST属性就可以发送广播了。广播虽然有很多优点可是一旦被滥用很容易产生网络风暴所以路由器默认是不转发广播报文的。
## 用更精准的组播来做服务发现
当你用UDP广播来做分布式系统的服务发现会遇到这样一个问题若并非网络内的所有主机都属于分布式系统那么当指定了端口的UDP广播报文到达其他主机时会怎么样呢这些广播报文在这3个步骤后会被丢弃
* 第1步网卡设备收到报文后查看报文中的目标MAC地址是否与本机的MAC地址匹配如果不匹配就会丢弃。广播MAC地址默认匹配继续交由上层的IP协议栈处理
* 第2步IP协议栈查看目标IP地址是否为本机IP地址不匹配也会丢弃报文。上文介绍过的广播IP地址同样默认匹配交由传输层协议继续处理。
* 第3步传输层检查目标端口是否有进程在监听如果没有则丢弃报文反之则交付给进程处理。不属于集群的主机自然不会启动服务监听端口在这一步才会丢弃广播报文。
![](https://static001.geekbang.org/resource/image/1c/b3/1c8b6032474debdd2a4d4569a1752ab3.jpg)
可见对于不属于分布式集群的主机而言广播报文既占用了它们的带宽这3步协议栈的操作也消耗了CPU的计算力。有什么办法能缩小广播的范围消除它加在无关主机上的负载呢
组播可以做到。组播是一种“定向广播”它设定了一个虚拟组用组播IP来标识。这个虚拟组中可以包含多个主机的IP当向对应的组播IP发送消息时仅在这个组内的主机才能收到消息。
组播IP与常见的单播IP不同它是前文介绍过5类IP地址中的D类地址32位IP地址的前4位必须是1110因此组播IP地址的范围是从224.0.0.0到239.255.255.255。
当设置好组播IP地址后还要通过管理组播地址的IGMP协议Internet Group Management Protocol将主机IP地址添加进虚拟组中。编程语言提供的setsockopt函数就可以操作IGMP协议管理组播地址。比如使用参数IP\_ADD\_MEMBERSHIP就能够向虚拟组中增加IP而IP\_DROP\_MEMBERSHIP则可以从组中去除某个主机的IP。
[这里](https://github.com/russelltao/geektime-webprotocol/tree/master/python%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81)有一个可运行的python测试代码供你参考。如果你想进一步了解组播的细节可以观看[《Web协议详解与抓包实战》第117课](https://time.geekbang.org/course/detail/175-134405)。
组播相对于广播而言 除了能够更精准的管理组播范围还能够跨越多个网络工作。当然如果将多个网络中的IP加入同一虚拟组时需要涉及到的路由器都可以正确地处理这些IP地址且都能支持IGMP协议。
## 小结
最后我们对这一讲做一个总结。
由于一对多通讯能够充分利用整体网络的性能,而且通过交换机能够同时向许多主机发送消息,所以在局域网内有广泛的应用。
在TCP协议分层后IP协议天然就支持一对多通讯方式。TCP协议面向连接的特性使它放弃了一对多的通讯方式而UDP协议则继承了IP协议的这一功能。所以在一对多通讯场景中我们会选择UDP协议。
正确输入广播地址的前提是理解IP地址如何划分为网络ID和主机ID。当主机ID所有的比特位改为全1时IP地址就表示该网络下的所有主机这就是广播地址。当向广播地址发送UDP消息时网络中的所有主机都会收到。广播在局域网中有广泛的应用转换IP地址与MAC地址的ARP协议就是用广播实现的。
广播对无关的主机增加了不必要的负担而组播可以更精准地“定向”广播。组播地址也被称为D类地址它描述的虚拟组要通过IGMP协议管理。网络API中的setsockopt函数可以通过IGMP协议向虚拟组中添加或者删除IP地址。当路由器支持IGMP协议时组播就可以跨越多个网络实现更广泛的一对多通讯。
广播和组播能够充分地使用全网带宽,也通过交换机等网络设备分散了发送主机的负载。但它很难对每台接收主机提供定制化服务,这样可靠传输就很难实现。这使得它们在更关注及时性、对丢包不敏感的流媒体直播中更有应用前景。
这一讲我们介绍了许多网络概念,这些也是理解后续内容的基础。从下一讲开始,我们将进入更复杂的一对一通讯协议。
## 思考题
最后,请你思考下,你使用或者了解过哪些一对多的通讯协议?它们的优缺点,以及未来的发展方向又是什么?欢迎你留言与我探讨。
感谢阅读,如果你觉得今天学习的内容对你有帮助,也欢迎把它分享给你的朋友。