This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<audio id="audio" title="开篇词 | 想成为技术牛人?先搞定网络协议!" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8a/57/8a2b87bb302fd9e99711328559eb9157.mp3"></audio>
你好,我是刘超,网易研究院云计算技术部的首席架构师。我主要负责两部分工作,对内支撑网易核心业务上云,对外帮助客户搞定容器化与微服务化架构。
当极客时间约我做“趣谈网络协议”专栏的时候我非常开心因为网络协议也是我长期研究和关注的点。摸爬滚打15年有了一些收获也溅了一身血我才能在这里和你分享。
为什么网络协议这么重要呢为什么“计算机组成与系统结构”“数据结构与算法”“操作系统”“计算机网络”“编译原理”会成为大学计算机的核心课程呢至少看起来这些内容没有“多少天搞定MFC、Structs”这样的内容更容易帮你找到工作。我毕业的时候也感到很困惑。
不过当时我抱着一个理想,也可能是大多数程序员的理想:我要做技术牛人,我要搞定大系统。
工作15年我在EMC做过类似GFS的分布式存储开发做过基于Lucene的搜索引擎做过Hadoop的运维在HP和华为做过OpenStack的开发、实施和解决方案还创业倒腾过Mesos容器平台后来在网易做Kubernetes。
随着见过的世面越来越多,我渐渐发现,无论是对于大规模系统的架构,还是对于程序员的个人职业生涯,网络和网络协议都是绕不过去的坎儿。
集群规模一大,我们首先想到的就是网络互通的问题;应用吞吐量压不上去,我们首先想到的也是网络互通的问题。不客气地讲,很多情况下,只要搞定了网络,一个大型系统也就搞定了一半。所以,要成为技术牛人,搞定大系统,一定要过网络这一关,而网络协议在网络中占有举足轻重的地位。
相信大部分人都思考过“技术变化太快,容易过时”的问题。毕竟,技术浪潮一浪接一浪,新技术层出不穷。从搜索引擎、大数据、云计算,到人工智能、区块链,简直就是“你方唱罢我登场”。**这里面究竟有没有最本质的东西,使得你掌握了它,就能在新技术的滚滚浪潮中,保持快速学习的能力?**
通过对大量开源技术的代码进行分析,我发现很多技术看起来轰轰烈烈,扒下外衣,本质的东西其实就是基础知识和核心概念。**想要不被滚滚而来的新技术淘汰就要掌握这些可以长久使用的知识而网络协议就是值得你学习而且是到40岁之后依然有价值的知识。**
但是,要想真正学习和掌握网络协议,也并非易事。下面这些场景,你是不是也感同身受呢?
<li>**网络协议知识点太多,学完记不住。**我们都学过计算机网络课程,学的时候感觉并不难。尤其这门课没有公式,更像是文科。学了一大堆,也背了一大堆,应付完考试之后,最终都“还给老师”了。
</li>
<li>**看上去懂了,但是经不住问。**没关系,网上有很多的文章嘛。于是,你会搜索很多文章去看。看的时候,你感觉别人说的很有道理,好像理解了,但是经不住问,一问就发现,你只是了解了大概的流程,很多细节还是不知道。所以说,从能看懂到能给别人讲明白,中间还有很长一段距离。
</li>
<li>**知识学会了,实际应用依旧不会**。细节都摸索得差不多了,但是当你自己去应用和调试的时候,发现还是没有思路。比如,当创建出来的虚拟机不能上网的时候,该怎么办呢?学过的东西,怎么还是不会用?
</li>
我把这样的网络协议学习过程总结为:**一看觉得懂,一问就打鼓,一用就糊涂。**
那网络协议究竟该怎么学?基于这个问题,我决定从以下三个角度和你分享我所理解的网络协议。
**第一,我会从身边经常见到的事情出发,用故事来讲解各种网络协议,然后慢慢扩展到不熟悉的领域。**
例如每个人都会查看IP地址那我们就从这个命令开始展开一些概念很多人都在大学宿舍组过简单的网络来打游戏我就从宿舍里最简单的网络概念开始讲然后说到办公室说到日常上网、购物、视频下载等过程涉及的协议最后说到最陌生的数据中心。
**第二,我会用贴近场景的方式来讲解网络协议,将各个层次的关系串起来,而非孤立地讲解某个概念。**
常见的计算机网络课程往往会按照网络分层一层一层地讲却很少讲层与层之间的关系。例如我们学习路由协议的时候在真实场景中这么多的算法和二层是什么关系呢和四层又是什么关系呢例如在真实的网络通信中我们访问一个网站做一个支付在TCP进行三次握手的时候IP层在干嘛MAC层又在干嘛这些你是不是都清楚
**第三,我会在讲解完各个层次的网络协议之后,着重剖析如何在当下热门领域使用这些协议,比如云计算、容器和微服务。**
一方面你可以知道网络协议真实应用的地方,另一方面你也可以通过上手使用云计算、容器、微服务来进一步加深对于协议的理解。
千里之行,始于足下。不管何时,我相信,扎实的功底和过硬的技术,都会是你职业发展的助力器。
希望这个专栏,不仅可以帮你理清繁杂的网络协议概念,帮你构建一个精准的网络协议知识框架,帮你在热门领域应用这些底层知识,更重要的是给你一种学习知识的方法和态度:**看似最枯燥、最基础的东西往往具有最长久的生命力**。

View File

@@ -0,0 +1,154 @@
<audio id="audio" title="第1讲 | 为什么要学习网络协议?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3b/da/3b2bd6909ecb4479e60fa0aa310882da.mp3"></audio>
《圣经》中有一个通天塔的故事,大致是说,上帝为了阻止人类联合起来,就让人类说不同的语言。人类没法儿沟通,达不成“协议”,通天塔的计划就失败了。
但是千年以后,有一种叫“程序猿”的物种,敲着一种这个群体通用的语言,连接着全世界所有的人,打造这互联网世界的通天塔。如今的世界,正是因为互联网,才连接在一起。
当"Hello World!"从显示器打印出来的时候,还记得你激动的心情吗?
```
public class HelloWorld {
public static void main(String[] args){
System.out.println(&quot;Hello World!&quot;);
}
}
```
如果你是程序员,一定看得懂上面这一段文字。这是每一个程序员向计算机世界说“你好,世界”的方式。但是,你不一定知道,这段文字也是一种协议,是人类和计算机沟通的协议,**只有通过这种协议,计算机才知道我们想让它做什么。**
## 协议三要素
当然这种协议还是更接近人类语言机器不能直接读懂需要进行翻译翻译的工作教给编译器也就是程序员常说的compile。这个过程比较复杂其中的编译原理非常复杂我在这里不进行详述。
<img src="https://static001.geekbang.org/resource/image/e8/7d/e823209e795faacdbb9b557750e7d37d.jpg" alt="">
但是可以看得出,计算机语言作为程序员控制一台计算机工作的协议,具备了协议的三要素。
<li>
**语法**,就是这一段内容要符合一定的规则和格式。例如,括号要成对,结束要使用分号等。
</li>
<li>
**语义**,就是这一段内容要代表某种意义。例如数字减去数字是有意义的,数字减去文本一般来说就没有意义。
</li>
<li>
**顺序**,就是先干啥,后干啥。例如,可以先加上某个数值,然后再减去某个数值。
</li>
会了计算机语言,你就能够教给一台计算机完成你的工作了。恭喜你,入门了!
但是,要想打造互联网世界的通天塔,只教给一台机器做什么是不够的,你需要学会教给一大片机器做什么。这就需要网络协议。**只有通过网络协议,才能使一大片机器互相协作、共同完成一件事。**
这个时候,你可能会问,网络协议长啥样,这么神奇,能干成啥事?我先拿一个简单的例子,让你尝尝鲜,然后再讲一个大事。
当你想要买一个商品,常规的做法就是打开浏览器,输入购物网站的地址。浏览器就会给你显示一个缤纷多彩的页面。
那你有没有深入思考过浏览器是如何做到这件事情的它之所以能够显示缤纷多彩的页面是因为它收到了一段来自HTTP协议的“东西”。我拿网易考拉来举例格式就像下面这样
```
HTTP/1.1 200 OK
Date: Tue, 27 Mar 2018 16:50:26 GMT
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;base href=&quot;https://pages.kaola.com/&quot; /&gt;
&lt;meta charset=&quot;utf-8&quot;/&gt; &lt;title&gt;网易考拉3周年主会场&lt;/title&gt;
```
这符合协议的三要素吗?我带你来看一下。
首先,符合语法,也就是说,只有按照上面那个格式来,浏览器才认。例如,上来是**状态**,然后是**首部**,然后是**内容**。
第二符合语义就是要按照约定的意思来。例如状态200表述的意思是网页成功返回。如果不成功就是我们常见的“404”。
第三符合顺序你一点浏览器就是发送出一个HTTP请求然后才有上面那一串HTTP返回的东西。
浏览器显然按照协议商定好的做了,最后一个五彩缤纷的页面就出现在你面前了。
## 我们常用的网络协议有哪些?
接下来揭秘我要说的大事情,“双十一”。这和我们要讲的网络协议有什么关系呢?
在经济学领域有个伦纳德·里德Leonard E. Read创作的《铅笔的故事》。这个故事通过一个铅笔的诞生过程来讲述复杂的经济学理论。这里我也用一个下单的过程看看互联网世界的运行过程中都使用了哪些网络协议。
你先在浏览器里面输入 [https://www.kaola.com](https://www.kaola.com) ,这是一个**URL**。浏览器只知道名字是“www.kaola.com”但是不知道具体的地点所以不知道应该如何访问。于是它打开地址簿去查找。可以使用一般的地址簿协议**DNS**去查找,还可以使用另一种更加精准的地址簿查找协议**HTTPDNS**。
无论用哪一种方法查找最终都会得到这个地址106.114.138.24。这个是**IP**地址,是互联网世界的“门牌号”。
知道了目标地址,浏览器就开始打包它的请求。对于普通的浏览请求,往往会使用**HTTP**协议;但是对于购物的请求,往往需要进行加密传输,因而会使用**HTTPS**协议。无论是什么协议,里面都会写明“你要买什么和买多少”。
<img src="https://static001.geekbang.org/resource/image/70/e9/70e8ad0531ada8baac174ce6862fede9.jpg" alt="">
DNS、HTTP、HTTPS所在的层我们称为**应用层**。经过应用层封装后浏览器会将应用层的包交给下一层去完成通过socket编程来实现。下一层是**传输层**。传输层有两种协议,一种是无连接的协议**UDP**,一种是面向连接的协议**TCP**。对于支付来讲往往使用TCP协议。所谓的面向连接就是TCP会保证这个包能够到达目的地。如果不能到达就会重新发送直至到达。
TCP协议里面会有两个端口一个是浏览器监听的端口一个是电商的服务器监听的端口。操作系统往往通过端口来判断它得到的包应该给哪个进程。
<img src="https://static001.geekbang.org/resource/image/64/0c/64fcf0cc5baade70769da2160637d70c.jpg" alt="">
传输层封装完毕后,浏览器会将包交给操作系统的**网络层**。网络层的协议是IP协议。在IP协议里面会有源IP地址即浏览器所在机器的IP地址和目标IP地址也即电商网站所在服务器的IP地址。
<img src="https://static001.geekbang.org/resource/image/19/62/191947411849da8738dfea8394780962.jpg" alt="">
操作系统既然知道了目标IP地址就开始想如何根据这个门牌号找到目标机器。操作系统往往会判断这个目标IP地址是本地人还是外地人。如果是本地人从门牌号就能看出来但是显然电商网站不在本地而在遥远的地方。
操作系统知道要离开本地去远方。虽然不知道远方在何处,但是可以这样类比一下:如果去国外要去海关,去外地就要去**网关**。而操作系统启动的时候就会被DHCP协议配置IP地址以及默认的网关的IP地址192.168.1.1。
操作系统如何将IP地址发给网关呢在本地通信基本靠吼于是操作系统大吼一声谁是192.168.1.1啊?网关会回答它,我就是,我的本地地址在村东头。这个本地地址就是**MAC**地址,而大吼的那一声是**ARP**协议。
<img src="https://static001.geekbang.org/resource/image/9b/79/9b0d10b384ecd0de11c8596f8890df79.jpg" alt="">
于是操作系统将IP包交给了下一层也就是**MAC层**。网卡再将包发出去。由于这个包里面是有MAC地址的因而它能够到达网关。
网关收到包之后会根据自己的知识判断下一步应该怎么走。网关往往是一个路由器到某个IP地址应该怎么走这个叫作路由表。
路由器有点像玄奘西行路过的一个个国家的一个个城关。每个城关都连着两个国家每个国家相当于一个局域网在每个国家内部都可以使用本地的地址MAC进行通信。
一旦跨越城关就需要拿出IP头来里面写着贫僧来自东土大唐就是源IP地址欲往西天拜佛求经指的是目标IP地址。路过宝地借宿一晚明日启程请问接下来该怎么走啊
<img src="https://static001.geekbang.org/resource/image/08/6f/080495a63148905877f252d097be786f.jpg" alt="">
城关往往是知道这些“知识”的,因为城关和临近的城关也会经常沟通。到哪里应该怎么走,这种沟通的协议称为**路由协议**,常用的有**OSPF**和**BGP**。
<img src="https://static001.geekbang.org/resource/image/7a/39/7a59ab81ffe91bbe98cc0b55eba25d39.jpg" alt="">
城关与城关之间是一个国家当网络包知道了下一步去哪个城关还是要使用国家内部的MAC地址通过下一个城关的MAC地址找到下一个城关然后再问下一步的路怎么走一直到走出最后一个城关。
最后一个城关知道这个网络包要去的地方。于是对着这个国家吼一声谁是目标IP啊目标服务器就会回复一个MAC地址。网络包过关后通过这个MAC地址就能找到目标服务器。
目标服务器发现MAC地址对上了取下MAC头来发送给操作系统的网络层。发现IP也对上了就取下IP头。IP头里会写上一层封装的是TCP协议然后将其交给传输层即**TCP层**。
在这一层里对于收到的每个包都会有一个回复的包说明收到了。这个回复的包绝非这次下单请求的结果例如购物是否成功扣了多少钱等而仅仅是TCP层的一个说明即收到之后的回复。当然这个回复会沿着刚才来的方向走回去报个平安。
因为一旦出了国门,西行路上千难万险,如果在这个过程中,网络包走丢了,例如进了大沙漠,或者被强盗抢劫杀害怎么办呢?因而到了要报个平安。
如果过一段时间还是没到发送端的TCP层会重新发送这个包还是上面的过程直到有一天收到平安到达的回复。**这个重试绝非你的浏览器重新将下单这个动作重新请求一次**。对于浏览器来讲就发送了一次下单请求TCP层不断自己闷头重试。除非TCP这一层出了问题例如连接断了才轮到浏览器的应用层重新发送下单请求。
当网络包平安到达TCP层之后TCP头中有目标端口号通过这个端口号可以找到电商网站的进程正在监听这个端口号假设一个Tomcat将这个包发给电商网站。
<img src="https://static001.geekbang.org/resource/image/a3/9e/a35e16acd0912ae3e79567ca0358df9e.jpg" alt="">
电商网站的进程得到HTTP请求的内容知道了要买东西买多少。往往一个电商网站最初接待请求的这个Tomcat只是个接待员负责统筹处理这个请求而不是所有的事情都自己做。例如这个接待员要告诉专门管理订单的进程登记要买某个商品买多少要告诉管理库存的进程库存要减少多少要告诉支付的进程应该付多少钱等等。
如何告诉相关的进程呢往往通过RPC调用即远程过程调用的方式来实现。远程过程调用就是当告诉管理订单进程的时候接待员不用关心中间的网络互连问题会由RPC框架统一处理。RPC框架有很多种有基于HTTP协议放在HTTP的报文里面的有直接封装在TCP报文里面的。
当接待员发现相应的部门都处理完毕就回复一个HTTPS的包告知下单成功。这个HTTPS的包会像来的时候一样经过千难万险到达你的个人电脑最终进入浏览器显示支付成功。
## 小结
看到了吧,一个简简单单的下单过程,中间牵扯到这么多的协议。而管理一大片机器,更是一件特别有技术含量的事情。除此之外,像最近比较火的云计算、容器、微服务等技术,也都需要借助各种协议,来达成大规模机器之间的合作。
我在这里列一下之后要讲的网络协议,之后我会按照从底层到上层的顺序来讲述。
<img src="https://static001.geekbang.org/resource/image/59/54/5985d6d430e1b1d3f165bf0f916ed954.jpg" alt="">
上面的“双十一”故事只是为了给你一个大致的框架,这里面有些协议,我在故事里已经提到了,有些还没有提到。在这门课的最后一章,当所有的协议都讲过之后,我会再重新讲一遍这个故事,到时候你就能明白更多的细节。
最后,学完了这一节,给你留一个问题吧。
当网络包到达一个城关的时候可以通过路由表得到下一个城关的IP地址直接通过IP地址找就可以了为什么还要通过本地的MAC地址呢
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,99 @@
<audio id="audio" title="第2讲 | 网络分层的真实含义是什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e5/05/e539fc94a00a3db0b72acd53bba93f05.mp3"></audio>
长时间从事计算机网络相关的工作,我发现,计算机网络有一个显著的特点,就是这是一个不仅需要背诵,而且特别需要将原理烂熟于胸的学科。很多问题看起来懂了,但是就怕往细里问,一问就发现你懂得没有那么透彻。
我们上一节列了之后要讲的网络协议。这些协议本来没什么稀奇,每一本教科书都会讲,并且都要求你背下来。因为考试会考,面试会问。可以这么说,毕业了去找工作还答不出这类题目的,那你的笔试基本上也就挂了。
当你听到什么二层设备、三层设备、四层LB和七层LB中层的时候是否有点一头雾水不知道这些所谓的层对应的各种协议具体要做什么“工作”
## 这四个问题你真的懂了吗?
因为教科书或者老师往往会打一个十分不恰当的比喻:为什么网络要分层呀?因为不同的层次之间有不同的沟通方式,这个叫作协议。例如,一家公司也是分“层次”的,分总经理、经理、组长、员工。总经理之间有他们的沟通方式,经理和经理之间也有沟通方式,同理组长和员工。有没有听过类似的比喻?
那么**第一个问题**来了。请问经理在握手的时候员工在干什么很多人听过TCP建立连接的**三次握手协议**也会把它当知识点背诵。同理问你TCP在进行三次握手的时候IP层和MAC层对应都有什么操作呢
除了上面这个不恰当的比喻,教科书还会列出每个层次所包含的协议,然后开始逐层地去讲这些协议。但是这些协议之间的关系呢?却很少有教科书会讲。
学习第三层的时候会提到IP协议里面包含**目标地址**和**源地址。<strong>第三层里往往还会学习**路由协议</strong>。路由就像中转站我们从原始地址A到目标地址D中间经过两个中转站A-&gt;B-&gt;C-&gt;D是通过路由转发的。
那么**第二个问题**来了。A知道自己的下一个中转站是B那从A发出来的包应该把B的IP地址放在哪里呢B知道自己的下一个中转站是C从B发出来的包应该把C的IP地址放在哪里呢如果放在IP协议中的目标地址那包到了中转站怎么知道最终的目的地址是D呢
教科书不会通过场景化的例子,将网络包的生命周期讲出来,所以你就会很困惑,不知道这些协议实际的应用场景是什么。
我**再问你一个问题**。你一定经常听说二层设备、三层设备。二层设备处理的通常是MAC层的东西。那我发送一个HTTP的包是在第七层工作的那是不是不需要经过二层设备或者即便经过了二层设备也不处理呢或者换一种问法二层设备处理的包里有没有HTTP层的内容呢
最终,我想问你**一个综合的问题**。从你的电脑通过SSH登录到公有云主机里面都需要经历哪些过程或者说你打开一个电商网站都需要经历哪些过程说得越详细越好。
实际情况可能是,很多人回答不上来。尽管对每一层都很熟悉,但是知识点却串不起来。
上面的这些问题,有的在这一节就会有一个解释,有的则会贯穿我们整个课程。好在后面一节中我会举一个贯穿的例子,将很多层的细节讲过后,你很容易就能把这些知识点串起来。
## 网络为什么要分层?
这里我们先探讨第一个问题,网络为什么要分层?因为,是个复杂的程序都要分层。
理解计算机网络中的概念一个很好的角度是想象网络包就是一段Buffer或者一块内存是有格式的。同时想象自己是一个处理网络包的程序而且这个程序可以跑在电脑上可以跑在服务器上可以跑在交换机上也可以跑在路由器上。你想象自己有很多的网口从某个口拿进一个网络包来用自己的程序处理一下再从另一个网口发送出去。
当然网络包的格式很复杂,这个程序也很复杂。**复杂的程序都要分层,这是程序设计的要求。**比如复杂的电商还会分数据库层、缓存层、Compose层、Controller层和接入层每一层专注做本层的事情。
## 程序是如何工作的?
我们可以简单地想象“你”这个程序的工作过程。
<img src="https://static001.geekbang.org/resource/image/5c/76/5c00f6e610f533d17fb4ad7decacc776.jpg" alt="" />
当一个网络包从一个网口经过的时候,你看到了,首先先看看要不要请进来,处理一把。有的网口配置了混杂模式,凡是经过的,全部拿进来。
拿进来以后,就要交给一段程序来处理。于是,你调用**process_layer2(buffer)**。当然这是一个假的函数。但是你明白其中的意思知道肯定是有这么个函数的。那这个函数是干什么的呢从Buffer中摘掉二层的头看一看应该根据头里面的内容做什么操作。
假设你发现这个包的MAC地址和你的相符那说明就是发给你的于是需要调用**process_layer3(buffer)**。这个时候Buffer里面往往就没有二层的头了因为已经在上一个函数的处理过程中拿掉了或者将开始的偏移量移动了一下。在这个函数里面摘掉三层的头看看到底是发送给自己的还是希望自己转发出去的。
如何判断呢如果IP地址不是自己的那就应该转发出去如果IP地址是自己的那就是发给自己的。根据IP头里面的标示拿掉三层的头进行下一层的处理到底是调用process_tcp(buffer)呢还是调用process_udp(buffer)呢?
假设这个地址是TCP的则会调用**process_tcp(buffer)**。这时候Buffer里面没有三层的头就需要查看四层的头看这是一个发起还是一个应答又或者是一个正常的数据包然后分别由不同的逻辑进行处理。如果是发起或者应答接下来可能要发送一个回复包如果是一个正常的数据包就需要交给上层了。交给谁呢是不是有process_http(buffer)函数呢?
没有的如果你是一个网络包处理程序你不需要有process_http(buffer),而是应该交给应用去处理。交给哪个应用呢?在四层的头里面有端口号,不同的应用监听不同的端口号。如果发现浏览器应用在监听这个端口,那你发给浏览器就行了。至于浏览器怎么处理,和你没有关系。
浏览器自然是解析HTML显示出页面来。电脑的主人看到页面很开心就点了鼠标。点击鼠标的动作被浏览器捕获。浏览器知道又要发起另一个HTTP请求了于是使用端口号将请求发给了你。
你应该调用**send_tcp(buffer)**。不用说Buffer里面就是HTTP请求的内容。这个函数里面加一个TCP的头记录下源端口号。浏览器会给你目的端口号一般为80端口。
然后调用**send_layer3(buffer)**。Buffer里面已经有了HTTP的头和内容以及TCP的头。在这个函数里面加一个IP的头记录下源IP的地址和目标IP的地址。
然后调用**send_layer2(buffer)**。Buffer里面已经有了HTTP的头和内容、TCP的头以及IP的头。这个函数里面要加一下MAC的头记录下源MAC地址得到的就是本机器的MAC地址和目标的MAC地址。不过这个还要看当前知道不知道知道就直接加上不知道的话就要通过一定的协议处理过程找到MAC地址。反正要填一个不能空着。
万事俱备只要Buffer里面的内容完整就可以从网口发出去了你作为一个程序的任务就算告一段落了。
## 揭秘层与层之间的关系
知道了这个过程之后,我们再来看一下原来困惑的问题。
首先是分层的比喻。**所有不能表示出层层封装含义的比喻,都是不恰当的。**总经理握手,不需要员工在吧,总经理之间谈什么,不需要员工参与吧,但是网络世界不是这样的。正确的应该是,总经理之间沟通的时候,经理将总经理放在自己兜里,然后组长把经理放自己兜里,员工把组长放自己兜里,像套娃娃一样。那员工直接沟通,不带上总经理,就不恰当了。
现实生活中,往往是员工说一句,组长补充两句,然后经理补充两句,最后总经理再补充两句。但是在网络世界,应该是总经理说话,经理补充两句,组长补充两句,员工再补充两句。
那TCP在三次握手的时候IP层和MAC层在做什么呢当然是TCP发送每一个消息都会带着IP层和MAC层了。因为TCP每发送一个消息IP层和MAC层的所有机制都要运行一遍。而你只看到TCP三次握手了其实IP层和MAC层为此也忙活好久了。
这里要记住一点:**只要是在网络上跑的包,都是完整的。可以有下层没上层,绝对不可能有上层没下层。**
所以,**对TCP协议来说三次握手也好重试也好只要想发出去包就要有IP层和MAC层不然是发不出去的。**
经常有人会问这样一个问题我都知道那台机器的IP地址了直接发给他消息呗要MAC地址干啥这里的关键就是没有MAC地址消息是发不出去的。
所以如果一个HTTP协议的包跑在网络上它一定是完整的。无论这个包经过哪些设备它都是完整的。
所谓的二层设备、三层设备都是这些设备上跑的程序不同而已。一个HTTP协议的包经过一个二层设备二层设备收进去的是整个网络包。这里面HTTP、TCP、 IP、 MAC都有。什么叫二层设备呀就是只把MAC头摘下来看看到底是丢弃、转发还是自己留着。那什么叫三层设备呢就是把MAC头摘下来之后再把IP头摘下来看看到底是丢弃、转发还是自己留着。
## 小结
总结一下今天的内容,理解网络协议的工作模式,有两个小窍门:
- 始终想象自己是一个处理网络包的程序:如何拿到网络包,如何根据规则进行处理,如何发出去;
- 始终牢记一个原则:只要是在网络上跑的包,都是完整的。可以有下层没上层,绝对不可能有上层没下层。
最后,给你留两个思考题吧。
1. 如果你也觉得总经理和员工的比喻不恰当,你有更恰当的比喻吗?
1. 要想学习网络协议IP这个概念是最最基本的那你知道如何查看IP地址吗
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,149 @@
<audio id="audio" title="第3讲 | ifconfig最熟悉又陌生的命令行" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2b/da/2be5885efb35636c8351bf2058aba2da.mp3"></audio>
上一节结尾给你留的一个思考题是你知道怎么查看IP地址吗
当面试听到这个问题的时候,面试者常常会觉得走错了房间。我面试的是技术岗位啊,怎么问这么简单的问题?
的确即便没有专业学过计算机的人只要倒腾过电脑重装过系统大多也会知道这个问题的答案在Windows上是ipconfig在Linux上是ifconfig。
那你知道在Linux上还有什么其他命令可以查看IP地址吗答案是ip addr。如果回答不上来这个问题那你可能没怎么用过Linux。
那你知道ifconfig和ip addr的区别吗这是一个有关net-tools和iproute2的“历史”故事你刚来到第三节暂时不用了解这么细但这也是一个常考的知识点。
想象一下你登录进入一个被裁剪过的非常小的Linux系统中发现既没有ifconfig命令也没有ip addr命令你是不是感觉这个系统压根儿没法用这个时候你可以自行安装net-tools和iproute2这两个工具。当然大多数时候这两个命令是系统自带的。
安装好后我们来运行一下ip addr。不出意外应该会输出下面的内容。
```
root@test:~# ip addr
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff
inet 10.100.122.2/24 brd 10.100.122.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fec7:7975/64 scope link
valid_lft forever preferred_lft forever
```
这个命令显示了这台机器上所有的网卡。大部分的网卡都会有一个IP地址当然这不是必须的。在后面的分享中我们会遇到没有IP地址的情况。
IP地址是一个网卡在网络世界的通讯地址相当于我们现实世界的门牌号码。既然是门牌号码不能大家都一样不然就会起冲突。比方说假如大家都叫六单元1001号那快递就找不到地方了。所以有时候咱们的电脑弹出网络地址冲突出现上不去网的情况多半是IP地址冲突了。
如上输出的结果10.100.122.2就是一个IP地址。这个地址被点分隔为四个部分每个部分8个bit所以IP地址总共是32位。这样产生的IP地址的数量很快就不够用了。因为当时设计IP地址的时候哪知道今天会有这么多的计算机啊因为不够用于是就有了IPv6也就是上面输出结果里面inet6 fe80::f816:3eff:fec7:7975/64。这个有128位现在看来是够了但是未来的事情谁知道呢
本来32位的IP地址就不够还被分成了5类。现在想想当时分配地址的时候真是太奢侈了。
<img src="https://static001.geekbang.org/resource/image/fa/5e/fa9a00346b8a83a84f4c00de948a5b5e.jpg" alt="">
在网络地址中至少在当时设计的时候对于A、B、 C类主要分两部分前面一部分是网络号后面一部分是主机号。这很好理解大家都是六单元1001号我是小区A的六单元1001号而你是小区B的六单元1001号。
下面这个表格详细地展示了A、B、C三类地址所能包含的主机的数量。在后文中我也会多次借助这个表格来讲解。
<img src="https://static001.geekbang.org/resource/image/6d/4c/6de9d39a68520c8e3b75aa919fadb24c.jpg" alt="">
这里面有个尴尬的事情就是C类地址能包含的最大主机数量实在太少了只有254个。当时设计的时候恐怕没想到现在估计一个网吧都不够用吧。而B类地址能包含的最大主机数量又太多了。6万多台机器放在一个网络下面一般的企业基本达不到这个规模闲着的地址就是浪费。
## 无类型域间选路CIDR
于是有了一个折中的方式叫作**无类型域间选路**,简称**CIDR**。这种方式打破了原来设计的几类地址的做法将32位的IP地址一分为二前面是**网络号**,后面是**主机号**。从哪里分呢你如果注意观察的话可以看到10.100.122.2/24这个IP地址中有一个斜杠斜杠后面有个数字24。这种地址表示形式就是CIDR。后面24的意思是32位中前24位是网络号后8位是主机号。
伴随着CIDR存在的一个是**广播地址**10.100.122.255。如果发送这个地址所有10.100.122网络里面的机器都可以收到。另一个是**子网掩码**255.255.255.0。
将子网掩码和IP地址进行AND计算。前面三个255转成二进制都是1。1和任何数值取AND都是原来数值因而前三个数不变为10.100.122。后面一个0转换成二进制是00和任何数值取AND都是0因而最后一个数变为0合起来就是10.100.122.0。这就是**网络号**。**将子网掩码和IP地址按位计算AND就可得到网络号。**
## 公有IP地址和私有IP地址
在日常的工作中几乎不用划分A类、B类或者C类所以时间长了很多人就忘记了这个分类而只记得CIDR。但是有一点还是要注意的就是公有IP地址和私有IP地址。
<img src="https://static001.geekbang.org/resource/image/df/a9/df90239efec6e35880b9abe55089ffa9.jpg" alt="">
我们继续看上面的表格。表格最右列是私有IP地址段。平时我们看到的数据中心里办公室、家里或学校的IP地址一般都是私有IP地址段。因为这些地址允许组织内部的IT人员自己管理、自己分配而且可以重复。因此你学校的某个私有IP地址段和我学校的可以是一样的。
这就像每个小区有自己的楼编号和门牌号你们小区可以叫6栋我们小区也叫6栋没有任何问题。但是一旦出了小区就需要使用公有IP地址。就像人民路888号是国家统一分配的不能两个小区都叫人民路888号。
公有IP地址有个组织统一分配你需要去买。如果你搭建一个网站给你学校的人使用让你们学校的IT人员给你一个IP地址就行。但是假如你要做一个类似网易163这样的网站就需要有公有IP地址这样全世界的人才能访问。
表格中的192.168.0.x是最常用的私有IP地址。你家里有Wi-Fi对应就会有一个IP地址。一般你家里地上网设备不会超过256个所以/24基本就够了。有时候我们也能见到/16的CIDR这两种是最常见的也是最容易理解的。
不需要将十进制转换为二进制32位就能明显看出192.168.0是网络号后面是主机号。而整个网络里面的第一个地址192.168.0.1往往就是你这个私有网络的出口地址。例如你家里的电脑连接Wi-FiWi-Fi路由器的地址就是192.168.0.1而192.168.0.255就是广播地址。一旦发送这个地址整个192.168.0网络里面的所有机器都能收到。
但是也不总都是这样的情况。因此,其他情况往往就会很难理解,还容易出错。
## 举例一个容易“犯错”的CIDR
我们来看16.158.165.91/22这个CIDR。求一下这个网络的第一个地址、子网掩码和广播地址。
你要是上来就写16.158.165.1,那就大错特错了。
/22不是8的整数倍不好办只能先变成二进制来看。16.158的部分不会动它占了前16位。中间的165变为二进制为10100101。除了前面的16位还剩6位。所以这8位中前6位是网络号16.158.&lt;101001&gt;,而&lt;01&gt;.91是机器号。
第一个地址是16.158.&lt;101001&gt;&lt;00&gt;.1即16.158.164.1。子网掩码是255.255.&lt;111111&gt;&lt;00&gt;.0即255.255.252.0。广播地址为16.158.&lt;101001&gt;&lt;11&gt;.255即16.158.167.255。
这五类地址中还有一类D类是**组播地址**。使用这一类地址属于某个组的机器都能收到。这有点类似在公司里面大家都加入了一个邮件组。发送邮件加入这个组的都能收到。组播地址在后面讲述VXLAN协议的时候会提到。
讲了这么多才讲了上面的输出结果中很小的一部分是不是觉得原来并没有真的理解ip addr呢我们接着来分析。
在IP地址的后面有个scope对于eth0这张网卡来讲是global说明这张网卡是可以对外的可以接收来自各个地方的包。对于lo来讲是host说明这张网卡仅仅可以供本机相互通信。
lo全称是**loopback**,又称**环回接口**往往会被分配到127.0.0.1这个地址。这个地址用于本机通信,经过内核处理后直接返回,不会在任何网络中出现。
## MAC地址
在IP地址的上一行是link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff这个被称为**MAC地址**是一个网卡的物理地址用十六进制6个byte表示。
MAC地址是一个很容易让人“误解”的地址。因为MAC地址号称全局唯一不会有两个网卡有相同的MAC地址而且网卡自生产出来就带着这个地址。很多人看到这里就会想既然这样整个互联网的通信全部用MAC地址好了只要知道了对方的MAC地址就可以把信息传过去。
这样当然是不行的。 **一个网络包要从一个地方传到另一个地方,除了要有确定的地址,还需要有定位功能。** 而有门牌号码属性的IP地址才是有远程定位功能的。
例如你去杭州市网商路599号B楼6层找刘超你在路上问路可能被问的人不知道B楼是哪个但是可以给你指网商路怎么去。但是如果你问一个人你知道这个身份证号的人在哪里吗可想而知没有人知道。
MAC地址更像是身份证是一个唯一的标识。它的唯一性设计是为了组网的时候不同的网卡放在一个网络里面的时候可以不用担心冲突。从硬件角度保证不同的网卡有不同的标识。
MAC地址是有一定定位功能的只不过范围非常有限。你可以根据IP地址找到杭州市网商路599号B楼6层但是依然找不到我你就可以靠吼了大声喊身份证XXXX的是哪位我听到了我就会站起来说是我啊。但是如果你在上海到处喊身份证XXXX的是哪位我不在现场当然不会回答因为我在杭州不在上海。
所以MAC地址的通信范围比较小局限在一个子网里面。例如从192.168.0.2/24访问192.168.0.3/24是可以用MAC地址的。一旦跨子网即从192.168.0.2/24到192.168.1.2/24MAC地址就不行了需要IP地址起作用了。
## 网络设备的状态标识
解析完了MAC地址我们再来看 &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt;是干什么的?这个叫做**net_device flags****网络设备的状态标识**。
UP表示网卡处于启动的状态BROADCAST表示这个网卡有广播地址可以发送广播包MULTICAST表示网卡可以发送多播包LOWER_UP表示L1是启动的也即网线插着呢。MTU1500是指什么意思呢是哪一层的概念呢最大传输单元MTU为1500这是以太网的默认值。
上一节我们讲过网络包是层层封装的。MTU是二层MAC层的概念。MAC层有MAC的头以太网规定正文部分不允许超过1500个字节。正文里面有IP的头、TCP的头、HTTP的头。如果放不下就需要分片来传输。
qdisc pfifo_fast是什么意思呢qdisc全称是**queueing discipline**,中文叫**排队规则**。内核如果需要通过某个网络接口发送数据包它都需要按照为这个接口配置的qdisc排队规则把数据包加入队列。
最简单的qdisc是pfifo它不对进入的数据包做任何的处理数据包采用先入先出的方式通过队列。pfifo_fast稍微复杂一些它的队列包括三个波段band。在每个波段里面使用先进先出规则。
三个波段band的优先级也不相同。band 0的优先级最高band 2的最低。如果band 0里面有数据包系统就不会处理band 1里面的数据包band 1和band 2之间也是一样。
数据包是按照服务类型(**Type of ServiceTOS**被分配到三个波段band里面的。TOS是IP头里面的一个字段代表了当前的包是高优先级的还是低优先级的。
队列是个好东西,后面我们讲云计算中的网络的时候,会有很多用户共享一个网络出口的情况,这个时候如何排队,每个队列有多粗,队列处理速度应该怎么提升,我都会详细为你讲解。
## 小结
怎么样,看起来很简单的一个命令,里面学问很大吧?通过这一节,希望你能记住以下的知识点,后面都能用得上:
<li>
IP是地址有定位功能MAC是身份证无定位功能
</li>
<li>
CIDR可以用来判断是不是本地人
</li>
<li>
IP分公有的IP和私有的IP。后面的章节中我会谈到“出国门”就与这个有关。
</li>
最后,给你留两个思考题。
1. 你知道net-tools和iproute2的“历史”故事吗
1. 这一节讲的是如何查看IP地址那你知道IP地址是怎么来的吗
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,178 @@
<audio id="audio" title="第4讲 | DHCP与PXEIP是怎么来的又是怎么没的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8f/35/8fcc98e770f39dc43af82f717ffd4f35.mp3"></audio>
上一节我们讲了IP的一些基本概念。如果需要和其他机器通讯我们就需要一个通讯地址我们需要给网卡配置这么一个地址。
## 如何配置IP地址
那如何配置呢如果有相关的知识和积累你可以用命令行自己配置一个地址。可以使用ifconfig也可以使用ip addr。设置好了以后用这两个命令将网卡up一下就可以开始工作了。
**使用net-tools**
```
$ sudo ifconfig eth1 10.0.0.1/24
$ sudo ifconfig eth1 up
```
**使用iproute2**
```
$ sudo ip addr add 10.0.0.1/24 dev eth1
$ sudo ip link set up eth1
```
你可能会问了自己配置这个自由度太大了吧我是不是配置什么都可以如果配置一个和谁都不搭边的地址呢例如旁边的机器都是192.168.1.x我非得配置一个16.158.23.6,会出现什么现象呢?
不会出现任何现象,就是包发不出去呗。为什么发不出去呢?我来举例说明。
192.168.1.6就在你这台机器的旁边甚至是在同一个交换机上而你把机器的地址设为了16.158.23.6。在这台机器上你企图去ping192.168.1.6,你觉得只要将包发出去,同一个交换机的另一台机器马上就能收到,对不对?
可是Linux系统不是这样的它没你想的那么智能。你用肉眼看到那台机器就在旁边它则需要根据自己的逻辑进行处理。
还记得我们在第二节说过的原则吗?**只要是在网络上跑的包,都是完整的,可以有下层没上层,绝对不可能有上层没下层。**
所以你看着它有自己的源IP地址16.158.23.6也有目标IP地址192.168.1.6但是包发不出去这是因为MAC层还没填。
自己的MAC地址自己知道这个容易。但是目标MAC填什么呢是不是填192.168.1.6这台机器的MAC地址呢
当然不是。Linux首先会判断要去的这个地址和我是一个网段的吗或者和我的一个网卡是同一网段的吗只有是一个网段的它才会发送ARP请求获取MAC地址。如果发现不是呢
**Linux默认的逻辑是如果这是一个跨网段的调用它便不会直接将包发送到网络上而是企图将包发送到网关。**
如果你配置了网关的话Linux会获取网关的MAC地址然后将包发出去。对于192.168.1.6这台机器来讲虽然路过它家门的这个包目标IP是它但是无奈MAC地址不是它的所以它的网卡是不会把包收进去的。
如果没有配置网关呢?那包压根就发不出去。
如果将网关配置为192.168.1.6呢不可能Linux不会让你配置成功的因为网关要和当前的网络至少一个网卡是同一个网段的怎么可能16.158.23.6的网关是192.168.1.6呢?
所以当你需要手动配置一台机器的网络IP时一定要好好问问你的网络管理员。如果在机房里面要去网络管理员那里申请让他给你分配一段正确的IP地址。当然真正配置的时候一定不是直接用命令配置的而是放在一个配置文件里面。**不同系统的配置文件格式不同但是无非就是CIDR、子网掩码、广播地址和网关地址**。
## 动态主机配置协议DHCP
原来配置IP有这么多门道儿啊。你可能会问了配置了IP之后一般不能变的配置一个服务端的机器还可以但是如果是客户端的机器呢我抱着一台笔记本电脑在公司里走来走去或者白天来晚上走每次使用都要配置IP地址那可怎么办还有人事、行政等非技术人员如果公司所有的电脑都需要IT人员配置肯定忙不过来啊。
因此,我们需要有一个自动配置的协议,也就是**动态主机配置协议Dynamic Host Configuration Protocol**,简称**DHCP**。
有了这个协议网络管理员就轻松多了。他只需要配置一段共享的IP地址。每一台新接入的机器都通过DHCP协议来这个共享的IP地址里申请然后自动配置好就可以了。等人走了或者用完了还回去这样其他的机器也能用。
所以说,**如果是数据中心里面的服务器IP一旦配置好基本不会变这就相当于买房自己装修。DHCP的方式就相当于租房。你不用装修都是帮你配置好的。你暂时用一下用完退租就可以了。**
## 解析DHCP的工作方式
当一台机器新加入一个网络的时候肯定一脸懵啥情况都不知道只知道自己的MAC地址。怎么办先吼一句我来啦有人吗这时候的沟通基本靠“吼”。这一步我们称为**DHCP Discover。**
新来的机器使用IP地址0.0.0.0发送了一个广播包目的IP地址为255.255.255.255。广播包封装了UDPUDP封装了BOOTP。其实DHCP是BOOTP的增强版但是如果你去抓包的话很可能看到的名称还是BOOTP协议。
在这个广播包里面新人大声喊我是新来的Boot request我的MAC地址是这个我还没有IP谁能给租给我个IP地址
格式就像这样:
<img src="https://static001.geekbang.org/resource/image/90/81/90b4d41ee38e891031705d987d5d8481.jpg" alt="">
如果一个网络管理员在网络里面配置了**DHCP Server**的话他就相当于这些IP的管理员。他立刻能知道来了一个“新人”。这个时候我们可以体会MAC地址唯一的重要性了。当一台机器带着自己的MAC地址加入一个网络的时候MAC是它唯一的身份如果连这个都重复了就没办法配置了。
只有MAC唯一IP管理员才能知道这是一个新人需要租给它一个IP地址这个过程我们称为**DHCP Offer**。同时DHCP Server为此客户保留为它提供的IP地址从而不会为其他DHCP客户分配此IP地址。
DHCP Offer的格式就像这样里面有给新人分配的地址。
<img src="https://static001.geekbang.org/resource/image/a5/6b/a52c8c87b925b52059febe9dfcd6be6b.jpg" alt="">
DHCP Server仍然使用广播地址作为目的地址因为此时请求分配IP的新人还没有自己的IP。DHCP Server回复说我分配了一个可用的IP给你你看如何除此之外服务器还发送了子网掩码、网关和IP地址租用期等信息。
新来的机器很开心它的“吼”得到了回复并且有人愿意租给它一个IP地址了这意味着它可以在网络上立足了。当然更令人开心的是如果有多个DHCP Server这台新机器会收到多个IP地址简直受宠若惊。
它会选择其中一个DHCP Offer一般是最先到达的那个并且会向网络发送一个DHCP Request广播数据包包中包含客户端的MAC地址、接受的租约中的IP地址、提供此租约的DHCP服务器地址等并告诉所有DHCP Server它将接受哪一台服务器提供的IP地址告诉其他DHCP服务器谢谢你们的接纳并请求撤销它们提供的IP地址以便提供给下一个IP租用请求者。
<img src="https://static001.geekbang.org/resource/image/cd/fa/cdbcaad24e1a4d24dd724e38f6f043fa.jpg" alt="">
此时由于还没有得到DHCP Server的最后确认客户端仍然使用0.0.0.0为源IP地址、255.255.255.255为目标地址进行广播。在BOOTP里面接受某个DHCP Server的分配的IP。
当DHCP Server接收到客户机的DHCP request之后会广播返回给客户机一个DHCP ACK消息包表明已经接受客户机的选择并将这一IP地址的合法租用信息和其他的配置信息都放入该广播包发给客户机欢迎它加入网络大家庭。
<img src="https://static001.geekbang.org/resource/image/cc/a9/cca8b0baa4749bb359e453b1b482e1a9.jpg" alt="">
最终租约达成的时候,还是需要广播一下,让大家都知道。
## IP地址的收回和续租
既然是租房子就是有租期的。租期到了管理员就要将IP收回。
如果不用的话收回就收回了。就像你租房子一样如果还要续租的话不能到了时间再续租而是要提前一段时间给房东说。DHCP也是这样。
客户机会在租期过去50%的时候直接向为其提供IP地址的DHCP Server发送DHCP request消息包。客户机接收到该服务器回应的DHCP ACK消息包会根据包中所提供的新的租期以及其他已经更新的TCP/IP参数更新自己的配置。这样IP租用更新就完成了。
好了一切看起来完美。DHCP协议大部分人都知道但是其实里面隐藏着一个细节很多人可能不会去注意。接下来我就讲一个有意思的事情网络管理员不仅能自动分配IP地址还能帮你自动安装操作系统
## 预启动执行环境PXE
普通的笔记本电脑,一般不会有这种需求。因为你拿到电脑时,就已经有操作系统了,即便你自己重装操作系统,也不是很麻烦的事情。但是,在数据中心里就不一样了。数据中心里面的管理员可能一下子就拿到几百台空的机器,一个个安装操作系统,会累死的。
所以管理员希望的不仅仅是自动分配IP地址还要自动安装系统。装好系统之后自动分配IP地址直接启动就能用了这样当然最好了
这事儿其实仔细一想,还是挺有难度的。安装操作系统,应该有个光盘吧。数据中心里不能用光盘吧,想了一个办法就是,可以将光盘里面要安装的操作系统放在一个服务器上,让客户端去下载。但是客户端放在哪里呢?它怎么知道去哪个服务器上下载呢?客户端总得安装在一个操作系统上呀,可是这个客户端本来就是用来安装操作系统的呀?
其实这个过程和操作系统启动的过程有点儿像。首先启动BIOS。这是一个特别小的小系统只能干特别小的一件事情。其实就是读取硬盘的MBR启动扇区将GRUB启动起来然后将权力交给GRUBGRUB加载内核、加载作为根文件系统的initramfs文件然后将权力交给内核最后内核启动初始化整个操作系统。
那我们安装操作系统的过程只能插在BIOS启动之后了。因为没安装系统之前连启动扇区都没有。因而这个过程叫做**预启动执行环境Pre-boot Execution Environment**,简称**PXE。**
PXE协议分为客户端和服务器端由于还没有操作系统只能先把客户端放在BIOS里面。当计算机启动时BIOS把PXE客户端调入内存里面就可以连接到服务端做一些操作了。
首先PXE客户端自己也需要有个IP地址。因为PXE的客户端启动起来就可以发送一个DHCP的请求让DHCP Server给它分配一个地址。PXE客户端有了自己的地址那它怎么知道PXE服务器在哪里呢对于其他的协议都好办要有人告诉他。例如告诉浏览器要访问的IP地址或者在配置中告诉它例如微服务之间的相互调用。
但是PXE客户端启动的时候啥都没有。好在DHCP Server除了分配IP地址以外还可以做一些其他的事情。这里有一个DHCP Server的一个样例配置
```
ddns-update-style interim;
ignore client-updates;
allow booting;
allow bootp;
subnet 192.168.1.0 netmask 255.255.255.0
{
option routers 192.168.1.1;
option subnet-mask 255.255.255.0;
option time-offset -18000;
default-lease-time 21600;
max-lease-time 43200;
range dynamic-bootp 192.168.1.240 192.168.1.250;
filename &quot;pxelinux.0&quot;;
next-server 192.168.1.180;
}
```
按照上面的原理默认的DHCP Server是需要配置的无非是我们配置IP的时候所需要的IP地址段、子网掩码、网关地址、租期等。如果想使用PXE则需要配置next-server指向PXE服务器的地址另外要配置初始启动文件filename。
这样PXE客户端启动之后发送DHCP请求之后除了能得到一个IP地址还可以知道PXE服务器在哪里也可以知道如何从PXE服务器上下载某个文件去初始化操作系统。
## 解析PXE的工作过程
接下来我们来详细看一下PXE的工作过程。
首先启动PXE客户端。第一步是通过DHCP协议告诉DHCP Server我刚来一穷二白啥都没有。DHCP Server便租给它一个IP地址同时也给它PXE服务器的地址、启动文件pxelinux.0。
其次PXE客户端知道要去PXE服务器下载这个文件后就可以初始化机器。于是便开始下载下载的时候使用的是TFTP协议。所以PXE服务器上往往还需要有一个TFTP服务器。PXE客户端向TFTP服务器请求下载这个文件TFTP服务器说好啊于是就将这个文件传给它。
然后PXE客户端收到这个文件后就开始执行这个文件。这个文件会指示PXE客户端向TFTP服务器请求计算机的配置信息pxelinux.cfg。TFTP服务器会给PXE客户端一个配置文件里面会说内核在哪里、initramfs在哪里。PXE客户端会请求这些文件。
最后启动Linux内核。一旦启动了操作系统以后就啥都好办了。
<img src="https://static001.geekbang.org/resource/image/bb/8e/bbc2b660bba0ad00b5d1179db158498e.jpg" alt="">
## 小结
好了,这一节就到这里了。我来总结一下今天的内容:
<li>
DHCP协议主要是用来给客户租用IP地址和房产中介很像要商谈、签约、续租广播还不能“抢单”
</li>
<li>
DHCP协议能给客户推荐“装修队”PXE能够安装操作系统这个在云计算领域大有用处。
</li>
最后,学完了这一节,给你留两个思考题吧。
1. PXE协议可以用来安装操作系统但是如果每次重启都安装操作系统就会很麻烦。你知道如何使得第一次安装操作系统后面就正常启动吗
1. 现在上网很简单了买个家用路由器连上WIFI给DHCP分配一个IP地址就可以上网了。那你是否用过更原始的方法自己组过简单的网呢说来听听。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,169 @@
<audio id="audio" title="第24讲 | 云中网络:自己拿地成本高,购买公寓更灵活" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/01/43/01c265bd1bb26282aa21e2e745fe0643.mp3"></audio>
前面我们讲了,数据中心里面堆着一大片一大片的机器,用网络连接起来,机器数目一旦非常多,人们就发现,维护这么一大片机器还挺麻烦的,有好多不灵活的地方。
<li>
采购不灵活如果客户需要一台电脑那就需要自己采购、上架、插网线、安装操作系统周期非常长。一旦采购了一用就N年不能退货哪怕业务不做了机器还在数据中心里留着。
</li>
<li>
运维不灵活一旦需要扩容CPU、内存、硬盘都需要去机房手动弄非常麻烦。
</li>
<li>
规格不灵活采购的机器往往动不动几百G的内存而每个应用往往可能只需要4核8G所以很多应用混合部署在上面端口各种冲突容易相互影响。
</li>
<li>
复用不灵活:一台机器,一旦一个用户不用了,给另外一个用户,那就需要重装操作系统。因为原来的操作系统可能遗留很多数据,非常麻烦。
</li>
## 从物理机到虚拟机
为了解决这些问题,人们发明了一种叫虚拟机的东西,并基于它产生了云计算技术。
其实在你的个人电脑上就可以使用虚拟机。如果你对虚拟机没有什么概念你可以下载一个桌面虚拟化的软件自己动手尝试一下。它可以让你灵活地指定CPU的数目、内存的大小、硬盘的大小可以有多个网卡然后在一台笔记本电脑里面创建一台或者多台虚拟电脑。不用的时候一点删除就没有了。
在数据中心里面也有一种类似的开源技术qemu-kvm能让你在一台巨大的物理机里面掏出一台台小的机器。这套软件就能解决上面的问题一点就能创建一点就能销毁。你想要多大就有多大每次创建的系统还都是新的。
**我们常把物理机比喻为自己拿地盖房子,而虚拟机则相当于购买公寓,更加灵活方面,随时可买可卖。** 那这个软件为什么能做到这些事儿呢?
它用的是**软件模拟硬件**的方式。刚才说了数据中心里面用的qemu-kvm。从名字上来讲emu就是Emulator模拟器的意思主要会模拟CPU、内存、网络、硬盘使得虚拟机感觉自己在使用独立的设备但是真正使用的时候当然还是使用物理的设备。
例如多个虚拟机轮流使用物理CPU内存也是使用虚拟内存映射的方式最终映射到物理内存上。硬盘在一块大的文件系统上创建一个N个G的文件作为虚拟机的硬盘。
简单比喻,虚拟化软件就像一个“骗子”,向上“骗”虚拟机里面的应用,让它们感觉独享资源,其实自己啥都没有,全部向下从物理机里面弄。
## 虚拟网卡的原理
那网络是如何“骗”应用的呢?如何将虚拟机的网络和物理机的网络连接起来?
<img src="https://static001.geekbang.org/resource/image/93/ca/93ec56f83d51c17f788c715a45c6bfca.jpeg" alt="">
首先虚拟机要有一张网卡。对于qemu-kvm来说这是通过Linux上的一种TUN/TAP技术来实现的。
虚拟机是物理机上跑着的一个软件。这个软件可以像其他应用打开文件一样打开一个称为TUN/TAP的Char Dev字符设备文件。打开了这个字符设备文件之后在物理机上就能看到一张虚拟TAP网卡。
虚拟化软件作为“骗子”,会将打开的这个文件,在虚拟机里面虚拟出一张网卡,让虚拟机里面的应用觉得它们真有一张网卡。于是,所有的网络包都往这里发。
当然网络包会到虚拟化软件这里。它会将网络包转换成为文件流写入字符设备就像写一个文件一样。内核中TUN/TAP字符设备驱动会收到这个写入的文件流交给TUN/TAP的虚拟网卡驱动。这个驱动将文件流再次转成网络包交给TCP/IP协议栈最终从虚拟TAP网卡发出来成为标准的网络包。
就这样,几经转手,数据终于从虚拟机里面,发到了虚拟机外面。
## 虚拟网卡连接到云中
我们就这样有了虚拟TAP网卡。接下来就要看这个卡怎么接入庞大的数据中心网络中。
在接入之前,我们先来看,云计算中的网络都需要注意哪些点。
<li>
**共享**:尽管每个虚拟机都会有一个或者多个虚拟网卡,但是物理机上可能只有有限的网卡。那这么多虚拟网卡如何共享同一个出口?
</li>
<li>
**隔离**:分两个方面,一个是安全隔离,两个虚拟机可能属于两个用户,那怎么保证一个用户的数据不被另一个用户窃听?一个是流量隔离,两个虚拟机,如果有一个疯狂下片,会不会导致另外一个上不了网?
</li>
<li>
**互通**:分两个方面,一个是如果同一台机器上的两个虚拟机,属于同一个用户的话,这两个如何相互通信?另一个是如果不同物理机上的两个虚拟机,属于同一个用户的话,这两个如何相互通信?
</li>
<li>
**灵活**:虚拟机和物理不同,会经常创建、删除,从一个机器漂移到另一台机器,有的互通、有的不通等等,灵活性比物理网络要好得多,需要能够灵活配置。
</li>
### 共享与互通问题
这些问题,我们一个个来解决。
首先,一台物理机上有多个虚拟机,有多个虚拟网卡,这些虚拟网卡如何连在一起,进行相互访问,并且可以访问外网呢?
还记得我们在大学宿舍里做的事情吗?你可以想象你的物理机就是你们宿舍,虚拟机就是你的个人电脑,这些电脑应该怎么连接起来呢?当然应该买一个交换机。
在物理机上应该有一个虚拟的交换机在Linux上有一个命令叫作brctl可以创建虚拟的网桥brctl addbr br0。创建出来以后将两个虚拟机的虚拟网卡都连接到虚拟网桥brctl addif br0 tap0上这样将两个虚拟机配置相同的子网网段两台虚拟机就能够相互通信了。
<img src="https://static001.geekbang.org/resource/image/79/f1/793469d10d57b4845d5678c266a9a6f1.jpeg" alt="">
那这些虚拟机如何连接外网呢?在桌面虚拟化软件上面,我们能看到以下选项。
<img src="https://static001.geekbang.org/resource/image/ee/24/ee3424547c32433e04fb174fdbaa9924.jpg" alt="">
这里面host-only的网络对应的其实就是上面两个虚拟机连到一个br0虚拟网桥上而且不考虑访问外部的场景只要虚拟机之间能够相互访问就可以了。
如果要访问外部,往往有两种方式。
一种方式称为**桥接**。如果在桌面虚拟化软件上选择桥接网络,则在你的笔记本电脑上,就会形成下面的结构。
<img src="https://static001.geekbang.org/resource/image/64/d4/64d80d328f318b90d8d83a584138ffd4.jpeg" alt="">
每个虚拟机都会有虚拟网卡,在你的笔记本电脑上,会发现多了几个网卡,其实是虚拟交换机。这个虚拟交换机将虚拟机连接在一起。在桥接模式下,物理网卡也连接到这个虚拟交换机上,物理网卡在桌面虚拟化软件上,在“界面名称”那里选定。
如果使用桥接网络当你登录虚拟机里看IP地址的时候会发现你的虚拟机的地址和你的笔记本电脑的以及你旁边的同事的电脑的网段是一个网段。这是为什么呢这其实相当于将物理机和虚拟机放在同一个网桥上相当于这个网桥上有三台机器是一个网段的全部打平了。我将图画成下面的样子你就好理解了。
<img src="https://static001.geekbang.org/resource/image/f9/e3/f90500a85292a466df2dd4e6cab1e2e3.jpeg" alt="">
在数据中心里面采取的也是类似的技术只不过都是Linux在每台机器上都创建网桥br0虚拟机的网卡都连到br0上物理网卡也连到br0上所有的br0都通过物理网卡出来连接到物理交换机上。
<img src="https://static001.geekbang.org/resource/image/44/44/44d70c3de1258337af8b352c767bb044.jpeg" alt="">
同样我们换一个角度看待这个拓扑图。同样是将网络打平,虚拟机会和你的物理网络具有相同的网段。
<img src="https://static001.geekbang.org/resource/image/03/30/03e8e31978040de9ffcb21413bb0f830.jpeg" alt="">
在这种方式下,不但解决了同一台机器的互通问题,也解决了跨物理机的互通问题,因为都在一个二层网络里面,彼此用相同的网段访问就可以了。但是当规模很大的时候,会存在问题。
你还记得吗在一个二层网络里面最大的问题是广播。一个数据中心的物理机已经很多了广播已经非常严重需要通过VLAN进行划分。如果使用了虚拟机假设一台物理机里面创建10台虚拟机全部在一个二层网络里面那广播就会很严重所以除非是你的桌面虚拟机或者数据中心规模非常小才可以使用这种相对简单的方式。
另外一种方式称为**NAT**。如果在桌面虚拟化软件中使用NAT模式在你的笔记本电脑上会出现如下的网络结构。
<img src="https://static001.geekbang.org/resource/image/2e/41/2e959deab0e3e3a5c183033bf108eb41.jpeg" alt="">
在这种方式下你登录到虚拟机里面查看IP地址会发现虚拟机的网络是虚拟机的物理机的网络是物理机的两个不相同。虚拟机要想访问物理机的时候需要将地址NAT成为物理机的地址。
除此之外它还会在你的笔记本电脑里内置一个DHCP服务器为笔记本电脑上的虚拟机动态分配IP地址。因为虚拟机的网络自成体系需要进行IP管理。为什么桥接方式不需要呢因为桥接将网络打平了虚拟机的IP地址应该由物理网络的DHCP服务器分配。
在数据中心里面,也是使用类似的方式。这种方式更像是真的将你宿舍里面的情况,搬到一台物理机上来。
<img src="https://static001.geekbang.org/resource/image/a3/2a/a37a80a624d8acee6f2cd8e41571972a.jpeg" alt="">
虚拟机是你的电脑路由器和DHCP Server相当于家用路由器或者寝室长的电脑物理网卡相当于你们宿舍的外网网口用于访问互联网。所有电脑都通过内网网口连接到一个网桥br0上虚拟机要想访问互联网需要通过br0连到路由器上然后通过路由器将请求NAT成为物理网络的地址转发到物理网络。
如果是你自己登录到物理机上做个简单配置你可以简化一下。例如将虚拟机所在网络的网关的地址直接配置到br0上不用DHCP Server手动配置每台虚拟机的IP地址通过命令iptables -t nat -A POSTROUTING -o ethX -j MASQUERADE直接在物理网卡ethX上进行NAT所有从这个网卡出去的包都NAT成这个网卡的地址。通过设置net.ipv4.ip_forward = 1开启物理机的转发功能直接做路由器而不用单独的路由器这样虚拟机就能直接上网了。
<img src="https://static001.geekbang.org/resource/image/8f/72/8f4f531e929b196bff0fc2eaba7a9172.jpeg" alt="">
### 隔离问题
解决了互通的问题,接下来就是隔离的问题。
如果一台机器上的两个虚拟机不属于同一个用户怎么办呢好在brctl创建的网桥也是支持VLAN功能的可以设置两个虚拟机的tag这样在这个虚拟网桥上两个虚拟机是不互通的。
但是如何跨物理机互通并且实现VLAN的隔离呢由于brctl创建的网桥上面的tag是没办法在网桥之外的范围内起作用的因此我们需要寻找其他的方式。
有一个命令**vconfig**可以基于物理网卡eth0创建带VLAN的虚拟网卡所有从这个虚拟网卡出去的包都带这个VLAN如果这样跨物理机的互通和隔离就可以通过这个网卡来实现。
<img src="https://static001.geekbang.org/resource/image/4b/12/4b6aa2b970ac285be94c3af082153912.jpg" alt="">
首先为每个用户分配不同的VLAN例如有一个用户VLAN 10一个用户VLAN 20。在一台物理机上基于物理网卡为每个用户用vconfig创建一个带VLAN的网卡。不同的用户使用不同的虚拟网桥带VLAN的虚拟网卡也连接到虚拟网桥上。
这样是否能保证两个用户的隔离性呢不同的用户由于网桥不通不能相互通信一旦出了网桥由于VLAN不同也不会将包转发到另一个网桥上。另外出了物理机也是带着VLAN ID的。只要物理交换机也是支持VLAN的到达另一台物理机的时候VLAN ID依然在它只会将包转发给相同VLAN的网卡和网桥所以跨物理机不同的VLAN也不会相互通信。
使用brctl创建出来的网桥功能是简单的基于VLAN的虚拟网卡也能实现简单的隔离。但是这都不是大规模云平台能够满足的一个是VLAN的隔离数目太少。前面我们学过VLAN ID只有4096个明显不够用。另外一点是这个配置不够灵活。谁和谁通谁和谁不通流量的隔离也没有实现还有大量改进的空间。
## 小结
好了,这一节就到这里了,我们来总结一下:
<li>
云计算的关键技术是虚拟化这里我们重点关注的是虚拟网卡通过打开TUN/TAP字符设备的方式将虚拟机内外连接起来
</li>
<li>
云中的网络重点关注四个方面共享、隔离、互通、灵活。其中共享和互通有两种常用的方式分别是桥接和NAT隔离可以通过VLAN的方式。
</li>
接下来,给你留两个思考题。
<li>
为了直观这一节的内容我们以桌面虚拟化系统举例。在数据中心里面有一款著名的开源软件OpenStack这一节讲的网络连通方式对应OpenStack中的哪些模型呢
</li>
<li>
这一节的最后,我们也提到了,本节提到的网络配置方式比较不灵活,你知道什么更加灵活的方式吗?
</li>
我们的专栏更新到第24讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,303 @@
<audio id="audio" title="第25讲 | 软件定义网络:共享基础设施的小区物业管理办法" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/b5/4c8f54c1d5ce1b83f305a3b603d6fcb5.mp3"></audio>
上一节我们说到使用原生的VLAN和Linux网桥的方式来进行云平台的管理但是这样在灵活性、隔离性方面都显得不足而且整个网络缺少统一的视图、统一的管理。
可以这样比喻,云计算就像大家一起住公寓,要共享小区里面的基础设施,其中网络就相当于小区里面的电梯、楼道、路、大门等,大家都走,往往会常出现问题,尤其在上班高峰期,出门的人太多,对小区的物业管理就带来了挑战。
物业可以派自己的物业管理人员,到每个单元的楼梯那里,将电梯的上下行速度调快一点,可以派人将隔离健身区、景色区的栅栏门暂时打开,让大家可以横穿小区,直接上地铁,还可以派人将多个小区出入口,改成出口多、入口少等等。等过了十点半,上班高峰过去,再派人都改回来。
## 软件定义网络SDN
这种模式就像传统的网络设备和普通的Linux网桥的模式配置整个云平台的网络通路你需要登录到这台机器上配置这个再登录到另外一个设备配置那个才能成功。
如果物业管理人员有一套智能的控制系统,在物业监控室里就能看到小区里每个单元、每个电梯的人流情况,然后在监控室里面,只要通过远程控制的方式,拨弄一个手柄,电梯的速度就调整了,栅栏门就打开了,某个入口就改出口了。
这就是软件定义网络SDN。它主要有以下三个特点。
<img src="https://static001.geekbang.org/resource/image/34/f9/346fe3b3dbe1024e7119ec4ffa9377f9.jpg" alt="">
<li>
**控制与转发分离**:转发平面就是一个个虚拟或者物理的网络设备,就像小区里面的一条条路。控制平面就是统一的控制中心,就像小区物业的监控室。它们原来是一起的,物业管理员要从监控室出来,到路上去管理设备,现在是分离的,路就是走人的,控制都在监控室。
</li>
<li>
**控制平面与转发平面之间的开放接口**:控制器向上提供接口,被应用层调用,就像总控室提供按钮,让物业管理员使用。控制器向下调用接口,来控制网络设备,就像总控室会远程控制电梯的速度。这里经常使用两个名词,前面这个接口称为**北向接口**,后面这个接口称为**南向接口**,上北下南嘛。
</li>
<li>
**逻辑上的集中控制**:逻辑上集中的控制平面可以控制多个转发面设备,也就是控制整个物理网络,因而可以获得全局的网络状态视图,并根据该全局网络状态视图实现对网络的优化控制,就像物业管理员在监控室能够看到整个小区的情况,并根据情况优化出入方案。
</li>
## OpenFlow和OpenvSwitch
SDN有很多种实现方式我们来看一种开源的实现方式。
OpenFlow是SDN控制器和网络设备之间互通的南向接口协议OpenvSwitch用于创建软件的虚拟交换机。OpenvSwitch是支持OpenFlow协议的当然也有一些硬件交换机也支持OpenFlow协议。它们都可以被统一的SDN控制器管理从而实现物理机和虚拟机的网络连通。
<img src="https://static001.geekbang.org/resource/image/22/15/220b53c0f027763yy54c1a08yy2e5a15.jpg" alt="">
SDN控制器是如何通过OpenFlow协议控制网络的呢
<img src="https://static001.geekbang.org/resource/image/1c/78/1c739b9ecc23e9b7c246136782fcd078.jpg" alt="">
在OpenvSwitch里面有一个流表规则任何通过这个交换机的包都会经过这些规则进行处理从而接收、转发、放弃。
那流表长啥样呢?其实就是一个个表格,每个表格好多行,每行都是一条规则。每条规则都有优先级,先看高优先级的规则,再看低优先级的规则。
<img src="https://static001.geekbang.org/resource/image/31/2f/314c82553a164444437b25b215fa012f.jpg" alt="">
对于每一条规则,要看是否满足匹配条件。这些条件包括,从哪个端口进来的,网络包头里面有什么等等。满足了条件的网络包,就要执行一个动作,对这个网络包进行处理。可以修改包头里的内容,可以跳到任何一个表格,可以转发到某个网口出去,也可以丢弃。
通过这些表格,可以对收到的网络包随意处理。
<img src="https://static001.geekbang.org/resource/image/8e/1a/8e8751005253a47a823f08d6ac6cfc1a.jpg" alt="">
具体都能做什么处理呢通过上面的表格可以看出简直是想怎么处理怎么处理可以覆盖TCP/IP协议栈的四层。
对于物理层:
<li>
匹配规则包括从哪个口进来;
</li>
<li>
执行动作包括从哪个口出去。
</li>
对于MAC层
<li>
匹配规则包括源MAC地址是多少dl_src目标MAC是多少dl_dst所属vlan是多少dl_vlan
</li>
<li>
执行动作包括修改源MACmod_dl_src修改目标MACmod_dl_dst修改VLANmod_vlan_vid删除VLANstrip_vlanMAC地址学习learn
</li>
对于网络层:
<li>
匹配规则包括源IP地址是多少(nw_src)目标IP是多少nw_dst
</li>
<li>
执行动作包括修改源IP地址mod_nw_src修改目标IP地址mod_nw_dst
</li>
对于传输层:
<li>
匹配规则包括源端口是多少tp_src目标端口是多少tp_dst
</li>
<li>
执行动作包括修改源端口mod_tp_src修改目标端口mod_tp_dst
</li>
总而言之对于OpenvSwitch来讲网络包到了我手里就是一个Buffer我想怎么改怎么改想发到哪个端口就发送到哪个端口。
OpenvSwitch有本地的命令行可以进行配置能够实验咱们前面讲过的一些功能。我们可以通过OpenvSwitch的命令创建一个虚拟交换机。然后可以将多个虚拟端口port添加到这个虚拟交换机上。
```
ovs-vsctl add-br ubuntu_br
```
## 实验一用OpenvSwitch实现VLAN的功能
下面我们实验一下通过OpenvSwitch实现VLAN的功能在OpenvSwitch中端口port分两种。
第一类是access port
<li>
这个端口配置tag从这个端口进来的包会被打上这个tag
</li>
<li>
如果网络包本身带有的VLAN ID等于tag则会从这个port发出
</li>
<li>
从access port发出的包不带VLAN ID。
</li>
第二类是trunk port
<li>
这个port不配置tag配置trunks
</li>
<li>
如果trunks为空则所有的VLAN都trunk也就意味着对于所有VLAN的包本身带什么VLAN ID就是携带着什么VLAN ID如果没有设置VLAN就属于VLAN 0全部允许通过
</li>
<li>
如果trunks不为空则仅仅带着这些VLAN ID的包通过。
</li>
我们通过以下命令创建如下的环境:
```
ovs-vsctl add-port ubuntu_br first_br
ovs-vsctl add-port ubuntu_br second_br
ovs-vsctl add-port ubuntu_br third_br
ovs-vsctl set Port vnet0 tag=101
ovs-vsctl set Port vnet1 tag=102
ovs-vsctl set Port vnet2 tag=103
ovs-vsctl set Port first_br tag=103
ovs-vsctl clear Port second_br tag
ovs-vsctl set Port third_br trunks=101,102
```
另外要配置禁止MAC地址学习。
```
ovs-vsctl set bridge ubuntu_br flood-vlans=101,102,103
```
<img src="https://static001.geekbang.org/resource/image/8f/08/8ffb2a94ae0393f1785f8fefd5ea5908.jpg" alt="">
创建好了环境以后,我们来做这个实验。
<li>
从192.168.100.102来ping 192.168.100.103然后用tcpdump进行抓包。first_if收到包了从first_br出来的包头是没有VLAN ID的。second_if也收到包了由于second_br是trunk port因而出来的包头是有VLAN ID的third_if收不到包。
</li>
<li>
从192.168.100.100来ping 192.168.100.105, 则second_if和third_if可以收到包当然ping不通因为third_if不属于某个VLAN。first_if是收不到包的。second_if能够收到包而且包头里面是VLAN ID=101。third_if也能收到包而且包头里面是VLAN ID=101。
</li>
<li>
从192.168.100.101来ping 192.168.100.104 则second_if和third_if可以收到包。first_if是收不到包的。second_br能够收到包而且包头里面是VLAN ID=102。third_if也能收到包而且包头里面是VLAN ID=102。
</li>
通过这个例子我们可以看到通过OpenvSwitch不用买一个支持VLAN的交换机你也能学习VLAN的工作模式了。
## 实验二用OpenvSwitch模拟网卡绑定连接交换机
接下来我们来做另一个实验。在前面我们还说过为了高可用可以使用网卡绑定连接到交换机OpenvSwitch也可以模拟这一点。
在OpenvSwitch里面有个bond_mode可以设置为以下三个值
<li>
active-backup一个连接是active其他的是backup当active失效的时候backup顶上
</li>
<li>
balance-slb流量安装源MAC和output VLAN进行负载均衡
</li>
<li>
balance-tcp必须在支持LACP协议的情况下才可以可根据L2, L3, L4进行负载均衡。
</li>
我们搭建一个测试环境。
<img src="https://static001.geekbang.org/resource/image/d2/ce/d2381bd12ab8620c64249f05fd7d19ce.jpg" alt="">
我们使用下面的命令建立bond连接。
```
ovs-vsctl add-bond br0 bond0 first_br second_br
ovs-vsctl add-bond br1 bond1 first_if second_if
ovs-vsctl set Port bond0 lacp=active
ovs-vsctl set Port bond1 lacp=active
```
默认情况下bond_mode是active-backup模式一开始active的是first_br和first_if。
这个时候我们从192.168.100.100 ping 192.168.100.102以及从192.168.100.101 ping 192.168.100.103的时候tcpdump可以看到所有的包都是从first_if通过。
如果把first_if设成down则包的走向会变发现second_if开始有流量对于192.168.100.100和192.168.100.101似乎没有收到影响。
如果我们通过以下命令把bond_mode设为balance-slb。然后我们同时在192.168.100.100 ping 192.168.100.102在192.168.100.101 ping 192.168.100.103我们通过tcpdump发现包已经被分流了。
```
ovs-vsctl set Port bond0 bond_mode=balance-slb
ovs-vsctl set Port bond1 bond_mode=balance-slb
```
通过这个例子我们可以看到通过OpenvSwitch你不用买两台支持bond的交换机也能看到bond的效果。
那OpenvSwitch是怎么做到这些的呢我们来看OpenvSwitch的架构图。
OpenvSwitch包含很多的模块在用户态有两个重要的进程也有两个重要的命令行工具。
<li>
第一个进程是OVSDB进程。ovs-vsctl命令行会和这个进程通信去创建虚拟交换机创建端口将端口添加到虚拟交换机上OVSDB会将这些拓扑信息保存在一个本地的文件中。
</li>
<li>
第一个进程是vswitchd进程。ovs-ofctl命令行会和这个进程通信去下发流表规则规则里面会规定如何对网络包进行处理vswitchd会将流表放在用户态Flow Table中。
</li>
在内核态OpenvSwitch有内核模块OpenvSwitch.ko对应图中的Datapath部分。在网卡上注册一个函数每当有网络包到达网卡的时候这个函数就会被调用。
在内核的这个函数里面,会拿到网络包,将各个层次的重要信息拿出来,例如:
<li>
在物理层in_port即包进入的网口的ID
</li>
<li>
在MAC层源和目的MAC地址
</li>
<li>
在IP层源和目的IP地址
</li>
<li>
在传输层,源和目的端口号。
</li>
在内核中有一个内核态Flow Table。接下来内核模块在这个内核流表中匹配规则如果匹配上了则执行操作、修改包或者转发或者放弃。如果内核没有匹配上则需要进入用户态用户态和内核态之间通过Linux的一个机制Netlink相互通信。
内核通过upcall告知用户态进程vswitchd在用户态Flow Table里面去匹配规则这里面的规则是全量的流表规则而内核Flow Table里面的只是为了快速处理保留了部分规则内核里面的规则过一阵就会过期。
当在用户态匹配到了流表规则之后就在用户态执行操作同时将这个匹配成功的流表通过reinject下发到内核从而接下来的包都能在内核找到这个规则。
这里调用openflow协议的是本地的命令行工具也可以是远程的SDN控制器一个重要的SDN控制器是OpenDaylight。
下面这个图就是OpenDaylight中看到的拓扑图。是不是有种物业管理员在监控室里的感觉
<img src="https://static001.geekbang.org/resource/image/27/a8/274442ba251fdc63c88bc5dbfc6183a8.jpg" alt="">
我们可以通过在OpenDaylight里将两个交换机之间配置通也可以配置不通还可以配置一个虚拟IP地址VIP在不同的机器之间实现负载均衡等等所有的策略都可以灵活配置。
## 如何在云计算中使用OpenvSwitch
OpenvSwitch这么牛如何用在云计算中呢
<img src="https://static001.geekbang.org/resource/image/10/e5/102a3c46c36047498139d8bcbd7551e5.jpg" alt="">
我们还是讨论VLAN的场景。
在没有OpenvSwitch的时候如果一个新的用户要使用一个新的VLAN还需要创建一个属于新的VLAN的虚拟网卡并且为这个租户创建一个单独的虚拟网桥这样用户越来越多的时候虚拟网卡和虚拟网桥会越来越多管理非常复杂。
另一个问题是虚拟机的VLAN和物理环境的VLAN是透传的也即从一开始规划的时候就需要匹配起来将物理环境和虚拟环境强绑定本来就不灵活。
而引入了OpenvSwitch状态就得到了改观。
首先由于OpenvSwitch本身就是支持VLAN的所有的虚拟机都可以放在一个网桥br0上通过不同的用户配置不同的tag就能够实现隔离。例如上面的图用户A的虚拟机都在br0上用户B的虚拟机都在br1上有了OpenvSwitch就可以都放在br0上只是设置了不同的tag。
另外还可以创建一个虚拟交换机br1将物理网络和虚拟网络进行隔离。物理网络有物理网络的VLAN规划虚拟机在一台物理机上所有的VLAN都是从1开始的。由于一台机器上的虚拟机不会超过4096个所以VLAN在一台物理机上如果从1开始肯定够用了。
例如在图中上面的物理机里面用户A被分配的tag是1用户B被分配的tag是2而在下面的物理机里面用户A被分配的tag是7用户B被分配的tag是6。
如果物理机之间的通信和隔离还是通过VLAN的话需要将虚拟机的VLAN和物理环境的VLAN对应起来但为了灵活性不一定一致这样可以实现分别管理物理机的网络和虚拟机的网络。好在OpenvSwitch可以对包的内容进行修改。例如通过匹配dl_vlan然后执行mod_vlan_vid来改进进出出物理机的网络包。
尽管租户多了物理环境的VLAN还是不够用但是有了OpenvSwitch的映射将物理和虚拟解耦从而可以让物理环境使用其他技术而不影响虚拟机环境这个我们后面再讲。
## 小结
好了,这一节就到这里了,我们来总结一下:
<li>
用SDN控制整个云里面的网络就像小区保安从总控室管理整个物业是一样的将控制面和数据面进行了分离
</li>
<li>
一种开源的虚拟交换机的实现OpenvSwitch它能对经过自己的包做任意修改从而使得云对网络的控制十分灵活
</li>
<li>
将OpenvSwitch引入了云之后可以使得配置简单而灵活并且可以解耦物理网络和虚拟网络。
</li>
最后,给你留两个思考题:
<li>
在这一节中提到了通过VIP可以通过流表在不同的机器之间实现复杂均衡你知道怎样才能做到吗
</li>
<li>
虽然OpenvSwitch可以解耦物理网络和虚拟网络但是在物理网络里面使用VLAN数目还是不够你知道该怎么办吗
</li>
我们的专栏更新到第25讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,220 @@
<audio id="audio" title="第26讲 | 云中的网络安全:虽然不是土豪,也需要基本安全和保障" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/54/7e/543080b04cde38e3a36987e3bb026f7e.mp3"></audio>
在今天的内容开始之前,我先卖个关子。文章结尾,我会放一个超级彩蛋,所以,今天的内容你一定要看到最后哦!
上一节我们看到,做一个小区物业维护一个大家共享的环境,还是挺不容易的。如果都是自觉遵守规则的住户那还好,如果遇上不自觉的住户就会很麻烦。
就像公有云的环境,其实没有你想的那么纯净,各怀鬼胎的黑客到处都是。扫描你的端口呀,探测一下你启动的什么应用啊,看一看是否有各种漏洞啊。这就像小偷潜入小区后,这儿看看,那儿瞧瞧,窗户有没有关严了啊,窗帘有没有拉上啊,主人睡了没,是不是时机潜入室内啊,等等。
假如你创建了一台虚拟机,里面明明跑了一个电商应用,这是你非常重要的一个应用,你会把它进行安全加固。这台虚拟机的操作系统里,不小心安装了另外一个后台应用,监听着一个端口,而你的警觉性没有这么高。
虚拟机的这个端口是对着公网开放的,碰巧这个后台应用本身是有漏洞的,黑客就可以扫描到这个端口,然后通过这个后台应用的端口侵入你的机器,将你加固好的电商网站黑掉。这就像你买了一个五星级的防盗门,卡车都撞不开,但是厕所窗户的门把手是坏的,小偷从厕所里面就进来了。
所以**对于公有云上的虚拟机,我的建议是仅仅开放需要的端口,而将其他的端口一概关闭。这个时候,你只要通过安全措施守护好这个唯一的入口就可以了**。采用的方式常常是用**ACL**Access Control List访问控制列表来控制IP和端口。
设置好了这些规则只有指定的IP段能够访问指定的开放接口就算有个有漏洞的后台进程在那里也会被屏蔽黑客进不来。在云平台上这些规则的集合常称为**安全组**。那安全组怎么实现呢?
我们来复习一下,当一个网络包进入一台机器的时候,都会做什么事情。
首先拿下MAC头看看是不是我的。如果是则拿下IP头来。得到目标IP之后呢就开始进行路由判断。在路由判断之前这个节点我们称为**PREROUTING**。如果发现IP是我的包就应该是我的就发给上面的传输层这个节点叫作**INPUT**。如果发现IP不是我的就需要转发出去这个节点称为**FORWARD**。如果是我的,上层处理完毕后,一般会返回一个处理结果,这个处理结果会发出去,这个节点称为**OUTPUT**无论是FORWARD还是OUTPUT都是路由判断之后发生的最后一个节点是**POSTROUTING**。
整个过程如图所示。
<img src="https://static001.geekbang.org/resource/image/a7/49/a7b549514cf6d750f7dfb1e488ebdd49.jpg" alt="">
整个包的处理过程还是原来的过程,只不过为什么要格外关注这**五个节点**呢?
是因为在Linux内核中有一个框架叫Netfilter。它可以在这些节点插入hook函数。这些函数可以截获数据包对数据包进行干预。例如做一定的修改然后决策是否接着交给TCP/IP协议栈处理或者可以交回给协议栈那就是**ACCEPT**;或者过滤掉,不再传输,就是**DROP**;还有就是**QUEUE**,发送给某个用户态进程处理。
这个比较难理解经常用在内部负载均衡就是过来的数据一会儿传给目标地址1一会儿传给目标地址2而且目标地址的个数和权重都可能变。协议栈往往处理不了这么复杂的逻辑需要写一个函数接管这个数据实现自己的逻辑。
有了这个Netfilter框架就太好了你可以在IP转发的过程中随时干预这个过程只要你能实现这些hook函数。
一个著名的实现,就是**内核模块ip_tables**。它在这五个节点上埋下函数从而可以根据规则进行包的处理。按功能可分为四大类连接跟踪conntrack、数据包的过滤filter、网络地址转换nat和数据包的修改mangle。其中连接跟踪是基础功能被其他功能所依赖。其他三个可以实现包的过滤、修改和网络地址转换。
在用户态还有一个你肯定知道的客户端程序iptables用命令行来干预内核的规则。内核的功能对应iptables的命令行来讲就是**表和链**的概念。
<img src="https://static001.geekbang.org/resource/image/74/95/746131a029919768c4f00a0fe7fayy95.jpg" alt="">
iptables的表分为四种raw--&gt;mangle--&gt;nat--&gt;filter。这四个优先级依次降低raw不常用所以主要功能都在其他三种表里实现。每个表可以设置多个链。
filter表处理过滤功能主要包含三个链
<li>
INPUT链过滤所有目标地址是本机的数据包
</li>
<li>
FORWARD链过滤所有路过本机的数据包
</li>
<li>
OUTPUT链过滤所有由本机产生的数据包。
</li>
nat表主要是处理网络地址转换可以进行Snat改变数据包的源地址、Dnat改变数据包的目标地址包含三个链
<li>
PREROUTING链可以在数据包到达防火墙时改变目标地址
</li>
<li>
OUTPUT链可以改变本地产生的数据包的目标地址
</li>
<li>
POSTROUTING链在数据包离开防火墙时改变数据包的源地址。
</li>
mangle表主要是修改数据包包含
<li>
PREROUTING链
</li>
<li>
INPUT链
</li>
<li>
FORWARD链
</li>
<li>
OUTPUT链
</li>
<li>
POSTROUTING链。
</li>
将iptables的表和链加入到上面的过程图中就形成了下面的图和过程。
<img src="https://static001.geekbang.org/resource/image/e0/ec/e06c112fce8e3f3bc8a89c52f86fdfec.jpg" alt="">
<li>
数据包进入的时候先进mangle表的PREROUTING链。在这里可以根据需要改变数据包头内容之后进入nat表的PREROUTING链在这里可以根据需要做Dnat也就是目标地址转换。
</li>
<li>
进入路由判断,要判断是进入本地的还是转发的。
</li>
<li>
如果是进入本地的就进入INPUT链之后按条件过滤限制进入。
</li>
<li>
之后进入本机再进入OUTPUT链按条件过滤限制出去离开本地。
</li>
<li>
如果是转发就进入FORWARD链根据条件过滤限制转发。
</li>
<li>
之后进入POSTROUTING链这里可以做Snat离开网络接口。
</li>
有了iptables命令我们就可以在云中实现一定的安全策略。例如我们可以处理前面的偷窥事件。首先我们将所有的门都关闭。
```
iptables -t filter -A INPUT -s 0.0.0.0/0.0.0.0 -d X.X.X.X -j DROP
```
-s表示源IP地址段-d表示目标地址段DROP表示丢弃也即无论从哪里来的要想访问我这台机器全部拒绝谁也黑不进来。
但是你发现坏了ssh也进不来了都不能远程运维了可以打开一下。
```
iptables -I INPUT -s 0.0.0.0/0.0.0.0 -d X.X.X.X -p tcp --dport 22 -j ACCEPT
```
如果这台机器是提供的是web服务80端口也应该打开当然一旦打开这个80端口就需要很好的防护但是从规则角度还是要打开。
```
iptables -A INPUT -s 0.0.0.0/0.0.0.0 -d X.X.X.X -p tcp --dport 80 -j ACCEPT
```
这样就搞定了,其他的账户都封死,就一个防盗门可以进出,只要防盗门是五星级的,就比较安全了。
这些规则都可以在虚拟机里自己安装iptables自己配置。但是如果虚拟机数目非常多都要配置对于用户来讲就太麻烦了能不能让云平台把这部分工作做掉呢
当然可以了。在云平台上,一般允许一个或者多个虚拟机属于某个安全组,而属于不同安全组的虚拟机之间的访问以及外网访问虚拟机,都需要通过安全组进行过滤。
<img src="https://static001.geekbang.org/resource/image/06/21/069e51d949312246yy9d2764e6d6c921.jpg" alt="">
例如图中我们会创建一系列的网站都是前端在Tomcat里面对外开放8080端口。数据库使用MySQL开放3306端口。
为了方便运维我们创建两个安全组将Tomcat所在的虚拟机放在安全组A里面。在安全组A里面允许任意IP地址0.0.0.0/0访问8080端口但是对于ssh的22端口仅仅允许管理员网段203.0.113.0/24访问。
我们将MySQL所在的虚拟机放在安全组B里面。在安全组B里面仅仅允许来自安全组A的机器访问3306端口但是对于ssh的22端口同样允许管理员网段203.0.113.0/24访问。
这些安全组规则都可以自动下发到每个在安全组里面的虚拟机上,从而控制一大批虚拟机的安全策略。这种批量下发是怎么做到的呢?你还记得这幅图吗?
<img src="https://static001.geekbang.org/resource/image/24/24/246db57c915d9ccf6e0d66182de0fe24.jpg" alt="">
两个VM都通过tap网卡连接到一个网桥上但是网桥是二层的两个VM之间是可以随意互通的因而需要有一个地方统一配置这些iptables规则。
可以多加一个网桥在这个网桥上配置iptables规则将在用户在界面上配置的规则放到这个网桥上。然后在每台机器上跑一个Agent将用户配置的安全组变成iptables规则配置在这个网桥上。
安全问题解决了iptables真强大别忙iptables除了filter还有nat呢这个功能也非常重要。
前面的章节我们说过在设计云平台的时候我们想让虚拟机之间的网络和物理网络进行隔离但是虚拟机毕竟还是要通过物理网和外界通信的因而需要在出物理网的时候做一次网络地址转换也即nat这个就可以用iptables来做。
我们学过IP头里面包含源IP地址和目标IP地址这两种IP地址都可以转换成其他地址。转换源IP地址的我们称为Snat转换目标IP地址的我们称为Dnat。
你有没有思考过这个问题TCP的访问都是一去一回的而你在你家里连接WiFi的IP地址是一个私网IP192.168.1.x。当你通过你们家的路由器访问163网站之后网站的返回结果如何能够到达你的笔记本电脑呢肯定不能通过192.168.1.x这是个私网IP不具有公网上的定位能力而且用这个网段的人很多茫茫人海怎么能够找到你呢
所以当你从你家里访问163网站的时候在你路由器的出口会做Snat的运营商的出口也可能做Snat将你的私网IP地址最终转换为公网IP地址然后163网站就可以通过这个公网IP地址返回结果然后再nat回来直到到达你的笔记本电脑。
云平台里面的虚拟机也是这样子的它只有私网IP地址到达外网网口要做一次Snat转换成为机房网IP然后出数据中心的时候再转换为公网IP。
<img src="https://static001.geekbang.org/resource/image/1a/1a/1a5d299c2eb5480eda93a8f8e3b3ca1a.jpg" alt="">
这里有一个问题是在外网网口上做Snat的时候是全部转换成一个机房网IP呢还是每个虚拟机都对应一个机房网IP最终对应一个公网IP呢前面也说过了公网IP非常贵虚拟机也很多当然不能每个都有单独的机房网和公网IP了因此这种Snat是一种特殊的SnatMASQUERADE地址伪装
这种方式下所有的虚拟机共享一个机房网和公网的IP地址所有从外网网口出去的都转换成为这个IP地址。那又一个问题来了都变成一个公网IP了当163网站返回结果的时候给谁呢再nat成为哪个私网的IP呢
这就是Netfilter的连接跟踪conntrack功能了。对于TCP协议来讲肯定是上来先建立一个连接可以用“源/目的IP+源/目的端口”唯一标识一条连接这个连接会放在conntrack表里面。当时是这台机器去请求163网站的虽然源地址已经Snat成公网IP地址了但是conntrack表里面还是有这个连接的记录的。当163网站返回数据的时候会找到记录从而找到正确的私网IP地址。
这是虚拟机做客户端的情况如果虚拟机做服务器呢也就是说如果虚拟机里面部署的就是163网站呢
这个时候就需要给这个网站配置固定的物理网的IP地址和公网IP地址了。这时候就需要详细配置Snat规则和Dnat规则了。
当外部访问进来的时候外网网口会通过Dnat规则将公网IP地址转换为私网IP地址到达虚拟机虚拟机里面是163网站返回结果外网网口会通过Snat规则将私网IP地址转换为那个分配给它的固定的公网IP地址。
类似的规则如下:
<li>
源地址转换(Snat)iptables -t nat -A -s 私网IP -j Snat --to-source 外网IP
</li>
<li>
目的地址转换(Dnat)iptables -t nat -A -PREROUTING -d 外网IP -j Dnat --to-destination 私网IP
</li>
到此为止iptables解决了非法偷窥隐私的问题。
## 小结
好了,这一节就讲到这里了,我们来总结一下。
<li>
云中的安全策略的常用方式是使用iptables的规则请记住它的五个阶段PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。
</li>
<li>
iptables分为四种表raw、mangle、nat、filter。其中安全策略主要在filter表中实现而虚拟网络和物理网络地址的转换主要在nat表中实现。
</li>
最后,给你留两个思考题。
<li>
这一节中重点讲了iptables的filter和nat功能iptables还可以通过QUEUE实现负载均衡你知道怎么做吗
</li>
<li>
这一节仅仅讲述了云中偷窥的问题,如果是一个合法的用户,但是不自觉抢占网络通道,应该采取什么策略呢?
</li>
我们的专栏更新到第26讲不知你掌握得如何是不是有很多问题想要跟我面对面探讨呢这里就有一个机会。
今天晚上8:30我会在极客时间APP里做一个直播主题是“技术人如何在技术浪潮中线性成长我会把我们讲过的网络协议作为案例在直播中展开讲解也会分享我从业多年来的心得体会。**你可以直接在这里留言提问,也可以准备好问题在直播的时候和我交流。**
欢迎你来看直播!我们晚上见!
<img src="https://static001.geekbang.org/resource/image/00/ab/001997ebd09dd7e9f649f183207cdfab.jpg" alt="">

View File

@@ -0,0 +1,169 @@
<audio id="audio" title="第27讲 | 云中的网络QoS邻居疯狂下电影我该怎么办" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/00/26/006be4d2d2e2e70b6063bdd40c1dbc26.mp3"></audio>
在小区里面,是不是经常有住户不自觉就霸占公共通道,如果你找他理论,他的话就像一个相声《楼道曲》说的一样:“公用公用,你用我用,大家都用,我为什么不能用?”。
除此之外你租房子的时候有没有碰到这样的情况本来合租共享WiFi一个人狂下小电影从而你网都上不去是不是很懊恼
在云平台上,也有这种现象,好在有一种流量控制的技术,可以实现**QoS**Quality of Service从而保障大多数用户的服务质量。
对于控制一台机器的网络的QoS分两个方向一个是入方向一个是出方向。
<img src="https://static001.geekbang.org/resource/image/74/11/747b0d537fd1705171ffcca3faf96211.jpg" alt="">
其实我们能控制的只有出方向通过Shaping将出的流量控制成自己想要的模样。而进入的方向是无法控制的只能通过Policy将包丢弃。
## 控制网络的QoS有哪些方式
在Linux下可以通过TC控制网络的QoS主要就是通过队列的方式。
### 无类别排队规则
第一大类称为**无类别排队规则**Classless Queuing Disciplines。还记得我们讲[ip addr](https://time.geekbang.org/column/article/7772)的时候讲过的**pfifo_fast**,这是一种不把网络包分类的技术。
<img src="https://static001.geekbang.org/resource/image/e3/6c/e391b4b79580a7d66afe4307ff3f6f6c.jpg" alt="">
pfifo_fast分为三个先入先出的队列称为三个Band。根据网络包里面TOS看这个包到底应该进入哪个队列。TOS总共四位每一位表示的意思不同总共十六种类型。
通过命令行tc qdisc show dev eth0可以输出结果priomap也是十六个数字。在0到2之间和TOS的十六种类型对应起来表示不同的TOS对应的不同的队列。其中Band 0优先级最高发送完毕后才轮到Band 1发送最后才是Band 2。
另外一种无类别队列规则叫作**随机公平队列**Stochastic Fair Queuing
<img src="https://static001.geekbang.org/resource/image/b6/99/b6ec2e4e20ddee7d6952b7fa4586ba99.jpg" alt="">
会建立很多的FIFO的队列TCP Session会计算hash值通过hash值分配到某个队列。在队列的另一端网络包会通过轮询策略从各个队列中取出发送。这样不会有一个Session占据所有的流量。
当然如果两个Session的hash是一样的会共享一个队列也有可能互相影响。hash函数会经常改变从而session不会总是相互影响。
还有一种无类别队列规则称为**令牌桶规则**TBFToken Bucket Filte
<img src="https://static001.geekbang.org/resource/image/14/9b/145c6f8593bf7603eae79246b9d6859b.jpg" alt="">
所有的网络包排成队列进行发送,但不是到了队头就能发送,而是需要拿到令牌才能发送。
令牌根据设定的速度生成,所以即便队列很长,也是按照一定的速度进行发送的。
当没有包在队列中的时候,令牌还是以既定的速度生成,但是不是无限累积的,而是放满了桶为止。设置桶的大小为了避免下面的情况:当长时间没有网络包发送的时候,积累了大量的令牌,突然来了大量的网络包,每个都能得到令牌,造成瞬间流量大增。
### 基于类别的队列规则
另外一大类是**基于类别的队列规则**Classful Queuing Disciplines其中典型的为**分层令牌桶规则****HTB** Hierarchical Token Bucket
HTB往往是一棵树接下来我举个具体的例子通过TC如何构建一棵HTB树来带你理解。
<img src="https://static001.geekbang.org/resource/image/e6/f5/e6de57bf00f2fe8865ec3548bf8c67f5.jpg" alt="">
使用TC可以为某个网卡eth0创建一个HTB的队列规则需要付给它一个句柄为1:)。
这是整棵树的根节点,接下来会有分支。例如图中有三个分支,句柄分别为(:10:11:12。最后的参数default 12表示默认发送给1:12也即发送给第三个分支。
```
tc qdisc add dev eth0 root handle 1: htb default 12
```
对于这个网卡,需要规定发送的速度。一般有两个速度可以配置,一个是**rate**,表示一般情况下的速度;一个是**ceil**表示最高情况下的速度。对于根节点来讲这两个速度是一样的于是创建一个root class速度为rate=100kbpsceil=100kbps
```
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
```
接下来要创建分支也即创建几个子class。每个子class统一有两个速度。三个分支分别为rate=30kbpsceil=100kbpsrate=10kbpsceil=100kbpsrate=60kbpsceil=100kbps
```
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps
```
你会发现三个rate加起来是整个网卡允许的最大速度。
HTB有个很好的特性同一个root class下的子类可以相互借流量如果不直接在队列规则下面创建一个root class而是直接创建三个class它们之间是不能相互借流量的。借流量的策略可以使得当前不使用这个分支的流量的时候可以借给另一个分支从而不浪费带宽使带宽发挥最大的作用。
最后,创建叶子队列规则,分别为**fifo**和**sfq**。
```
tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 5
tc qdisc add dev eth0 parent 1:11 handle 30: pfifo limit 5
tc qdisc add dev eth0 parent 1:12 handle 40: sfq perturb 10
```
基于这个队列规则我们还可以通过TC设定发送规则从1.2.3.4来的发送给port 80的包从第一个分支1:10走其他从1.2.3.4发送来的包从第二个分支1:11走其他的走默认分支。
```
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flowid 1:11
```
## 如何控制QoS
我们讲过使用OpenvSwitch将云中的网卡连通在一起那如何控制QoS呢
就像我们上面说的一样OpenvSwitch支持两种
- 对于进入的流量可以设置策略Ingress policy
```
ovs-vsctl set Interface tap0 ingress_policing_rate=100000
ovs-vsctl set Interface tap0 ingress_policing_burst=10000
```
- 对于发出的流量可以设置QoS规则Egress shaping支持HTB。
我们构建一个拓扑图来看看OpenvSwitch的QoS是如何工作的。
<img src="https://static001.geekbang.org/resource/image/3b/ee/3b0de72bc937e108519a473067f607ee.jpg" alt="">
首先在port上可以创建QoS规则一个QoS规则可以有多个队列Queue。
<img src="https://static001.geekbang.org/resource/image/e6/84/e65435bde65a255a085f10d02d2ff184.jpg" alt="">
```
ovs-vsctl set port first_br qos=@newqos -- --id=@newqos create qos type=linux-htb other-config:max-rate=10000000 queues=0=@q0,1=@q1,2=@q2 -- --id=@q0 create queue other-config:min-rate=3000000 other-config:max-rate=10000000 -- --id=@q1 create queue other-config:min-rate=1000000 other-config:max-rate=10000000 -- --id=@q2 create queue other-config:min-rate=6000000 other-config:max-rate=10000000
```
上面的命令创建了一个QoS规则对应三个Queue。min-rate就是上面的ratemax-rate就是上面的ceil。通过交换机的网络包要通过流表规则匹配后进入不同的队列。然后我们就可以添加流表规则Flow(first_br是br0上的port 5)。
```
ovs-ofctl add-flow br0 &quot;in_port=6 nw_src=192.168.100.100 actions=enqueue:5:0&quot;
ovs-ofctl add-flow br0 &quot;in_port=7 nw_src=192.168.100.101 actions=enqueue:5:1&quot;
ovs-ofctl add-flow br0 &quot;in_port=8 nw_src=192.168.100.102 actions=enqueue:5:2&quot;
```
接下来我们单独测试从192.168.100.100192.168.100.101192.168.100.102到192.168.100.103的带宽的时候,每个都是能够打满带宽的。
如果三个一起测试一起狂发网络包会发现是按照3:1:6的比例进行的正是根据配置的队列的带宽比例分配的。
如果192.168.100.100和192.168.100.101一起测试发现带宽占用比例为3:1但是占满了总的流量也即没有发包的192.168.100.102有60%的带宽被借用了。
如果192.168.100.100和192.168.100.102一起测试发现带宽占用比例为1:2。如果192.168.100.101和192.168.100.102一起测试发现带宽占用比例为1:6。
## 小结
好了,这一节就讲到这里了,我们来总结一下。
<li>
云中的流量控制主要通过队列进行的,队列分为两大类:无类别队列规则和基于类别的队列规则。
</li>
<li>
在云中网络Openvswitch中主要使用的是分层令牌桶规则HTB将总的带宽在一棵树上按照配置的比例进行分配并且在一个分支不用的时候可以借给另外的分支从而增强带宽利用率。
</li>
最后,给你留两个思考题。
<li>
这一节中提到,入口流量其实没有办法控制,出口流量是可以很好控制的,你能想出一个控制云中的虚拟机的入口流量的方式吗?
</li>
<li>
安全性和流量控制大概解决了,但是不同用户在物理网络的隔离还是没有解决,你知道怎么解决吗?
</li>
我们的专栏更新到第27讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,277 @@
<audio id="audio" title="第28讲 | 云中网络的隔离GRE、VXLAN虽然住一个小区也要保护隐私" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/05/c1/05d27405cee757dcf8a8f13bab4f68c1.mp3"></audio>
对于云平台中的隔离问题前面咱们用的策略一直都是VLAN但是我们也说过这种策略的问题VLAN只有12位共4096个。当时设计的时候看起来是够了但是现在绝对不够用怎么办呢
**一种方式是修改这个协议**。这种方法往往不可行,因为当这个协议形成一定标准后,千千万万设备上跑的程序都要按这个规则来。现在说改就放,谁去挨个儿告诉这些程序呢?很显然,这是一项不可能的工程。
**另一种方式就是扩展**在原来包的格式的基础上扩展出一个头里面包含足够用于区分租户的ID外层的包的格式尽量和传统的一样依然兼容原来的格式。一旦遇到需要区分用户的地方我们就用这个特殊的程序来处理这个特殊的包的格式。
这个概念很像咱们[第22讲](https://time.geekbang.org/column/article/10386)讲过的**隧道理论**,还记得自驾游通过摆渡轮到海南岛的那个故事吗?在那一节,我们说过,扩展的包头主要是用于加密的,而我们现在需要的包头是要能够区分用户的。
底层的物理网络设备组成的网络我们称为**Underlay网络**,而用于虚拟机和云中的这些技术组成的网络称为**Overlay网络****这是一种基于物理网络的虚拟化网络实现**。这一节我们重点讲两个Overlay的网络技术。
## GRE
第一个技术是**GRE**全称Generic Routing Encapsulation它是一种IP-over-IP的隧道技术。它将IP包封装在GRE包里外面加上IP头在隧道的一端封装数据包并在通路上进行传输到另外一端的时候解封装。你可以认为Tunnel是一个虚拟的、点对点的连接。
<img src="https://static001.geekbang.org/resource/image/b1/31/b189df0a6ee4b0462818bf2f154c9531.jpg" alt="">
从这个图中可以看到在GRE头中前32位是一定会有的后面的都是可选的。在前4位标识位里面有标识后面到底有没有可选项这里面有个很重要的key字段是一个32位的字段里面存放的往往就是用于区分用户的Tunnel ID。32位够任何云平台喝一壶的了
下面的格式类型专门用于网络虚拟化的GRE包头格式称为**NVGRE**也给网络ID号24位也完全够用了。
除此之外GRE还需要有一个地方来封装和解封装GRE的包这个地方往往是路由器或者有路由功能的Linux机器。
使用GRE隧道传输的过程就像下面这张图。这里面有两个网段、两个路由器中间要通过GRE隧道进行通信。当隧道建立之后会多出两个Tunnel端口用于封包、解封包。
<img src="https://static001.geekbang.org/resource/image/76/ce/76298ce51d349bc9805fbf317312e4ce.jpg" alt="">
<li>
主机A在左边的网络IP地址为192.168.1.102它想要访问主机B主机B在右边的网络IP地址为192.168.2.115。于是发送一个包源地址为192.168.1.102目标地址为192.168.2.115。因为要跨网段访问于是根据默认的default路由表规则要发给默认的网关192.168.1.1,也即左边的路由器。
</li>
<li>
根据路由表从左边的路由器去192.168.2.0/24这个网段应该走一条GRE的隧道从隧道一端的网卡Tunnel0进入隧道。
</li>
<li>
在Tunnel隧道的端点进行包的封装在内部的IP头之外加上GRE头。对于NVGRE来讲是在MAC头之外加上GRE头然后加上外部的IP地址也即路由器的外网IP地址。源IP地址为172.17.10.10目标IP地址为172.16.11.10然后从E1的物理网卡发送到公共网络里。
</li>
<li>
在公共网络里面沿着路由器一跳一跳地走全部都按照外部的公网IP地址进行。
</li>
<li>
当网络包到达对端路由器的时候也要到达对端的Tunnel0然后开始解封装将外层的IP头取下来然后根据里面的网络包根据路由表从E3口转发出去到达服务器B。
</li>
从GRE的原理可以看出GRE通过隧道的方式很好地解决了VLAN ID不足的问题。但是GRE技术本身还是存在一些不足之处。
首先是**Tunnel的数量问题**。GRE是一种点对点隧道如果有三个网络就需要在每两个网络之间建立一个隧道。如果网络数目增多这样隧道的数目会呈指数性增长。
<img src="https://static001.geekbang.org/resource/image/00/fe/006cc8a4bf7a13fea0f456905c263afe.jpg" alt="">
其次,**GRE不支持组播**因此一个网络中的一个虚机发出一个广播帧后GRE会将其广播到所有与该节点有隧道连接的节点。
另外一个问题是目前还是**有很多防火墙和三层网络设备无法解析GRE**因此它们无法对GRE封装包做合适地过滤和负载均衡。
## VXLAN
第二种Overlay的技术称为VXLAN。和三层外面再套三层的GRE不同VXLAN则是从二层外面就套了一个VXLAN的头这里面包含的VXLAN ID为24位也够用了。在VXLAN头外面还封装了UDP、IP以及外层的MAC头。
<img src="https://static001.geekbang.org/resource/image/3b/0f/3b02d54c9093e3de6d1a847dd0eb060f.jpg" alt="">
VXLAN作为扩展性协议也需要一个地方对VXLAN的包进行封装和解封装实现这个功能的点称为**VTEP**VXLAN Tunnel Endpoint
VTEP相当于虚拟机网络的管家。每台物理机上都可以有一个VTEP。每个虚拟机启动的时候都需要向这个VTEP管家注册每个VTEP都知道自己上面注册了多少个虚拟机。当虚拟机要跨VTEP进行通信的时候需要通过VTEP代理进行由VTEP进行包的封装和解封装。
和GRE端到端的隧道不同VXLAN不是点对点的而是支持通过组播的来定位目标机器的而非一定是这一端发出另一端接收。
当一个VTEP启动的时候它们都需要通过IGMP协议。加入一个组播组就像加入一个邮件列表或者加入一个微信群一样所有发到这个邮件列表里面的邮件或者发送到微信群里面的消息大家都能收到。而当每个物理机上的虚拟机启动之后VTEP就知道有一个新的VM上线了它归我管。
<img src="https://static001.geekbang.org/resource/image/e4/77/e4541dfd1571aab11694343520970a77.jpg" alt="">
如图虚拟机1、2、3属于云中同一个用户的虚拟机因而需要分配相同的VXLAN ID=101。在云的界面上就可以知道它们的IP地址于是可以在虚拟机1上ping虚拟机2。
虚拟机1发现它不知道虚拟机2的MAC地址因而包没办法发出去于是要发送ARP广播。
<img src="https://static001.geekbang.org/resource/image/27/79/27b0f23c0e94c10bac63d2ec6401e079.jpg" alt="">
ARP请求到达VTEP1的时候VTEP1知道我这里有一台虚拟机要访问一台不归我管的虚拟机需要知道MAC地址可是我不知道啊这该咋办呢
VTEP1想我不是加入了一个微信群么可以在里面@all 一下问问虚拟机2归谁管。于是VTEP1将ARP请求封装在VXLAN里面组播出去。
当然在群里面VTEP2和VTEP3都收到了消息因而都会解开VXLAN包看里面是一个ARP。
VTEP3在本地广播了半天没人回都说虚拟机2不归自己管。
VTEP2在本地广播虚拟机2回了说虚拟机2归我管MAC地址是这个。通过这次通信VTEP2也学到了虚拟机1归VTEP1管以后要找虚拟机1去找VTEP1就可以了。
<img src="https://static001.geekbang.org/resource/image/37/6c/377070660f2474b90489b0f10f8f686c.jpg" alt="">
VTEP2将ARP的回复封装在VXLAN里面这次不用组播了直接发回给VTEP1。
VTEP1解开VXLAN的包发现是ARP的回复于是发给虚拟机1。通过这次通信VTEP1也学到了虚拟机2归VTEP2管以后找虚拟机2去找VTEP2就可以了。
虚拟机1的ARP得到了回复知道了虚拟机2的MAC地址于是就可以发送包了。
<img src="https://static001.geekbang.org/resource/image/5b/6e/5ba8f197a682de2539594ef5d7e7d56e.jpg" alt="">
虚拟机1发给虚拟机2的包到达VTEP1它当然记得刚才学的东西要找虚拟机2就去VTEP2于是将包封装在VXLAN里面外层加上VTEP1和VTEP2的IP地址发送出去。
网络包到达VTEP2之后VTEP2解开VXLAN封装将包转发给虚拟机2。
虚拟机2回复的包到达VTEP2的时候它当然也记得刚才学的东西要找虚拟机1就去VTEP1于是将包封装在VXLAN里面外层加上VTEP1和VTEP2的IP地址也发送出去。
网络包到达VTEP1之后VTEP1解开VXLAN封装将包转发给虚拟机1。
<img src="https://static001.geekbang.org/resource/image/b4/19/b4d39cfc833be380be67eeee8019d319.jpg" alt="">
有了GRE和VXLAN技术我们就可以解决云计算中VLAN的限制了。那如何将这个技术融入云平台呢
还记得将你宿舍里面的情况,所有东西都搬到一台物理机上那个故事吗?
<img src="https://static001.geekbang.org/resource/image/8f/4c/8fc4b26b9e42c37adb87db81e36cc64c.jpg" alt="">
虚拟机是你的电脑路由器和DHCP Server相当于家用路由器或者寝室长的电脑外网网口访问互联网所有的电脑都通过内网网口连接到一个交换机br0上虚拟机要想访问互联网需要通过br0连到路由器上然后通过路由器将请求NAT后转发到公网。
接下来的事情就惨了你们宿舍闹矛盾了你们要分成三个宿舍住对应上面的图你们寝室长也即路由器单独在一台物理机上其他的室友也即VM分别在两台物理机上。这下把一个完整的br0一刀三断每个宿舍都是单独的一段。
<img src="https://static001.geekbang.org/resource/image/18/cb/184a02e0ee2404b46409cbf3d34837cb.jpg" alt="">
可是只有你的寝室长有公网口可以上网于是你偷偷在三个宿舍中间打了一个隧道用网线通过隧道将三个宿舍的两个br0连接起来让其他室友的电脑和你寝室长的电脑看起来还是连到同一个br0上其实中间是通过你隧道中的网线做了转发。
为什么要多一个br1这个虚拟交换机呢主要通过br1这一层将虚拟机之间的互联和物理机机之间的互联分成两层来设计中间隧道可以有各种挖法GRE、VXLAN都可以。
使用了OpenvSwitch之后br0可以使用OpenvSwitch的Tunnel功能和Flow功能。
OpenvSwitch支持三类隧道GRE、VXLAN、IPsec_GRE。在使用OpenvSwitch的时候虚拟交换机就相当于GRE和VXLAN封装的端点。
我们模拟创建一个如下的网络拓扑结构,来看隧道应该如何工作。
<img src="https://static001.geekbang.org/resource/image/fc/49/fca6857aaca4f3549b02445ffce71f49.jpg" alt="">
三台物理机每台上都有两台虚拟机分别属于两个不同的用户因而VLAN tag都得打地不一样这样才不能相互通信。但是不同物理机上的相同用户是可以通过隧道相互通信的因而通过GRE隧道可以连接到一起。
接下来所有的Flow Table规则都设置在br1上每个br1都有三个网卡其中网卡1是对内的网卡2和3是对外的。
下面我们具体来看Flow Table的设计。
<img src="https://static001.geekbang.org/resource/image/88/69/8888426b42555043c3e05ec26fe47f69.png" alt="">
1.Table 0是所有流量的入口所有进入br1的流量分为两种流量一个是进入物理机的流量一个是从物理机发出的流量。
从port 1进来的都是发出去的流量全部由Table 1处理。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 in_port=1 actions=resubmit(,1)&quot;
```
从port 2、3进来的都是进入物理机的流量全部由Table 3处理。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 in_port=2 actions=resubmit(,3)&quot;
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 in_port=3 actions=resubmit(,3)&quot;
```
如果都没匹配上,就默认丢弃。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=0 actions=drop&quot;
```
2.Table 1用于处理所有出去的网络包分为两种情况一种是单播一种是多播。
对于单播由Table 20处理。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 table=1 dl_dst=00:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,20)&quot;
```
对于多播由Table 21处理。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 table=1 dl_dst=01:00:00:00:00:00/01:00:00:00:00:00 actions=resubmit(,21)&quot;
```
3.Table 2是紧接着Table1的如果既不是单播也不是多播就默认丢弃。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=0 table=2 actions=drop&quot;
```
4.Table 3用于处理所有进来的网络包需要将隧道Tunnel ID转换为VLAN ID。
如果匹配不上Tunnel ID就默认丢弃。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=0 table=3 actions=drop&quot;
```
如果匹配上了Tunnel ID就转换为相应的VLAN ID然后跳到Table 10。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 table=3 tun_id=0x1 actions=mod_vlan_vid:1,resubmit(,10)&quot;
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 table=3 tun_id=0x2 actions=mod_vlan_vid:2,resubmit(,10)&quot;
```
5.对于进来的包Table 10会进行MAC地址学习。这是一个二层交换机应该做的事情学习完了之后再从port 1发出去。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1 table=10 actions=learn(table=20,priority=1,hard_timeout=300,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0-&gt;NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]-&gt;NXM_NX_TUN_ID[],output:NXM_OF_IN_PORT[]),output:1&quot;
```
Table 10是用来学习MAC地址的学习的结果放在Table 20里面。Table20被称为MAC learning table。
NXM_OF_VLAN_TCI是VLAN tag。在MAC learning table中每一个entry都仅仅是针对某一个VLAN来说的不同VLAN的learning table是分开的。在学习结果的entry中会标出这个entry是针对哪个VLAN的。
NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]表示当前包里面的MAC Source Address会被放在学习结果的entry里的dl_dst里。这是因为每个交换机都是通过进入的网络包来学习的。某个MAC从某个port进来交换机就应该记住以后发往这个MAC的包都要从这个port出去因而源MAC地址就被放在了目标MAC地址里面因为这是为了发送才这么做的。
load:0-&gt;NXM_OF_VLAN_TCI[]是说在Table20中将包从物理机发送出去的时候VLAN tag设为0所以学习完了之后Table 20中会有actions=strip_vlan。
load:NXM_NX_TUN_ID[]-&gt;NXM_NX_TUN_ID[]的意思是在Table 20中将包从物理机发出去的时候设置Tunnel ID进来的时候是多少发送的时候就是多少所以学习完了之后Table 20中会有set_tunnel。
output:NXM_OF_IN_PORT[]是发送给哪个port。例如是从port 2进来的那学习完了之后Table 20中会有output:2。
<img src="https://static001.geekbang.org/resource/image/4d/dd/4dc0fe34819ee02a53a97c89811747dd.jpg" alt="">
所以如图所示通过左边的MAC地址学习规则学习到的结果就像右边的一样这个结果会被放在Table 20里面。
6.Table 20是MAC Address Learning Table。如果不为空就按照规则处理如果为空就说明没有进行过MAC地址学习只好进行广播了因而要交给Table 21处理。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=0 table=20 actions=resubmit(,21)&quot;
```
7.Table 21用于处理多播的包。
如果匹配不上VLAN ID就默认丢弃。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=0 table=21 actions=drop&quot;
```
如果匹配上了VLAN ID就将VLAN ID转换为Tunnel ID从两个网卡port 2和port 3都发出去进行多播。
```
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1table=21dl_vlan=1 actions=strip_vlan,set_tunnel:0x1,output:2,output:3&quot;
ovs-ofctl add-flow br1 &quot;hard_timeout=0 idle_timeout=0 priority=1table=21dl_vlan=2 actions=strip_vlan,set_tunnel:0x2,output:2,output:3&quot;
```
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
要对不同用户的网络进行隔离解决VLAN数目有限的问题需要通过Overlay的方式常用的有GRE和VXLAN。
</li>
<li>
GRE是一种点对点的隧道模式VXLAN支持组播的隧道模式它们都要在某个Tunnel Endpoint进行封装和解封装来实现跨物理机的互通。
</li>
<li>
OpenvSwitch可以作为Tunnel Endpoint通过设置流表的规则将虚拟机网络和物理机网络进行隔离、转换。
</li>
最后,给你留两个思考题。
<li>
虽然VXLAN可以支持组播但是如果虚拟机数目比较多在Overlay网络里面广播风暴问题依然会很严重你能想到什么办法解决这个问题吗
</li>
<li>
基于虚拟机的云比较复杂,而且虚拟机里面的网卡,到物理网络转换层次比较多,有一种比虚拟机更加轻量级的云的模式,你知道是什么吗?
</li>
我们的专栏更新到第28讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,350 @@
<audio id="audio" title="第29讲 | 容器网络:来去自由的日子,不买公寓去合租" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/95/18/9519c73093e8c1165c6ba4fcf7f4e118.mp3"></audio>
如果说虚拟机是买公寓容器则相当于合租有一定的隔离但是隔离性没有那么好。云计算解决了基础资源层的弹性伸缩却没有解决PaaS层应用随基础资源层弹性伸缩而带来的批量、快速部署问题。于是容器应运而生。
容器就是Container而Container的另一个意思是集装箱。其实**容器的思想就是要变成软件交付的集装箱**。集装箱的特点,一是打包,二是标准。
<img src="https://static001.geekbang.org/resource/image/a5/dc/a50157f1084c946b9e27f3b328b8d2dc.jpg" alt="">
在没有集装箱的时代假设要将货物从A运到B中间要经过三个码头、换三次船。每次都要将货物卸下船来弄得乱七八糟然后还要再搬上船重新整齐摆好。因此在没有集装箱的时候每次换船船员们都要在岸上待几天才能干完活。
有了尺寸全部都一样的集装箱以后,可以把所有的货物都打包在一起,所以每次换船的时候,一个箱子整体搬过去就行了,小时级别就能完成,船员再也不用耗费很长时间了。这是集装箱的“打包”“标准”两大特点在生活中的应用。
<img src="https://static001.geekbang.org/resource/image/50/cb/50c12f33ec178972c315e57b370dffcb.jpg" alt="">
那么容器如何对应用打包呢?
学习集装箱,首先要有个封闭的环境,将货物封装起来,让货物之间互不干扰,互相隔离,这样装货卸货才方便。
封闭的环境主要使用了两种技术,一种是**看起来是隔离的技术**,称为**namespace**,也即每个 namespace中的应用看到的是不同的 IP地址、用户空间、程号等。另一种是**用起来是隔离的技术**,称为**cgroup**,也即明明整台机器有很多的 CPU、内存而一个应用只能用其中的一部分。
有了这两项技术,就相当于我们焊好了集装箱。接下来的问题就是如何“将这个集装箱标准化”,并在哪艘船上都能运输。这里的标准首先就是**镜像**。
所谓镜像,就是将你焊好集装箱的那一刻,将集装箱的状态保存下来,就像孙悟空说:“定!”,集装箱里的状态就被定在了那一刻,然后将这一刻的状态保存成一系列文件。无论从哪里运行这个镜像,都能完整地还原当时的情况。
<img src="https://static001.geekbang.org/resource/image/ad/9d/ad59fa4271e5dd7b40588a2dfeb6f79d.jpg" alt="">
接下来我们就具体来看看,这两种网络方面的打包技术。
## 命名空间namespace
我们首先来看网络namespace。
namespace翻译过来就是命名空间。其实很多面向对象的程序设计语言里面都有命名空间这个东西。大家一起写代码难免会起相同的名词编译就会冲突。而每个功能都有自己的命名空间在不同的空间里面类名相同不会冲突。
在Linux下也是这样的很多的资源都是全局的。比如进程有全局的进程ID网络也有全局的路由表。但是当一台Linux上跑多个进程的时候如果我们觉得使用不同的路由策略这些进程可能会冲突那就需要将这个进程放在一个独立的namespace里面这样就可以独立配置网络了。
网络的namespace由ip netns命令操作。它可以创建、删除、查询namespace。
我们再来看将你们宿舍放进一台物理机的那个图。你们宿舍长的电脑是一台路由器你现在应该知道怎么实现这个路由器吧可以创建一个Router虚拟机来做这件事情但是还有一个更加简单的办法就是我在图里画的这条虚线这个就是通过namespace实现的。
<img src="https://static001.geekbang.org/resource/image/1a/1a/1a5d299c2eb5480eda93a8f8e3b3ca1a.jpg" alt="">
我们创建一个routerns于是一个独立的网络空间就产生了。你可以在里面尽情设置自己的规则。
```
ip netns add routerns
```
既然是路由器肯定要能转发嘛因而forward开关要打开。
```
ip netns exec routerns sysctl -w net.ipv4.ip_forward=1
```
exec的意思就是进入这个网络空间做点事情。初始化一下iptables因为这里面要配置NAT规则。
```
ip netns exec routerns iptables-save -c
ip netns exec routerns iptables-restore -c
```
路由器需要有一张网卡连到br0上因而要创建一个网卡。
```
ovs-vsctl -- add-port br0 taprouter -- set Interface taprouter type=internal -- set Interface taprouter external-ids:iface-status=active -- set Interface taprouter external-ids:attached-mac=fa:16:3e:84:6e:cc
```
这个网络创建完了但是是在namespace外面的如何进去呢可以通过这个命令
```
ip link set taprouter netns routerns
```
要给这个网卡配置一个IP地址当然应该是虚拟机网络的网关地址。例如虚拟机私网网段为192.168.1.0/24网关的地址往往为192.168.1.1。
```
ip netns exec routerns ip -4 addr add 192.168.1.1/24 brd 192.168.1.255 scope global dev taprouter
```
为了访问外网还需要另一个网卡连在外网网桥br-ex上并且塞在namespace里面。
```
ovs-vsctl -- add-port br-ex taprouterex -- set Interface taprouterex type=internal -- set Interface taprouterex external-ids:iface-status=active -- set Interface taprouterex external-ids:attached-mac=fa:16:3e:68:12:c0
```
```
ip link set taprouterex netns routerns
```
我们还需要为这个网卡分配一个地址这个地址应该和物理外网网络在一个网段。假设物理外网为16.158.1.0/24可以分配一个外网地址16.158.1.100/24。
```
ip netns exec routerns ip -4 addr add 16.158.1.100/24 brd 16.158.1.255 scope global dev taprouterex
```
接下来,既然是路由器,就需要配置路由表,路由表是这样的:
```
ip netns exec routerns route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 16.158.1.1 0.0.0.0 UG 0 0 0 taprouterex
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 taprouter
16.158.1.0 0.0.0.0 255.255.255.0 U 0 0 0 taprouterex
```
路由表中的默认路由是去物理外网的去192.168.1.0/24也即虚拟机私网走下面的网卡去16.158.1.0/24也即物理外网走上面的网卡。
我们在前面的章节讲过如果要在虚拟机里面提供服务提供给外网的客户端访问客户端需要访问外网IP3会在外网网口NAT称为虚拟机私网IP。这个NAT规则要在这个namespace里面配置。
```
ip netns exec routerns iptables -t nat -nvL
Chain PREROUTING
target prot opt in out source destination
DNAT all -- * * 0.0.0.0/0 16.158.1.103 to:192.168.1.3
Chain POSTROUTING
target prot opt in out source destination
SNAT all -- * * 192.168.1.3 0.0.0.0/0 to:16.158.1.103
```
这里面有两个规则一个是SNAT将虚拟机的私网IP 192.168.1.3 NAT成物理外网IP 16.158.1.103。一个是DNAT将物理外网IP 16.158.1.103 NAT成虚拟机私网IP 192.168.1.3。
至此为止基于网络namespace的路由器实现完毕。
## 机制网络cgroup
我们再来看打包的另一个机制网络cgroup。
cgroup全称control groups是Linux内核提供的一种可以限制、隔离进程使用的资源机制。
cgroup能控制哪些资源呢它有很多子系统
<li>
CPU子系统使用调度程序为进程控制CPU的访问
</li>
<li>
cpuset如果是多核心的CPU这个子系统会为进程分配单独的CPU和内存
</li>
<li>
memory子系统设置进程的内存限制以及产生内存资源报告
</li>
<li>
blkio子系统设置限制每个块设备的输入输出控制
</li>
<li>
net_cls这个子系统使用等级识别符classid标记网络数据包可允许Linux 流量控制程序tc识别从具体cgroup中生成的数据包。
</li>
我们这里最关心的是net_cls它可以和前面讲过的TC关联起来。
cgroup提供了一个虚拟文件系统作为进行分组管理和各子系统设置的用户接口。要使用cgroup必须挂载cgroup文件系统一般情况下都是挂载到/sys/fs/cgroup目录下。
所以首先我们要挂载一个net_cls的文件系统。
```
mkdir /sys/fs/cgroup/net_cls
mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
```
接下来我们要配置TC了。还记得咱们实验TC的时候那颗树吗
<img src="https://static001.geekbang.org/resource/image/9a/b5/9a1b8a7c0c5403a2b4b3c277545991b5.jpg" alt="">
当时我们通过这个命令设定了规则从1.2.3.4来的发送给port 80的包从1:10走其他从1.2.3.4发送来的包从1:11走其他的走默认。
```
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flowid 1:11
```
这里是根据源IP来设定的现在有了cgroup我们按照cgroup再来设定规则。
```
tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 1: cgroup
```
假设我们有两个用户a和b要对它们进行带宽限制。
首先我们要创建两个net_cls。
```
mkdir /sys/fs/cgroup/net_cls/a
mkdir /sys/fs/cgroup/net_cls/b
```
假设用户a启动的进程ID为12345把它放在net_cls/a/tasks文件中。同样假设用户b启动的进程ID为12346把它放在net_cls/b/tasks文件中。
net_cls/a目录下面还有一个文件net_cls.classid我们放flowid 1:10。net_cls/b目录下面也创建一个文件net_cls.classid我们放flowid 1:11。
这个数字怎么放呢要转换成一个0xAAAABBBB的值AAAA对应class中冒号前面的数字而BBBB对应后面的数字。
```
echo 0x00010010 &gt; /sys/fs/cgroup/net_cls/a/net_cls.classid
echo 0x00010011 &gt; /sys/fs/cgroup/net_cls/b/net_cls.classid
```
这样用户a的进程发的包会打上1:10这个标签用户b的进程发的包会打上1:11这个标签。然后TC根据这两个标签让用户a的进程的包走左边的分支用户b的进程的包走右边的分支。
## 容器网络中如何融入物理网络?
了解了容器背后的技术,接下来我们来看,容器网络究竟是如何融入物理网络的?
如果你使用docker run运行一个容器你应该能看到这样一个拓扑结构。
<img src="https://static001.geekbang.org/resource/image/20/d2/20e87bc215b9d049a4a504d775d26dd2.jpg" alt="">
是不是和虚拟机很像容器里面有张网卡容器外有张网卡容器外的网卡连到docker0网桥通过这个网桥容器直接实现相互访问。
如果你用brctl查看docker0网桥你会发现它上面连着一些网卡。其实这个网桥和[第24讲](https://time.geekbang.org/column/article/10742)咱们自己用brctl创建的网桥没什么两样。
那连接容器和网桥的那个网卡和虚拟机一样吗在虚拟机场景下有一个虚拟化软件通过TUN/TAP设备虚拟一个网卡给虚拟机但是容器场景下并没有虚拟化软件这该怎么办呢
在Linux下可以创建一对veth pair的网卡从一边发送包另一边就能收到。
我们首先通过这个命令创建这么一对。
```
ip link add name veth1 mtu 1500 type veth peer name veth2 mtu 1500
```
其中一边可以打到docker0网桥上。
```
ip link set veth1 master testbr
ip link set veth1 up
```
那另一端如何放到容器里呢?
一个容器的启动会对应一个namespace我们要先找到这个namespace。对于docker来讲pid就是namespace的名字可以通过这个命令获取。
```
docker inspect '--format={{ .State.Pid }}' test
```
假设结果为12065这个就是namespace名字。
默认Docker创建的网络namespace不在默认路径下 ip netns看不到所以需要ln软链接一下。链接完毕以后我们就可以通过ip netns命令操作了。
```
rm -f /var/run/netns/12065
ln -s /proc/12065/ns/net /var/run/netns/12065
```
然后我们就可以将另一端veth2塞到namespace里面。
```
ip link set veth2 netns 12065
```
然后,将容器内的网卡重命名。
```
ip netns exec 12065 ip link set veth2 name eth0
```
然后给容器内网卡设置ip地址。
```
ip netns exec 12065 ip addr add 172.17.0.2/16 dev eth0
ip netns exec 12065 ip link set eth0 up
```
一台机器内部容器的互相访问没有问题了,那如何访问外网呢?
你先想想看有没有思路就是虚拟机里面的桥接模式和NAT模式。Docker默认使用NAT模式。NAT模式分为SNAT和DNAT如果是容器内部访问外部就需要通过SNAT。
从容器内部的客户端访问外部网络中的服务器,我画了一张图。在[虚拟机](https://time.geekbang.org/column/article/10742)那一节,也有一张类似的图。
<img src="https://static001.geekbang.org/resource/image/54/93/5452971c96e8fea33c3f873860e25c93.jpg" alt="">
在宿主机上有这么一条iptables规则
```
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
```
所有从容器内部发出来的包都要做地址伪装将源IP地址转换为物理网卡的IP地址。如果有多个容器所有的容器共享一个外网的IP地址但是在conntrack表中记录下这个出去的连接。
当服务器返回结果的时候到达物理机会根据conntrack表中的规则取出原来的私网IP通过DNAT将地址转换为私网IP地址通过网桥docker0实现对内的访问。
如果在容器内部属于一个服务例如部署一个网站提供给外部进行访问需要通过Docker的端口映射技术将容器内部的端口映射到物理机上来。
例如容器内部监听80端口可以通Docker run命令中的参数-p 10080:80将物理机上的10080端口和容器的80端口映射起来 当外部的客户端访问这个网站的时候通过访问物理机的10080端口就能访问到容器内的80端口了。
<img src="https://static001.geekbang.org/resource/image/49/bb/49bb6b2a30fe76b124182980da935ebb.jpg" alt="">
Docker有两种方式一种是通过一个进程**docker-proxy**的方式监听10080转换为80端口。
```
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10080 -container-ip 172.17.0.2 -container-port 80
```
另外一种方式是通过**DNAT**方式,在-A PREROUTING阶段加一个规则将到端口10080的DNAT称为容器的私有网络。
```
-A DOCKER -p tcp -m tcp --dport 10080 -j DNAT --to-destination 172.17.0.2:80
```
如此就可以实现容器和物理网络之间的互通了。
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
容器是一种比虚拟机更加轻量级的隔离方式主要通过namespace和cgroup技术进行资源的隔离namespace用于负责看起来隔离cgroup用于负责用起来隔离。
</li>
<li>
容器网络连接到物理网络的方式和虚拟机很像通过桥接的方式实现一台物理机上的容器进行相互访问如果要访问外网最简单的方式还是通过NAT。
</li>
最后,给你留两个思考题:
<li>
容器内的网络和物理机网络可以使用NAT的方式相互访问如果这种方式用于部署应用有什么问题呢
</li>
<li>
和虚拟机一样,不同物理机上的容器需要相互通信,你知道容器是怎么做到这一点吗?
</li>
我们的专栏更新到第29讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,111 @@
<audio id="audio" title="第30讲 | 容器网络之Flannel每人一亩三分地" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/43/d9/43537d3108145697419700fac4ad6ad9.mp3"></audio>
上一节我们讲了容器网络的模型以及如何通过NAT的方式与物理网络进行互通。
每一台物理机上面安装好了Docker以后都会默认分配一个172.17.0.0/16的网段。一台机器上新创建的第一个容器一般都会给172.17.0.2这个地址,当然一台机器这样玩玩倒也没啥问题。但是容器里面是要部署应用的,就像上一节讲过的一样,它既然是集装箱,里面就需要装载货物。
如果这个应用是比较传统的单体应用自己就一个进程所有的代码逻辑都在这个进程里面上面的模式没有任何问题只要通过NAT就能访问进来。
但是因为无法解决快速迭代和高并发的问题,单体应用越来越跟不上时代发展的需要了。
你可以回想一下,无论是各种网络直播平台,还是共享单车,是不是都是很短时间内就要积累大量用户,否则就会错过风口。所以应用需要在很短的时间内快速迭代,不断调整,满足用户体验;还要在很短的时间内,具有支撑高并发请求的能力。
单体应用作为个人英雄主义的时代已经过去了。如果所有的代码都在一个工程里面,开发的时候必然存在大量冲突,上线的时候,需要开大会进行协调,一个月上线一次就很不错了。而且所有的流量都让一个进程扛,怎么也扛不住啊!
没办法,一个字:拆!拆开了,每个子模块独自变化,减少相互影响。拆开了,原来一个进程扛流量,现在多个进程一起扛。所以,微服务就是从个人英雄主义,变成集团军作战。
容器作为集装箱可以保证应用在不同的环境中快速迁移提高迭代的效率。但是如果要形成容器集团军还需要一个集团军作战的调度平台这就是Kubernetes。它可以灵活地将一个容器调度到任何一台机器上并且当某个应用扛不住的时候只要在Kubernetes上修改容器的副本数一个应用马上就能变八个而且都能提供服务。
然而集团军作战有个重要的问题就是通信。这里面包含两个问题第一个是集团军的A部队如何实时地知道B部队的位置变化第二个是两个部队之间如何相互通信。
第一个问题位置变化往往是通过一个称为注册中心的地方统一管理的这个是应用自己做的。当一个应用启动的时候将自己所在环境的IP地址和端口注册到注册中心指挥部这样其他的应用请求它的时候到指挥部问一下它在哪里就好了。当某个应用发生了变化例如一台机器挂了容器要迁移到另一台机器这个时候IP改变了应用会重新注册则其他的应用请求它的时候还是能够从指挥部得到最新的位置。
<img src="https://static001.geekbang.org/resource/image/a0/0d/a0763d50fc4e8dcec37ae25a2f6cc60d.jpeg" alt="">
接下来是如何相互通信的问题。NAT这种模式在多个主机的场景下是存在很大问题的。在物理机A上的应用A看到的IP地址是容器A的是172.17.0.2在物理机B上的应用B看到的IP地址是容器B的不巧也是172.17.0.2,当它们都注册到注册中心的时候,注册中心就是这个图里这样子。
<img src="https://static001.geekbang.org/resource/image/e2/dd/e20596506dd34122e302a7cfc8bb85dd.jpg" alt="">
这个时候应用A要访问应用B当应用A从注册中心将应用B的IP地址读出来的时候就彻底困惑了这不是自己访问自己吗
怎么解决这个问题呢一种办法是不去注册容器内的IP地址而是注册所在物理机的IP地址端口也要是物理机上映射的端口。
<img src="https://static001.geekbang.org/resource/image/8f/18/8fabf1de2a7d346856a032dbf2417b18.jpg" alt="">
这样存在的问题是应用是在容器里面的它怎么知道物理机上的IP地址和端口呢这明明是运维人员配置的除非应用配合读取容器平台的接口获得这个IP和端口。一方面大部分分布式框架都是容器诞生之前就有了它们不会适配这种场景另一方面让容器内的应用意识到容器外的环境本来就是非常不好的设计。
说好的集装箱说好的随意迁移呢难道要让集装箱内的货物意识到自己传的信息而且本来Tomcat都是监听8080端口的结果到了物理机上就不能大家都用这个端口了否则端口就冲突了因而就需要随机分配端口于是在注册中心就出现了各种各样奇怪的端口。无论是注册中心还是调用方都会觉得很奇怪而且不是默认的端口很多情况下也容易出错。
Kubernetes作为集团军作战管理平台提出指导意见说网络模型要变平但是没说怎么实现。于是业界就涌现了大量的方案Flannel就是其中之一。
对于IP冲突的问题如果每一个物理机都是网段172.17.0.0/16肯定会冲突啊但是这个网段实在太大了一台物理机上根本启动不了这么多的容器所以能不能每台物理机在这个大网段里面抠出一个小的网段每个物理机网段都不同自己看好自己的一亩三分地谁也不和谁冲突。
例如物理机A是网段172.17.8.0/24物理机B是网段172.17.9.0/24这样两台机器上启动的容器IP肯定不一样而且就看IP地址我们就一下子识别出这个容器是本机的还是远程的如果是远程的也能从网段一下子就识别出它归哪台物理机管太方便了。
接下来的问题,就是**物理机A上的容器如何访问到物理机B上的容器呢**
你是不是想到了熟悉的场景虚拟机也需要跨物理机互通往往通过Overlay的方式容器是不是也可以这样做呢
**这里我要说Flannel使用UDP实现Overlay网络的方案。**
<img src="https://static001.geekbang.org/resource/image/07/71/07217a9ee64e1970ac04de9080505871.jpeg" alt="">
在物理机A上的容器A里面能看到的容器的IP地址是172.17.8.2/24里面设置了默认的路由规则default via 172.17.8.1 dev eth0。
如果容器A要访问172.17.9.2就会发往这个默认的网关172.17.8.1。172.17.8.1就是物理机上面docker0网桥的IP地址这台物理机上的所有容器都是连接到这个网桥的。
在物理机上面查看路由策略会有这样一条172.17.0.0/24 via 172.17.0.0 dev flannel.1也就是说发往172.17.9.2的网络包会被转发到flannel.1这个网卡。
这个网卡是怎么出来的呢在每台物理机上都会跑一个flanneld进程这个进程打开一个/dev/net/tun字符设备的时候就出现了这个网卡。
你有没有想起qemu-kvm打开这个字符设备的时候物理机上也会出现一个网卡所有发到这个网卡上的网络包会被qemu-kvm接收进来变成二进制串。只不过接下来qemu-kvm会模拟一个虚拟机里面的网卡将二进制的串变成网络包发给虚拟机里面的网卡。但是flanneld不用这样做所有发到flannel.1这个网卡的包都会被flanneld进程读进去接下来flanneld要对网络包进行处理。
物理机A上的flanneld会将网络包封装在UDP包里面然后外层加上物理机A和物理机B的IP地址发送给物理机B上的flanneld。
为什么是UDP呢因为不想在flanneld之间建立两两连接而UDP没有连接的概念任何一台机器都能发给另一台。
物理机B上的flanneld收到包之后解开UDP的包将里面的网络包拿出来从物理机B的flannel.1网卡发出去。
在物理机B上有路由规则172.17.9.0/24 dev docker0 proto kernel scope link src 172.17.9.1。
将包发给docker0docker0将包转给容器B。通信成功。
上面的过程连通性没有问题,但是由于全部在用户态,所以性能差了一些。
跨物理机的连通性问题在虚拟机那里有成熟的方案就是VXLAN那**能不能Flannel也用VXLAN呢**
当然可以了。如果使用VXLAN就不需要打开一个TUN设备了而是要建立一个VXLAN的VTEP。如何建立呢可以通过netlink通知内核建立一个VTEP的网卡flannel.1。在我们讲OpenvSwitch的时候提过netlink是一种用户态和内核态通信的机制。
当网络包从物理机A上的容器A发送给物理机B上的容器B在容器A里面通过默认路由到达物理机A上的docker0网卡然后根据路由规则在物理机A上将包转发给flannel.1。这个时候flannel.1就是一个VXLAN的VTEP了它将网络包进行封装。
内部的MAC地址这样写源为物理机A的flannel.1的MAC地址目标为物理机B的flannel.1的MAC地址在外面加上VXLAN的头。
外层的IP地址这样写源为物理机A的IP地址目标为物理机B的IP地址外面加上物理机的MAC地址。
这样就能通过VXLAN将包转发到另一台机器从物理机B的flannel.1上解包变成内部的网络包通过物理机B上的路由转发到docker0然后转发到容器B里面。通信成功。
<img src="https://static001.geekbang.org/resource/image/01/79/01f86f6049eef051d48e2e235fa43d79.jpeg" alt="">
## 小结
好了,今天的内容就到这里,我来总结一下。
<li>
基于NAT的容器网络模型在微服务架构下有两个问题一个是IP重叠一个是端口冲突需要通过Overlay网络的机制保持跨节点的连通性。
</li>
<li>
Flannel是跨节点容器网络方案之一它提供的Overlay方案主要有两种方式一种是UDP在用户态封装一种是VXLAN在内核态封装而VXLAN的性能更好一些。
</li>
最后,给你留两个问题:
<li>
通过Flannel的网络模型可以实现容器与容器直接跨主机的互相访问那你知道如果容器内部访问外部的服务应该怎么融合到这个网络模型中吗
</li>
<li>
基于Overlay的网络毕竟做了一次网络虚拟化有没有更加高性能的方案呢
</li>
我们的专栏更新到第30讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,224 @@
<audio id="audio" title="第31讲 | 容器网络之Calico为高效说出善意的谎言" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1c/98/1ca02d9aac71d9173ec414fd7ec34e98.mp3"></audio>
上一节我们讲了Flannel如何解决容器跨主机互通的问题这个解决方式其实和虚拟机的网络互通模式是差不多的都是通过隧道。但是Flannel有一个非常好的模式就是给不同的物理机设置不同网段这一点和虚拟机的Overlay的模式完全不一样。
在虚拟机的场景下,整个网段在所有的物理机之间都是可以“飘来飘去”的。网段不同,就给了我们做路由策略的可能。
## Calico网络模型的设计思路
我们看图中的两台物理机。它们的物理网卡是同一个二层网络里面的。由于两台物理机的容器网段不同,我们完全可以将两台物理机配置成为路由器,并按照容器的网段配置路由表。
<img src="https://static001.geekbang.org/resource/image/19/37/1957b75dd689127c4621b5460c356137.jpg" alt="">
例如在物理机A中我们可以这样配置要想访问网段172.17.9.0/24下一跳是192.168.100.101也即到物理机B上去。
这样在容器A中访问容器B当包到达物理机A的时候就能够匹配到这条路由规则并将包发给下一跳的路由器也即发给物理机B。在物理机B上也有路由规则要访问172.17.9.0/24从docker0的网卡进去即可。
当容器B返回结果的时候在物理机B上可以做类似的配置要想访问网段172.17.8.0/24下一跳是192.168.100.100也即到物理机A上去。
当包到达物理机B的时候能够匹配到这条路由规则将包发给下一跳的路由器也即发给物理机A。在物理机A上也有路由规则要访问172.17.8.0/24从docker0的网卡进去即可。
这就是**Calico网络的大概思路****即不走Overlay网络不引入另外的网络性能损耗而是将转发全部用三层网络的路由转发来实现**,只不过具体的实现和上面的过程稍有区别。
首先如果全部走三层的路由规则没必要每台机器都用一个docker0从而浪费了一个IP地址而是可以直接用路由转发到veth pair在物理机这一端的网卡。同样在容器内路由规则也可以这样设定把容器外面的veth pair网卡算作默认网关下一跳就是外面的物理机。
于是,整个拓扑结构就变成了这个图中的样子。
<img src="https://static001.geekbang.org/resource/image/f4/58/f4fab81e3f981827577aa7790b78dc58.jpg" alt="">
## Calico网络的转发细节
我们来看其中的一些细节。
容器A1的IP地址为172.17.8.2/32这里注意不是/24而是/32将容器A1作为一个单点的局域网了。
容器A1里面的默认路由Calico配置得比较有技巧。
```
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
```
这个IP地址169.254.1.1是默认的网关,但是整个拓扑图中没有一张网卡是这个地址。那如何到达这个地址呢?
前面我们讲网关的原理的时候说过当一台机器要访问网关的时候首先会通过ARP获得网关的MAC地址然后将目标MAC变为网关的MAC而网关的IP地址不会在任何网络包头里面出现也就是说没有人在乎这个地址具体是什么只要能找到对应的MAC响应ARP就可以了。
ARP本地有缓存通过ip neigh命令可以查看。
```
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee STALE
```
这个MAC地址是Calico硬塞进去的但是没有关系它能响应ARP于是发出的包的目标MAC就是这个MAC地址。
在物理机A上查看所有网卡的MAC地址的时候我们会发现veth1就是这个MAC地址。所以容器A1里发出的网络包第一跳就是这个veth1这个网卡也就到达了物理机A这个路由器。
在物理机A上有三条路由规则分别是去两个本机的容器的路由以及去172.17.9.0/24下一跳为物理机B。
```
172.17.8.2 dev veth1 scope link
172.17.8.3 dev veth2 scope link
172.17.9.0/24 via 192.168.100.101 dev eth0 proto bird onlink
```
同理物理机B上也有三条路由规则分别是去两个本机的容器的路由以及去172.17.8.0/24下一跳为物理机A。
```
172.17.9.2 dev veth1 scope link
172.17.9.3 dev veth2 scope link
172.17.8.0/24 via 192.168.100.100 dev eth0 proto bird onlink
```
如果你觉得这些规则过于复杂,我将刚才的拓扑图转换为这个更加容易理解的图。
<img src="https://static001.geekbang.org/resource/image/5f/47/5f23071c1e1b17cc46f1cb8955084247.jpg" alt="">
在这里,物理机化身为路由器,通过路由器上的路由规则,将包转发到目的地。在这个过程中,没有隧道封装解封装,仅仅是单纯的路由转发,性能会好很多。但是,这种模式也有很多问题。
## Calico的架构
### 路由配置组件Felix
如果只有两台机器,每台机器只有两个容器,而且保持不变。我手动配置一下,倒也没啥问题。但是如果容器不断地创建、删除,节点不断地加入、退出,情况就会变得非常复杂。
<img src="https://static001.geekbang.org/resource/image/f2/31/f29027cca71f3dfbba8c2f1a35c29331.jpg" alt="">
就像图中,有三台物理机,两两之间都需要配置路由,每台物理机上对外的路由就有两条。如果有六台物理机,则每台物理机上对外的路由就有五条。新加入一个节点,需要通知每一台物理机添加一条路由。
这还是在物理机之间一台物理机上每创建一个容器也需要多配置一条指向这个容器的路由。如此复杂肯定不能手动配置需要每台物理机上有一个agent当创建和删除容器的时候自动做这件事情。这个agent在Calico中称为Felix。
### 路由广播组件BGP Speaker
当Felix配置了路由之后接下来的问题就是如何将路由信息也即将“如何到达我这个节点访问我这个节点上的容器”这些信息广播出去。
能想起来吗这其实就是路由协议啊路由协议就是将“我能到哪里如何能到我”的信息广播给全网传出去从而客户端可以一跳一跳地访问目标地址的。路由协议有很多种Calico使用的是BGP协议。
在Calico中每个Node上运行一个软件BIRD作为BGP的客户端或者叫作BGP Speaker将“如何到达我这个Node访问我这个Node上的容器”的路由信息广播出去。所有Node上的BGP Speaker 都互相建立连接,就形成了全互连的情况,这样每当路由有所变化的时候,所有节点就都能够收到了。
### 安全策略组件
Calico中还实现了灵活配置网络策略Network Policy可以灵活配置两个容器通或者不通。这个怎么实现呢
<img src="https://static001.geekbang.org/resource/image/dd/f2/ddae28956780cc3e45fde76ae96701f2.jpg" alt="">
虚拟机中的安全组是用iptables实现的。Calico中也是用iptables实现的。这个图里的内容是iptables在内核处理网络包的过程中可以嵌入的处理点。Calico也是在这些点上设置相应的规则。
<img src="https://static001.geekbang.org/resource/image/8f/3f/8f2be6638615fc5501c460d1206bff3f.jpg" alt="">
当网络包进入物理机上的时候进入PREOUTING规则这里面有一个规则是cali-fip-dnat这是实现浮动IPFloating IP的场景主要将外网的IP地址dnat作为容器内的IP地址。在虚拟机场景下路由器的网络namespace里面有一个外网网卡上也设置过这样一个DNAT规则。
接下来可以根据路由判断,是到本地的,还是要转发出去的。
如果是本地的走INPUT规则里面有个规则是cali-wl-to-hostwl的意思是workload也即容器也即这是用来判断从容器发到物理机的网络包是否符合规则的。这里面内嵌一个规则cali-from-wl-dispatch也是匹配从容器来的包。如果有两个容器则会有两个容器网卡这里面内嵌有详细的规则“cali-fw-cali网卡1”和“cali-fw-cali网卡2”fw就是from workload也就是匹配从容器1来的网络包和从容器2来的网络包。
如果是转发出去的走FORWARD规则里面有个规则cali-FORWARD。这里面分两种情况一种是从容器里面发出来转发到外面的另一种是从外面发进来转发到容器里面的。
第一种情况匹配的规则仍然是cali-from-wl-dispatch也即from workload。第二种情况匹配的规则是cali-to-wl-dispatch也即to workload。如果有两个容器则会有两个容器网卡在这里面内嵌有详细的规则“cali-tw-cali网卡1”和“cali-tw-cali网卡2”tw就是to workload也就是匹配发往容器1的网络包和发送到容器2的网络包。
接下来是匹配OUTPUT规则里面有cali-OUTPUT。接下来是POSTROUTING规则里面有一个规则是cali-fip-snat也即发出去的时候将容器网络IP转换为浮动IP地址。在虚拟机场景下路由器的网络namespace里面有一个外网网卡上也设置过这样一个SNAT规则。
至此为止Calico的所有组件基本凑齐。来看看我汇总的图。
<img src="https://static001.geekbang.org/resource/image/71/07/71f22fd9e8336c7e10c8ff7bd276af07.jpg" alt="">
## 全连接复杂性与规模问题
这里面还存在问题就是BGP全连接的复杂性问题。
你看刚才的例子里只有六个节点BGP的互连已经如此复杂如果节点数据再多这种全互连的模式肯定不行到时候都成蜘蛛网了。于是多出了一个组件BGP Route Reflector它也是用BIRD实现的。有了它BGP Speaker就不用全互连了而是都直连它它负责将全网的路由信息广播出去。
可是问题来了规模大了大家都连它它受得了吗这个BGP Router Reflector会不会成为瓶颈呢
所以肯定不能让一个BGP Router Reflector管理所有的路由分发而是应该有多个BGP Router Reflector每个BGP Router Reflector管一部分。
多大算一部分呢咱们讲述数据中心的时候说服务器都是放在机架上的每个机架上最顶端有个TOR交换机。那将机架上的机器连在一起这样一个机架是不是可以作为一个单元让一个BGP Router Reflector来管理呢如果要跨机架如何进行通信呢这就需要BGP Router Reflector也直接进行路由交换。它们之间的交换和一个机架之间的交换有什么关系吗
有没有觉得在这个场景下一个机架就像一个数据中心可以把它设置为一个AS而BGP Router Reflector有点儿像数据中心的边界路由器。在一个AS内部也即服务器和BGP Router Reflector之间使用的是数据中心内部的路由协议iBGPBGP Router Reflector之间使用的是数据中心之间的路由协议eBGP。
<img src="https://static001.geekbang.org/resource/image/47/a0/474cb05d5536f11d75baeb6332d788a0.jpg" alt="">
这个图中一个机架上有多台机器每台机器上面启动多个容器每台机器上都有可以到达这些容器的路由。每台机器上都启动一个BGP Speaker然后将这些路由规则上报到这个Rack上接入交换机的BGP Route Reflector将这些路由通过iBGP协议告知到接入交换机的三层路由功能。
在接入交换机之间也建立BGP连接相互告知路由因而一个Rack里面的路由可以告知另一个Rack。有多个核心或者汇聚交换机将接入交换机连接起来如果核心和汇聚起二层互通的作用则接入和接入之间之间交换路由即可。如果核心和汇聚交换机起三层路由的作用则路由需要通过核心或者汇聚交换机进行告知。
## 跨网段访问问题
上面的Calico模式还有一个问题就是跨网段问题这里的跨网段是指物理机跨网段。
前面我们说的那些逻辑成立的条件是我们假设物理机可以作为路由器进行使用。例如物理机A要告诉物理机B你要访问172.17.8.0/24下一跳是我192.168.100.100同理物理机B要告诉物理机A你要访问172.17.9.0/24下一跳是我192.168.100.101。
之所以能够这样是因为物理机A和物理机B是同一个网段的是连接在同一个交换机上的。那如果物理机A和物理机B不是在同一个网段呢
<img src="https://static001.geekbang.org/resource/image/58/89/58bb1d0965c383b1eaac06946998f089.jpg" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/1b/37/1b4514ed6d0e952a9d14f55yy36c0937.jpg" alt="">
例如物理机A的网段是192.168.100.100/24物理机B的网段是192.168.200.101/24这样两台机器就不能通过二层交换机连接起来了需要在中间放一台路由器做一次路由转发才能跨网段访问。
本来物理机A要告诉物理机B你要访问172.17.8.0/24下一跳是我192.168.100.100的,但是中间多了一台路由器,下一跳不是我了,而是中间的这台路由器了,这台路由器的再下一跳,才是我。这样之前的逻辑就不成立了。
我们看刚才那张图的下半部分。物理机B上的容器要访问物理机A上的容器第一跳就是物理机BIP为192.168.200.101第二跳是中间的物理路由器右面的网口IP为192.168.200.1第三跳才是物理机AIP为192.168.100.100。
这是咱们通过拓扑图看到的关键问题是在系统中物理机A如何告诉物理机B怎么让它才能到我这里物理机A根本不可能知道从物理机B出来之后的下一跳是谁况且现在只是中间隔着一个路由器这种简单的情况如果隔着多个路由器呢谁能把这一串的路径告诉物理机B呢
我们能想到的第一种方式是让中间所有的路由器都来适配Calico。本来它们互相告知路由只互相告知物理机的现在还要告知容器的网段。这在大部分情况下是不可能的。
第二种方式还是在物理机A和物理机B之间打一个隧道这个隧道有两个端点在端点上进行封装将容器的IP作为乘客协议放在隧道里面而物理主机的IP放在外面作为承载协议。这样不管外层的IP通过传统的物理网络走多少跳到达目标物理机从隧道两端看起来物理机A的下一跳就是物理机B这样前面的逻辑才能成立。
这就是Calico的**IPIP模式**。使用了IPIP模式之后在物理机A上我们能看到这样的路由表
```
172.17.8.2 dev veth1 scope link
172.17.8.3 dev veth2 scope link
172.17.9.0/24 via 192.168.200.101 dev tun0 proto bird onlink
```
这和原来模式的区别在于下一跳不再是同一个网段的物理机B了IP为192.168.200.101并且不是从eth0跳而是建立一个隧道的端点tun0从这里才是下一跳。
如果我们在容器A1里面的172.17.8.2去ping容器B1里面的172.17.9.2首先会到物理机A。在物理机A上根据上面的规则会转发给tun0并在这里对包做封装
<li>
内层源IP为172.17.8.2
</li>
<li>
内层目标IP为172.17.9.2
</li>
<li>
外层源IP为192.168.100.100
</li>
<li>
外层目标IP为192.168.200.101。
</li>
将这个包从eth0发出去在物理网络上会使用外层的IP进行路由最终到达物理机B。在物理机B上tun0会解封装将内层的源IP和目标IP拿出来转发给相应的容器。
## 小结
好了,这一节就到这里,我们来总结一下。
<li>
Calico推荐使用物理机作为路由器的模式这种模式没有虚拟化开销性能比较高。
</li>
<li>
Calico的主要组件包括路由、iptables的配置组件Felix、路由广播组件BGP Speaker以及大规模场景下的BGP Route Reflector。
</li>
<li>
为解决跨网段的问题Calico还有一种IPIP模式也即通过打隧道的方式从隧道端点来看将本来不是邻居的两台机器变成相邻的机器。
</li>
最后,给你留两个思考题:
<li>
将Calico部署在公有云上的时候经常会选择使用IPIP模式你知道这是为什么吗
</li>
<li>
容器是用来部署微服务的,微服务之间的通信,除了网络要互通,还需要高效地传输信息,例如下单的商品、价格、数量、支付的钱等等,这些要通过什么样的协议呢?
</li>
我们的专栏更新到第31讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,177 @@
<audio id="audio" title="第32讲 | RPC协议综述远在天边近在眼前" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/97/2d/97718e118ab62bfaa6283e753ee9392d.mp3"></audio>
前面我们讲了容器网络如何实现跨主机互通,以及微服务之间的相互调用。
<img src="https://static001.geekbang.org/resource/image/06/65/06ba300a78aef37b9d190aba61c37865.jpg" alt="">
网络是打通了那服务之间的互相调用该怎么实现呢你可能说咱不是学过Socket吗。服务之间分调用方和被调用方我们就建立一个TCP或者UDP的连接不就可以通信了
<img src="https://static001.geekbang.org/resource/image/77/92/77d5eeb659d5347874bda5e8f711f692.jpg" alt="">
你仔细想一下,这事儿没这么简单。我们就拿最简单的场景,客户端调用一个加法函数,将两个整数加起来,返回它们的和。
如果放在本地调用,那是简单的不能再简单了,只要稍微学过一种编程语言,三下五除二就搞定了。但是一旦变成了远程调用,门槛一下子就上去了。
首先你要会Socket编程至少先要把咱们这门网络协议课学一下然后再看N本砖头厚的Socket程序设计的书学会咱们学过的几种Socket程序设计的模型。这就使得本来大学毕业就能干的一项工作变成了一件五年工作经验都不一定干好的工作而且搞定了Socket程序设计才是万里长征的第一步。后面还有很多问题呢
## 如何解决这五个问题?
### 问题一:如何规定远程调用的语法?
客户端如何告诉服务端我是一个加法而另一个是乘法。我是用字符串“add”传给你还是传给你一个整数比如1表示加法2表示乘法服务端该如何告诉客户端我的这个加法目前只能加整数不能加小数不能加字符串而另一个加法“add1”它能实现小数和整数的混合加法。那返回值是什么正确的时候返回什么错误的时候又返回什么
### 问题二:如果传递参数?
我是先传两个整数后传一个操作符“add”还是先传操作符再传两个整数是不是像咱们数据结构里一样如果都是UDP想要实现一个逆波兰表达式放在一个报文里面还好如果是TCP是一个流在这个流里面如何将两次调用进行分界什么时候是头什么时候是尾把这次的参数和上次的参数混了起来TCP一端发送出去的数据另外一端不一定能一下子全部读取出来。所以怎么才算读完呢
### 问题三:如何表示数据?
在这个简单的例子中传递的就是一个固定长度的int值这种情况还好如果是变长的类型是一个结构体甚至是一个类应该怎么办呢如果是int不同的平台上长度也不同该怎么办呢
在网络上传输超过一个Byte的类型还有大端Big Endian和小端Little Endian的问题。
假设我们要在32位四个Byte的一个空间存放整数1很显然只要一个Byte放1其他三个Byte放0就可以了。那问题是最后一个Byte放1呢还是第一个Byte放1呢或者说1作为最低位应该是放在32位的最后一个位置呢还是放在第一个位置呢
最低位放在最后一个位置叫作Little Endian最低位放在第一个位置叫作Big Endian。TCP/IP协议栈是按照Big Endian来设计的而X86机器多按照Little Endian来设计的因而发出去的时候需要做一个转换。
### 问题四:如何知道一个服务端都实现了哪些远程调用?从哪个端口可以访问这个远程调用?
假设服务端实现了多个远程调用,每个可能实现在不同的进程中,监听的端口也不一样,而且由于服务端都是自己实现的,不可能使用一个大家都公认的端口,而且有可能多个进程部署在一台机器上,大家需要抢占端口,为了防止冲突,往往使用随机端口,那客户端如何找到这些监听的端口呢?
### 问题五:发生了错误、重传、丢包、性能等问题怎么办?
本地调用没有这个问题但是一旦到网络上这些问题都需要处理因为网络是不可靠的虽然在同一个连接中我们还可通过TCP协议保证丢包、重传的问题但是如果服务器崩溃了又重启当前连接断开了TCP就保证不了了需要应用自己进行重新调用重新传输会不会同样的操作做两遍远程调用性能会不会受影响呢
## 协议约定问题
看到这么多问题,你是不是想起了我[第一节](https://time.geekbang.org/column/article/7581)讲过的这张图。
<img src="https://static001.geekbang.org/resource/image/98/ab/984b421d4e13d42e2b0500d0427d94ab.jpg" alt="">
本地调用函数里有很多问题,比如词法分析、语法分析、语义分析等等,这些编译器本来都能帮你做了。但是在远程调用中,这些问题你都需要重新操心。
很多公司的解决方法是弄一个核心通信组里面都是Socket编程的大牛实现一个统一的库让其他业务组的人来调用业务的人不需要知道中间传输的细节。通信双方的语法、语义、格式、端口、错误处理等都需要调用方和被调用方开会协商双方达成一致。一旦有一方改变要及时通知对方否则通信就会有问题。
可是不是每一个公司都有这种大牛团队,往往只有大公司才配得起,那有没有已经实现好的框架可以使用呢?
当然有。一个大牛Bruce Jay Nelson写了一篇论文[Implementing Remote Procedure Calls](http://www.cs.cmu.edu/~dga/15-712/F07/papers/birrell842.pdf)定义了RPC的调用标准。后面所有RPC框架都是按照这个标准模式来的。
<img src="https://static001.geekbang.org/resource/image/85/25/8534c52daf3682cd1cfe5a3375ec9525.jpg" alt="">
当客户端的应用想发起一个远程调用时它实际是通过本地调用本地调用方的Stub。它负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的RPCRuntime进行传输将调用网络包发送到服务器。
服务器端的RPCRuntime收到请求后交给提供方Stub进行解码然后调用服务端的方法服务端执行方法返回结果提供方Stub将返回结果编码后发送给客户端客户端的RPCRuntime收到结果发给调用方Stub解码得到结果返回给客户端。
这里面分了三个层次对于用户层和服务端都像是本地调用一样专注于业务逻辑的处理就可以了。对于Stub层处理双方约定好的语法、语义、封装、解封装。对于RPCRuntime主要处理高性能的传输以及网络的错误和异常。
最早的RPC的一种实现方式称为Sun RPC或ONC RPC。Sun公司是第一个提供商业化RPC库和 RPC编译器的公司。这个RPC框架是在NFS协议中使用的。
NFSNetwork File System就是网络文件系统。要使NFS成功运行要启动两个服务端一个是mountd用来挂载文件路径一个是nfsd用来读写文件。NFS可以在本地mount一个远程的目录到本地的一个目录从而本地的用户在这个目录里面写入、读出任何文件的时候其实操作的是远程另一台机器上的文件。
操作远程和远程调用的思路是一样的就像操作本地一样。所以NFS协议就是基于RPC实现的。当然无论是什么RPC底层都是Socket编程。
<img src="https://static001.geekbang.org/resource/image/2a/eb/2a0fd84c2d3dced623511e2a5226d0eb.jpg" alt="">
XDRExternal Data Representation外部数据表示法是一个标准的数据压缩格式可以表示基本的数据类型也可以表示结构体。
这里是几种基本的数据类型。
<img src="https://static001.geekbang.org/resource/image/4a/af/4a649954fea1cee22fcfa8bdb34c03af.jpg" alt="">
在RPC的调用过程中所有的数据类型都要封装成类似的格式。而且RPC的调用和结果返回也有严格的格式。
<li>
XID唯一标识一对请求和回复。请求为0回复为1。
</li>
<li>
RPC有版本号两端要匹配RPC协议的版本号。如果不匹配就会返回Deny原因就是RPC_MISMATCH。
</li>
<li>
程序有编号。如果服务端找不到这个程序就会返回PROG_UNAVAIL。
</li>
<li>
程序有版本号。如果程序的版本号不匹配就会返回PROG_MISMATCH。
</li>
<li>
一个程序可以有多个方法方法也有编号如果找不到方法就会返回PROC_UNAVAIL。
</li>
<li>
调用需要认证鉴权如果不通过则Deny。
</li>
<li>
最后是参数列表如果参数无法解析则返回GABAGE_ARGS。
</li>
<img src="https://static001.geekbang.org/resource/image/c7/65/c724675527afdbd43964bdf24684fa65.jpg" alt="">
为了可以成功调用RPC在客户端和服务端实现RPC的时候首先要定义一个双方都认可的程序、版本、方法、参数等。
<img src="https://static001.geekbang.org/resource/image/5c/58/5c3ebb31ac4415d7895247bf8758fa58.jpg" alt="">
如果还是上面的加法则双方约定为一个协议定义文件同理如果是NFS、mount和读写也会有类似的定义。
有了协议定义文件ONC RPC会提供一个工具根据这个文件生成客户端和服务器端的Stub程序。
<img src="https://static001.geekbang.org/resource/image/27/b9/27dc1ccd0481408055c87e0e5d8b02b9.jpg" alt="">
最下层的是XDR文件用于编码和解码参数。这个文件是客户端和服务端共享的因为只有双方一致才能成功通信。
在客户端会调用clnt_create创建一个连接然后调用add_1这是一个Stub函数感觉是在调用本地一样。其实是这个函数发起了一个RPC调用通过调用clnt_call来调用ONC RPC的类库来真正发送请求。调用的过程非常复杂一会儿我详细说这个。
当然服务端也有一个Stub程序监听客户端的请求当调用到达的时候判断如果是add则调用真正的服务端逻辑也即将两个数加起来。
服务端将结果返回服务端的Stub这个Stub程序发送结果给客户端客户端的Stub程序正在等待结果当结果到达客户端Stub就将结果返回给客户端的应用程序从而完成整个调用过程。
有了这个RPC的框架前面五个问题中的前三个“如何规定远程调用的语法”“如何传递参数”以及“如何表示数据”基本解决了这三个问题我们统称为**协议约定问题**。
## 传输问题
但是错误、重传、丢包、性能等问题还没有解决,这些问题我们统称为**传输问题**。这个就不用Stub操心了而是由ONC RPC的类库来实现。这是大牛们实现的我们只要调用就可以了。
<img src="https://static001.geekbang.org/resource/image/33/e4/33e1afe4a79e81096e09b850424930e4.jpg" alt="">
在这个类库中为了解决传输问题对于每一个客户端都会创建一个传输管理层而每一次RPC调用都会是一个任务在传输管理层你可以看到熟悉的队列机制、拥塞窗口机制等。
由于在网络传输的时候经常需要等待因而同步的方式往往效率比较低因而也就有Socket的异步模型。为了能够异步处理对于远程调用的处理往往是通过状态机来实现的。只有当满足某个状态的时候才进行下一步如果不满足状态不是在那里等而是将资源留出来用来处理其他的RPC调用。
<img src="https://static001.geekbang.org/resource/image/02/f5/0258775aac1126735504c9a6399745f5.jpg" alt="">
从这个图可以看出,这个状态转换图还是很复杂的。
首先进入起始状态查看RPC的传输层队列中有没有空闲的位置可以处理新的RPC任务。如果没有说明太忙了或直接结束或重试。如果申请成功就可以分配内存获取服务的端口号然后连接服务器。
连接的过程要有一段时间因而要等待连接的结果会有连接失败或直接结束或重试。如果连接成功则开始发送RPC请求然后等待获取RPC结果这个过程也需要一定的时间如果发送出错可以重新发送如果连接断了可以重新连接如果超时可以重新传输如果获取到结果就可以解码正常结束。
这里处理了连接失败、重试、发送失败、超时、重试等场景。不是大牛真写不出来因而实现一个RPC的框架其实很有难度。
## 服务发现问题
传输问题解决了我们还遗留一个问题就是问题四“如何找到RPC服务端的那个随机端口”。这个问题我们称为服务发现问题。在ONC RPC中服务发现是通过portmapper实现的。
<img src="https://static001.geekbang.org/resource/image/2a/7c/2aff190d1f878749d2a5bd73228ca37c.jpg" alt="">
portmapper会启动在一个众所周知的端口上RPC程序由于是用户自己写的会监听在一个随机端口上但是RPC程序启动的时候会向portmapper注册。客户端要访问RPC服务端这个程序的时候首先查询portmapper获取RPC服务端程序的随机端口然后向这个随机端口建立连接开始RPC调用。从图中可以看出mount命令的RPC调用就是这样实现的。
## 小结
好了,这一节就到这里,我们来总结一下。
<li>
远程调用看起来用Socket编程就可以了其实是很复杂的要解决协议约定问题、传输问题和服务发现问题。
</li>
<li>
大牛Bruce Jay Nelson的论文、早期ONC RPC框架以及NFS的实现给出了解决这三大问题的示范性实现也即协议约定要公用协议描述文件并通过这个文件生成Stub程序RPC的传输一般需要一个状态机需要另外一个进程专门做服务发现。
</li>
最后,给你留两个思考题。
<li>
在这篇文章中mount的过程是通过系统调用最终调用到RPC层。一旦mount完毕之后客户端就像写入本地文件一样写入NFS了这个过程是如何触发RPC层的呢
</li>
<li>
ONC RPC是早期的RPC框架你觉得它有哪些问题呢
</li>
我们的专栏更新到第32讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送**学习奖励礼券**和我整理的**独家网络协议知识图谱**。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,212 @@
<audio id="audio" title="第33讲 | 基于XML的SOAP协议不要说NBA请说美国职业篮球联赛" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/9a/7e3f7b2cbb61438d484ab627a7dcf39a.mp3"></audio>
上一节我们讲了RPC的经典模型和设计要点并用最早期的ONC RPC为例子详述了具体的实现。
## ONC RPC存在哪些问题
ONC RPC将客户端要发送的参数以及服务端要发送的回复都压缩为一个二进制串这样固然能够解决双方的协议约定问题但是存在一定的不方便。
首先,**需要双方的压缩格式完全一致**,一点都不能差。一旦有少许的差错,多一位,少一位或者错一位,都可能造成无法解压缩。当然,我们可以用传输层的可靠性以及加入校验值等方式,来减少传输过程中的差错。
其次,**协议修改不灵活**。如果不是传输过程中造成的差错,而是客户端因为业务逻辑的改变,添加或者删除了字段,或者服务端添加或者删除了字段,而双方没有及时通知,或者线上系统没有及时升级,就会造成解压缩不成功。
因而当业务发生改变需要多传输一些参数或者少传输一些参数的时候都需要及时通知对方并且根据约定好的协议文件重新生成双方的Stub程序。自然这样灵活性比较差。
如果仅仅是沟通的问题也还好解决,其实更难弄的还有**版本的问题**。比如在服务端提供一个服务参数的格式是版本一的已经有50个客户端在线上调用了。现在有一个客户端有个需求要加一个字段怎么办呢这可是一个大工程所有的客户端都要适配这个需要重新写程序加上这个字段但是传输值是0不需要这个字段的客户端很“冤”本来没我啥事儿为啥让我也忙活
最后,**ONC RPC的设计明显是面向函数的而非面向对象**。而当前面向对象的业务逻辑设计与实现方式已经成为主流。
这一切的根源就在于压缩。这就像平时我们爱用缩略语。如果是篮球爱好者你直接说NBA他马上就知道什么意思但是如果你给一个大妈说NBA她可能就不知所云。
所以这种RPC框架只能用于客户端和服务端全由一拨人开发的场景或者至少客户端和服务端的开发人员要密切沟通相互合作有大量的共同语言才能按照既定的协议顺畅地进行工作。
## XML与SOAP
但是一般情况下我们做一个服务都是要提供给陌生人用的你和客户不会经常沟通也没有什么共同语言。就像你给别人介绍NBA你要说美国职业篮球赛这样不管他是干啥的都能听得懂。
放到我们的场景中,对应的就是用**文本类**的方式进行传输。无论哪个客户端获得这个文本,都能够知道它的意义。
一种常见的文本类格式是XML。我们这里举个例子来看。
```
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;geek:purchaseOrder xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:geek=&quot;http://www.example.com/geek&quot;&gt;
&lt;order&gt;
&lt;date&gt;2018-07-01&lt;/date&gt;
&lt;className&gt;趣谈网络协议&lt;/className&gt;
&lt;Author&gt;刘超&lt;/Author&gt;
&lt;price&gt;68&lt;/price&gt;
&lt;/order&gt;
&lt;/geek:purchaseOrder&gt;
```
我这里不准备详细讲述XML的语法规则但是你相信我看完下面的内容即便你没有学过XML也能一看就懂这段XML描述的是什么不像全面的二进制你看到的都是010101不知所云。
有了这个,刚才我们说的那几个问题就都不是问题了。
首先,**格式没必要完全一致**。比如如果我们把price和author换个位置并不影响客户端和服务端解析这个文本也根本不会误会说这个作者的名字叫68。
如果有的客户端想增加一个字段,例如添加一个推荐人字段,只需要在上面的文件中加一行:
```
&lt;recommended&gt; Gary &lt;/recommended&gt;
```
对于不需要这个字段的客户端,只要不解析这一行就是了。只要用简单的处理,就不会出现错误。
另外,这种表述方式显然是描述一个订单对象的,是一种面向对象的、更加接近用户场景的表示方式。
既然XML这么好接下来我们来看看怎么把它用在RPC中。
### 传输协议问题
我们先解决第一个,传输协议的问题。
基于XML的最著名的通信协议就是**SOAP**了,全称**简单对象访问协议**Simple Object Access Protocol。它使用XML编写简单的请求和回复消息并用HTTP协议进行传输。
SOAP将请求和回复放在一个信封里面就像传递一个邮件一样。信封里面的信分**抬头**和**正文**。
```
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
```
```
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;soap:Envelope xmlns:soap=&quot;http://www.w3.org/2001/12/soap-envelope&quot;
soap:encodingStyle=&quot;http://www.w3.org/2001/12/soap-encoding&quot;&gt;
&lt;soap:Header&gt;
&lt;m:Trans xmlns:m=&quot;http://www.w3schools.com/transaction/&quot;
soap:mustUnderstand=&quot;1&quot;&gt;1234
&lt;/m:Trans&gt;
&lt;/soap:Header&gt;
&lt;soap:Body xmlns:m=&quot;http://www.geektime.com/perchaseOrder&quot;&gt;
&lt;m:purchaseOrder&quot;&gt;
&lt;order&gt;
&lt;date&gt;2018-07-01&lt;/date&gt;
&lt;className&gt;趣谈网络协议&lt;/className&gt;
&lt;Author&gt;刘超&lt;/Author&gt;
&lt;price&gt;68&lt;/price&gt;
&lt;/order&gt;
&lt;/m:purchaseOrder&gt;
&lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;
```
HTTP协议我们学过这个请求使用POST方法发送一个格式为 application/soap + xml 的XML正文给 [www.geektime.com](http://www.geektime.com)从而下一个单这个订单封装在SOAP的信封里面并且表明这是一笔交易transaction而且订单的详情都已经写明了。
### 协议约定问题
接下来我们解决第二个问题,就是双方的协议约定是什么样的?
因为服务开发出来是给陌生人用的就像上面下单的那个XML文件对于客户端来说它如何知道应该拼装成上面的格式呢这就需要对于服务进行描述因为调用的人不认识你所以没办法找到你问你的服务应该如何调用。
当然你可以写文档,然后放在官方网站上,但是你的文档不一定更新得那么及时,而且你也写的文档也不一定那么严谨,所以常常会有调试不成功的情况。因而,我们需要一种相对比较严谨的**Web服务描述语言****WSDL**Web Service Description Languages。它也是一个XML文件。
在这个文件中要定义一个类型order与上面的XML对应起来。
```
&lt;wsdl:types&gt;
&lt;xsd:schema targetNamespace=&quot;http://www.example.org/geektime&quot;&gt;
&lt;xsd:complexType name=&quot;order&quot;&gt;
&lt;xsd:element name=&quot;date&quot; type=&quot;xsd:string&quot;&gt;&lt;/xsd:element&gt;
&lt;xsd:element name=&quot;className&quot; type=&quot;xsd:string&quot;&gt;&lt;/xsd:element&gt;
&lt;xsd:element name=&quot;Author&quot; type=&quot;xsd:string&quot;&gt;&lt;/xsd:element&gt;
&lt;xsd:element name=&quot;price&quot; type=&quot;xsd:int&quot;&gt;&lt;/xsd:element&gt;
&lt;/xsd:complexType&gt;
&lt;/xsd:schema&gt;
&lt;/wsdl:types&gt;
```
接下来需要定义一个message的结构。
```
&lt;wsdl:message name=&quot;purchase&quot;&gt;
&lt;wsdl:part name=&quot;purchaseOrder&quot; element=&quot;tns:order&quot;&gt;&lt;/wsdl:part&gt;
&lt;/wsdl:message&gt;
```
接下来,应该暴露一个端口。
```
&lt;wsdl:portType name=&quot;PurchaseOrderService&quot;&gt;
&lt;wsdl:operation name=&quot;purchase&quot;&gt;
&lt;wsdl:input message=&quot;tns:purchase&quot;&gt;&lt;/wsdl:input&gt;
&lt;wsdl:output message=&quot;......&quot;&gt;&lt;/wsdl:output&gt;
&lt;/wsdl:operation&gt;
&lt;/wsdl:portType&gt;
```
然后我们来编写一个binding将上面定义的信息绑定到SOAP请求的body里面。
```
&lt;wsdl:binding name=&quot;purchaseOrderServiceSOAP&quot; type=&quot;tns:PurchaseOrderService&quot;&gt;
&lt;soap:binding style=&quot;rpc&quot;
transport=&quot;http://schemas.xmlsoap.org/soap/http&quot; /&gt;
&lt;wsdl:operation name=&quot;purchase&quot;&gt;
&lt;wsdl:input&gt;
&lt;soap:body use=&quot;literal&quot; /&gt;
&lt;/wsdl:input&gt;
&lt;wsdl:output&gt;
&lt;soap:body use=&quot;literal&quot; /&gt;
&lt;/wsdl:output&gt;
&lt;/wsdl:operation&gt;
&lt;/wsdl:binding&gt;
```
最后我们需要编写service。
```
&lt;wsdl:service name=&quot;PurchaseOrderServiceImplService&quot;&gt;
&lt;wsdl:port binding=&quot;tns:purchaseOrderServiceSOAP&quot; name=&quot;PurchaseOrderServiceImplPort&quot;&gt;
&lt;soap:address location=&quot;http://www.geektime.com:8080/purchaseOrder&quot; /&gt;
&lt;/wsdl:port&gt;
&lt;/wsdl:service&gt;
```
WSDL还是有些复杂的不过好在有工具可以生成。
对于某个服务,哪怕是一个陌生人,都可以通过在服务地址后面加上“?wsdl”来获取到这个文件但是这个文件还是比较复杂比较难以看懂。不过好在也有工具可以根据WSDL生成客户端Stub让客户端通过Stub进行远程调用就跟调用本地的方法一样。
### 服务发现问题
最后解决第三个问题,服务发现问题。
这里有一个**UDDI**Universal Description, Discovery, and Integration也即**统一描述、发现和集成协议**。它其实是一个注册中心服务提供方可以将上面的WSDL描述文件发布到这个注册中心注册完毕后服务使用方可以查找到服务的描述封装为本地的客户端进行调用。
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
原来的二进制RPC有很多缺点格式要求严格修改过于复杂不面向对象于是产生了基于文本的调用方式——基于XML的SOAP。
</li>
<li>
SOAP有三大要素协议约定用WSDL、传输协议用HTTP、服务发现用UDDL。
</li>
最后,给你留两个思考题:
<li>
对于HTTP协议来讲有多种方法但是SOAP只用了POST这样会有什么问题吗
</li>
<li>
基于文本的RPC虽然解决了二进制的问题但是SOAP还是有点复杂还有一种更便捷的接口规则你知道是什么吗
</li>
我们的专栏更新到第33讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,142 @@
<audio id="audio" title="第34讲 | 基于JSON的RESTful接口协议我不关心过程请给我结果" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7a/cb/7a7e5b3b70e60c69b8a533659260dacb.mp3"></audio>
上一节我们讲了基于XML的SOAP协议SOAP的S是啥意思来着是Simple但是好像一点儿都不简单啊
你会发现对于SOAP来讲无论XML中调用的是什么函数多是通过HTTP的POST方法发送的。但是咱们原来学HTTP的时候我们知道HTTP除了POST还有PUT、DELETE、GET等方法这些也可以代表一个个动作而且基本满足增、删、查、改的需求比如增是POST删是DELETE查是GET改是PUT。
## 传输协议问题
对于SOAP来讲比如我创建一个订单用POST在XML里面写明动作是CreateOrder删除一个订单还是用POST在XML里面写明了动作是DeleteOrder。其实创建订单完全可以使用POST动作然后在XML里面放一个订单<!-- [[[read_end]]] -->的信息就可以了而删除用DELETE动作然后在XML里面放一个订单的ID就可以了。
于是上面的那个SOAP就变成下面这个简单的模样。
```
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/xml; charset=utf-8
Content-Length: nnn
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;order&gt;
&lt;date&gt;2018-07-01&lt;/date&gt;
&lt;className&gt;趣谈网络协议&lt;/className&gt;
&lt;Author&gt;刘超&lt;/Author&gt;
&lt;price&gt;68&lt;/price&gt;
&lt;/order&gt;
```
而且XML的格式也可以改成另外一种简单的文本化的对象表示格式JSON。
```
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/json; charset=utf-8
Content-Length: nnn
{
&quot;order&quot;: {
&quot;date&quot;: &quot;2018-07-01&quot;,
&quot;className&quot;: &quot;趣谈网络协议&quot;,
&quot;Author&quot;: &quot;刘超&quot;,
&quot;price&quot;: &quot;68&quot;
}
}
```
经常写Web应用的应该已经发现这就是RESTful格式的API的样子。
## 协议约定问题
然而RESTful可不仅仅是指API而是一种架构风格全称Representational State Transfer表述性状态转移来自一篇重要的论文《架构风格与基于网络的软件架构设计》Architectural Styles and the Design of Network-based Software Architectures
这篇文章从深层次更加抽象地论证了一个互联网应用应该有的设计要点而这些设计要点成为后来我们能看到的所有高并发应用设计都必须要考虑的问题再加上REST API比较简单直接所以后来几乎成为互联网应用的标准接口。
因此和SOAP不一样REST不是一种严格规定的标准它其实是一种设计风格。如果按这种风格进行设计RESTful接口和SOAP接口都能做到只不过后面的架构是REST倡导的而SOAP相对比较关注前面的接口。
而且由于能够通过WSDL生成客户端的Stub因而SOAP常常被用于类似传统的RPC方式也即调用远端和调用本地是一样的。
然而本地调用和远程跨网络调用毕竟不一样,这里的不一样还不仅仅是因为有网络而导致的客户端和服务端的分离,从而带来的网络性能问题。更重要的问题是,客户端和服务端谁来维护状态。所谓的状态就是对某个数据当前处理到什么程度了。
这里举几个例子,例如,我浏览到哪个目录了,我看到第几页了,我要买个东西,需要扣减一下库存,这些都是状态。本地调用其实没有人纠结这个问题,因为数据都在本地,谁处理都一样,而且一边处理了,另一边马上就能看到。
当有了RPC之后我们本来期望对上层透明就像上一节说的“远在天边尽在眼前”。于是使用RPC的时候对于状态的问题也没有太多的考虑。
就像NFS一样客户端会告诉服务端我要进入哪个目录服务端必须要为某个客户端维护一个状态就是当前这个客户端浏览到哪个目录了。例如客户端输入cd hello服务端要在某个地方记住上次浏览到/root/liuchao了因而客户的这次输入应该给它显示/root/liuchao/hello下面的文件列表。而如果有另一个客户端同样输入cd hello服务端也在某个地方记住上次浏览到/var/lib因而要给客户显示的是/var/lib/hello。
不光NFS如果浏览翻页我们经常要实现函数next()在一个列表中取下一页但是这就需要服务端记住客户端A上次浏览到2030页了那它调用next()应该显示3040页而客户端B上次浏览到100110页了调用next()应该显示110120页。
上面的例子都是在RPC场景下由服务端来维护状态很多SOAP接口设计的时候也常常按这种模式。这种模式原来没有问题是因为客户端和服务端之间的比例没有失衡。因为一般不会同时有太多的客户端同时连上来所以NFS还能把每个客户端的状态都记住。
公司内部使用的ERP系统如果使用SOAP的方式实现并且服务端为每个登录的用户维护浏览到报表那一页的状态由于一个公司内部的人也不会太多把ERP放在一个强大的物理机上也能记得过来。
但是互联网场景下,客户端和服务端就彻底失衡了。你可以想象“双十一”,多少人同时来购物,作为服务端,它能记得过来吗?当然不可能,只好多个服务端同时提供服务,大家分担一下。但是这就存在一个问题,服务端怎么把自己记住的客户端状态告诉另一个服务端呢?或者说,你让我给你分担工作,你也要把工作的前因后果给我说清楚啊!
那服务端索性就要想了,既然这么多客户端,那大家就分分工吧。服务端就只记录资源的状态,例如文件的状态,报表的状态,库存的状态,而客户端自己维护自己的状态。比如,你访问到哪个目录了啊,报表的哪一页了啊,等等。
这样对于API也有影响也就是说当客户端维护了自己的状态就不能这样调用服务端了。例如客户端说我想访问当前目录下的hello路径。服务端说我怎么知道你的当前路径。所以客户端要先看看自己当前路径是/root/liuchao然后告诉服务端说我想访问/root/liuchao/hello路径。
再比如客户端说我想访问下一页服务端说我怎么知道你当前访问到哪一页了。所以客户端要先看看自己访问到了100110页然后告诉服务器说我想访问110120页。
这就是服务端的无状态化。这样服务端就可以横向扩展了,一百个人一起服务,不用交接,每个人都能处理。
所谓的无状态其实是服务端维护资源的状态客户端维护会话的状态。对于服务端来讲只有资源的状态改变了客户端才调用POST、PUT、DELETE方法来找我如果资源的状态没变只是客户端的状态变了就不用告诉我了对于我来说都是统一的GET。
虽然这只改进了GET但是已经带来了很大的进步。因为对于互联网应用大多数是读多写少的。而且只要服务端的资源状态不变就给了我们缓存的可能。例如可以将状态缓存到接入层甚至缓存到CDN的边缘节点这都是资源状态不变的好处。
按照这种思路对于API的设计就慢慢变成了以资源为核心而非以过程为核心。也就是说客户端只要告诉服务端你想让资源状态最终变成什么样就可以了而不用告诉我过程不用告诉我动作。
还是文件目录的例子。客户端应该访问哪个绝对路径,而非一个动作,我就要进入某个路径。再如,库存的调用,应该查看当前的库存数目,然后减去购买的数量,得到结果的库存数。这个时候应该设置为目标库存数(但是当前库存数要匹配),而非告知减去多少库存。
这种API的设计需要实现幂等因为网络不稳定就会经常出错因而需要重试但是一旦重试就会存在幂等的问题也就是同一个调用多次调用的结果应该一样不能一次支付调用因为调用三次变成了支付三次。不能进入cd a做了三次就变成了cd a/a/a。也不能扣减库存调用了三次就扣减三次库存。
当然按照这种设计模式无论RESTful API还是SOAP API都可以将架构实现成无状态的面向资源的、幂等的、横向扩展的、可缓存的。
但是SOAP的XML正文中是可以放任何动作的。例如XML里面可以写&lt; ADD &gt;&lt; MINUS &gt;等。这就方便使用SOAP的人将大量的动作放在API里面。
RESTful没这么复杂也没给客户提供这么多的可能性正文里的JSON基本描述的就是资源的状态没办法描述动作而且能够出发的动作只有CRUD也即POST、GET、PUT、DELETE也就是对于状态的改变。
所以从接口角度就让你死了这条心。当然也有很多技巧的方法在使用RESTful API的情况下依然提供基于动作的有状态请求这属于反模式了。
## 服务发现问题
对于RESTful API来讲我们已经解决了传输协议的问题——基于HTTP协议约定问题——基于JSON最后要解决的是服务发现问题。
有个著名的基于RESTful API的跨系统调用框架叫Spring Cloud。在Spring Cloud中有一个组件叫 Eureka。传说阿基米德在洗澡时发现浮力原理高兴得来不及穿上裤子跑到街上大喊“Eureka我找到了”所以Eureka是用来实现注册中心的负责维护注册的服务列表。
服务分服务提供方它向Eureka做服务注册、续约和下线等操作注册的主要数据包括服务名、机器IP、端口号、域名等等。
另外一方是服务消费方向Eureka获取服务提供方的注册信息。为了实现负载均衡和容错服务提供方可以注册多个。
当消费方要调用服务的时候会从注册中心读出多个服务来那怎么调用呢当然是RESTful方式了。
Spring Cloud提供一个RestTemplate工具用于将请求对象转换为JSON并发起Rest调用RestTemplate的调用也是分POST、PUT、GET、 DELETE的当结果返回的时候根据返回的JSON解析成对象。
通过这样封装,调用起来也很方便。
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
SOAP过于复杂而且设计是面向动作的因而往往因为架构问题导致并发量上不去。
</li>
<li>
RESTful不仅仅是一个API而且是一种架构模式主要面向资源提供无状态服务有利于横向扩展应对高并发。
</li>
最后,给你留两个思考题:
<li>
在讨论RESTful模型的时候举了一个库存的例子但是这种方法有很大问题那你知道为什么要这样设计吗
</li>
<li>
基于文本的RPC虽然解决了二进制的问题但是它本身也有问题你能举出一些例子吗
</li>
我们的专栏更新到第34讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,207 @@
<audio id="audio" title="第35讲 | 二进制类RPC协议还是叫NBA吧总说全称多费劲" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/46/ea/46bc0b8723c669416304ffe550db3fea.mp3"></audio>
前面我们讲了两个常用文本类的RPC协议对于陌生人之间的沟通用NBA、CBA这样的缩略语会使得协议约定非常不方便。
在讲CDN和DNS的时候我们讲过接入层的设计对于静态资源或者动态资源静态化的部分都可以做缓存。但是对于下单、支付等交易场景还是需要调用API。
对于微服务的架构API需要一个API网关统一的管理。API网关有多种实现方式用Nginx或者OpenResty结合Lua脚本是常用的方式。在上一节讲过的Spring Cloud体系中有个组件Zuul也是干这个的。
## 数据中心内部是如何相互调用的?
API网关用来管理API但是API的实现一般在一个叫作**Controller层**的地方。这一层对外提供API。由于是让陌生人访问的我们能看到目前业界主流的基本都是RESTful的API是面向大规模互联网应用的。
<img src="https://static001.geekbang.org/resource/image/f0/b8/f08ef51889add2c26c57c9edd3db93b8.jpg" alt="">
在Controller之内就是咱们互联网应用的业务逻辑实现。上节讲RESTful的时候说过业务逻辑的实现最好是无状态的从而可以横向扩展但是资源的状态还需要服务端去维护。资源的状态不应该维护在业务逻辑层而是在最底层的持久化层一般会使用分布式数据库和ElasticSearch。
这些服务端的状态例如订单、库存、商品等都是重中之重都需要持久化到硬盘上数据不能丢但是由于硬盘读写性能差因而持久化层往往吞吐量不能达到互联网应用要求的吞吐量因而前面要有一层缓存层使用Redis或者memcached将请求拦截一道不能让所有的请求都进入数据库“中军大营”。
缓存和持久化层之上一般是**基础服务层**,这里面提供一些原子化的接口。例如,对于用户、商品、订单、库存的增删查改,将缓存和数据库对再上层的业务逻辑屏蔽一道。有了这一层,上层业务逻辑看到的都是接口,而不会调用数据库和缓存。因而对于缓存层的扩容,数据库的分库分表,所有的改变,都截止到这一层,这样有利于将来对于缓存和数据库的运维。
再往上就是**组合层**。因为基础服务层只是提供简单的接口,实现简单的业务逻辑,而复杂的业务逻辑,比如下单,要扣优惠券,扣减库存等,就要在组合服务层实现。
这样Controller层、组合服务层、基础服务层就会相互调用这个调用是在数据中心内部的量也会比较大还是使用RPC的机制实现的。
由于服务比较多,需要一个单独的注册中心来做服务发现。服务提供方会将自己提供哪些服务注册到注册中心中去,同时服务消费方订阅这个服务,从而可以对这个服务进行调用。
调用的时候有一个问题这里的RPC调用应该用二进制还是文本类其实文本的最大问题是占用字节数目比较多。比如数字123其实本来二进制8位就够了但是如果变成文本就成了字符串123。如果是UTF-8编码的话就是三个字节如果是UTF-16就是六个字节。同样的信息要多费好多的空间传输起来也更加占带宽时延也高。
因而对于数据中心内部的相互调用,很多公司选型的时候,还是希望采用更加省空间和带宽的二进制的方案。
这里一个著名的例子就是Dubbo服务化框架二进制的RPC方式。
<img src="https://static001.geekbang.org/resource/image/c6/c2/c622af64f47e264453088e79c3e631c2.jpg" alt="">
Dubbo会在客户端的本地启动一个Proxy其实就是客户端的Stub对于远程的调用都通过这个Stub进行封装。
接下来Dubbo会从注册中心获取服务端的列表根据路由规则和负载均衡规则在多个服务端中选择一个最合适的服务端进行调用。
调用服务端的时候首先要进行编码和序列化形成Dubbo头和序列化的方法和参数。将编码好的数据交给网络客户端进行发送网络服务端收到消息后进行解码。然后将任务分发给某个线程进行处理在线程中会调用服务端的代码逻辑然后返回结果。
这个过程和经典的RPC模式何其相似啊
## 如何解决协议约定问题?
接下来我们还是来看RPC的三大问题其中注册发现问题已经通过注册中心解决了。下面我们就来看协议约定问题。
Dubbo中默认的RPC协议是Hessian2。为了保证传输的效率Hessian2将远程调用序列化为二进制进行传输并且可以进行一定的压缩。这个时候你可能会疑惑同为二进制的序列化协议Hessian2和前面的二进制的RPC有什么区别呢这不绕了一圈又回来了吗
Hessian2是解决了一些问题的。例如原来要定义一个协议文件然后通过这个文件生成客户端和服务端的Stub才能进行相互调用这样使得修改就会不方便。Hessian2不需要定义这个协议文件而是自描述的。什么是自描述呢
所谓自描述就是关于调用哪个函数参数是什么另一方不需要拿到某个协议文件、拿到二进制靠它本身根据Hessian2的规则就能解析出来。
原来有协议文件的场景有点儿像两个人事先约定好0表示方法add然后后面会传两个数。服务端把两个数加起来这样一方发送012另一方知道是将1和2加起来但是不知道协议文件的当它收到012的时候完全不知道代表什么意思。
而自描述的场景就像两个人说的每句话都带前因后果。例如传递的是“函数add第一个参数1第二个参数2”。这样无论谁拿到这个表述都知道是什么意思。但是只不过都是以二进制的形式编码的。这其实相当于综合了XML和二进制共同优势的一个协议。
Hessian2是如何做到这一点的呢这就需要去看Hessian2的序列化的[语法描述文件](http://hessian.caucho.com/doc/hessian-serialization.html)。
<img src="https://static001.geekbang.org/resource/image/61/66/618bad147f6933f61ef56cf73d671166.jpg" alt="">
看起来很复杂,编译原理里面是有这样的语法规则的。
我们从Top看起下一层是value直到形成一棵树。这里面的有个思想为了防止歧义每一个类型的起始数字都设置成为独一无二的。这样解析的时候看到这个数字就知道后面跟的是什么了。
这里还是以加法为例子“add(2,3)”被序列化之后是什么样的呢?
```
H x02 x00 # Hessian 2.0
C # RPC call
x03 add # method &quot;add&quot;
x92 # two arguments
x92 # 2 - argument 1
x93 # 3 - argument 2
```
<li>
H开头表示使用的协议是HessionH的二进制是0x48。
</li>
<li>
C开头表示这是一个RPC调用。
</li>
<li>
0x03表示方法名是三个字符。
</li>
<li>
0x92表示有两个参数。其实这里存的应该是2之所以加上0x90就是为了防止歧义表示这里一定是一个int。
</li>
<li>
第一个参数是2编码为0x92第二个参数是3编码为0x93。
</li>
这个就叫作**自描述**。
另外Hessian2是面向对象的可以传输一个对象。
```
class Car {
String color;
String model;
}
out.writeObject(new Car(&quot;red&quot;, &quot;corvette&quot;));
out.writeObject(new Car(&quot;green&quot;, &quot;civic&quot;));
---
C # object definition (#0)
x0b example.Car # type is example.Car
x92 # two fields
x05 color # color field name
x05 model # model field name
O # object def (long form)
x90 # object definition #0
x03 red # color field value
x08 corvette # model field value
x60 # object def #0 (short form)
x05 green # color field value
x05 civic # model field value
```
首先定义这个类。对于类型的定义也传过去因而也是自描述的。类名为example.Car字符长11位因而前面长度为0x0b。有两个成员变量一个是color一个是model字符长5位因而前面长度0x05,。
然后传输的对象引用这个类。由于类定义在位置0因而对象会指向这个位置0编码为0x90。后面red和corvette是两个成员变量的值字符长分别为3和8。
接着又传输一个属于相同类的对象。这时候就不保存对于类的引用了只保存一个0x60表示同上就可以了。
可以看出Hessian2真的是能压缩尽量压缩多一个Byte都不传。
## 如何解决RPC传输问题
接下来我们再来看Dubbo的RPC传输问题。前面我们也说了基于Socket实现一个高性能的服务端是很复杂的一件事情在Dubbo里面使用了Netty的网络传输框架。
Netty是一个非阻塞的基于事件的网络传输框架在服务端启动的时候会监听一个端口并注册以下的事件。
<li>
**连接事件**当收到客户端的连接事件时会调用void connected(Channel channel) 方法。
</li>
<li>
当**可写事件**触发时会调用void sent(Channel channel, Object message),服务端向客户端返回响应数据。
</li>
<li>
当**可读事件**触发时会调用void received(Channel channel, Object message) ,服务端在收到客户端的请求数据。
</li>
<li>
当**发生异常**时会调用void caught(Channel channel, Throwable exception)。
</li>
当事件触发之后,服务端在这些函数中的逻辑,可以选择直接在这个函数里面进行操作,还是将请求分发到线程池去处理。一般异步的数据读写都需要另外的线程池参与,在线程池中会调用真正的服务端业务代码逻辑,返回结果。
Hessian2是Dubbo默认的RPC序列化方式当然还有其他选择。例如Dubbox从Spark那里借鉴Kryo实现高性能的序列化。
到这里我们说了数据中心里面的相互调用。为了高性能大家都愿意用二进制但是为什么后期Spring Cloud又兴起了呢这是因为并发量越来越大已经到了微服务的阶段。同原来的SOA不同微服务粒度更细模块之间的关系更加复杂。
在上面的架构中如果使用二进制的方式进行序列化虽然不用协议文件来生成Stub但是对于接口的定义以及传的对象DTO还是需要共享JAR。因为只有客户端和服务端都有这个JAR才能成功地序列化和反序列化。
但当关系复杂的时候JAR的依赖也变得异常复杂难以维护而且如果在DTO里加一个字段双方的JAR没有匹配好也会导致序列化不成功而且还有可能循环依赖。这个时候一般有两种选择。
第一种,建立严格的项目管理流程。
<li>
不允许循环调用,不允许跨层调用,只准上层调用下层,不允许下层调用上层。
</li>
<li>
接口要保持兼容性,不兼容的接口新添加而非改原来的,当接口通过监控,发现不用的时候,再下掉。
</li>
<li>
升级的时候,先升级服务提供端,再升级服务消费端。
</li>
第二种改用RESTful的方式。
<li>
使用Spring Cloud消费端和提供端不用共享JAR各声明各的只要能变成JSON就行而且JSON也是比较灵活的。
</li>
<li>
使用RESTful的方式性能会降低所以需要通过横向扩展来抵消单机的性能损耗。
</li>
这个时候,就看架构师的选择喽!
## 小结
好了,这节就到这里了,我们来总结一下。
<li>
RESTful API对于接入层和Controller层之外的调用已基本形成事实标准但是随着内部服务之间的调用越来越多性能也越来越重要于是Dubbo的RPC框架有了用武之地。
</li>
<li>
Dubbo通过注册中心解决服务发现问题通过Hessian2序列化解决协议约定的问题通过Netty解决网络传输的问题。
</li>
<li>
在更加复杂的微服务场景下Spring Cloud的RESTful方式在内部调用也会被考虑主要是JAR包的依赖和管理问题。
</li>
最后,给你留两个思考题。
<li>
对于微服务模式下的RPC框架的选择Dubbo和SpringCloud各有优缺点你能做个详细的对比吗
</li>
<li>
到目前为止我们讲过的RPC还没有跨语言调用的场景你知道如果跨语言应该怎么办吗
</li>
我们的专栏更新到第35讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,214 @@
<audio id="audio" title="第36讲 | 跨语言类RPC协议交流之前双方先来个专业术语表" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fc/ce/fc48ac4a5474ab681c2d9e50118924ce.mp3"></audio>
到目前为止咱们讲了四种RPC分别是ONC RPC、基于XML的SOAP、基于JSON的RESTful和Hessian2。
通过学习,我们知道,二进制的传输性能好,文本类的传输性能差一些;二进制的难以跨语言,文本类的可以跨语言;要写协议文件的严谨一些,不写协议文件的灵活一些。虽然都有服务发现机制,有的可以进行服务治理,有的则没有。
我们也看到了RPC从最初的客户端服务器模式最终演进到微服务。对于RPC框架的要求越来越多了具体有哪些要求呢
<li>
首先,传输性能很重要。因为服务之间的调用如此频繁了,还是二进制的越快越好。
</li>
<li>
其次,跨语言很重要。因为服务多了,什么语言写成的都有,而且不同的场景适宜用不同的语言,不能一个语言走到底。
</li>
<li>
最好既严谨又灵活,添加个字段不用重新编译和发布程序。
</li>
<li>
最好既有服务发现也有服务治理就像Dubbo和Spring Cloud一样。
</li>
## Protocol Buffers
这是要多快好省地建设社会主义啊。理想还是要有的嘛这里我就来介绍一个向“理想”迈进的GRPC。
GRPC首先满足二进制和跨语言这两条二进制说明压缩效率高跨语言说明更灵活。但是又是二进制又是跨语言这就相当于两个人沟通你不但说方言还说缩略语人家怎么听懂呢所以最好双方弄一个协议约定文件里面规定好双方沟通的专业术语这样沟通就顺畅多了。
对于GRPC来讲二进制序列化协议是Protocol Buffers。首先需要定义一个协议文件.proto。
我们还看买极客时间专栏的这个例子。
```
syntax = “proto3”;
package com.geektime.grpc
option java_package = “com.geektime.grpc”;
message Order {
required string date = 1;
required string classname = 2;
required string author = 3;
required int price = 4;
}
message OrderResponse {
required string message = 1;
}
service PurchaseOrder {
rpc Purchase (Order) returns (OrderResponse) {}
}
```
在这个协议文件中我们首先指定使用proto3的语法然后我们使用Protocol Buffers的语法定义两个消息的类型一个是发出去的参数一个是返回的结果。里面的每一个字段例如date、classname、author、price都有唯一的一个数字标识这样在压缩的时候就不用传输字段名称了只传输这个数字标识就行了能节省很多空间。
最后定义一个Service里面会有一个RPC调用的声明。
无论使用什么语言都有相应的工具生成客户端和服务端的Stub程序这样客户端就可以像调用本地一样调用远程的服务了。
## 协议约定问题
Protocol Buffers是一款压缩效率极高的序列化协议有很多设计精巧的序列化方法。
对于int类型32位的一般都需要4个Byte进行存储。在Protocol Buffers中使用的是变长整数的形式。对于每一个Byte的8位最高位都有特殊的含义。
如果该位为 1表示这个数字没完后续的Byte也属于这个数字如果该位为 0则这个数字到此结束。其他的7个Bit才是用来表示数字的内容。因此小于128的数字都可以用一个Byte表示大于128的数字比如130会用两个字节来表示。
对于每一个字段使用的是TLVTagLengthValue的存储办法。
其中Tag = (field_num &lt;&lt; 3) | wire_type。field_num就是在proto文件中给每个字段指定唯一的数字标识而wire_type用于标识后面的数据类型。
<img src="https://static001.geekbang.org/resource/image/a6/10/a66aa9ca6c6575f4b335881ae786ba10.jpg" alt="">
例如对于string author = 3在这里field_num为3string的wire_type为2于是 (field_num &lt;&lt; 3) | wire_type = (11000) | 10 = 11010 = 26接下来是Length最后是Value为“liuchao”如果使用UTF-8编码长度为7个字符因而Length为7。
可见在序列化效率方面Protocol Buffers简直做到了极致。
在灵活性方面,这种基于协议文件的二进制压缩协议往往存在更新不方便的问题。例如,客户端和服务器因为需求的改变需要添加或者删除字段。
这一点上Protocol Buffers考虑了兼容性。在上面的协议文件中每一个字段都有修饰符。比如
<li>
required这个值不能为空一定要有这么一个字段出现
</li>
<li>
optional可选字段可以设置也可以不设置如果不设置则使用默认值
</li>
<li>
repeated可以重复0到多次。
</li>
如果我们想修改协议文件对于赋给某个标签的数字例如string author=3这个就不要改变了改变了就不认了也不要添加或者删除required字段因为解析的时候发现没有这个字段就会报错。对于optional和repeated字段可以删除也可以添加。这就给了客户端和服务端升级的可能性。
例如我们在协议里面新增一个string recommended字段表示这个课程是谁推荐的就将这个字段设置为optional。我们可以先升级服务端当客户端发过来消息的时候是没有这个值的将它设置为一个默认值。我们也可以先升级客户端当客户端发过来消息的时候是有这个值的那它将被服务端忽略。
至此,我们解决了协议约定的问题。
## 网络传输问题
接下来,我们来看网络传输的问题。
如果是Java技术栈GRPC的客户端和服务器之间通过Netty Channel作为数据通道每个请求都被封装成HTTP 2.0的Stream。
Netty是一个高效的基于异步IO的网络传输框架这个上一节我们已经介绍过了。HTTP 2.0在[第14讲](https://time.geekbang.org/column/article/9410)我们也介绍过。HTTP 2.0协议将一个TCP的连接切分成多个流每个流都有自己的ID而且流是有优先级的。流可以是客户端发往服务端也可以是服务端发往客户端。它其实只是一个虚拟的通道。
HTTP 2.0还将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码。
通过这两种机制HTTP 2.0的客户端可以将多个请求分到不同的流中,然后将请求内容拆成帧,进行二进制传输。这些帧可以打散乱序发送, 然后根据每个帧首部的流标识符重新组装,并且可以根据优先级,决定优先处理哪个流的数据。
<img src="https://static001.geekbang.org/resource/image/03/dd/03d4a216c024a9e761ed43c6787bf7dd.jpg" alt="">
由于基于HTTP 2.0GRPC和其他的RPC不同可以定义四种服务方法。
第一种,也是最常用的方式是**单向RPC**,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
```
rpc SayHello(HelloRequest) returns (HelloResponse){}
```
第二种方式是**服务端流式RPC**,即服务端返回的不是一个结果,而是一批。客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取,直到没有更多消息为止。
```
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
```
第三种方式为**客户端流式RPC**,也即客户端的请求不是一个,而是一批。客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
```
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}
```
第四种方式为**双向流式 RPC**,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者读写相结合的其他方式。每个数据流里消息的顺序会被保持。
```
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}
```
如果基于HTTP 2.0,客户端和服务器之间的交互方式要丰富得多,不仅可以单方向远程调用,还可以实现当服务端状态改变的时候,主动通知客户端。
至此,传输问题得到了解决。
## 服务发现与治理问题
最后是服务发现与服务治理的问题。
GRPC本身没有提供服务发现的机制需要借助其他的组件发现要访问的服务端在多个服务端之间进行容错和负载均衡。
其实负载均衡本身比较简单LVS、HAProxy、Nginx都可以做关键问题是如何发现服务端并根据服务端的变化动态修改负载均衡器的配置。
在这里我们介绍一种对于GRPC支持比较好的负载均衡器Envoy。其实Envoy不仅仅是负载均衡器它还是一个高性能的C++写的Proxy转发器可以配置非常灵活的转发规则。
这些规则可以是静态的放在配置文件中的在启动的时候加载。要想重新加载一般需要重新启动但是Envoy支持热加载和热重启这在一定程度上缓解了这个问题。
当然最好的方式是将规则设置为动态的放在统一的地方维护。这个统一的地方在Envoy眼中被称为服务发现Discovery Service过一段时间去这里拿一下配置就修改了转发策略。
无论是静态的,还是动态的,在配置里面往往会配置四个东西。
第一个是listener。Envoy既然是Proxy专门做转发就得监听一个端口接入请求然后才能够根据策略转发这个监听的端口就称为listener。
第二个是endpoint是目标的IP地址和端口。这个是Proxy最终将请求转发到的地方。
第三个是cluster。一个cluster是具有完全相同行为的多个endpoint也即如果有三个服务端在运行就会有三个IP和端口但是部署的是完全相同的三个服务它们组成一个cluster从cluster到endpoint的过程称为负载均衡可以轮询。
第四个是route。有时候多个cluster具有类似的功能但是是不同的版本号可以通过route规则选择将请求路由到某一个版本号也即某一个cluster。
如果是静态的则将后端的服务端的IP地址拿到然后放在配置文件里面就可以了。
如果是动态的就需要配置一个服务发现中心这个服务发现中心要实现Envoy的APIEnvoy可以主动去服务发现中心拉取转发策略。
<img src="https://static001.geekbang.org/resource/image/ef/ce/ef916f46dc293ac2d5739b496f0b27ce.jpg" alt="">
看来Envoy进程和服务发现中心之间要经常相互通信互相推送数据所以Envoy在控制面和服务发现中心沟通的时候就可以使用GRPC也就天然具备在用户面支撑GRPC的能力。
Envoy如果复杂的配置都能干什么事呢
一种常见的规则是**配置路由策略**。例如后端的服务有两个版本可以通过配置Envoy的route来设置两个版本之间也即两个cluster之间的route规则一个占99%的流量一个占1%的流量。
另一种常见的规则就是**负载均衡策略**。对于一个cluster下的多个endpoint可以配置负载均衡机制和健康检查机制当服务端新增了一个或者挂了一个都能够及时配置Envoy进行负载均衡。
<img src="https://static001.geekbang.org/resource/image/50/3c/50443d6848f890e475e71be11489d33c.jpg" alt="">
所有这些节点的变化都会上传到注册中心,所有这些策略都可以通过注册中心进行下发,所以,更严格的意义上讲,注册中心可以称为**注册治理中心**。
Envoy这么牛是不是能够将服务之间的相互调用全部由它代理如果这样服务也不用像Dubbo或者Spring Cloud一样自己感知到注册中心自己注册自己治理对应用干预比较大。
如果我们的应用能够意识不到服务治理的存在就可以直接进行GRPC的调用。
这就是未来服务治理的趋势**Serivce Mesh**也即应用之间的相互调用全部由Envoy进行代理服务之间的治理也被Envoy进行代理完全将服务治理抽象出来到平台层解决。
<img src="https://static001.geekbang.org/resource/image/15/02/15e254a8e92e031b20feb6ebdcc32402.jpg" alt="">
至此RPC框架中有治理功能的Dubbo、Spring Cloud、Service Mesh就聚齐了。
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
GRPC是一种二进制性能好跨语言还灵活同时可以进行服务治理的多快好省的RPC框架唯一不足就是还是要写协议文件。
</li>
<li>
GRPC序列化使用Protocol Buffers网络传输使用HTTP 2.0服务治理可以使用基于Envoy的Service Mesh。
</li>
最后,给你留一个思考题吧。
在讲述Service Mesh的时候我们说了希望Envoy能够在服务不感知的情况下将服务之间的调用全部代理了你知道怎么做到这一点吗
我们《趣谈网络协议》专栏已经接近尾声了。你还记得专栏开始,我们讲过的那个“双十一”下单的故事吗?
下节开始,我会将这个过程涉及的网络协议细节,全部串联起来,给你还原一个完整的网络协议使用场景。信息量会很大,做好准备哦,我们下期见!

View File

@@ -0,0 +1,120 @@
<audio id="audio" title="第5讲 | 从物理层到MAC层如何在宿舍里自己组网玩联机游戏" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c0/4e/c01745571018a2a0dd62c5fa269d644e.mp3"></audio>
上一节我们见证了IP地址的诞生或者说是整个操作系统的诞生。一旦机器有了IP就可以在网络的环境里和其他的机器展开沟通了。
故事就从我的大学宿舍开始讲起吧。作为一个八零后,我要暴露年龄了。
我们宿舍四个人,大一的时候学校不让上网,不给开通网络。但是,宿舍有一个人比较有钱,率先买了一台电脑。那买了电脑干什么呢?
首先,有单机游戏可以打,比如说《拳皇》。两个人用一个键盘,照样打得火热。后来有第二个人买了电脑,那两台电脑能不能连接起来呢?你会说,当然能啊,买个路由器不就行了。
现在一台家用路由器非常便宜,一百多块的事情。那时候路由器绝对是奢侈品。一直到大四,我们宿舍都没有买路由器。可能是因为那时候技术没有现在这么发达,导致我对网络技术的认知是逐渐深入的,而且每一层都是实实在在接触到的。
## 第一层(物理层)
使用路由器,是在第三层上。我们先从第一层物理层开始说。
物理层能折腾啥?现在的同学可能想不到,我们当时去学校配电脑的地方买网线,卖网线的师傅都会问,你的网线是要电脑连电脑啊,还是电脑连网口啊?
我们要的是电脑连电脑。这种方式就是一根网线,有两个头。一头插在一台电脑的网卡上,另一头插在另一台电脑的网卡上。但是在当时,普通的网线这样是通不了的,所以水晶头要做交叉线,用的就是所谓的**13**、**26交叉接法**。
水晶头的第1、2和第3、6脚它们分别起着收、发信号的作用。将一端的1号和3号线、2号和6号线互换一下位置就能够在物理层实现一端发送的信号另一端能收到。
当然电脑连电脑除了网线要交叉还需要配置这两台电脑的IP地址、子网掩码和默认网关。这三个概念上一节详细描述过了。要想两台电脑能够通信这三项必须配置成为一个网络可以一个是192.168.0.1/24另一个是192.168.0.2/24否则是不通的。
这里我想问你一个问题两台电脑之间的网络包包含MAC层吗当然包含要完整。IP层要封装了MAC层才能将包放入物理层。
到此为止,两台电脑已经构成了一个最小的**局域网**,也即**LAN。**可以玩联机局域网游戏啦!
等到第三个哥们也买了一台电脑,怎么把三台电脑连在一起呢?
先别说交换机,当时交换机也贵。有一个叫做**Hub**的东西,也就是**集线器**。这种设备有多个口,可以将宿舍里的多台电脑连接起来。但是,和交换机不同,集线器没有大脑,它完全在物理层工作。它会将自己收到的每一个字节,都复制到其他端口上去。这是第一层物理层联通的方案。
## 第二层(数据链路层)
你可能已经发现问题了。Hub采取的是广播的模式如果每一台电脑发出的包宿舍的每个电脑都能收到那就麻烦了。这就需要解决几个问题
1. 这个包是发给谁的?谁应该接收?
1. 大家都在发,会不会产生混乱?有没有谁先发、谁后发的规则?
1. 如果发送的时候出现了错误,怎么办?
这几个问题都是第二层数据链路层也即MAC层要解决的问题。**MAC**的全称是**Medium Access Control**,即**媒体访问控制。<strong>控制什么呢?其实就是控制在往媒体上发数据的时候,谁先发、谁后发的问题。防止发生混乱。这解决的是第二个问题。这个问题中的规则,学名叫**多路访问</strong>。有很多算法可以解决这个问题。就像车管所管束马路上跑的车,能想的办法都想过了。
比如接下来这三种方式:
<li>
方式一:分多个车道。每个车一个车道,你走你的,我走我的。这在计算机网络里叫作**信道划分;**
</li>
<li>
方式二:今天单号出行,明天双号出行,轮着来。这在计算机网络里叫作**轮流协议;**
</li>
<li>
方式三:不管三七二十一,有事儿先出门,发现特堵,就回去。错过高峰再出。我们叫作**随机接入协议。**著名的以太网,用的就是这个方式。
</li>
解决了第二个问题就是解决了媒体接入控制的问题MAC的问题也就解决好了。这和MAC地址没什么关系。
接下来要解决第一个问题:发给谁,谁接收?这里用到一个物理地址,叫作**链路层地址。<strong>但是因为第二层主要解决媒体接入控制的问题,所以它常被称为**MAC地址</strong>
解决第一个问题就牵扯到第二层的网络包**格式**。对于以太网第二层的最开始就是目标的MAC地址和源的MAC地址。
<img src="https://static001.geekbang.org/resource/image/80/41/8072e4885b0cbc6cb5384ea84d487e41.jpg" alt="">
接下来是**类型**大部分的类型是IP数据包然后IP里面包含TCP、UDP以及HTTP等这都是里层封装的事情。
有了这个目标MAC地址数据包在链路上广播MAC的网卡才能发现这个包是给它的。MAC的网卡把包收进来然后打开IP包发现IP地址也是自己的再打开TCP包发现端口是自己也就是80而nginx就是监听80。
于是将请求提交给nginxnginx返回一个网页。然后将网页需要发回请求的机器。然后层层封装最后到MAC层。因为来的时候有源MAC地址返回的时候源MAC就变成了目标MAC再返给请求的机器。
对于以太网,第二层的最后面是**CRC**,也就是**循环冗余检测**。通过XOR异或的算法来计算整个包是否在发送的过程中出现了错误主要解决第三个问题。
这里还有一个没有解决的问题当源机器知道目标机器的时候可以将目标地址放入包里面如果不知道呢一个广播的网络里面接入了N台机器我怎么知道每个MAC地址是谁呢这就是**ARP协议**也就是已知IP地址求MAC地址的协议。
<img src="https://static001.geekbang.org/resource/image/56/37/561324a275460a4abbc15e73a476e037.jpg" alt="">
在一个局域网里面当知道了IP地址不知道MAC怎么办呢靠“吼”。
<img src="https://static001.geekbang.org/resource/image/48/ad/485b5902066131de547acbcf3579c4ad.jpg" alt="">
广而告之发送一个广播包谁是这个IP谁来回答。具体询问和回答的报文就像下面这样
<img src="https://static001.geekbang.org/resource/image/1f/9b/1f7cfe6046c5df606cfbb6bb6c7f899b.jpg" alt="">
为了避免每次都用ARP请求机器本地也会进行ARP缓存。当然机器会不断地上线下线IP也可能会变所以ARP的MAC地址缓存过一段时间就会过期。
## 局域网
好了至此我们宿舍四个电脑就组成了一个局域网。用Hub连接起来就可以玩局域网版的《魔兽争霸》了。
<img src="https://static001.geekbang.org/resource/image/33/ac/33d180e376439ca10e3f126eb2e36bac.jpg" alt="">
打开游戏,进入“局域网选项”,选择一张地图,点击“创建游戏”,就可以进入这张地图的房间中。等同一个局域网里的其他小伙伴加入后,游戏就可以开始了。
这种组网的方法对一个宿舍来说没有问题但是一旦机器数目增多问题就出现了。因为Hub是广播的不管某个接口是否需要所有的Bit都会被发送出去然后让主机来判断是不是需要。这种方式路上的车少就没问题车一多产生冲突的概率就提高了。而且把不需要的包转发过去纯属浪费。看来Hub这种不管三七二十一都转发的设备是不行了需要点儿智能的。因为每个口都只连接一台电脑这台电脑又不怎么换IP和MAC地址只要记住这台电脑的MAC地址如果目标MAC地址不是这台电脑的这个口就不用转发了。
谁能知道目标MAC地址是否就是连接某个口的电脑的MAC地址呢这就需要一个能把MAC头拿下来检查一下目标MAC地址然后根据策略转发的设备按第二节课中讲过的这个设备显然是个二层设备我们称为**交换机**。
交换机怎么知道每个口的电脑的MAC地址呢这需要交换机会学习。
一台MAC1电脑将一个包发送给另一台MAC2电脑当这个包到达交换机的时候一开始交换机也不知道MAC2的电脑在哪个口所以没办法它只能将包转发给除了来的那个口之外的其他所有的口。但是这个时候交换机会干一件非常聪明的事情就是交换机会记住MAC1是来自一个明确的口。以后有包的目的地址是MAC1的直接发送到这个口就可以了。
当交换机作为一个关卡一样过了一段时间之后就有了整个网络的一个结构了这个时候基本上不用广播了全部可以准确转发。当然每个机器的IP地址会变所在的口也会变因而交换机上的学习的结果我们称为**转发表**,是有一个过期时间的。
有了交换机,一般来说,你接个几十台、上百台机器打游戏,应该没啥问题。你可以组个战队了。能上网了,就可以玩网游了。
## 小结
好了,今天的内容差不多了,我们来总结一下,有三个重点需要你记住:
第一MAC层是用来解决多路访问的堵车问题的
第二ARP是通过吼的方式来寻找目标MAC地址的吼完之后记住一段时间这个叫作缓存
第三交换机是有MAC地址学习能力的学完了它就知道谁在哪儿了不用广播了。
最后,给你留两个思考题吧。
1. 在二层中我们讲了ARP协议即已知IP地址求MAC还有一种RARP协议即已知MAC求IP的你知道它可以用来干什么吗
1. 如果一个局域网里面有多个交换机ARP广播的模式会出现什么问题呢
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,155 @@
<audio id="audio" title="第6讲 | 交换机与VLAN办公室太复杂我要回学校" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/0c/2312a98fcc46cea1d9f5e3f3ed16df0c.mp3"></audio>
上一次我们在宿舍里组建了一个本地的局域网LAN可以愉快地玩游戏了。这是一个非常简单的场景因为只有一台交换机电脑数目很少。今天让我们切换到一个稍微复杂一点的场景办公室。
## 拓扑结构是怎么形成的?
我们常见到的办公室大多是一排排的桌子,每个桌子都有网口,一排十几个座位就有十几个网口,一个楼层就会有几十个甚至上百个网口。如果算上所有楼层,这个场景自然比你宿舍里的复杂多了。具体哪里复杂呢?我来给你具体讲解。
首先,这个时候,一个交换机肯定不够用,需要多台交换机,交换机之间连接起来,就形成一个稍微复杂的**拓扑结构**。
我们先来看**两台交换机**的情形。两台交换机连接着三个局域网每个局域网上都有多台机器。如果机器1只知道机器4的IP地址当它想要访问机器4把包发出去的时候它必须要知道机器4的MAC地址。
<img src="https://static001.geekbang.org/resource/image/08/29/0867321c36cc52bd3dd4d7622583fa29.jpg" alt="">
于是机器1发起广播机器2收到这个广播但是这不是找它的所以没它什么事。交换机A一开始是不知道任何拓扑信息的在它收到这个广播后采取的策略是除了广播包来的方向外它还要转发给其他所有的网口。于是机器3也收到广播信息了但是这和它也没什么关系。
当然交换机B也是能够收到广播信息的但是这时候它也是不知道任何拓扑信息的因而也是进行广播的策略将包转发到局域网三。这个时候机器4和机器5都收到了广播信息。机器4主动响应说这是找我的这是我的MAC地址。于是一个ARP请求就成功完成了。
在上面的过程中交换机A和交换机B都是能够学习到这样的信息机器1是在左边这个网口的。当了解到这些拓扑信息之后情况就好转起来。当机器2要访问机器1的时候机器2并不知道机器1的MAC地址所以机器2会发起一个ARP请求。这个广播消息会到达机器1也同时会到达交换机A。这个时候交换机A已经知道机器1是不可能在右边的网口的所以这个广播信息就不会广播到局域网二和局域网三。
当机器3要访问机器1的时候也需要发起一个广播的ARP请求。这个时候交换机A和交换机B都能够收到这个广播请求。交换机A当然知道主机A是在左边这个网口的所以会把广播消息转发到局域网一。同时交换机B收到这个广播消息之后由于它知道机器1是不在右边这个网口的所以不会将消息广播到局域网三。
## 如何解决常见的环路问题?
这样看起来,两台交换机工作得非常好。随着办公室越来越大,交换机数目肯定越来越多。当整个拓扑结构复杂了,这么多网线,绕过来绕过去,不可避免地会出现一些意料不到的情况。其中常见的问题就是**环路问题**。
例如这个图,当两个交换机将两个局域网同时连接起来的时候。你可能会觉得,这样反而有了高可用性。但是却不幸地出现了环路。出现了环路会有什么结果呢?
<img src="https://static001.geekbang.org/resource/image/1f/ea/1f909508a8253d4842ffe962883421ea.jpg" alt="">
我们来想象一下机器1访问机器2的过程。一开始机器1并不知道机器2的MAC地址所以它需要发起一个ARP的广播。广播到达机器2机器2会把MAC地址返回来看起来没有这两个交换机什么事情。
但是问题来了这两个交换机还是都能够收到广播包的。交换机A一开始是不知道机器2在哪个局域网的所以它会把广播消息放到局域网二在局域网二广播的时候交换机B右边这个网口也是能够收到广播消息的。交换机B会将这个广播信息发送到局域网一。局域网一的这个广播消息又会到达交换机A左边的这个接口。交换机A这个时候还是不知道机器2在哪个局域网于是将广播包又转发到局域网二。左转左转左转好像是个圈哦。
可能有人会说,当两台交换机都能够逐渐学习到拓扑结构之后,是不是就可以了?
别想了压根儿学不会的。机器1的广播包到达交换机A和交换机B的时候本来两个交换机都学会了机器1是在局域网一的但是当交换机A将包广播到局域网二之后交换机B右边的网口收到了来自交换机A的广播包。根据学习机制这彻底损坏了交换机B的三观刚才机器1还在左边的网口呢怎么又出现在右边的网口呢那肯定是机器1换位置了于是就误会了交换机B就学会了机器1是从右边这个网口来的把刚才学习的那一条清理掉。同理交换机A右边的网口也能收到交换机B转发过来的广播包同样也误会了于是也学会了机器1从右边的网口来不是从左边的网口来。
然而当广播包从左边的局域网一广播的时候两个交换机再次刷新三观原来机器1是在左边的过一会儿又发现不对是在右边的过一会又发现不对是在左边的。
这还是一个包转来转去,每台机器都会发广播包,交换机转发也会复制广播包,当广播包越来越多的时候,按照上一节讲过一个共享道路的算法,也就是路会越来越堵,最后谁也别想走。所以,必须有一个方法解决环路的问题,怎么破除环路呢?
## STP协议中那些难以理解的概念
在数据结构中,有一个方法叫做**最小生成树**。有环的我们常称为**图**。将图中的环破了,就生成了**树**。在计算机网络中,生成树的算法叫作**STP**,全称**Spanning Tree Protocol**。
STP协议比较复杂一开始很难看懂但是其实这是一场血雨腥风的武林比武或者华山论剑最终决出五岳盟主的方式。
<img src="https://static001.geekbang.org/resource/image/47/23/47baa69073b38357e0ae3f88ff74dd23.jpg" alt="">
在STP协议里面有很多概念译名就非常拗口但是我一作比喻你很容易就明白了。
<li>
**Root Bridge**,也就是**根交换机**。这个比较容易理解,可以比喻为“掌门”交换机,是某棵树的老大,是掌门,最大的大哥。
</li>
<li>
**Designated Bridges**,有的翻译为**指定交换机**。这个比较难理解,可以想像成一个“小弟”,对于树来说,就是一棵树的树枝。所谓“指定”的意思是,我拜谁做大哥,其他交换机通过这个交换机到达根交换机,也就相当于拜他做了大哥。这里注意是树枝,不是叶子,因为叶子往往是主机。
</li>
<li>
**Bridge Protocol Data Units BPDU** **网桥协议数据单元**。可以比喻为“相互比较实力”的协议。行走江湖比的就是武功拼的就是实力。当两个交换机碰见的时候也就是相连的时候就需要互相比一比内力了。BPDU只有掌门能发已经隶属于某个掌门的交换机只能传达掌门的指示。
</li>
<li>
**Priority Vector****优先级向量**。可以比喻为实力 值越小越牛。实力是啥就是一组ID数目[Root Bridge ID, Root Path Cost, Bridge ID, and Port ID]。为什么这样设计呢这是因为要看怎么来比实力。先看Root Bridge ID。拿出老大的ID看看发现掌门一样那就是师兄弟再比Root Path Cost也即我距离我的老大的距离也就是拿和掌门关系比看同一个门派内谁和老大关系铁最后比Bridge ID比我自己的ID拿自己的本事比。
</li>
## STP的工作过程是怎样的
接下来我们来看STP的工作过程。
一开始江湖纷争异常混乱。大家都觉得自己是掌门谁也不服谁。于是所有的交换机都认为自己是掌门每个网桥都被分配了一个ID。这个ID里有管理员分配的优先级当然网络管理员知道哪些交换机贵哪些交换机好就会给它们分配高的优先级。这种交换机生下来武功就很高起步就是乔峰。
<img src="https://static001.geekbang.org/resource/image/66/2b/66237be156bea81a801dca8d507c1e2b.jpg" alt="">
既然都是掌门互相都连着网线就互相发送BPDU来比功夫呗。这一比就发现有人是岳不群有人是封不平赢的接着当掌门输的就只好做小弟了。当掌门的还会继续发BPDU而输的人就没有机会了。它们只有在收到掌门发的BPDU的时候转发一下表示服从命令。
<img src="https://static001.geekbang.org/resource/image/5d/47/5da50b7e328ea3cf8f90430f1deb3f47.jpg" alt="">
数字表示优先级。就像这个图5和6碰见了6的优先级低所以乖乖做小弟。于是一个小门派形成5是掌门6是小弟。其他诸如1-7、2-8、3-4这样的小门派也诞生了。于是江湖出现了很多小的门派小的门派接着合并。
合并的过程会出现以下四种情形,我分别来介绍。
### 情形一:掌门遇到掌门
当5碰到了1掌门碰见掌门1觉得自己是掌门5也刚刚跟别人PK完成为掌门。这俩掌门比较功夫最终1胜出。于是输掉的掌门5就会率领所有的小弟归顺。结果就是1成为大掌门。
<img src="https://static001.geekbang.org/resource/image/fb/56/fb0e19a14e00b5825dac11d359ffe056.jpg" alt="">
### 情形二:同门相遇
同门相遇可以是掌门与自己的小弟相遇这说明存在“环”了。这个小弟已经通过其他门路拜在你门下结果你还不认识就PK了一把。结果掌门发现这个小弟功夫不错不应该级别这么低就把它招到门下亲自带那这个小弟就相当于升职了。
我们再来看假如1和6相遇。6原来就拜在1的门下只不过6的上司是55的上司是1。1发现6距离我才只有2比从5这里过来的5=4+1近多了那6就直接汇报给我吧。于是5和6分别汇报给1。
<img src="https://static001.geekbang.org/resource/image/1e/d8/1ef3c9fb5b7d386c519402202233a8d8.jpg" alt="">
同门相遇还可以是小弟相遇。这个时候就要比较谁和掌门的关系近当然近的当大哥。刚才5和6同时汇报给1了后来5和6在比较功夫的时候发现5你直接汇报给1距离是4如果5汇报给6再汇报给1距离只有2+1=3所以5干脆拜6为上司。
### 情形三:掌门与其他帮派小弟相遇
小弟拿本帮掌门和这个掌门比较,赢了,这个掌门拜入门来。输了,会拜入新掌门,并且逐渐拉拢和自己连接的兄弟,一起弃暗投明。
<img src="https://static001.geekbang.org/resource/image/8e/da/8e852604ac81ab453115470edb9e70da.jpg" alt="">
例如2和7相遇虽然7是小弟2是掌门。就个人武功而言2比7强但是7的掌门是1比2牛所以没办法2要拜入7的门派并且连同自己的小弟都一起拜入。
### 情形四:不同门小弟相遇
各自拿掌门比较,输了的拜入赢的门派,并且逐渐将与自己连接的兄弟弃暗投明。<br>
<img src="https://static001.geekbang.org/resource/image/fd/bf/fdab777fb2f69666e1fd5d838278b1bf.jpg" alt="">
例如5和4相遇。虽然4的武功好于5但是5的掌门是1比4牛于是4拜入5的门派。后来当3和4相遇的时候3发现4已经叛变了4说我现在老大是1比你牛要不你也来吧于是3也拜入1。
最终,生成一棵树,武林一统,天下太平。但是天下大势,分久必合,合久必分,天下统一久了,也会有相应的问题。
### 如何解决广播问题和安全问题?
毕竟机器多了交换机也多了就算交换机比Hub智能一些但是还是难免有广播的问题一大波机器相关的部门、不相关的部门广播一大堆性能就下来了。就像一家公司创业的时候一二十个人坐在一个会议室有事情大家讨论一下非常方便。但是如果变成了50个人全在一个会议室里面吵吵就会乱得不得了。
你们公司有不同的部门有的部门需要保密的比如人事部门肯定要讨论升职加薪的事儿。由于在同一个广播域里面很多包都会在一个局域网里面飘啊飘碰到了一个会抓包的程序员就能抓到这些包如果没有加密就能看到这些敏感信息了。还是上面的例子50个人在一个会议室里面七嘴八舌地讨论其中有两个HR那他们讨论的问题肯定被其他人偷偷听走了。
那咋办,分部门,分会议室呗。那我们就来看看怎么分。
有两种分的方法,一个是**物理隔离**。每个部门设一个单独的会议室,对应到网络方面,就是每个部门有单独的交换机,配置单独的子网,这样部门之间的沟通就需要路由器了。路由器咱们还没讲到,以后再说。这样的问题在于,有的部门人多,有的部门人少。人少的部门慢慢人会变多,人多的部门也可能人越变越少。如果每个部门有单独的交换机,口多了浪费,少了又不够用。
另外一种方式是**虚拟隔离**,就是用我们常说的**VLAN**,或者叫**虚拟局域网**。使用VLAN一个交换机上会连属于多个局域网的机器那交换机怎么区分哪个机器属于哪个局域网呢<br>
<img src="https://static001.geekbang.org/resource/image/ba/60/ba720f6988558f95c381f4deaab11660.jpg" alt="">
我们只需要在原来的二层的头上加一个TAG里面有一个VLAN ID一共12位。为什么是12位呢因为12位可以划分4096个VLAN。这样是不是还不够啊。现在的情况证明目前云计算厂商里面绝对不止4096个用户。当然每个用户需要一个VLAN了啊怎么办呢这个我们在后面的章节再说。
如果我们买的交换机是支持VLAN的当这个交换机把二层的头取下来的时候就能够识别这个VLAN ID。这样只有相同VLAN的包才会互相转发不同VLAN的包是看不到的。这样广播问题和安全问题就都能够解决了。<br>
<img src="https://static001.geekbang.org/resource/image/5c/4a/5c207a6e2c1c9881823b04e648f4ba4a.jpg" alt="">
我们可以设置交换机每个口所属的VLAN。如果某个口坐的是程序员他们属于VLAN 10如果某个口坐的是人事他们属于VLAN 20如果某个口坐的是财务他们属于VLAN 30。这样财务发的包交换机只会转发到VLAN 30的口上。程序员啊你就监听VLAN 10吧里面除了代码啥都没有。
而且对于交换机来讲每个VLAN的口都是可以重新设置的。一个财务走了把他所在座位的口从VLAN 30移除掉来了一个程序员坐在财务的位置上就把这个口设置为VLAN 10十分灵活。
有人会问交换机之间怎么连接呢将两个交换机连接起来的口应该设置成什么VLAN呢对于支持VLAN的交换机有一种口叫作**Trunk口**。它可以转发属于任何VLAN的口。交换机之间可以通过这种口相互连接。
好了,解决这么多交换机连接在一起的问题,办公室的问题似乎搞定了。然而这只是一般复杂的场景,因为你能接触到的网络,到目前为止,不管是你的台式机,还是笔记本所连接的网络,对于带宽、高可用等都要求不高。就算出了问题,一会儿上不了网,也不会有什么大事。
我们在宿舍、学校或者办公室,经常会访问一些网站,这些网站似乎永远不会“挂掉”。那是因为这些网站都生活在一个叫做数据中心的地方,那里的网络世界更加复杂。在后面的章节,我会为你详细讲解。
## 小结
好了,这节就到这里,我们这里来总结一下:
- 当交换机的数目越来越多的时候会遭遇环路问题让网络包迷路这就需要使用STP协议通过华山论剑比武的方式将有环路的图变成没有环路的树从而解决环路问题。
- 交换机数目多会面临隔离问题可以通过VLAN形成虚拟局域网从而解决广播问题和安全问题。
最后,给你留两个思考题。
1. STP协议能够很好地解决环路问题但是也有它的缺点你能举几个例子吗
1. 在一个比较大的网络中,如果两台机器不通,你知道应该用什么方式调试吗?
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,118 @@
<audio id="audio" title="第7讲 | ICMP与ping投石问路的侦察兵" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/24/ad/2486b041bd60d50f2c45a19ce827e8ad.mp3"></audio>
无论是在宿舍,还是在办公室,或者运维一个数据中心,我们常常会遇到网络不通的问题。那台机器明明就在那里,你甚至都可以通过机器的终端连上去看。它看着好好的,可是就是连不上去,究竟是哪里出了问题呢?
## ICMP协议的格式
一般情况下你会想到ping一下。那你知道ping是如何工作的吗
ping是基于ICMP协议工作的。**ICMP**全称**Internet Control Message Protocol**,就是**互联网控制报文协议**。这里面的关键词是“控制”,那具体是怎么控制的呢?
网络包在异常复杂的网络环境中传输时,常常会遇到各种各样的问题。当遇到问题的时候,总不能“死个不明不白”,要传出消息来,报告情况,这样才可以调整传输策略。这就相当于我们经常看到的电视剧里,古代行军的时候,为将为帅者需要通过侦察兵、哨探或传令兵等人肉的方式来掌握情况,控制整个战局。
ICMP报文是封装在IP包里面的。因为传输指令的时候肯定需要源地址和目标地址。它本身非常简单。因为作为侦查兵要轻装上阵不能携带大量的包袱。
<img src="https://static001.geekbang.org/resource/image/20/e2/201589bb205c5b00ad42e0081aa46fe2.jpg" alt="">
ICMP报文有很多的类型不同的类型有不同的代码。**最常用的类型是主动请求为8主动请求的应答为0**。
## 查询报文类型
我们经常在电视剧里听到这样的话:主帅说,来人哪!前方战事如何,快去派人打探,一有情况,立即通报!
这种是主帅发起的主动查看敌情对应ICMP的**查询报文类型**。例如,常用的**ping就是查询报文是一种主动请求并且获得主动应答的ICMP协议。**所以ping发的包也是符合ICMP协议格式的只不过它在后面增加了自己的格式。
对ping的主动请求进行网络抓包称为**ICMP ECHO REQUEST。<strong>同理主动请求的回复,称为**ICMP ECHO REPLY</strong>。比起原生的ICMP这里面多了两个字段一个是**标识符**。这个很好理解,你派出去两队侦查兵,一队是侦查战况的,一队是去查找水源的,要有个标识才能区分。另一个是**序号**你派出去的侦查兵都要编个号。如果派出去10个回来10个就说明前方战况不错如果派出去10个回来2个说明情况可能不妙。
在选项数据中ping还会存放发送请求的时间值来计算往返时间说明路程的长短。
## 差错报文类型
当然也有另外一种方式,就是差错报文。
主帅骑马走着走着突然来了一匹快马上面的小兵气喘吁吁的报告主公不好啦张将军遭遇埋伏全军覆没啦这种是异常情况发起的来报告发生了不好的事情对应ICMP的**差错报文类型**。
我举几个ICMP差错报文的例子**终点不可达为3源抑制为4超时为11重定向为5**。这些都是什么意思呢?我给你具体解释一下。
**第一种是终点不可达**。小兵:报告主公,您让把粮草送到张将军那里,结果没有送到。
如果你是主公你肯定会问为啥送不到具体的原因在代码中表示就是网络不可达代码为0主机不可达代码为1协议不可达代码为2端口不可达代码为3需要进行分片但设置了不分片位代码为4。
具体的场景就像这样:
- 网络不可达:主公,找不到地方呀?
- 主机不可达:主公,找到地方没这个人呀?
- 协议不可达主公找到地方找到人口号没对上人家天王盖地虎我说12345
- 端口不可达:主公,找到地方,找到人,对了口号,事儿没对上,我去送粮草,人家说他们在等救兵。
- 需要进行分片但设置了不分片位:主公,走到一半,山路狭窄,想换小车,但是您的将令,严禁换小车,就没办法送到了。
**第二种是源站抑制**,也就是让源站放慢发送速度。小兵:报告主公,您粮草送的太多了吃不完。
**第三种是时间超时**,也就是超过网络包的生存时间还是没到。小兵:报告主公,送粮草的人,自己把粮草吃完了,还没找到地方,已经饿死啦。
**第四种是路由重定向**,也就是让下次发给另一个路由器。小兵:报告主公,上次送粮草的人本来只要走一站地铁,非得从五环绕,下次别这样了啊。
差错报文的结构相对复杂一些。除了前面还是IPICMP的前8字节不变后面则跟上出错的那个IP包的IP头和IP正文的前8个字节。
而且这类侦查兵特别恪尽职守,不但自己返回来报信,还把一部分遗物也带回来。
- 侦察兵:报告主公,张将军已经战死沙场,这是张将军的印信和佩剑。
- 主公神马张将军是怎么死的可以查看ICMP的前8字节没错这是张将军的剑是他的剑IP数据包的头及正文前8字节
## ping查询报文类型的使用
接下来我们重点来看ping的发送和接收过程。
<img src="https://static001.geekbang.org/resource/image/57/21/57a77fb89bc4a5653842276c70c0d621.jpg" alt="">
假定主机A的IP地址是192.168.1.1主机B的IP地址是192.168.1.2它们都在同一个子网。那当你在主机A上运行“ping 192.168.1.2”后,会发生什么呢?
ping命令执行的时候源主机首先会构建一个ICMP请求数据包ICMP数据包内包含多个字段。最重要的是两个第一个是**类型字段**,对于请求数据包而言该字段为 8另外一个是**顺序号**主要用于区分连续ping的时候发出的多个数据包。每发出一个请求数据包顺序号会自动加1。为了能够计算往返时间RTT它会在报文的数据部分插入发送时间。
然后由ICMP协议将这个数据包连同地址192.168.1.2一起交给IP层。IP层将以192.168.1.2作为目的地址本机IP地址作为源地址加上一些其他控制信息构建一个IP数据包。
接下来需要加入MAC头。如果在本节ARP映射表中查找出IP地址192.168.1.2所对应的MAC地址则可以直接使用如果没有则需要发送ARP协议查询MAC地址获得MAC地址后由数据链路层构建一个数据帧目的地址是IP层传过来的MAC地址源地址则是本机的MAC地址还要附加上一些控制信息依据以太网的介质访问规则将它们传送出去。
主机B收到这个数据帧后先检查它的目的MAC地址并和本机的MAC地址对比如符合则接收否则就丢弃。接收后检查该数据帧将IP数据包从帧中提取出来交给本机的IP层。同样IP层检查后将有用的信息提取后交给ICMP协议。
主机B会构建一个 ICMP 应答包,应答数据包的类型字段为 0顺序号为接收到的请求数据包中的顺序号然后再发送出去给主机A。
在规定的时候间内,源主机如果没有接到 ICMP 的应答包,则说明目标主机不可达;如果接收到了 ICMP 应答包,则说明目标主机可达。此时,源主机会检查,用当前时刻减去该数据包最初从源主机上发出的时刻,就是 ICMP 数据包的时间延迟。
当然这只是最简单的同一个局域网里面的情况。如果跨网段的话还会涉及网关的转发、路由器的转发等等。但是对于ICMP的头来讲是没什么影响的。会影响的是根据目标IP地址选择路由的下一跳还有每经过一个路由器到达一个新的局域网需要换MAC头里面的MAC地址。这个过程后面几节会详细描述这里暂时不多说。
如果在自己的可控范围之内当遇到网络不通的问题的时候除了直接ping目标的IP地址之外还应该有一个清晰的网络拓扑图。并且从理论上来讲应该要清楚地知道一个网络包从源地址到目标地址都需要经过哪些设备然后逐个ping中间的这些设备或者机器。如果可能的话在这些关键点通过tcpdump -i eth0 icmp查看包有没有到达某个点回复的包到达了哪个点可以更加容易推断出错的位置。
经常会遇到一个问题如果不在我们的控制范围内很多中间设备都是禁止ping的但是ping不通不代表网络不通。这个时候就要使用telnet通过其他协议来测试网络是否通这个就不在本篇的讲述范围了。
说了这么多你应该可以看出ping这个程序是使用了ICMP里面的ECHO REQUEST和ECHO REPLY类型的。
## Traceroute差错报文类型的使用
那其他的类型呢是不是只有真正遇到错误的时候才能收到呢那也不是有一个程序Traceroute是个“大骗子”。它会使用ICMP的规则故意制造一些能够产生错误的场景。
所以,**Traceroute的第一个作用就是故意设置特殊的TTL来追踪去往目的地时沿途经过的路由器**。Traceroute的参数指向某个目的IP地址它会发送一个UDP的数据包。将TTL设置成1也就是说一旦遇到一个路由器或者一个关卡就表示它“牺牲”了。
如果中间的路由器不止一个当然碰到第一个就“牺牲”。于是返回一个ICMP包也就是网络差错包类型是时间超时。那大军前行就带一顿饭试一试走多远会被饿死然后找个哨探回来报告那我就知道大军只带一顿饭能走多远了。
接下来将TTL设置为2。第一关过了第二关就“牺牲”了那我就知道第二关有多远。如此反复直到到达目的主机。这样Traceroute就拿到了所有的路由器IP。当然有的路由器压根不会回这个ICMP。这也是Traceroute一个公网的地址看不到中间路由的原因。
怎么知道UDP有没有到达目的主机呢Traceroute程序会发送一份UDP数据报给目的主机但它会选择一个不可能的值作为UDP端口号大于30000。当该数据报到达时将使目的主机的 UDP模块产生一份“端口不可达”错误ICMP报文。如果数据报没有到达则可能是超时。
这就相当于故意派人去西天如来那里去请一本《道德经》结果人家信佛不信道消息就会被打出来。被打的消息传回来你就知道西天是能够到达的。为什么不去取《心经》呢因为UDP是无连接的。也就是说这人一派出去你就得不到任何音信。你无法区别到底是半路走丢了还是真的信佛遁入空门了只有让人家打出来你才会得到消息。
**Traceroute还有一个作用是故意设置不分片从而确定路径的MTU。**要做的工作首先是发送分组并设置“不分片”标志。发送的第一个分组的长度正好与出口MTU相等。如果中间遇到窄的关口会被卡住会发送ICMP网络差错包类型为“需要进行分片但设置了不分片位”。其实这是人家故意的好吧每次收到ICMP“不能分片”差错时就减小分组的长度直到到达目标主机。
## 小结
好了,这一节内容差不多了,我来总结一下:
- ICMP相当于网络世界的侦察兵。我讲了两种类型的ICMP报文一种是主动探查的查询报文一种异常报告的差错报文
- ping使用查询报文Traceroute使用差错报文。
最后,给你留两个思考题吧。
1. 当发送的报文出问题的时候会发送一个ICMP的差错报文来报告错误但是如果ICMP的差错报文也出问题了呢
1. 这一节只说了一个局域网互相ping的情况。如果跨路由器、跨网关的过程会是什么样的呢
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,222 @@
<audio id="audio" title="第8讲 | 世界这么大,我想出网关:欧洲十国游与玄奘西行" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fb/b1/fb36e0987432e257d1226e874a77bab1.mp3"></audio>
前几节,我主要跟你讲了宿舍里和办公室里用到的网络协议。你已经有了一些基础,是时候去外网逛逛了!
## 怎么在宿舍上网?
还记得咱们在宿舍的时候买了台交换机几台机器组了一个局域网打游戏吗可惜啊只能打局域网的游戏不能上网啊盼啊盼啊终于盼到大二允许宿舍开通网络了。学校给每个宿舍的网口分配了一个IP地址。这个IP是校园网的IP完全由网管部门控制。宿舍网的IP地址多为192.168.1.x。校园网的IP地址假设是10.10.x.x。
这个时候,你要在宿舍上网,有两个办法:
第一个办法让你们宿舍长再买一个网卡。这个时候你们宿舍长的电脑里就有两张网卡。一张网卡的线插到你们宿舍的交换机上另一张网卡的线插到校园网的网口。而且这张新的网卡的IP地址要按照学校网管部门分配的配置不然上不了网。**这种情况下,如果你们宿舍的人要上网,就需要一直开着宿舍长的电脑。**
第二个办法,你们共同出钱买个家庭路由器(反正当时我们买不起)。家庭路由器会有内网网口和外网网口。把外网网口的线插到校园网的网口上,将这个外网网口配置成和网管部的一样。内网网口连上你们宿舍的所有的电脑。**这种情况下,如果你们宿舍的人要上网,就需要一直开着路由器。**
这两种方法其实是一样的。只不过第一种方式,让你的宿舍长的电脑,变成一个有多个口的路由器而已。而你买的家庭路由器,里面也跑着程序,和你宿舍长电脑里的功能一样,只不过是一个嵌入式的系统。
当你的宿舍长能够上网之后,接下来,就是其他人的电脑怎么上网的问题。这就需要配置你们的**网卡。<strong>当然DHCP是可以默认配置的。在进行网卡配置的时候除了IP地址还需要配置一个**Gateway</strong>的东西,这个就是**网关**。
## 你了解MAC头和IP头的细节吗
一旦配置了IP地址和网关往往就能够指定目标地址进行访问了。由于在跨网关访问的时候牵扯到MAC地址和IP地址的变化这里有必要详细描述一下MAC头和IP头的细节。
<img src="https://static001.geekbang.org/resource/image/82/65/825e54560a6de08a32e4cab4e0f59f65.jpg" alt="">
在MAC头里面先是目标MAC地址然后是源MAC地址然后有一个协议类型用来说明里面是IP协议。IP头里面的版本号目前主流的还是IPv4服务类型TOS在第三节讲ip addr命令的时候讲过TTL在第7节讲ICMP协议的时候讲过。另外还有8位标识协议。这里到了下一层的协议也就是是TCP还是UDP。最重要的就是源IP和目标IP。先是源IP地址然后是目标IP地址。
在任何一台机器上当要访问另一个IP地址的时候都会先判断这个目标IP地址和当前机器的IP地址是否在同一个网段。怎么判断同一个网段呢需要CIDR和子网掩码这个在第三节的时候也讲过了。
**如果是同一个网段**例如你访问你旁边的兄弟的电脑那就没网关什么事情直接将源地址和目标地址放入IP头中然后通过ARP获得MAC地址将源MAC和目的MAC放入MAC头中发出去就可以了。
**如果不是同一网段**例如你要访问你们校园网里面的BBS该怎么办这就需要发往默认网关Gateway。Gateway的地址一定是和源IP地址是一个网段的。往往不是第一个就是第二个。例如192.168.1.0/24这个网段Gateway往往会是192.168.1.1/24或者192.168.1.2/24。
如何发往默认网关呢网关不是和源IP地址是一个网段的么这个过程就和发往同一个网段的其他机器是一样的将源地址和目标IP地址放入IP头中通过ARP获得网关的MAC地址将源MAC和网关的MAC放入MAC头中发送出去。网关所在的端口例如192.168.1.1/24将网络包收进来然后接下来怎么做就完全看网关的了。
**网关往往是一个路由器,是一个三层转发的设备。**啥叫三层设备前面也说过了就是把MAC头和IP头都取下来然后根据里面的内容看看接下来把包往哪里转发的设备。
在你的宿舍里面网关就是你宿舍长的电脑。一个路由器往往有多个网口如果是一台服务器做这个事情则就有多个网卡其中一个网卡是和源IP同网段的。
很多情况下,人们把网关就叫做路由器。其实不完全准确,而另一种比喻更加恰当:**路由器是一台设备它有五个网口或者网卡相当于有五只手分别连着五个局域网。每只手的IP地址都和局域网的IP地址相同的网段每只手都是它握住的那个局域网的网关。**
任何一个想发往其他局域网的包都会到达其中一只手被拿进来拿下MAC头和IP头看看根据自己的路由算法选择另一只手加上IP头和MAC头然后扔出去。
## 静态路由是什么?
这个时候问题来了该选择哪一只手IP头和MAC头加什么内容哪些变、哪些不变呢这个问题比较复杂大致可以分为两类一个是**静态路由**,一个是**动态路由**。动态路由下一节我们详细地讲。这一节我们先说静态路由。
**静态路由,其实就是在路由器上,配置一条一条规则。**这些规则包括想访问BBS站它肯定有个网段从2号口出去下一跳是IP2想访问教学视频站它也有个自己的网段从3号口出去下一跳是IP3然后保存在路由器里。
每当要选择从哪只手抛出去的时候就一条一条的匹配规则找到符合的规则就按规则中设置的那样从某个口抛出去找下一跳IPX。
## IP头和MAC头哪些变、哪些不变
对于IP头和MAC头哪些变、哪些不变的问题可以分两种类型。我把它们称为“**欧洲十国游”型**和“**玄奘西行”型**。
之前我说过MAC地址是一个局域网内才有效的地址。因而MAC地址只要过网关就必定会改变因为已经换了局域网。两者主要的区别在于IP地址是否改变。不改变IP地址的网关我们称为**转发网关;<strong>改变IP地址的网关我们称为**NAT网关</strong>
### “欧洲十国游”型
结合这个图,我们先来看“欧洲十国游”型。
<img src="https://static001.geekbang.org/resource/image/1d/8c/1d604f88456096a73e40437d8f9e458c.jpg" alt="">
服务器A要访问服务器B。首先服务器A会思考192.168.4.101和我不是一个网段的因而需要先发给网关。那网关是谁呢已经静态配置好了网关是192.168.1.1。网关的MAC地址是多少呢发送ARP获取网关的MAC地址然后发送包。包的内容是这样的
<li>
源MAC服务器A的MAC
</li>
<li>
目标MAC192.168.1.1这个网口的MAC
</li>
<li>
源IP192.168.1.101
</li>
<li>
目标IP192.168.4.101
</li>
包到达192.168.1.1这个网口发现MAC一致将包收进来开始思考往哪里转发。
在路由器A中配置了静态路由之后要想访问192.168.4.0/24要从192.168.56.1这个口出去下一跳为192.168.56.2。
于是路由器A思考的时候匹配上了这条路由要从192.168.56.1这个口发出去发给192.168.56.2那192.168.56.2的MAC地址是多少呢路由器A发送ARP获取192.168.56.2的MAC地址然后发送包。包的内容是这样的
<li>
源MAC192.168.56.1的MAC地址
</li>
<li>
目标MAC192.168.56.2的MAC地址
</li>
<li>
源IP192.168.1.101
</li>
<li>
目标IP192.168.4.101
</li>
包到达192.168.56.2这个网口发现MAC一致将包收进来开始思考往哪里转发。
在路由器B中配置了静态路由要想访问192.168.4.0/24要从192.168.4.1这个口出去,没有下一跳了。因为我右手这个网卡,就是这个网段的,我是最后一跳了。
于是路由器B思考的时候匹配上了这条路由要从192.168.4.1这个口发出去发给192.168.4.101。那192.168.4.101的MAC地址是多少呢路由器B发送ARP获取192.168.4.101的MAC地址然后发送包。包的内容是这样的
<li>
源MAC192.168.4.1的MAC地址
</li>
<li>
目标MAC192.168.4.101的MAC地址
</li>
<li>
源IP192.168.1.101
</li>
<li>
目标IP192.168.4.101
</li>
包到达服务器BMAC地址匹配将包收进来。
通过这个过程可以看出每到一个新的局域网MAC都是要变的但是IP地址都不变。在IP头里面不会保存任何网关的IP地址。**所谓的下一跳是某个IP要将这个IP地址转换为MAC放入MAC头。**
之所以将这种模式比喻称为欧洲十国游是因为在整个过程中IP头里面的地址都是不变的。IP地址在三个局域网都可见在三个局域网之间的网段都不会冲突。在三个网段之间传输包IP头不改变。这就像在欧洲各国之间旅游一个签证就能搞定。
<img src="https://static001.geekbang.org/resource/image/35/3b/35fb548bbaa7d77012ab46151bfbe63b.jpg" alt="">
### “玄奘西行”型
我们再来看“玄奘西行”型。
这里遇见的第一个问题是局域网之间没有商量过各定各的网段因而IP段冲突了。最左面大唐的地址是192.168.1.101最右面印度的地址也是192.168.1.101如果单从IP地址上看简直是自己访问自己其实是大唐的192.168.1.101要访问印度的192.168.1.101。
怎么解决这个问题呢?既然局域网之间没有商量过,你们各管各的,那到国际上,也即中间的局域网里面,就需要使用另外的地址。就像出国,不能用咱们自己的身份证,而要改用护照一样,玄奘西游也要拿着专门取经的通关文牒,而不能用自己国家的身份证。
首先目标服务器B在国际上要有一个国际的身份我们给它一个192.168.56.2。在网关B上我们记下来国际身份192.168.56.2对应国内身份192.168.1.101。凡是要访问192.168.56.2都转成192.168.1.101。
于是源服务器A要访问目标服务器B要指定的目标地址为192.168.56.2。这是它的国际身份。服务器A想192.168.56.2和我不是一个网段的因而需要发给网关网关是谁已经静态配置好了网关是192.168.1.1网关的MAC地址是多少发送ARP获取网关的MAC地址然后发送包。包的内容是这样的
<li>
源MAC服务器A的MAC
</li>
<li>
目标MAC192.168.1.1这个网口的MAC
</li>
<li>
源IP192.168.1.101
</li>
<li>
目标IP192.168.56.2
</li>
包到达192.168.1.1这个网口发现MAC一致将包收进来开始思考往哪里转发。
在路由器A中配置了静态路由要想访问192.168.56.2/24要从192.168.56.1这个口出去,没有下一跳了,因为我右手这个网卡,就是这个网段的,我是最后一跳了。
于是路由器A思考的时候匹配上了这条路由要从192.168.56.1这个口发出去发给192.168.56.2。那192.168.56.2的MAC地址是多少呢路由器A发送ARP获取192.168.56.2的MAC地址。
当网络包发送到中间的局域网的时候服务器A也需要有个国际身份因而在国际上源IP地址也不能用192.168.1.101需要改成192.168.56.1。发送包的内容是这样的:
<li>
源MAC192.168.56.1的MAC地址
</li>
<li>
目标MAC192.168.56.2的MAC地址
</li>
<li>
源IP192.168.56.1
</li>
<li>
目标IP192.168.56.2
</li>
包到达192.168.56.2这个网口发现MAC一致将包收进来开始思考往哪里转发。
路由器B是一个NAT网关它上面配置了要访问国际身份192.168.56.2对应国内身份192.168.1.101于是改为访问192.168.1.101。
在路由器B中配置了静态路由要想访问192.168.1.0/24要从192.168.1.1这个口出去,没有下一跳了,因为我右手这个网卡,就是这个网段的,我是最后一跳了。
于是路由器B思考的时候匹配上了这条路由要从192.168.1.1这个口发出去发给192.168.1.101。
那192.168.1.101的MAC地址是多少呢路由器B发送ARP获取192.168.1.101的MAC地址然后发送包。内容是这样的
<li>
源MAC192.168.1.1的MAC地址
</li>
<li>
目标MAC192.168.1.101的MAC地址
</li>
<li>
源IP192.168.56.1
</li>
<li>
目标IP192.168.1.101
</li>
包到达服务器BMAC地址匹配将包收进来。
从服务器B接收的包可以看出源IP为服务器A的国际身份因而发送返回包的时候也发给这个国际身份由路由器A做NAT转换为国内身份。
从这个过程可以看出IP地址也会变。这个过程用英文说就是**Network Address Translation**,简称**NAT**。
其实这第二种方式我们经常见现在大家每家都有家用路由器家里的网段都是192.168.1.x所以你肯定访问不了你邻居家的这个私网的IP地址的。所以当我们家里的包发出去的时候都被家用路由器NAT成为了运营商的地址了。
很多办公室访问外网的时候也是被NAT过的因为不可能办公室里面的IP也是公网可见的公网地址实在是太贵了所以一般就是整个办公室共用一个到两个出口IP地址。你可以通过 [https://www.whatismyip.com/](https://www.whatismyip.com/) 查看自己的出口IP地址。
## 小结
好了,这一节内容差不多了,我来总结一下:
<li>
如果离开本局域网,就需要经过网关,网关是路由器的一个网口;
</li>
<li>
路由器是一个三层设备,里面有如何寻找下一跳的规则;
</li>
<li>
经过路由器之后MAC头要变如果IP不变相当于不换护照的欧洲旅游如果IP变相当于换护照的玄奘西行。
</li>
最后,给你留两个思考题吧。
1. 当在你家里要访问163网站的时候你的包需要NAT成为公网IP返回的包又要NAT成你的私有IP返回包怎么知道这是你的请求呢它怎么就这么智能的NAT成了你的IP而非别人的IP呢
1. 对于路由规则,这一节讲述了静态路由,需要手动配置,如果要自动配置,你觉得应该怎么办呢?
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,244 @@
<audio id="audio" title="第9讲 | 路由协议:西出网关无故人,敢问路在何方" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ad/a3/add1ddabb65aaa752e68e1c4c13704a3.mp3"></audio>
俗话说得好,在家千日好,出门一日难。网络包一旦出了网关,就像玄奘西行一样踏上了江湖漂泊的路。
上一节我们描述的是一个相对简单的情形。出了网关之后,只有一条路可以走。但是,网络世界复杂得多,一旦出了网关,会面临着很多路由器,有很多条道路可以选。如何选择一个更快速的道路求取真经呢?这里面还有很多门道可以讲。
## 如何配置路由?
通过上一节的内容,你应该已经知道,路由器就是一台网络设备,它有多张网卡。当一个入口的网络包送到路由器时,它会根据一个本地的转发信息库,来决定如何正确地转发流量。这个转发信息库通常被称为**路由表**。
一张路由表中会有多条路由规则。每一条规则至少包含这三项信息。
<li>
目的网络:这个包想去哪儿?
</li>
<li>
出口设备:将包从哪个口扔出去?
</li>
<li>
下一跳网关:下一个路由器的地址。
</li>
通过route命令和ip route命令都可以进行查询或者配置。
例如我们设置ip route add 10.176.48.0/20 via 10.173.32.1 dev eth0就说明要去10.176.48.0/20这个目标网络要从eth0端口出去经过10.173.32.1。
上一节的例子中,网关上的路由策略就是按照这三项配置信息进行配置的。这种配置方式的一个核心思想是:**根据目的IP地址来配置路由**。
## 如何配置策略路由?
当然在真实的复杂的网络环境中除了可以根据目的ip地址配置路由外还可以根据多个参数来配置路由这就称为**策略路由**。
可以配置多个路由表可以根据源IP地址、入口设备、TOS等选择路由表然后在路由表中查找路由。这样可以使得来自不同来源的包走不同的路由。
例如,我们设置:
```
ip rule add from 192.168.1.0/24 table 10
ip rule add from 192.168.2.0/24 table 20
```
表示从192.168.1.10/24这个网段来的使用table 10中的路由表而从192.168.2.0/24网段来的使用table20的路由表。
在一条路由规则中,也可以走多条路径。例如,在下面的路由规则中:
```
ip route add default scope global nexthop via 100.100.100.1 weight 1 nexthop via 200.200.200.1 weight 2
```
下一跳有两个地方分别是100.100.100.1和200.200.200.1权重分别为1比2。
在什么情况下会用到如此复杂的配置呢?我来举一个现实中的例子。
我是房东,家里从运营商那儿拉了两根网线。这两根网线分别属于两个运行商。一个带宽大一些,一个带宽小一些。这个时候,我就不能买普通的家用路由器了,得买个高级点的,可以接两个外网的。
家里的网络呢就是普通的家用网段192.168.1.x/24。家里有两个租户分别把线连到路由器上。IP地址为192.168.1.101/24和192.168.1.102/24网关都是192.168.1.1/24网关在路由器上。
就像上一节说的一样家里的网段是私有网段出去的包需要NAT成公网的IP地址因而路由器是一个NAT路由器。
两个运营商都要为这个网关配置一个公网的IP地址。如果你去查看你们家路由器里的网段基本就是我图中画的样子。
<img src="https://static001.geekbang.org/resource/image/c3/db/c3f476eb7ce8f185befb6c7a2b1752db.jpg" alt="">
运行商里面也有一个IP地址在运营商网络里面的网关。不同的运营商方法不一样有的是/32的也即一个一对一连接。
例如运营商1给路由器分配的地址是183.134.189.34/32而运营商网络里面的网关是183.134.188.1/32。有的是/30的也就是分了一个特别小的网段。运营商2给路由器分配的地址是60.190.27.190/30运营商网络里面的网关是60.190.27.189/30。
根据这个网络拓扑图,可以将路由配置成这样:
```
$ ip route list table main
60.190.27.189/30 dev eth3 proto kernel scope link src 60.190.27.190
183.134.188.1 dev eth2 proto kernel scope link src 183.134.189.34
192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1
127.0.0.0/8 dev lo scope link
default via 183.134.188.1 dev eth2
```
当路由这样配置的时候,就告诉这个路由器如下的规则:
<li>
如果去运营商二就走eth3
</li>
<li>
如果去运营商一呢就走eth2
</li>
<li>
如果访问内网就走eth1
</li>
<li>
如果所有的规则都匹配不上,默认走运营商一,也即走快的网络。
</li>
但是问题来了租户A不想多付钱他说我就上上网页从不看电影凭什么收我同样贵的网费啊没关系咱有技术可以解决。
下面我添加一个Table名字叫**chao**。
```
# echo 200 chao &gt;&gt; /etc/iproute2/rt_tables
```
添加一条规则:
```
# ip rule add from 192.168.1.101 table chao
# ip rule ls
0: from all lookup local
32765: from 10.0.0.10 lookup chao
32766: from all lookup main
32767: from all lookup default
```
设定规则为从192.168.1.101来的包都查看个chao这个新的路由表。
在chao路由表中添加规则
```
# ip route add default via 60.190.27.189 dev eth3 table chao
# ip route flush cache
```
默认的路由走慢的,谁让你不付钱。
上面说的都是静态的路由,一般来说网络环境简单的时候,在自己的可控范围之内,自己捣鼓还是可以的。但是有时候网络环境复杂并且多变,如果总是用静态路由,一旦网络结构发生变化,让网络管理员手工修改路由太复杂了,因而需要动态路由算法。
## 动态路由算法
使用动态路由路由器,可以根据路由协议算法生成动态路由表,随网络运行状况的变化而变化。那路由算法是什么样的呢?
我们可以想象唐僧西天取经,需要解决两大问题,一个是在每个国家如何找到正确的路,去换通关文牒、吃饭、休息;一个是在国家之间,野外行走的时候,如何找到正确的路、水源的问题。
<img src="https://static001.geekbang.org/resource/image/85/65/85b3314e56cb5870a5cf9a0597ffa965.jpg" alt="">
无论是一个国家内部,还是国家之间,我们都可以将复杂的路径,抽象为一种叫作图的数据结构。至于唐僧西行取经,肯定想走的路越少越好,道路越短越好,因而这就转化成为**如何在途中找到最短路径**的问题。
咱们在大学里面学习计算机网络与数据结构的时候知道求最短路径常用的有两种方法一种是Bellman-Ford算法一种是Dijkstra算法。在计算机网络中基本也是用这两种方法计算的。
### 1.距离矢量路由算法
第一大类的算法称为**距离矢量路由****distance vector routing**。它是基于Bellman-Ford算法的。
这种算法的基本思路是,每个路由器都保存一个路由表,包含多行,每行对应网络中的一个路由器,每一行包含两部分信息,一个是要到目标路由器,从那条线出去,另一个是到目标路由器的距离。
由此可以看出,每个路由器都是知道全局信息的。那这个信息如何更新呢?每个路由器都知道自己和邻居之间的距离,每过几秒,每个路由器都将自己所知的到达所有的路由器的距离告知邻居,每个路由器也能从邻居那里得到相似的信息。
每个路由器根据新收集的信息计算和其他路由器的距离比如自己的一个邻居距离目标路由器的距离是M而自己距离邻居是x则自己距离目标路由器是x+M。
这个算法比较简单,但是还是有问题。
**第一个问题就是好消息传得快,坏消息传得慢。** 如果有个路由器加入了这个网络,它的邻居就能很快发现它,然后将消息广播出去。要不了多久,整个网络就都知道了。但是一旦一个路由器挂了,挂的消息是没有广播的。当每个路由器发现原来的道路到不了这个路由器的时候,感觉不到它已经挂了,而是试图通过其他的路径访问,直到试过了所有的路径,才发现这个路由器是真的挂了。
我再举个例子。
<img src="https://static001.geekbang.org/resource/image/e9/79/e9642f901c1d9c470c539ccc395e7879.jpg" alt="">
原来的网络包括两个节点B和C。A加入了网络它的邻居B很快就发现A启动起来了。于是它将自己和A的距离设为1同样C也发现A起来了将自己和A的距离设置为2。但是如果A挂掉情况就不妙了。B本来和A是邻居发现连不上A了但是C还是能够连上只不过距离远了点是2于是将自己的距离设置为3。殊不知C的距离2其实是基于原来自己的距离为1计算出来的。C发现自己也连不上A并且发现B设置为3于是自己改成距离4。依次类推数越来越大直到超过一个阈值我们才能判定A真的挂了。
这个道理有点像有人走丢了。当你突然发现找不到这个人了。于是你去学校问,是不是在他姨家呀?找到他姨家,他姨说,是不是在他舅舅家呀?他舅舅说,是不是在他姥姥家呀?他姥姥说,是不是在学校呀?总归要问一圈,或者是超过一定的时间,大家才会认为这个人的确走丢了。如果这个人其实只是去见了一个谁都不认识的网友去了,当这个人回来的时候,只要他随便见到其中的一个亲戚,这个亲戚就会拉着他到他的家长那里,说你赶紧回家,你妈都找你一天了。
**这种算法的第二个问题是,每次发送的时候,要发送整个全局路由表。**网络大了谁也受不了所以最早的路由协议RIP就是这个算法。它适用于小型网络小于15跳。当网络规模都小的时候没有问题。现在一个数据中心内部路由器数目就很多因而不适用了。
所以上面的两个问题,限制了距离矢量路由的网络规模。
### 2.链路状态路由算法
第二大类算法是**链路状态路由****link state routing**基于Dijkstra算法。
这种算法的基本思路是当一个路由器启动的时候首先是发现邻居向邻居say hello邻居都回复。然后计算和邻居的距离发送一个echo要求马上返回除以二就是距离。然后将自己和邻居之间的链路状态包广播出去发送到整个网络的每个路由器。这样每个路由器都能够收到它和邻居之间的关系的信息。因而每个路由器都能在自己本地构建一个完整的图然后针对这个图使用Dijkstra算法找到两点之间的最短路径。
不像距离距离矢量路由协议那样更新时发送整个路由表。链路状态路由协议只广播更新的或改变的网络拓扑这使得更新信息更小节省了带宽和CPU利用率。而且一旦一个路由器挂了它的邻居都会广播这个消息可以使得坏消息迅速收敛。
## 动态路由协议
### 1.基于链路状态路由算法的OSPF
**OSPF****Open Shortest Path First****开放式最短路径优先**)就是这样一个基于链路状态路由协议,广泛应用在数据中心中的协议。由于主要用在数据中心内部,用于路由决策,因而称为**内部网关协议****Interior Gateway Protocol**,简称**IGP**)。
内部网关协议的重点就是找到最短的路径。在一个组织内部路径最短往往最优。当然有时候OSPF可以发现多个最短的路径可以在这多个路径中进行负载均衡这常常被称为**等价路由**。
<img src="https://static001.geekbang.org/resource/image/2e/db/2eb5f4722689adf9926fded5005e02db.jpg" alt="">
这一点非常重要。有了等价路由到一个地方去可以有相同的两个路线可以分摊流量还可以当一条路不通的时候走另外一条路。这个在后面我们讲数据中心的网络的时候一般应用的接入层会有负载均衡LVS。它可以和OSPF一起实现高吞吐量的接入层设计。
有了内网的路由协议,在一个国家内,唐僧可以想怎么走怎么走了,两条路选一条也行。
### 2.基于距离矢量路由算法的BGP
但是外网的路由协议,也即国家之间的,又有所不同。我们称为**外网路由协议****Border Gateway Protocol**,简称**BGP**)。
在一个国家内部,有路当然选近的走。但是国家之间,不光远近的问题,还有政策的问题。例如,唐僧去西天取经,有的路近。但是路过的国家看不惯僧人,见了僧人就抓。例如灭法国,连光头都要抓。这样的情况即便路近,也最好绕远点走。
对于网络包同样每个数据中心都设置自己的Policy。例如哪些外部的IP可以让内部知晓哪些内部的IP可以让外部知晓哪些可以通过哪些不能通过。这就好比虽然从我家里到目的地最近但是不能谁都能从我家走啊
在网络世界,这一个个国家成为自治系统**AS**Autonomous System。自治系统分几种类型。
<li>
Stub AS对外只有一个连接。这类AS不会传输其他AS的包。例如个人或者小公司的网络。
</li>
<li>
Multihomed AS可能有多个连接连到其他的AS但是大多拒绝帮其他的AS传输包。例如一些大公司的网络。
</li>
<li>
Transit AS有多个连接连到其他的AS并且可以帮助其他的AS传输包。例如主干网。
</li>
每个自治系统都有边界路由器,通过它和外面的世界建立联系。
<img src="https://static001.geekbang.org/resource/image/69/3d/698e368848fdbf1eb8e270983e18143d.jpg" alt="">
**BGP又分为两类eBGP和iBGP。**自治系统间边界路由器之间使用eBGP广播路由。内部网络也需要访问其他的自治系统。边界路由器如何将BGP学习到的路由导入到内部网络呢就是通过运行iBGP使得内部的路由器能够找到到达外网目的地的最好的边界路由器。
BGP协议使用的算法是**路径矢量路由协议**path-vector protocol。它是距离矢量路由协议的升级版。
前面说了距离矢量路由协议的缺点。其中一个是收敛慢。在BGP里面除了下一跳hop之外还包括了自治系统AS的路径从而可以避免坏消息传得慢的问题也即上面所描述的B知道C原来能够到达A是因为通过自己一旦自己都到达不了A了就不用假设C还能到达A了。
另外,在路径中将一个自治系统看成一个整体,不区分自治系统内部的路由器,这样自治系统的数目是非常有限的。就像大家都能记住出去玩,从中国出发先到韩国然后到日本,只要不计算细到具体哪一站,就算是发送全局信息,也是没有问题的。
## 小结
好了,这一节就到这里了,我来做个总结:
<li>
路由分静态路由和动态路由,静态路由可以配置复杂的策略路由,控制转发策略;
</li>
<li>
动态路由主流算法有两种距离矢量算法和链路状态算法。基于两种算法产生两种协议BGP协议和OSPF协议。
</li>
最后,再给你留两个思考题:
<li>
路由协议要在路由器之间交换信息,这些信息的交换还需要走路由吗?不是死锁了吗?
</li>
<li>
路由器之间信息的交换使用什么协议呢?报文格式是什么样呢?
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,216 @@
<audio id="audio" title="第14讲 | HTTP协议看个新闻原来这么麻烦" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4f/34/4f71470a46d7f82c603d9d27b2756034.mp3"></audio>
前面讲述完**传输层**,接下来开始讲**应用层**的协议。从哪里开始讲呢就从咱们最常用的HTTP协议开始。
HTTP协议几乎是每个人上网用的第一个协议同时也是很容易被人忽略的协议。
既然说看新闻,咱们就先登录 [http://www.163.com](http://www.163.com) 。
[http://www.163.com](http://www.163.com) 是个URL叫作**统一资源定位符**。之所以叫统一是因为它是有格式的。HTTP称为协议www.163.com是一个域名表示互联网上的一个位置。有的URL会有更详细的位置标识例如 [http://www.163.com/index.html](http://www.163.com/index.html) 。正是因为这个东西是统一的,所以当你把这样一个字符串输入到浏览器的框里的时候,浏览器才知道如何进行统一处理。
## HTTP请求的准备
浏览器会将www.163.com这个域名发送给DNS服务器让它解析为IP地址。有关DNS的过程其实非常复杂这个在后面专门介绍DNS的时候我会详细描述这里我们先不管反正它会被解析成为IP地址。那接下来是发送HTTP请求吗
不是的HTTP是基于TCP协议的当然是要先建立TCP连接了怎么建立呢还记得第11节讲过的三次握手吗
目前使用的HTTP协议大部分都是1.1。在1.1的协议里面默认是开启了Keep-Alive的这样建立的TCP连接就可以在多次请求中复用。
学习了TCP之后你应该知道TCP的三次握手和四次挥手还是挺费劲的。如果好不容易建立了连接然后就做了一点儿事情就结束了有点儿浪费人力和物力。
## HTTP请求的构建
建立了连接以后浏览器就要发送HTTP的请求。
请求的格式就像这样。
<img src="https://static001.geekbang.org/resource/image/85/c1/85ebb0396cbaa45ce00b505229e523c1.jpeg" alt="">
HTTP的报文大概分为三大部分。第一部分是**请求行**,第二部分是请求的**首部**,第三部分才是请求的**正文实体**。
### 第一部分:请求行
在请求行中URL就是 [http://www.163.com](http://www.163.com) 版本为HTTP 1.1。这里要说一下的,就是方法。方法有几种类型。
对于访问网页来讲,最常用的类型就是**GET**。顾名思义GET就是去服务器获取一些资源。对于访问网页来讲要获取的资源往往是一个页面。其实也有很多其他的格式比如说返回一个JSON字符串到底要返回什么是由服务器端的实现决定的。
例如在云计算中如果我们的服务器端要提供一个基于HTTP协议的API获取所有云主机的列表这就会使用GET方法得到返回的可能是一个JSON字符串。字符串里面是一个列表列表里面是一项的云主机的信息。
另外一种类型叫做**POST**。它需要主动告诉服务端一些信息而非获取。要告诉服务端什么呢一般会放在正文里面。正文可以有各种各样的格式。常见的格式也是JSON。
例如我们下一节要讲的支付场景客户端就需要把“我是谁我要支付多少我要买啥”告诉服务器这就需要通过POST方法。
再如在云计算里如果我们的服务器端要提供一个基于HTTP协议的创建云主机的API也会用到POST方法。这个时候往往需要将“我要创建多大的云主机多少CPU多少内存多大硬盘”这些信息放在JSON字符串里面通过POST的方法告诉服务器端。
还有一种类型叫**PUT**就是向指定资源位置上传最新内容。但是HTTP的服务器往往是不允许上传文件的所以PUT和POST就都变成了要传给服务器东西的方法。
在实际使用过程中这两者还会有稍许的区别。POST往往是用来创建一个资源的而PUT往往是用来修改一个资源的。
例如云主机已经创建好了我想对这个云主机打一个标签说明这个云主机是生产环境的另外一个云主机是测试环境的。那怎么修改这个标签呢往往就是用PUT方法。
再有一种常见的就是**DELETE**。这个顾名思义就是用来删除资源的。例如我们要删除一个云主机就会调用DELETE方法。
### 第二部分:首部字段
请求行下面就是我们的首部字段。首部是key value通过冒号分隔。这里面往往保存了一些非常重要的字段。
例如,**Accept-Charset**,表示**客户端可以接受的字符集**。防止传过来的是另外的字符集,从而导致出现乱码。
再如,**Content-Type**是指**正文的格式**。例如我们进行POST的请求如果正文是JSON那么我们就应该将这个值设置为JSON。
这里需要重点说一下的就是**缓存**。为啥要使用缓存呢?那是因为一个非常大的页面有很多东西。
例如,我浏览一个商品的详情,里面有这个商品的价格、库存、展示图片、使用手册等等。商品的展示图片会保持较长时间不变,而库存会根据用户购买的情况经常改变。如果图片非常大,而库存数非常小,如果我们每次要更新数据的时候都要刷新整个页面,对于服务器的压力就会很大。
对于这种高并发场景下的系统,在真正的业务逻辑之前,都需要有个接入层,将这些静态资源的请求拦在最外面。
这个架构的图就像这样。
<img src="https://static001.geekbang.org/resource/image/ca/1d/caec3ba1086557cbf694c621e7e01e1d.jpeg" alt="">
其中DNS、CDN我在后面的章节会讲。和这一节关系比较大的就是Nginx这一层它如何处理HTTP协议呢对于静态资源有Vanish缓存层。当缓存过期的时候才会访问真正的Tomcat应用集群。
在HTTP头里面**Cache-control**是用来**控制缓存**的。当客户端发送的请求中包含max-age指令时如果判定缓存层中资源的缓存时间数值比指定时间的数值小那么客户端可以接受缓存的资源当指定max-age值为0那么缓存层通常需要将请求转发给应用集群。
另外,**If-Modified-Since**也是一个关于缓存的。也就是说如果服务器的资源在某个时间之后更新了那么客户端就应该下载最新的资源如果没有更新服务端会返回“304 Not Modified”的响应那客户端就不用下载了也会节省带宽。
到此为止我们仅仅是拼凑起了HTTP请求的报文格式接下来浏览器会把它交给下一层传输层。怎么交给传输层呢其实也无非是用Socket这些东西只不过用的浏览器里这些程序不需要你自己写有人已经帮你写好了。
## HTTP请求的发送
HTTP协议是基于TCP协议的所以它使用面向连接的方式发送请求通过stream二进制流的方式传给对方。当然到了TCP层它会把二进制流变成一个个报文段发送给服务器。
在发送给每个报文段的时候都需要对方有一个回应ACK来保证报文可靠地到达了对方。如果没有回应那么TCP这一层会进行重新传输直到可以到达。同一个包有可能被传了好多次但是HTTP这一层不需要知道这一点因为是TCP这一层在埋头苦干。
TCP层发送每一个报文的时候都需要加上自己的地址即源地址和它想要去的地方即目标地址将这两个信息放到IP头里面交给IP层进行传输。
IP层需要查看目标地址和自己是否是在同一个局域网。如果是就发送ARP协议来请求这个目标地址对应的MAC地址然后将源MAC和目标MAC放入MAC头发送出去即可如果不在同一个局域网就需要发送到网关还要需要发送ARP协议来获取网关的MAC地址然后将源MAC和网关MAC放入MAC头发送出去。
网关收到包发现MAC符合取出目标IP地址根据路由协议找到下一跳的路由器获取下一跳路由器的MAC地址将包发给下一跳路由器。
这样路由器一跳一跳终于到达目标的局域网。这个时候最后一跳的路由器能够发现目标地址就在自己的某一个出口的局域网上。于是在这个局域网上发送ARP获得这个目标地址的MAC地址将包发出去。
目标的机器发现MAC地址符合就将包收起来发现IP地址符合根据IP头中协议项知道自己上一层是TCP协议于是解析TCP的头里面有序列号需要看一看这个序列包是不是我要的如果是就放入缓存中然后返回一个ACK如果不是就丢弃。
TCP头里面还有端口号HTTP的服务器正在监听这个端口号。于是目标机器自然知道是HTTP服务器这个进程想要这个包于是将包发给HTTP服务器。HTTP服务器的进程看到原来这个请求是要访问一个网页于是就把这个网页发给客户端。
## HTTP返回的构建
HTTP的返回报文也是有一定格式的。这也是基于HTTP 1.1的。
<img src="https://static001.geekbang.org/resource/image/6b/63/6bc37ddcb4e7a61ca3275790820f2263.jpeg" alt="">
状态码会反映HTTP请求的结果。“200”意味着大吉大利而我们最不想见的就是“404”也就是“服务端无法响应这个请求”。然后短语会大概说一下原因。
接下来是返回首部的**key value**。
这里面,**Retry-After**表示告诉客户端应该在多长时间以后再次尝试一下。“503错误”是说“服务暂时不再和这个值配合使用”。
在返回的头部里面也会有**Content-Type**表示返回的是HTML还是JSON。
构造好了返回的HTTP报文接下来就是把这个报文发送出去。还是交给Socket去发送还是交给TCP层让TCP层将返回的HTML也分成一个个小的段并且保证每个段都可靠到达。
这些段加上TCP头后会交给IP层然后把刚才的发送过程反向走一遍。虽然两次不一定走相同的路径但是逻辑过程是一样的一直到达客户端。
客户端发现MAC地址符合、IP地址符合于是就会交给TCP层。根据序列号看是不是自己要的报文段如果是则会根据TCP头中的端口号发给相应的进程。这个进程就是浏览器浏览器作为客户端也在监听某个端口。
当浏览器拿到了HTTP的报文。发现返回“200”一切正常于是就从正文中将HTML拿出来。HTML是一个标准的网页格式。浏览器只要根据这个格式展示出一个绚丽多彩的网页。
这就是一个正常的HTTP请求和返回的完整过程。
## HTTP 2.0
当然HTTP协议也在不断的进化过程中在HTTP1.1基础上便有了HTTP 2.0。
HTTP 1.1在应用层以纯文本的形式进行通信。每次通信都要带完整的HTTP的头而且不考虑pipeline模式的话每次的过程总是像上面描述的那样一去一回。这样在实时性、并发性上都存在问题。
为了解决这些问题HTTP 2.0会对HTTP的头进行一定的压缩将原来每次都要携带的大量key value在两端建立一个索引表对相同的头只发送索引表中的索引。
另外HTTP 2.0协议将一个TCP的连接中切分成多个流每个流都有自己的ID而且流可以是客户端发往服务端也可以是服务端发往客户端。它其实只是一个虚拟的通道。流是有优先级的。
HTTP 2.0还将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码。常见的帧有**Header帧**用于传输Header内容并且会开启一个新的流。再就是**Data帧**用来传输正文实体。多个Data帧属于同一个流。
通过这两种机制HTTP 2.0的客户端可以将多个请求分到不同的流中,然后将请求内容拆成帧,进行二进制传输。这些帧可以打散乱序发送, 然后根据每个帧首部的流标识符重新组装,并且可以根据优先级,决定优先处理哪个流的数据。
我们来举一个例子。
假设我们的一个页面要发送三个独立的请求一个获取css一个获取js一个获取图片jpg。如果使用HTTP 1.1就是串行的但是如果使用HTTP 2.0,就可以在一个连接里,客户端和服务端都可以同时发送多个请求或回应,而且不用按照顺序一对一对应。
<img src="https://static001.geekbang.org/resource/image/9a/1a/9a54f97931377dyy2fde0de93f4ecf1a.jpeg" alt="">
HTTP 2.0其实是将三个请求变成三个流将数据分成帧乱序发送到一个TCP连接中。
<img src="https://static001.geekbang.org/resource/image/3d/d3/3da001fac5701949b94e51caaee887d3.jpeg" alt="">
HTTP 2.0成功解决了HTTP 1.1的队首阻塞问题同时也不需要通过HTTP 1.x的pipeline机制用多条TCP连接来实现并行请求与响应减少了TCP连接数对服务器性能的影响同时将页面的多个数据css、js、 jpg等通过一个数据链接进行传输能够加快页面组件的传输速度。
## QUIC协议的“城会玩”
HTTP 2.0虽然大大增加了并发性但还是有问题的。因为HTTP 2.0也是基于TCP协议的TCP协议在处理包时是有严格顺序的。
当其中一个数据包遇到问题TCP连接需要等待这个包完成重传之后才能继续进行。虽然HTTP 2.0通过多个stream使得逻辑上一个TCP连接上的并行内容进行多路数据的传输然而这中间并没有关联的数据。一前一后前面stream 2的帧没有收到后面stream 1的帧也会因此阻塞。
于是就又到了从TCP切换到UDP进行“城会玩”的时候了。这就是Google的QUIC协议接下来我们来看它是如何“城会玩”的。
### 机制一:自定义连接机制
我们都知道一条TCP连接是由四元组标识的分别是源 IP、源端口、目的 IP、目的端口。一旦一个元素发生变化时就需要断开重连重新连接。在移动互联情况下当手机信号不稳定或者在WIFI和 移动网络切换时,都会导致重连,从而进行再次的三次握手,导致一定的时延。
这在TCP是没有办法的但是基于UDP就可以在QUIC自己的逻辑里面维护连接的机制不再以四元组标识而是以一个64位的随机数作为ID来标识而且UDP是无连接的所以当IP或者端口变化的时候只要ID不变就不需要重新建立连接。
### 机制二:自定义重传机制
前面我们讲过TCP为了保证可靠性通过使用**序号**和**应答**机制,来解决顺序问题和丢包问题。
任何一个序号的包发过去,都要在一定的时间内得到应答,否则一旦超时,就会重发这个序号的包。那怎么样才算超时呢?还记得我们提过的**自适应重传算法**吗?这个超时是通过**采样往返时间RTT**不断调整的。
其实在TCP里面超时的采样存在不准确的问题。例如发送一个包序号为100发现没有返回于是再发送一个100过一阵返回一个ACK101。这个时候客户端知道这个包肯定收到了但是往返时间是多少呢是ACK到达的时间减去后一个100发送的时间还是减去前一个100发送的时间呢事实是第一种算法把时间算短了第二种算法把时间算长了。
QUIC也有个序列号是递增的。任何一个序列号的包只发送一次下次就要加一了。例如发送一个包序号是100发现没有返回再次发送的时候序号就是101了如果返回的ACK 100就是对第一个包的响应。如果返回ACK 101就是对第二个包的响应RTT计算相对准确。
但是这里有一个问题就是怎么知道包100和包101发送的是同样的内容呢QUIC定义了一个offset概念。QUIC既然是面向连接的也就像TCP一样是一个数据流发送的数据在这个数据流里面有个偏移量offset可以通过offset查看数据发送到了哪里这样只要这个offset的包没有来就要重发如果来了按照offset拼接还是能够拼成一个流。
<img src="https://static001.geekbang.org/resource/image/80/2c/805aa4261yyb30a2a0e5a2f06ce5162c.jpeg" alt="">
### 机制三:无阻塞的多路复用
有了自定义的连接和重传机制我们就可以解决上面HTTP 2.0的多路复用问题。
同HTTP 2.0一样同一条QUIC连接上可以创建多个stream来发送多个 HTTP 请求。但是QUIC是基于UDP的一个连接上的多个stream之间没有依赖。这样假如stream2丢了一个UDP包后面跟着stream3的一个UDP包虽然stream2的那个包需要重传但是stream3的包无需等待就可以发给用户。
### 机制四:自定义流量控制
TCP的流量控制是通过**滑动窗口协议**。QUIC的流量控制也是通过window_update来告诉对端它可以接受的字节数。但是QUIC的窗口是适应自己的多路复用机制的不但在一个连接上控制窗口还在一个连接中的每个stream控制窗口。
还记得吗在TCP协议中接收端的窗口的起始点是下一个要接收并且ACK的包即便后来的包都到了放在缓存里面窗口也不能右移因为TCP的ACK机制是基于序列号的累计应答一旦ACK了一个序列号就说明前面的都到了所以只要前面的没到后面的到了也不能ACK就会导致后面的到了也有可能超时重传浪费带宽。
QUIC的ACK是基于offset的每个offset的包来了进了缓存就可以应答应答后就不会重发中间的空档会等待到来或者重发即可而窗口的起始位置为当前收到的最大offset从这个offset到当前的stream所能容纳的最大缓存是真正的窗口大小。显然这样更加准确。
<img src="https://static001.geekbang.org/resource/image/a6/22/a66563b46906e7708cc69a02d43afb22.jpg" alt="">
另外还有整个连接的窗口需要对于所有的stream的窗口做一个统计。
## 小结
好了,今天就讲到这里,我们来总结一下:
<li>
HTTP协议虽然很常用也很复杂重点记住GET、POST、 PUT、DELETE这几个方法以及重要的首部字段
</li>
<li>
HTTP 2.0通过头压缩、分帧、二进制编码、多路复用等技术提升性能;
</li>
<li>
QUIC协议通过基于UDP自定义的类似TCP的连接、重试、多路复用、流量控制技术进一步提升性能。
</li>
接下来,给你留两个思考题吧。
<li>
QUIC是一个精巧的协议所以它肯定不止今天我提到的四种机制你知道它还有哪些吗
</li>
<li>
这一节主要讲了如何基于HTTP浏览网页如果要传输比较敏感的银行卡信息该怎么办呢
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,171 @@
<audio id="audio" title="第15讲 | HTTPS协议点外卖的过程原来这么复杂" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f3/8d/f32cb9ab428dd51e209d6c1ce260ae8d.mp3"></audio>
用HTTP协议看个新闻还没有问题但是换到更加严肃的场景中就存在很多的安全风险。例如你要下单做一次支付如果还是使用普通的HTTP协议那你很可能会被黑客盯上。
你发送一个请求,说我要点个外卖,但是这个网络包被截获了,于是在服务器回复你之前,黑客先假装自己就是外卖网站,然后给你回复一个假的消息说:“好啊好啊,来来来,银行卡号、密码拿来。”如果这时候你真把银行卡密码发给它,那你就真的上套了。
那怎么解决这个问题呢?当然一般的思路就是**加密**。加密分为两种方式一种是**对称加密**,一种是**非对称加密**。
在对称加密算法中,加密和解密使用的密钥是相同的。也就是说,加密和解密使用的是同一个密钥。因此,对称加密算法要保证安全性的话,密钥要做好保密。只能让使用的人知道,不能对外公开。
在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的。一把是作为公开的公钥,另一把是作为谁都不能给的私钥。公钥加密的信息,只有私钥才能解密。私钥加密的信息,只有公钥才能解密。
因为对称加密算法相比非对称加密算法来说,效率要高得多,性能也好,所以交互的场景下多用对称加密。
## 对称加密
假设你和外卖网站约定了一个密钥,你发送请求的时候用这个密钥进行加密,外卖网站用同样的密钥进行解密。这样就算中间的黑客截获了你的请求,但是它没有密钥,还是破解不了。
这看起来很完美,但是中间有个问题,你们两个怎么来约定这个密钥呢?如果这个密钥在互联网上传输,也是很有可能让黑客截获的。黑客一旦截获这个秘钥,它可以佯作不知,静静地等着你们两个交互。这时候你们之间互通的任何消息,它都能截获并且查看,就等你把银行卡账号和密码发出来。
我们在谍战剧里面经常看到这样的场景,就是特工破译的密码会有个密码本,截获无线电台,通过密码本就能将原文破解出来。怎么把密码本给对方呢?只能通过**线下传输**。
比如,你和外卖网站偷偷约定时间地点,它给你一个纸条,上面写着你们两个的密钥,然后说以后就用这个密钥在互联网上定外卖了。当然你们接头的时候,也会先约定一个口号,什么“天王盖地虎”之类的,口号对上了,才能把纸条给它。但是,“天王盖地虎”同样也是对称加密密钥,同样存在如何把“天王盖地虎”约定成口号的问题。而且在谍战剧中一对一接头可能还可以,在互联网应用中,客户太多,这样是不行的。
## 非对称加密
所以,只要是对称加密,就会永远在这个死循环里出不来,这个时候,就需要非对称加密介入进来。
非对称加密的私钥放在外卖网站这里,不会在互联网上传输,这样就能保证这个密钥的私密性。但是,对应私钥的公钥,是可以在互联网上随意传播的,只要外卖网站把这个公钥给你,你们就可以愉快地互通了。
比如说你用公钥加密,说“我要定外卖”,黑客在中间就算截获了这个报文,因为它没有私钥也是解不开的,所以这个报文可以顺利到达外卖网站,外卖网站用私钥把这个报文解出来,然后回复,“那给我银行卡和支付密码吧”。
先别太乐观,这里还是有问题的。回复的这句话,是外卖网站拿私钥加密的,互联网上人人都可以把它打开,当然包括黑客。那外卖网站可以拿公钥加密吗?当然不能,因为它自己的私钥只有它自己知道,谁也解不开。
另外,这个过程还有一个问题,黑客也可以模拟发送“我要定外卖”这个过程的,因为它也有外卖网站的公钥。
为了解决这个问题,看来一对公钥私钥是不够的,客户端也需要有自己的公钥和私钥,并且客户端要把自己的公钥,给外卖网站。
这样,客户端给外卖网站发送的时候,用外卖网站的公钥加密。而外卖网站给客户端发送消息的时候,使用客户端的公钥。这样就算有黑客企图模拟客户端获取一些信息,或者半路截获回复信息,但是由于它没有私钥,这些信息它还是打不开。
## 数字证书
不对称加密也会有同样的问题,如何将不对称加密的公钥给对方呢?一种是放在一个公网的地址上,让对方下载;另一种就是在建立连接的时候,传给对方。
这两种方法有相同的问题,那就是,作为一个普通网民,你怎么鉴别别人给你的公钥是对的。会不会有人冒充外卖网站,发给你一个它的公钥。接下来,你和它所有的互通,看起来都是没有任何问题的。毕竟每个人都可以创建自己的公钥和私钥。
例如我自己搭建了一个网站cliu8site可以通过这个命令先创建私钥。
```
openssl genrsa -out cliu8siteprivate.key 1024
```
然后,再根据这个私钥,创建对应的公钥。
```
openssl rsa -in cliu8siteprivate.key -pubout -outcliu8sitepublic.pem
```
这个时候就需要权威部门的介入了,就像每个人都可以打印自己的简历,说自己是谁,但是有公安局盖章的,就只有户口本,这个才能证明你是你。这个由权威部门颁发的称为**证书****Certificate**)。
证书里面有什么呢?当然应该有**公钥**,这是最重要的;还有证书的**所有者**,就像户口本上有你的姓名和身份证号,说明这个户口本是你的;另外还有证书的**发布机构**和证书的**有效期**,这个有点像身份证上的机构是哪个区公安局,有效期到多少年。
这个证书是怎么生成的呢?会不会有人假冒权威机构颁发证书呢?就像有假身份证、假户口本一样。生成证书需要发起一个证书请求,然后将这个请求发给一个权威机构去认证,这个权威机构我们称为**CA** **Certificate Authority**)。
证书请求可以通过这个命令生成。
```
openssl req -key cliu8siteprivate.key -new -out cliu8sitecertificate.req
```
将这个请求发给权威机构,权威机构会给这个证书卡一个章,我们称为**签名算法。**问题又来了那怎么签名才能保证是真的权威机构签名的呢当然只有用只掌握在权威机构手里的东西签名了才行这就是CA的私钥。
签名算法大概是这样工作的一般是对信息做一个Hash计算得到一个Hash值这个过程是不可逆的也就是说无法通过Hash值得出原来的信息内容。在把信息发送出去时把这个Hash值加密后作为一个签名和信息一起发出去。
权威机构给证书签名的命令是这样的。
```
openssl x509 -req -in cliu8sitecertificate.req -CA cacertificate.pem -CAkey caprivate.key -out cliu8sitecertificate.pem
```
这个命令会返回Signature ok而cliu8sitecertificate.pem就是签过名的证书。CA用自己的私钥给外卖网站的公钥签名就相当于给外卖网站背书形成了外卖网站的证书。
我们来查看这个证书的内容。
```
openssl x509 -in cliu8sitecertificate.pem -noout -text
```
这里面有个Issuer也即证书是谁颁发的Subject就是证书颁发给谁Validity是证书期限Public-key是公钥内容Signature Algorithm是签名算法。
这下好了你不会从外卖网站上得到一个公钥而是会得到一个证书这个证书有个发布机构CA你只要得到这个发布机构CA的公钥去解密外卖网站证书的签名如果解密成功了Hash也对的上就说明这个外卖网站的公钥没有啥问题。
你有没有发现又有新问题了。要想验证证书需要CA的公钥问题是你怎么确定CA的公钥就是对的呢
所以CA的公钥也需要更牛的CA给它签名然后形成CA的证书。要想知道某个CA的证书是否可靠要看CA的上级证书的公钥能不能解开这个CA的签名。就像你不相信区公安局可以打电话问市公安局让市公安局确认区公安局的合法性。这样层层上去直到全球皆知的几个著名大CA称为**root CA**,做最后的背书。通过这种**层层授信背书**的方式,从而保证了非对称加密模式的正常运转。
除此之外,还有一种证书,称为**Self-Signed Certificate**,就是自己给自己签名。这个给人一种“我就是我,你爱信不信”的感觉。这里我就不多说了。
## HTTPS的工作模式
我们可以知道,非对称加密在性能上不如对称加密,那是否能将两者结合起来呢?例如,公钥私钥主要用于传输对称加密的秘钥,而真正的双方大数据量的通信都是通过对称加密进行的。
当然是可以的。这就是HTTPS协议的总体思路。
<img src="https://static001.geekbang.org/resource/image/df/b4/df1685dd308cef1db97e91493f911ab4.jpg" alt="">
当你登录一个外卖网站的时候由于是HTTPS客户端会发送Client Hello消息到服务器以明文传输TLS版本信息、加密套件候选列表、压缩算法候选列表等信息。另外还会有一个随机数在协商对称密钥的时候使用。
这就类似在说:“您好,我想定外卖,但你要保密我吃的是什么。这是我的加密套路,再给你个随机数,你留着。”
然后外卖网站返回Server Hello消息, 告诉客户端,服务器选择使用的协议版本、加密套件、压缩算法等,还有一个随机数,用于后续的密钥协商。
这就类似在说“您好保密没问题你的加密套路还挺多咱们就按套路2来吧我这里也有个随机数你也留着。”
然后外卖网站会给你一个服务器端的证书然后说“Server Hello Done我这里就这些信息了。”
你当然不相信这个证书于是你从自己信任的CA仓库中拿CA的证书里面的公钥去解密外卖网站的证书。如果能够成功则说明外卖网站是可信的。这个过程中你可能会不断往上追溯CA、CA的CA、CA的CA的CA反正直到一个授信的CA就可以了。
证书验证完毕之后觉得这个外卖网站可信于是客户端计算产生随机数字Pre-master发送Client Key Exchange用证书中的公钥加密再发送给服务器服务器可以通过私钥解密出来。
到目前为止无论是客户端还是服务器都有了三个随机数分别是自己的、对端的以及刚生成的Pre-Master随机数。通过这三个随机数可以在客户端和服务器产生相同的对称密钥。
有了对称密钥客户端就可以说“Change Cipher Spec咱们以后都采用协商的通信密钥和加密算法进行加密通信了。”
然后发送一个Encrypted Handshake Message将已经商定好的参数等采用协商密钥进行加密发送给服务器用于数据与握手验证。
同样服务器也可以发送Change Cipher Spec“没问题咱们以后都采用协商的通信密钥和加密算法进行加密通信了”并且也发送Encrypted Handshake Message的消息试试。当双方握手结束之后就可以通过对称密钥进行加密传输了。
这个过程除了加密解密之外其他的过程和HTTP是一样的过程也非常复杂。
上面的过程只包含了HTTPS的单向认证也即客户端验证服务端的证书是大部分的场景也可以在更加严格安全要求的情况下启用双向认证双方互相验证证书。
## 重放与篡改
其实,这里还有一些没有解决的问题,例如重放和篡改的问题。
没错有了加密和解密黑客截获了包也打不开了但是它可以发送N次。这个往往通过Timestamp和Nonce随机数联合起来然后做一个不可逆的签名来保证。
Nonce随机数保证唯一或者Timestamp和Nonce合起来保证唯一同样的请求只接受一次于是服务器多次收到相同的Timestamp和Nonce则视为无效即可。
如果有人想篡改Timestamp和Nonce还有签名保证不可篡改性如果改了用签名算法解出来就对不上了可以丢弃了。
## 小结
好了,这一节就到这里了,我们来总结一下。
<li>
加密分对称加密和非对称加密。对称加密效率高,但是解决不了密钥传输问题;非对称加密可以解决这个问题,但是效率不高。
</li>
<li>
非对称加密需要通过证书和权威机构来验证公钥的合法性。
</li>
<li>
HTTPS是综合了对称加密和非对称加密算法的HTTP协议。既保证传输安全也保证传输效率。
</li>
最后,给你留两个思考题:
<li>
HTTPS协议比较复杂沟通过程太繁复这样会导致效率问题那你知道有哪些手段可以解决这些问题吗
</li>
<li>
HTTP和HTTPS协议的正文部分传输个JSON什么的还好如果播放视频就有问题了那这个时候应该使用什么协议呢
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,231 @@
<audio id="audio" title="第16讲 | 流媒体协议:如何在直播里看到美女帅哥?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/27/cf/271e9364c1c9a01d23780338e74a91cf.mp3"></audio>
最近直播比较火,很多人都喜欢看直播,那一个直播系统里面都有哪些组成部分,都使用了什么协议呢?
无论是直播还是点播,其实都是对于视频数据的传输。一提到视频,大家都爱看,但是一提到视频技术,大家都头疼,因为名词实在是太多了。
## 三个名词系列
我这里列三个名词系列,你先大致有个印象。
<li>
**名词系列一**AVI、MPEG、RMVB、MP4、MOV、FLV、WebM、WMV、ASF、MKV。例如RMVB和MP4看着是不是很熟悉
</li>
<li>
**名词系列二**H.261、 H.262、H.263、H.264、H.265。这个是不是就没怎么听过了别着急你先记住要重点关注H.264。
</li>
<li>
**名词系列**三MPEG-1、MPEG-2、MPEG-4、MPEG-7。MPEG好像听说过但是后面的数字是怎么回事是不是又熟悉又陌生
</li>
这里,我想问你个问题,视频是什么?我说,其实就是快速播放一连串连续的图片。
每一张图片,我们称为一**帧**。只要每秒钟帧的数据足够多也即播放得足够快。比如每秒30帧以人的眼睛的敏感程度是看不出这是一张张独立的图片的这就是我们常说的**帧率****FPS**)。
每一张图片,都是由**像素**组成的假设为1024*768这个像素数不算多。每个像素由RGB组成每个8位共24位。
我们来算一下,每秒钟的视频有多大?
30帧 × 1024 × 768 × 24 = 566,231,040Bits = 70,778,880Bytes
如果一分钟呢4,246,732,800Bytes已经是4个G了。
是不是不算不知道,一算吓一跳?这个数据量实在是太大,根本没办法存储和传输。如果这样存储,你的硬盘很快就满了;如果这样传输,那多少带宽也不够用啊!
怎么办呢?人们想到了**编码**就是看如何用尽量少的Bit数保存视频使播放的时候画面看起来仍然很精美。**编码是一个压缩的过程。**
## 视频和图片的压缩过程有什么特点?
之所以能够对视频流中的图片进行压缩,因为视频和图片有这样一些特点。
<li>
**空间冗余**:图像的相邻像素之间有较强的相关性,一张图片相邻像素往往是渐变的,不是突变的,没必要每个像素都完整地保存,可以隔几个保存一个,中间的用算法计算出来。
</li>
<li>
**时间冗余**:视频序列的相邻图像之间内容相似。一个视频中连续出现的图片也不是突变的,可以根据已有的图片进行预测和推断。
</li>
<li>
**视觉冗余**:人的视觉系统对某些细节不敏感,因此不会每一个细节都注意到,可以允许丢失一些数据。
</li>
<li>
**编码冗余**:不同像素值出现的概率不同,概率高的用的字节少,概率低的用的字节多,类似[霍夫曼编码Huffman Coding](https://zh.wikipedia.org/wiki/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81)的思路。
</li>
总之,用于编码的算法非常复杂,而且多种多样,但是编码过程其实都是类似的。
<img src="https://static001.geekbang.org/resource/image/46/9e/46be417bde30b0a22f25928c30a3049e.jpg" alt="">
## 视频编码的两大流派
能不能形成一定的标准呢?要不然开发视频播放的人得累死了。当然能,我这里就给你介绍,视频编码的两大流派。
<li>
流派一ITUInternational Telecommunications Union的VCEGVideo Coding Experts Group这个称为**国际电联下的VCEG**。既然是电信,可想而知,他们最初做视频编码,主要侧重传输。名词系列二,就是这个组织制定的标准。
</li>
<li>
流派二ISOInternational Standards Organization的MPEGMoving Picture Experts Group这个是**ISO旗下的MPEG**本来是做视频存储的。例如编码后保存在VCD和DVD中。当然后来也慢慢侧重视频传输了。名词系列三就是这个组织制定的标准。
</li>
后来ITU-T国际电信联盟电信标准化部门ITU Telecommunication Standardization Sector与MPEG联合制定了H.264/MPEG-4 AVC这才是我们这一节要重点关注的。
经过编码之后,生动活泼的一帧一帧的图像,就变成了一串串让人看不懂的二进制,这个二进制可以放在一个文件里面,按照一定的格式保存起来,这就是名词系列一。
其实这些就是视频保存成文件的格式。例如,前几个字节是什么意义,后几个字节是什么意义,然后是数据,数据中保存的就是编码好的结果。
## 如何在直播里看到帅哥美女?
当然,这个二进制也可以通过某种网络协议进行封装,放在互联网上传输,这个时候就可以进行网络直播了。
网络协议将**编码**好的视频流,从主播端推送到服务器,在服务器上有个运行了同样协议的服务端来接收这些网络包,从而得到里面的视频流,这个过程称为**接流**。
服务端接到视频流之后,可以对视频流进行一定的处理,例如**转码**,也即从一个编码格式,转成另一种格式。因为观众使用的客户端千差万别,要保证他们都能看到直播。
**流处理**完毕之后,就可以等待观众的客户端来请求这些视频流。观众的客户端请求的过程称为**拉流**
如果有非常多的观众,同时看一个视频直播,那都从一个服务器上**拉流**,压力太大了,因而需要一个视频的**分发**网络,将视频预先加载到就近的边缘节点,这样大部分观众看的视频,是从边缘节点拉取的,就能降低服务器的压力。
当观众的客户端将视频流拉下来之后,就需要进行**解码**,也即通过上述过程的逆过程,将一串串看不懂的二进制,再转变成一帧帧生动的图片,在客户端**播放**出来,这样你就能看到美女帅哥啦。
整个直播过程,可以用这个的图来描述。
<img src="https://static001.geekbang.org/resource/image/a9/5c/a90e05f4496baf25df15e0871a5e205c.jpg" alt=""><br>
接下来,我们依次来看一下每个过程。
### 编码:如何将丰富多彩的图片变成二进制流?
虽然我们说视频是一张张图片的序列,但是如果每张图片都完整,就太大了,因而会将视频序列分成三种帧。
<li>
**I帧**,也称关键帧。里面是完整的图片,只需要本帧数据,就可以完成解码。
</li>
<li>
**P帧**前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧或P帧的差别解码时需要用之前缓存的画面叠加上和本帧定义的差别生成最终画面。
</li>
<li>
**B帧**双向预测内插编码帧。B帧记录的是本帧与前后帧的差别。要解码B帧不仅要取得之前的缓存画面还要解码之后的画面通过前后画面的数据与本帧数据的叠加取得最终的画面。
</li>
可以看出I帧最完整B帧压缩率最高而压缩后帧的序列应该是在IBBP的间隔出现的。这就是**通过时序进行编码**。
<img src="https://static001.geekbang.org/resource/image/10/4f/10abca08bddaac3214cd69bb9a7b8a4f.jpg" alt="">
在一帧中,分成多个片,每个片中分成多个宏块,每个宏块分成多个子块,这样将一张大的图分解成一个个小块,可以方便进行**空间上的编码**。
尽管时空非常立体地组成了一个序列,但是总归还是要压缩成一个二进制流。这个流是有结构的,是一个个的**网络提取层单元****NALU****Network Abstraction Layer Unit**)。变成这种格式就是为了传输,因为网络上的传输,默认的是一个个的包,因而这里也就分成了一个个的单元。
<img src="https://static001.geekbang.org/resource/image/4d/c1/4df30b99a606504cb0bbbc611c27d8c1.jpg" alt="">
每一个NALU首先是一个起始标识符用于标识NALU之间的间隔然后是NALU的头里面主要配置了NALU的类型最终Payload里面是NALU承载的数据。
在NALU头里面主要的内容是类型**NAL Type**。
<li>
0x07表示SPS是序列参数集 包括一个图像序列的所有信息,如图像尺寸、视频格式等。
</li>
<li>
0x08表示PPS是图像参数集包括一个图像的所有分片的所有相关信息包括图像类型、序列号等。
</li>
在传输视频流之前必须要传输这两类参数不然无法解码。为了保证容错性每一个I帧前面都会传一遍这两个参数集合。
如果NALU Header里面的表示类型是SPS或者PPS则Payload中就是真正的参数集的内容。
如果类型是帧则Payload中才是正的视频数据当然也是一帧一帧存放的前面说了一帧的内容还是挺多的因而每一个NALU里面保存的是一片。对于每一片到底是I帧还是P帧还是B帧在片结构里面也有个Header这里面有个类型然后是片的内容。
这样,整个格式就出来了,**一个视频可以拆分成一系列的帧每一帧拆分成一系列的片每一片都放在一个NALU里面NALU之间都是通过特殊的起始标识符分隔在每一个I帧的第一片前面要插入单独保存SPS和PPS的NALU最终形成一个长长的NALU序列**。
### 推流:如何把数据流打包传输到对端?
那这个格式是不是就能够直接在网上传输到对端,开始直播了呢?其实还不是,还需要将这个二进制的流打包成网络包进行发送,这里我们使用**RTMP协议**。这就进入了第二个过程,**推流**。
RTMP是基于TCP的因而肯定需要双方建立一个TCP的连接。在有TCP的连接的基础上还需要建立一个RTMP的连接也即在程序里面你需要调用RTMP类库的Connect函数显示创建一个连接。
RTMP为什么需要建立一个单独的连接呢
因为它们需要商量一些事情,保证以后的传输能正常进行。主要就是两个事情,一个是**版本号**,如果客户端、服务器的版本号不一致,则不能工作。另一个就是**时间戳**,视频播放中,时间是很重要的,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。
未来沟通这些事情需要发送六条消息客户端发送C0、C1、 C2服务器发送S0、 S1、 S2。
首先客户端发送C0表示自己的版本号不必等对方的回复然后发送C1表示自己的时间戳。
服务器只有在收到C0的时候才能返回S0表明自己的版本号如果版本不匹配可以断开连接。
服务器发送完S0后也不用等什么就直接发送自己的时间戳S1。客户端收到S1的时候发一个知道了对方时间戳的ACK C2。同理服务器收到C1的时候发一个知道了对方时间戳的ACK S2。
于是,握手完成。
<img src="https://static001.geekbang.org/resource/image/ba/84/ba03ba5f2b2515df3669c469301e5784.jpg" alt="">
握手之后双方需要互相传递一些控制信息例如Chunk块的大小、窗口大小等。
真正传输数据的时候还是需要创建一个流Stream然后通过这个Stream来推流publish。
推流的过程就是将NALU放在Message里面发送这个也称为**RTMP Packet包**。Message的格式就像这样。
<img src="https://static001.geekbang.org/resource/image/20/b3/206yy043f9fdfa79d984156fb2fea4b3.jpg" alt="">
发送的时候去掉NALU的起始标识符。因为这部分对于RTMP协议来讲没有用。接下来将SPS和PPS参数集封装成一个RTMP包发送然后发送一个个片的NALU。
RTMP在收发数据的时候并不是以Message为单位的而是把Message拆分成Chunk发送而且必须在一个Chunk发送完成之后才能开始发送下一个Chunk。每个Chunk中都带有Message ID表示属于哪个Message接收端也会按照这个ID将Chunk组装成Message。
前面连接的时候设置的Chunk块大小就是指这个Chunk。将大的消息变为小的块再发送可以在低带宽的情况下减少网络拥塞。
这有一个分块的例子,你可以看一下。
假设一个视频的消息长度为307但是Chunk大小约定为128于是会拆分为三个Chunk。
第一个Chunk的Type0表示Chunk头是完整的头里面Timestamp为1000总长度Length 为307类型为9是个视频Stream ID为12346正文部分承担128个字节的Data。
第二个Chunk也要发送128个字节Chunk头由于和第一个Chunk一样因此采用Chunk Type3表示头一样就不再发送了。
第三个Chunk要发送的Data的长度为307-128-128=51个字节还是采用Type3。
<img src="https://static001.geekbang.org/resource/image/18/06/186f198d54300f772dcddcb6ec476a06.jpg" alt="">
就这样数据就源源不断到达流媒体服务器,整个过程就像这样。
<img src="https://static001.geekbang.org/resource/image/0b/64/0b4642e6c5130aeac225b9db1126fc64.jpg" alt="">
这个时候大量观看直播的观众就可以通过RTMP协议从流媒体服务器上拉取但是这么多的用户量都去同一个地方拉取服务器压力会很大而且用户分布在全国甚至全球如果都去统一的一个地方下载也会时延比较长需要有分发网络。
分发网络分为**中心**和**边缘**两层。边缘层服务器部署在全国各地及横跨各大运营商里,和用户距离很近。中心层是流媒体服务集群,负责内容的转发。智能负载均衡系统,根据用户的地理位置信息,就近选择边缘服务器,为用户提供推/拉流服务。中心层也负责转码服务例如把RTMP协议的码流转换为HLS码流。
<img src="https://static001.geekbang.org/resource/image/01/56/01b67e494f4705e71243f89b9cd1a556.jpg" alt="">
这套机制在后面的DNS、HTTPDNS、CDN的章节会更有详细的描述。
### 拉流:观众的客户端如何看到视频?
接下来我们再来看观众的客户端通过RTMP拉流的过程。
<img src="https://static001.geekbang.org/resource/image/2c/db/2cdaf16cc2ee71512a4bdf5995549fdb.jpg" alt="">
先读到的是H.264的解码参数例如SPS和PPS然后对收到的NALU组成的一个个帧进行解码交给播发器播放一个绚丽多彩的视频画面就出来了。
## 小结
好了,今天的内容就到这里了,我们来总结一下:
<li>
视频名词比较多,编码两大流派达成了一致,都是通过时间、空间的各种算法来压缩数据;
</li>
<li>
压缩好的数据为了传输组成一系列NALU按照帧和片依次排列
</li>
<li>
排列好的NALU在网络传输的时候要按照RTMP包的格式进行包装RTMP的包会拆分成Chunk进行传输
</li>
<li>
推送到流媒体集群的视频流经过转码和分发可以被客户端通过RTMP协议拉取然后组合为NALU解码成视频格式进行播放。
</li>
最后,给你留两个思考题:
<li>
你觉得基于RTMP的视频流传输的机制存在什么问题如何进行优化
</li>
<li>
在线看视频之前,大家都是把电影下载下来看的,电影这么大,你知道如何快速下载吗?
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,215 @@
<audio id="audio" title="第17讲 | P2P协议我下小电影99%急死你" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/70/af/70a9034e8e3d8cd4c67174c1e09bfcaf.mp3"></audio>
如果你想下载一个电影,一般会通过什么方式呢?
当然,最简单的方式就是通过**HTTP**进行下载。但是相信你有过这样的体验,通过浏览器下载的时候,只要文件稍微大点,下载的速度就奇慢无比。
还有种下载文件的方式,就是通过**FTP**,也即**文件传输协议**。FTP采用两个TCP连接来传输一个文件。
<li>
**控制连接**服务器以被动的方式打开众所周知用于FTP的端口21客户端则主动发起连接。该连接将命令从客户端传给服务器并传回服务器的应答。常用的命令有list——获取文件目录reter——取一个文件store——存一个文件。
</li>
<li>
**数据连接**:每当一个文件在客户端与服务器之间传输时,就创建一个数据连接。
</li>
## FTP的两种工作模式
每传输一个文件都要建立一个全新的数据连接。FTP有两种工作模式分别是**主动模式****PORT**)和**被动模式****PASV**这些都是站在FTP服务器的角度来说的。
主动模式下客户端随机打开一个大于1024的端口N向服务器的命令端口21发起连接同时开放N+1端口监听并向服务器发出 “port N+1” 命令由服务器从自己的数据端口20主动连接到客户端指定的数据端口N+1。
被动模式下当开启一个FTP连接时客户端打开两个任意的本地端口N大于1024和N+1。第一个端口连接服务器的21端口提交PASV命令。然后服务器会开启一个任意的端口P大于1024返回“227 entering passive mode”消息里面有FTP服务器开放的用来进行数据传输的端口。客户端收到消息取得端口号之后会通过N+1号端口连接服务器的端口P然后在两个端口之间进行数据传输。
## P2P是什么
但是无论是HTTP的方式还是FTP的方式都有一个比较大的缺点就是**难以解决单一服务器的带宽压力** 因为它们使用的都是传统的客户端服务器的方式。
后来一种创新的、称为P2P的方式流行起来。**P2P**就是**peer-to-peer**。资源开始并不集中地存储在某些设备上而是分散地存储在多台设备上。这些设备我们姑且称为peer。
想要下载一个文件的时候你只要得到那些已经存在了文件的peer并和这些peer之间建立点对点的连接而不需要到中心服务器上就可以就近下载文件。一旦下载了文件你也就成为peer中的一员你旁边的那些机器也可能会选择从你这里下载文件所以当你使用P2P软件的时候例如BitTorrent往往能够看到既有下载流量也有上传的流量也即你自己也加入了这个P2P的网络自己从别人那里下载同时也提供给其他人下载。可以想象这种方式参与的人越多下载速度越快一切完美。
## 种子(.torrent文件
但是有一个问题当你想下载一个文件的时候怎么知道哪些peer有这个文件呢
这就用到**种子**啦,也即咱们比较熟悉的**.torrent文件**。.torrent文件由两部分组成分别是**announce****tracker URL**)和**文件信息**。
文件信息里面有这些内容。
<li>
**info区**:这里指定的是该种子有几个文件、文件有多长、目录结构,以及目录和文件的名字。
</li>
<li>
**Name字段**:指定顶层目录名字。
</li>
<li>
**每个段的大小**BitTorrent简称BT协议把一个文件分成很多个小段然后分段下载。
</li>
<li>
**段哈希值**将整个种子中每个段的SHA-1哈希值拼在一起。
</li>
下载时BT客户端首先解析.torrent文件得到tracker地址然后连接tracker服务器。tracker服务器回应下载者的请求将其他下载者包括发布者的IP提供给下载者。下载者再连接其他下载者根据.torrent文件两者分别对方告知自己已经有的块然后交换对方没有的数据。此时不需要其他服务器参与并分散了单个线路上的数据流量因此减轻了服务器的负担。
下载者每得到一个块需要算出下载块的Hash验证码并与.torrent文件中的对比。如果一样则说明块正确不一样则需要重新下载这个块。这种规定是为了解决下载内容的准确性问题。
从这个过程也可以看出这种方式特别依赖tracker。tracker需要收集下载者信息的服务器并将此信息提供给其他下载者使下载者们相互连接起来传输数据。虽然下载的过程是非中心化的但是加入这个P2P网络的时候都需要借助tracker中心服务器这个服务器是用来登记有哪些用户在请求哪些资源。
所以这种工作方式有一个弊端一旦tracker服务器出现故障或者线路遭到屏蔽BT工具就无法正常工作了。
## 去中心化网络DHT
那能不能彻底非中心化呢?
于是,后来就有了一种叫作**DHT****Distributed Hash Table**的去中心化网络。每个加入这个DHT网络的人都要负责存储这个网络里的资源信息和其他成员的联系信息相当于所有人一起构成了一个庞大的分布式存储数据库。
有一种著名的DHT协议叫**Kademlia协议**。这个和区块链的概念一样,很抽象,我来详细讲一下这个协议。
任何一个BitTorrent启动之后它都有两个角色。一个是**peer**监听一个TCP端口用来上传和下载文件这个角色表明我这里有某个文件。另一个角色**DHT node**监听一个UDP的端口通过这个角色这个节点加入了一个DHT的网络。
<img src="https://static001.geekbang.org/resource/image/80/27/80ecacb45587d201cbb9a08c31476d27.jpg" alt=""><br>
在DHT网络里面每一个DHT node都有一个ID。这个ID是一个很长的串。每个DHT node都有责任掌握一些知识也就是**文件索引**,也即它应该知道某些文件是保存在哪些节点上。它只需要有这些知识就可以了,而它自己本身不一定就是保存这个文件的节点。
## 哈希值
当然每个DHT node不会有全局的知识也即不知道所有的文件保存在哪里它只需要知道一部分。那应该知道哪一部分呢这就需要用哈希算法计算出来。
每个文件可以计算出一个哈希值,而**DHT node的ID是和哈希值相同长度的串**。
DHT算法是这样规定的如果一个文件计算出一个哈希值则和这个哈希值一样的那个DHT node就有责任知道从哪里下载这个文件即便它自己没保存这个文件。
当然不一定这么巧总能找到和哈希值一模一样的有可能一模一样的DHT node也下线了所以DHT算法还规定除了一模一样的那个DHT node应该知道ID和这个哈希值非常接近的N个DHT node也应该知道。
什么叫和哈希值接近呢例如只修改了最后一位就很接近修改了倒数2位也不远修改了倒数3位也可以接受。总之凑齐了规定的N这个数就行。
刚才那个图里文件1通过哈希运算得到匹配ID的DHT node为node C当然还会有其他的我这里没有画出来。所以node C有责任知道文件1的存放地址虽然node C本身没有存放文件1。
同理文件2通过哈希运算得到匹配ID的DHT node为node E但是node D和E的ID值很近所以node D也知道。当然文件2本身没有必要一定在node D和E里但是碰巧这里就在E那有一份。
接下来一个新的节点node new上线了。如果想下载文件1它首先要加入DHT网络如何加入呢
在这种模式下,种子.torrent文件里面就不再是tracker的地址了而是一个list的node的地址而所有这些node都是已经在DHT网络里面的。当然随着时间的推移很可能有退出的有下线的但是我们假设不会所有的都联系不上总有一个能联系上。
node new只要在种子里面找到一个DHT node就加入了网络。
node new会计算文件1的哈希值并根据这个哈希值了解到和这个哈希值匹配或者很接近的node上知道如何下载这个文件例如计算出来的哈希值就是node C。
但是node new不知道怎么联系上node C因为种子里面的node列表里面很可能没有node C但是它可以问DHT网络特别像一个社交网络node new只有去它能联系上的node问你们知道不知道node C的联系方式呀
在DHT网络中每个node都保存了一定的联系方式但是肯定没有node的所有联系方式。DHT网络中节点之间通过互相通信也会交流联系方式也会删除联系方式。和人们的方式一样你有你的朋友圈你的朋友有它的朋友圈你们互相加微信就互相认识了过一段时间不联系就删除朋友关系。
有个理论是,社交网络中,任何两个人直接的距离不超过六度,也即你想联系比尔盖茨,也就六个人就能够联系到了。
所以node new想联系node C就去万能的朋友圈去问并且求转发朋友再问朋友很快就能找到。如果找不到C也能找到和C的ID很像的节点它们也知道如何下载文件1。
在node C上告诉node new下载文件1要去B、D、 F于是node new选择和node B进行peer连接开始下载它一旦开始下载自己本地也有文件1了于是node new告诉node C以及和node C的ID很像的那些节点我也有文件1了可以加入那个文件拥有者列表了。
但是你会发现node new上没有文件索引但是根据哈希算法一定会有某些文件的哈希值是和node new的ID匹配上的。在DHT网络中会有节点告诉它你既然加入了咱们这个网络你也有责任知道某些文件的下载地址。
好了,一切都分布式了。
这里面遗留几个细节的问题。
- DHT node ID以及文件哈希是个什么东西
节点ID是一个随机选择的160bits20字节空间文件的哈希也使用这样的160bits空间。
- 所谓ID相似具体到什么程度算相似
在Kademlia网络中距离是通过异或XOR计算的。我们就不以160bits举例了。我们以5位来举例。
01010与01000的距离就是两个ID之间的异或值为00010也即为2。 01010与00010的距离为01000也即为8,。01010与00011的距离为01001也即8+1=9 。以此类推,高位不同的,表示距离更远一些;低位不同的,表示距离更近一些,总的距离为所有的不同的位的距离之和。
这个距离不能比喻为地理位置因为在Kademlia网络中位置近不算近ID近才算近所以我把这个距离比喻为社交距离也即在朋友圈中的距离或者社交网络中的距离。这个和你住的位置没有关系和人的经历关系比较大。
还是以5位ID来举例就像在领英中排第一位的表示最近一份工作在哪里第二位的表示上一份工作在哪里然后第三位的是上上份工作第四位的是研究生在哪里读第五位的表示大学在哪里读。
如果你是一个猎头,在上面找候选人,当然最近的那份工作是最重要的。而对于工作经历越丰富的候选人,大学在哪里读的反而越不重要。
## DHT网络中的朋友圈是怎么维护的
就像人一样虽然我们常联系人的只有少数但是朋友圈里肯定是远近都有。DHT网络的朋友圈也是一样远近都有并且**按距离分层**。
假设某个节点的ID为01010如果一个节点的ID前面所有位数都与它相同只有最后1位不同。这样的节点只有1个为01011。与基础节点的异或值为00001即距离为1对于01010而言这样的节点归为“k-bucket 1”。
如果一个节点的ID前面所有位数都相同从倒数第2位开始不同这样的节点只有2个即01000和01001与基础节点的异或值为00010和00011即距离范围为2和3对于01010而言这样的节点归为“k-bucket 2”。
如果一个节点的ID前面所有位数相同从倒数第i位开始不同这样的节点只有2^(i-1)个,与基础节点的距离范围为[2^(i-1), 2^i)对于01010而言这样的节点归为“k-bucket i”。
最终到从倒数160位就开始都不同。
你会发现差距越大陌生人越多但是朋友圈不能都放下所以每一层都只放K个这是参数可以配置。
## DHT网络是如何查找朋友的
假设node A 的ID为00110要找node B ID为10000异或距离为10110距离范围在[2^4, 2^5)所以这个目标节点可能在“k-bucket 5”中这就说明B的ID与A的ID从第5位开始不同所以B可能在“k-bucket 5”中。
然后A看看自己的k-bucket 5有没有B。如果有太好了找到你了如果没有在k-bucket 5里随便找一个C。因为是二进制C、B都和A的第5位不同那么C的ID第5位肯定与B相同即它与B的距离会小于2^4相当于比A、B之间的距离缩短了一半以上。
再请求C在它自己的通讯录里按同样的查找方式找一下B。如果C知道B就告诉A如果C也不知道B那C按同样的搜索方法可以在自己的通讯录里找到一个离B更近的D朋友D、B之间距离小于2^3把D推荐给AA请求D进行下一步查找。
Kademlia的这种查询机制是通过折半查找的方式来收缩范围对于总的节点数目为N最多只需要查询log2(N)次,就能够找到。
例如,图中这个最差的情况。
<img src="https://static001.geekbang.org/resource/image/dc/b4/dc6d713751d09ea5dd0d79c65433aeb4.jpg" alt="">
A和B每一位都不一样所以相差31A找到的朋友C不巧正好在中间。和A的距离是16和B距离为15于是C去自己朋友圈找的时候不巧找到D正好又在中间距离C为8距离B为7。于是D去自己朋友圈找的时候不巧找到E正好又在中间距离D为4距离B为3E在朋友圈找到F距离E为2距离B为1最终在F的朋友圈距离1的地方找到B。当然这是最最不巧的情况每次找到的朋友都不远不近正好在中间。
如果碰巧了在A的朋友圈里面有G距离B只有3然后在G的朋友圈里面一下子就找到了B两次就找到了。
在DHT网络中朋友之间怎么沟通呢
Kademlia算法中每个节点只有4个指令。
<li>
PING测试一个节点是否在线还活着没相当于打个电话看还能打通不。
</li>
<li>
STORE要求一个节点存储一份数据既然加入了组织有义务保存一份数据。
</li>
<li>
FIND_NODE根据节点ID查找一个节点就是给一个160位的ID通过上面朋友圈的方式找到那个节点。
</li>
<li>
FIND_VALUE根据KEY查找一个数据实则上跟FIND_NODE非常类似。KEY就是文件对应的160位的ID就是要找到保存了文件的节点。
</li>
DHT网络中朋友圈如何更新呢
<li>
每个bucket里的节点都按最后一次接触的时间倒序排列这就相当于朋友圈里面最近联系过的人往往是最熟的。
</li>
<li>
每次执行四个指令中的任意一个都会触发更新。
</li>
<li>
当一个节点与自己接触时检查它是否已经在k-bucket中也就是说是否已经在朋友圈。如果在那么将它挪到k-bucket列表的最底也就是最新的位置刚联系过就置顶一下方便以后多联系如果不在新的联系人要不要加到通讯录里面呢假设通讯录已满的情况PING一下列表最上面也即最旧的一个节点。如果PING通了将旧节点挪到列表最底并丢弃新节点老朋友还是留一下如果PING不通删除旧节点并将新节点加入列表这人联系不上了删了吧。
</li>
这个机制保证了任意节点加入和离开都不影响整体网络。
## 小结
好了,今天的讲解就到这里了,我们总结一下:
<li>
下载一个文件可以使用HTTP或FTP这两种都是集中下载的方式而P2P则换了一种思路采取非中心化下载的方式
</li>
<li>
P2P也是有两种一种是依赖于tracker的也即元数据集中文件数据分散另一种是基于分布式的哈希算法元数据和文件数据全部分散。
</li>
接下来,给你留两个思考题:
<li>
除了这种去中心化分布式哈希的算法,你还能想到其他的应用场景吗?
</li>
<li>
在前面所有的章节中要下载一个文件都需要使用域名。但是网络通信是使用IP的那你知道怎么实现两者的映射机制吗
</li>
我们的专栏马上更新过半了,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从已发布的文章中选出一批认真留言的同学,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,135 @@
<audio id="audio" title="第10讲 | UDP协议因性善而简单难免碰到“城会玩”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/60/83/6046c29969d44eeb559bf99d0a366983.mp3"></audio>
讲完了IP层以后接下来我们开始讲传输层。传输层里比较重要的两个协议一个是TCP一个是UDP。对于不从事底层开发的人员来讲或者对于开发应用的人来讲最常用的就是这两个协议。由于面试的时候这两个协议经常会被放在一起问因而我在讲的时候也会结合着来讲。
## TCP和UDP有哪些区别
一般面试的时候我问这两个协议的区别大部分人会回答TCP是面向连接的UDP是面向无连接的。
什么叫面向连接什么叫无连接呢在互通之前面向连接的协议会先建立连接。例如TCP会三次握手而UDP不会。为什么要建立连接呢你TCP三次握手我UDP也可以发三个包玩玩有什么区别吗
**所谓的建立连接,是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,用这样的数据结构来保证所谓的面向连接的特性。**
例如,**TCP提供可靠交付**。通过TCP连接传输的数据无差错、不丢失、不重复、并且按序到达。我们都知道IP包是没有任何可靠性保证的一旦发出去就像西天取经走丢了、被妖怪吃了都只能随它去。但是TCP号称能做到那个连接维护的程序做的事情这个下两节我会详细描述。而**UDP继承了IP包的特性不保证不丢失不保证按顺序到达。**
再如,**TCP是面向字节流的**。发送的时候发的是一个流没头没尾。IP包可不是一个流而是一个个的IP包。之所以变成了流这也是TCP自己的状态维护做的事情。而**UDP继承了IP的特性基于数据报的一个一个地发一个一个地收。**
还有**TCP是可以有拥塞控制的**。它意识到包丢弃了或者网络的环境不好了,就会根据情况调整自己的行为,看看是不是发快了,要不要发慢点。**UDP就不会应用让我发我就发管它洪水滔天。**
因而**TCP其实是一个有状态服务**,通俗地讲就是有脑子的,里面精确地记着发送了没有,接收到没有,发送到哪个了,应该接收哪个了,错一点儿都不行。而**UDP则是无状态服务**。通俗地说是没脑子的,天真无邪的,发出去就发出去了。
我们可以这样比喻如果MAC层定义了本地局域网的传输行为IP层定义了整个网络端到端的传输行为这两层基本定义了这样的基因网络传输是以包为单位的二层叫帧网络层叫包传输层叫段。我们笼统地称为包。包单独传输自行选路在不同的设备封装解封装不保证到达。基于这个基因生下来的孩子UDP完全继承了这些特性几乎没有自己的思想。
## UDP包头是什么样的
我们来看一下UDP包头。
前面章节我已经讲过包的传输过程这里不再赘述。当我发送的UDP包到达目标机器后发现MAC地址匹配于是就取下来将剩下的包传给处理IP层的代码。把IP头取下来发现目标IP匹配接下来呢这里面的数据包是给谁呢
发送的时候我知道我发的是一个UDP的包收到的那台机器咋知道的呢所以在IP头里面有个8位协议这里会存放数据里面到底是TCP还是UDP当然这里是UDP。于是如果我们知道UDP头的格式就能从数据里面将它解析出来。解析出来以后呢数据给谁处理呢
处理完传输层的事情,内核的事情基本就干完了,里面的数据应该交给应用程序自己去处理,可是一台机器上跑着这么多的应用程序,应该给谁呢?
无论应用程序写的使用TCP传数据还是UDP传数据都要监听一个端口。正是这个端口用来区分应用程序要不说端口不能冲突呢。两个应用监听一个端口到时候包给谁呀所以按理说无论是TCP还是UDP包头里面应该有端口号根据端口号将数据交给相应的应用程序。
<img src="https://static001.geekbang.org/resource/image/2c/84/2c9a109f3be308dea901004a5a3b4c84.jpg" alt="" />
当我们看到UDP包头的时候发现的确有端口号有源端口号和目标端口号。因为是两端通信嘛这很好理解。但是你还会发现UDP除了端口号再没有其他的了。和下两节要讲的TCP头比起来这个简直简单得一塌糊涂啊
## UDP的三大特点
UDP就像小孩子一样有以下这些特点
第一,**沟通简单**,不需要一肚子花花肠子(大量的数据结构、处理逻辑、包头字段)。前提是它相信网络世界是美好的,秉承性善论,相信网络通路默认就是很容易送达的,不容易被丢弃的。
第二,**轻信他人**。它不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,他也可以传给任何人数据,甚至可以同时传给多个人数据。
第三,**愣头青,做事不懂权变**。不知道什么时候该坚持,什么时候该退让。它不会根据网络的情况进行发包的拥塞控制,无论网络丢包丢成啥样了,它该怎么发还怎么发。
## UDP的三大使用场景
基于UDP这种“小孩子”的特点我们可以考虑在以下的场景中使用。
第一,**需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用**。这很好理解,就像如果你是领导,你会让你们组刚毕业的小朋友去做一些没有那么难的项目,打一些没有那么难的客户,或者做一些失败了也能忍受的实验性项目。
我们在第四节讲的DHCP就是基于UDP协议的。一般的获取IP地址都是内网请求而且一次获取不到IP又没事过一会儿还有机会。我们讲过PXE可以在启动的时候自动安装操作系统操作系统镜像的下载使用的TFTP这个也是基于UDP协议的。在还没有操作系统的时候客户端拥有的资源很少不适合维护一个复杂的状态机而且因为是内网一般也没啥问题。
第二,**不需要一对一沟通,建立连接,而是可以广播的应用**。咱们小时候人都很简单,大家在班级里面,谁成绩好,谁写作好,应该表扬谁惩罚谁,谁得几个小红花都是当着全班的面讲的,公平公正公开。长大了人心复杂了,薪水、奖金要背靠背,和员工一对一沟通。
UDP的不面向连接的功能可以使得可以承载广播或者多播的协议。DHCP就是一种广播的形式就是基于UDP协议的而广播包的格式前面说过了。
对于多播我们在讲IP地址的时候讲过一个D类地址也即组播地址使用这个地址可以将包组播给一批机器。当一台机器上的某个进程想监听某个组播地址的时候需要发送IGMP包所在网络的路由器就能收到这个包知道有个机器上有个进程在监听这个组播地址。当路由器收到这个组播地址的时候会将包转发给这台机器这样就实现了跨路由器的组播。
在后面云中网络部分有一个协议VXLAN也是需要用到组播也是基于UDP协议的。
第三,**需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩,一往无前的时候**。记得曾国藩建立湘军的时候,专门招出生牛犊不怕虎的新兵,而不用那些“老油条”的八旗兵,就是因为八旗兵经历的事情多,遇到敌军不敢舍死忘生。
同理UDP简单、处理速度快不像TCP那样操这么多的心各种重传啊保证顺序啊前面的不收到后面的没法处理啊。不然等这些事情做完了时延早就上去了。而TCP在网络不好出现丢包的时候拥塞控制策略会主动的退缩降低发送速度这就相当于本来环境就差还自断臂膀用户本来就卡这下更卡了。
当前很多应用都是要求低时延的它们可不想用TCP如此复杂的机制而是想根据自己的场景实现自己的可靠和连接保证。例如如果应用自己觉得有的包丢了就丢了没必要重传了就可以算了有的比较重要则应用自己重传而不依赖于TCP。有的前面的包没到后面的包到了那就先给客户展示后面的嘛干嘛非得等到齐了呢如果网络不好丢了包那不能退缩啊要尽快传啊速度不能降下来啊要挤占带宽抢在客户失去耐心之前到达。
由于UDP十分简单基本啥都没做也就给了应用“城会玩”的机会。就像在和平年代每个人应该有独立的思考和行为应该可靠并且礼让但是如果在战争年代往往不太需要过于独立的思考而需要士兵简单服从命令就可以了。
曾国藩说哪支部队需要诱敌牺牲也就牺牲了相当于包丢了就丢了。两军狭路相逢的时候曾国藩说上没有带宽也要上这才给了曾国藩运筹帷幄城会玩的机会。同理如果你实现的应用需要有自己的连接策略可靠保证时延要求使用UDP然后再应用层实现这些是再好不过了。
## 基于UDP的“城会玩”的五个例子
我列举几种“城会玩”的例子。
### “城会玩”一网页或者APP的访问
原来访问网页和手机APP都是基于HTTP协议的。HTTP协议是基于TCP的建立连接都需要多次交互对于时延比较大的目前主流的移动互联网来讲建立一次连接需要的时间会比较长然而既然是移动中TCP可能还会断了重连也是很耗时的。而且目前的HTTP协议往往采取多个数据通道共享一个连接的情况这样本来为了加快传输速度但是TCP的严格顺序策略使得哪怕共享通道前一个不来后一个和前一个即便没关系也要等着时延也会加大。
而**QUIC**(全称**Quick UDP Internet Connections****快速UDP互联网连接**是Google提出的一种基于UDP改进的通信协议其目的是降低网络通信的延迟提供更好的用户互动体验。
QUIC在应用层上会自己实现快速连接建立、减少重传时延自适应拥塞控制是应用层“城会玩”的代表。这一节主要是讲UDPQUIC我们放到应用层去讲。
### “城会玩”二:流媒体的协议
现在直播比较火直播协议多使用RTMP这个协议我们后面的章节也会讲而这个RTMP协议也是基于TCP的。TCP的严格顺序传输要保证前一个收到了下一个才能确认如果前一个收不到下一个就算包已经收到了在缓存里面也需要等着。对于直播来讲这显然是不合适的因为老的视频帧丢了其实也就丢了就算再传过来用户也不在意了他们要看新的了如果老是没来就等着卡顿了新的也看不了那就会丢失客户所以直播实时性比较比较重要宁可丢包也不要卡顿的。
另外,对于丢包,其实对于视频播放来讲,有的包可以丢,有的包不能丢,因为视频的连续帧里面,有的帧重要,有的不重要,如果必须要丢包,隔几个帧丢一个,其实看视频的人不会感知,但是如果连续丢帧,就会感知了,因而在网络不好的情况下,应用希望选择性的丢帧。
还有就是当网络不好的时候TCP协议会主动降低发送速度这对本来当时就卡的看视频来讲是要命的应该应用层马上重传而不是主动让步。因而很多直播应用都基于UDP实现了自己的视频传输协议。
### “城会玩”三:实时游戏
游戏有一个特点,就是实时性比较高。快一秒你干掉别人,慢一秒你被别人爆头,所以很多职业玩家会买非常专业的鼠标和键盘,争分夺秒。
因而实时游戏中客户端和服务端要建立长连接来保证实时传输。但是游戏玩家很多服务器却不多。由于维护TCP连接需要在内核维护一些数据结构因而一台机器能够支撑的TCP连接数目是有限的然后UDP由于是没有连接的在异步IO机制引入之前常常是应对海量客户端连接的策略。
另外还是TCP的强顺序问题对战的游戏对网络的要求很简单玩家通过客户端发送给服务器鼠标和键盘行走的位置服务器会处理每个用户发送过来的所有场景处理完再返回给客户端客户端解析响应渲染最新的场景展示给玩家。
如果出现一个数据包丢失所有事情都需要停下来等待这个数据包重发。客户端会出现等待接收数据然而玩家并不关心过期的数据激战中卡1秒等能动了都已经死了。
游戏对实时要求较为严格的情况下采用自定义的可靠UDP协议自定义重传策略能够把丢包产生的延迟降到最低尽量减少网络问题对游戏性造成的影响。
### “城会玩”四IoT物联网
一方面物联网领域终端资源少很可能只是个内存非常小的嵌入式系统而维护TCP协议代价太大另一方面物联网对实时性要求也很高而TCP还是因为上面的那些原因导致时延大。Google旗下的Nest建立Thread Group推出了物联网通信协议Thread就是基于UDP协议的。
### “城会玩”五:移动通信领域
在4G网络里移动流量上网的数据面对的协议GTP-U是基于UDP的。因为移动网络协议比较复杂而GTP协议本身就包含复杂的手机上线下线的通信协议。如果基于TCPTCP的机制就显得非常多余这部分协议我会在后面的章节单独讲解。
## 小结
好了,这节就到这里了,我们来总结一下:
<li>
如果将TCP比作成熟的社会人UDP则是头脑简单的小朋友。TCP复杂UDP简单TCP维护连接UDP谁都相信TCP会坚持知进退UDP愣头青一个勇往直前
</li>
<li>
UDP虽然简单但它有简单的用法。它可以用在环境简单、需要多播、应用层自己控制传输的地方。例如DHCP、VXLAN、QUIC等。
</li>
最后,给你留两个思考题吧。
<li>
都说TCP是面向连接的在计算机看来怎么样才算一个连接呢
</li>
<li>
你知道TCP的连接是如何建立又是如何关闭的吗
</li>
欢迎你留言和讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="第11讲 | TCP协议因性恶而复杂先恶后善反轻松" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/32/50f570ae1182a6dd989401393329cc32.mp3"></audio>
上一节我们讲的UDP基本上包括了传输层所必须的端口字段。它就像我们小时候一样简单相信“网之初性本善不丢包不乱序”。
后来呢我们都慢慢长大了解了社会的残酷变得复杂而成熟就像TCP协议一样。它之所以这么复杂那是因为它秉承的是“性恶论”。它天然认为网络环境是恶劣的丢包、乱序、重传拥塞都是常有的事情一言不合就可能送达不了因而要从算法层面来保证可靠性。
## TCP包头格式
我们先来看TCP头的格式。从这个图上可以看出它比UDP复杂得多。
<img src="https://static001.geekbang.org/resource/image/64/bf/642947c94d6682a042ad981bfba39fbf.jpg" alt="">
首先源端口号和目标端口号是不可少的这一点和UDP是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。
接下来是包的序号。为什么要给包编号呢?当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该后到呢。编号是为了解决乱序问题。既然是社会老司机,做事当然要稳重,一件件来,面临再复杂的情况,也临危不乱。
还应该有的就是确认序号。发出去的包应该有确认,要不然我怎么知道对方有没有收到呢?如果没有收到就应该重新发送,直到送达。这个可以解决不丢包的问题。作为老司机,做事当然要靠谱,答应了就要做到,暂时做不到也要有个回复。
TCP是靠谱的协议但是这不能说明它面临的网络环境好。从IP层面来讲如果网络状况的确那么差是没有任何可靠性保证的而作为IP的上一层TCP也无能为力唯一能做的就是更加努力不断重传通过各种算法保证。也就是说对于TCP来讲IP层你丢不丢包我管不着但是我在我的层面上会努力保证可靠性。
这有点像如果你在北京,和客户约十点见面,那么你应该清楚堵车是常态,你干预不了,也控制不了,你唯一能做的就是早走。打车不行就改乘地铁,尽力不失约。
接下来有一些状态位。例如SYN是发起一个连接ACK是回复RST是重新连接FIN是结束连接等。TCP是面向连接的因而双方要维护连接的状态这些带状态位的包的发送会引起双方的状态变更。
不像小时候,随便一个不认识的小朋友都能玩在一起,人大了,就变得礼貌,优雅而警觉,人与人遇到会互相热情的寒暄,离开会不舍地道别,但是人与人之间的信任会经过多次交互才能建立。
还有一个重要的就是窗口大小。TCP要做流量控制通信双方各声明一个窗口标识自己当前能够的处理能力别发送的太快撑死我也别发的太慢饿死我。
作为老司机做事情要有分寸待人要把握尺度既能适当提出自己的要求又不强人所难。除了做流量控制以外TCP还会做拥塞控制对于真正的通路堵车不堵车它无能为力唯一能做的就是控制自己也即控制发送的速度。不能改变世界就改变自己嘛。
作为老司机,要会自我控制,知进退,知道什么时候应该坚持,什么时候应该让步。
通过对TCP头的解析我们知道要掌握TCP协议重点应该关注以下几个问题
<li>
顺序问题 ,稳重不乱;
</li>
<li>
丢包问题,承诺靠谱;
</li>
<li>
连接维护,有始有终;
</li>
<li>
流量控制,把握分寸;
</li>
<li>
拥塞控制,知进知退。
</li>
## TCP的三次握手
所有的问题,首先都要先建立一个连接,所以我们先来看连接维护问题。
TCP的连接建立我们常常称为三次握手。
A您好我是A。
B您好A我是B。
A您好B。
我们也常称为“请求-&gt;应答-&gt;应答之应答”的三个回合。这个看起来简单,其实里面还是有很多的学问,很多的细节。
首先,为什么要三次,而不是两次?按说两个人打招呼,一来一回就可以了啊?为了可靠,为什么不是四次?
我们还是假设这个通路是非常不可靠的A要发起一个连接当发了第一个请求杳无音信的时候会有很多的可能性比如第一个请求包丢了再如没有丢但是绕了弯路超时了还有B没有响应不想和我连接。
A不能确认结果于是再发再发。终于有一个请求包到了B但是请求包到了B的这个事情目前A还是不知道的A还有可能再发。
B收到了请求包就知道了A的存在并且知道A要和它建立连接。如果B不乐意建立连接则A会重试一阵后放弃连接建立失败没有问题如果B是乐意建立连接的则会发送应答包给A。
当然对于B来说这个应答包也是一入网络深似海不知道能不能到达A。这个时候B自然不能认为连接是建立好了因为应答包仍然会丢会绕弯路或者A已经挂了都有可能。
而且这个时候B还能碰到一个诡异的现象就是A和B原来建立了连接做了简单通信后结束了连接。还记得吗A建立连接的时候请求包重复发了几次有的请求包绕了一大圈又回来了B会认为这也是一个正常的的请求的话因此建立了连接可以想象这个连接不会进行下去也没有个终结的时候纯属单相思了。因而两次握手肯定不行。
B发送的应答可能会发送多次但是只要一次到达AA就认为连接已经建立了因为对于A来讲他的消息有去有回。A会给B发送应答之应答而B也在等这个消息才能确认连接的建立只有等到了这个消息对于B来讲才算它的消息有去有回。
当然A发给B的应答之应答也会丢也会绕路甚至B挂了。按理来说还应该有个应答之应答之应答这样下去就没底了。所以四次握手是可以的四十次都可以关键四百次也不能保证就真的可靠了。只要双方的消息都有去有回就基本可以了。
好在大部分情况下A和B建立了连接之后A会马上发送数据的一旦A发送数据则很多问题都得到了解决。例如A发给B的应答丢了当A后续发送的数据到达的时候B可以认为这个连接已经建立或者B压根就挂了A发送的数据会报错说B不可达A就知道B出事情了。
当然你可以说A比较坏就是不发数据建立连接后空着。我们在程序设计的时候可以要求开启keepalive机制即使没有真实的数据包也有探活包。
另外你作为服务端B的程序设计者对于A这种长时间不发包的客户端可以主动关闭从而空出资源来给其他客户端使用。
三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是**TCP包的序号的问题**。
A要告诉B我这面发起的包的序号起始是从哪个号开始的B同样也要告诉AB发起的包的序号起始是从哪个号开始的。为什么序号不能都从1开始呢因为这样往往会出现冲突。
例如A连上B之后发送了1、2、3三个包但是发送3的时候中间丢了或者绕路了于是重新发送后来A掉线了重新连上B后序号又从1开始然后发送2但是压根没想发送3但是上次绕路的那个3又回来了发给了BB自然认为这就是下一个包于是发生了错误。
因而每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的可以看成一个32位的计数器每4微秒加一如果计算一下如果到重复需要4个多小时那个绕路的包早就死翘翘了因为我们都知道IP包头里面有个TTL也即生存时间。
好了,双方终于建立了信任,建立了连接。前面也说过,为了维护这个连接,双方都要维护一个状态机,在连接建立的过程中,双方的状态变化时序图就像这样。
<img src="https://static001.geekbang.org/resource/image/c0/08/c067fe62f49e8152368c7be9d91adc08.jpg" alt="">
一开始客户端和服务端都处于CLOSED状态。先是服务端主动监听某个端口处于LISTEN状态。然后客户端主动发起连接SYN之后处于SYN-SENT状态。服务端收到发起的连接返回SYN并且ACK客户端的SYN之后处于SYN-RCVD状态。客户端收到服务端发送的SYN和ACK之后发送ACK的ACK之后处于ESTABLISHED状态因为它一发一收成功了。服务端收到ACK的ACK之后处于ESTABLISHED状态因为它也一发一收了。
## TCP四次挥手
好了,说完了连接,接下来说一说“拜拜”,好说好散。这常被称为四次挥手。
AB啊我不想玩了。
B你不想玩了啊我知道了。
这个时候还只是A不想玩了也即A不会再发送数据但是B能不能在ACK的时候直接关闭呢当然不可以了很有可能A是发完了最后的数据就准备不玩了但是B还没做完自己的事情还是可以发送数据的所以称为半关闭的状态。
这个时候A可以选择不再接收数据了也可以选择最后再接收一段数据等待B也主动关闭。
BA啊好吧我也不玩了拜拜。
A好的拜拜。
这样整个连接就关闭了。但是这个过程有没有异常情况呢?当然有,上面是和平分手的场面。
A开始说“不玩了”B说“知道了”这个回合是没什么问题的因为在此之前双方还处于合作的状态如果A说“不玩了”没有收到回复则A会重新发送“不玩了”。但是这个回合结束之后就有可能出现异常情况了因为已经有一方率先撕破脸。
一种情况是A说完“不玩了”之后直接跑路是会有问题的因为B还没有发起结束而如果A跑路B就算发起结束也得不到回答B就不知道该怎么办了。另一种情况是A说完“不玩了”B直接跑路也是有问题的因为A不知道B是还有事情要处理还是过一会儿会发送结束。
那怎么解决这些问题呢TCP协议专门设计了几个状态来处理这些问题。我们来看断开连接的时候的**状态时序图**。
<img src="https://static001.geekbang.org/resource/image/bf/13/bf1254f85d527c77cc4088a35ac11d13.jpg" alt="">
断开的时候我们可以看到当A说“不玩了”就进入FIN_WAIT_1的状态B收到“A不玩”的消息后发送知道了就进入CLOSE_WAIT的状态。
A收到“B说知道了”就进入FIN_WAIT_2的状态如果这个时候B直接跑路则A将永远在这个状态。TCP协议里面并没有对这个状态的处理但是Linux有可以调整tcp_fin_timeout这个参数设置一个超时时间。
如果B没有跑路发送了“B也不玩了”的请求到达A时A发送“知道B也不玩了”的ACK后从FIN_WAIT_2状态结束按说A可以跑路了但是最后的这个ACK万一B收不到呢则B会重新发一个“B不玩了”这个时候A已经跑路了的话B就再也收不到ACK了因而TCP协议要求A最后等待一段时间TIME_WAIT这个时间要足够长长到如果B没收到ACK的话“B说不玩了”会重发的A会重新发一个ACK并且足够时间到达B。
A直接跑路还有一个问题是A的端口就直接空出来了但是B不知道B原来发过的很多包很可能还在路上如果A的端口被一个新的应用占用了这个新的应用会收到上个连接中B发过来的包虽然序列号是重新生成的但是这里要上一个双保险防止产生混乱因而也需要等足够长的时间等到原来B发送的所有的包都死翘翘再空出端口来。
等待的时间设为2MSL**MSL**是**Maximum Segment Lifetime****报文最大生存时间**它是任何报文在网络上存在的最长时间超过这个时间报文将被丢弃。因为TCP报文基于是IP协议的而IP头中有一个TTL域是IP数据报可以经过的最大路由数每经过一个处理他的路由器此值就减1当此值为0则数据报将被丢弃同时发送ICMP报文通知源主机。协议规定MSL为2分钟实际应用中常用的是30秒1分钟和2分钟等。
还有一个异常情况就是B超过了2MSL的时间依然没有收到它发的FIN的ACK怎么办呢按照TCP的原理B当然还会重发FIN这个时候A再收到这个包之后A就表示我已经在这里等了这么长时间了已经仁至义尽了之后的我就都不认了于是就直接发送RSTB就知道A早就跑了。
## TCP状态机
将连接建立和连接断开的两个时序状态图综合起来就是这个著名的TCP的状态机。学习的时候比较建议将这个状态机和时序状态机对照着看不然容易晕。
<img src="https://static001.geekbang.org/resource/image/fd/2a/fd45f9ad6ed575ea6bfdaafeb3bfb62a.jpg" alt="">
在这个图中加黑加粗的部分是上面说到的主要流程其中阿拉伯数字的序号是连接过程中的顺序而大写中文数字的序号是连接断开过程中的顺序。加粗的实线是客户端A的状态变迁加粗的虚线是服务端B的状态变迁。
## 小结
好了,这一节就到这里了,我来做一个总结:
<li>
TCP包头很复杂但是主要关注五个问题顺序问题丢包问题连接维护流量控制拥塞控制
</li>
<li>
连接的建立是经过三次握手,断开的时候四次挥手,一定要掌握的我画的那个状态图。
</li>
最后,给你留两个思考题。
<li>
TCP的连接有这么多的状态你知道如何在系统中查看某个连接的状态吗
</li>
<li>
这一节仅仅讲了连接维护问题,其实为了维护连接的状态,还有其他的数据结构来处理其他的四个问题,那你知道是什么吗?
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,232 @@
<audio id="audio" title="第12讲 | TCP协议西行必定多妖孽恒心智慧消磨难" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/16/28/1621d135d4d92756ca0d9440c0651c28.mp3"></audio>
我们前面说到玄奘西行,要出网关。既然出了网关,那就是在公网上传输数据,公网往往是不可靠的,因而需要很多的机制去保证传输的可靠性,这里面需要恒心,也即各种**重传的策略**,还需要有智慧,也就是说,这里面包含着**大量的算法**。
## 如何做个靠谱的人?
TCP想成为一个成熟稳重的人成为一个靠谱的人。那一个人怎么样才算靠谱呢咱们工作中经常就有这样的场景比如你交代给下属一个事情以后下属到底能不能做到做到什么程度什么时候能够交付往往就会有应答有回复。这样处理事情的过程中一旦有异常你也可以尽快知道而不是交代完之后就石沉大海过了一个月再问他说啊我不记得了。
对应到网络协议上,就是客户端每发送的一个包,服务器端都应该有个回复,如果服务器端超过一定的时间没有回复,客户端就会重新发送这个包,直到有回复。
这个发送应答的过程是什么样呢?可以是**上一个收到了应答,再发送下一个**。这种模式有点像两个人直接打电话,你一句,我一句。但是这种方式的缺点是效率比较低。如果一方在电话那头处理的时间比较长,这一头就要干等着,双方都没办法干其他事情。咱们在日常工作中也不是这样的,不能你交代你的下属办一件事情,就一直打着电话看着他做,而是应该他按照你的安排,先将事情记录下来,办完一件回复一件。在他办事情的过程中,你还可以同时交代新的事情,这样双方就并行了。
如果使⽤这种模式,其实需要你和你的下属就不能靠脑⼦了,⽽是要都准备⼀个本⼦,你每交代下属⼀个事情,双方的本子都要记录⼀下。
当你的下属做完⼀件事情,就回复你,做完了,你就在你的本⼦上将这个事情划去。同时你的本⼦上每件事情都有时限,如果超过了时限下属还没有回复,你就要主动重新交代⼀下:上次那件事情,你还没回复我,咋样啦?
既然多件事情可以一起处理那就需要给每个事情编个号防止弄错了。例如程序员平时看任务的时候都会看JIRA的ID而不是每次都要描述一下具体的事情。在大部分情况下对于事情的处理是按照顺序来的先来的先处理这就给应答和汇报工作带来了方便。等开周会的时候每个程序员都可以将JIRA ID的列表拉出来说以上的都做完了⽽不⽤⼀个个说。
## 如何实现一个靠谱的协议?
TCP协议使用的也是同样的模式。为了保证顺序性每一个包都有一个ID。在建立连接的时候会商定起始的ID是什么然后按照ID一个个发送。为了保证不丢包对于发送的包都要进行应答但是这个应答也不是一个一个来的而是会应答某个之前的ID表示都收到了这种模式称为**累计确认**或者**累计应答****cumulative acknowledgment**)。
为了记录所有发送的包和接收的包TCP也需要发送端和接收端分别都有缓存来保存这些记录。发送端的缓存里是按照包的ID一个个排列根据处理的情况分成四个部分。
第一部分:发送了并且已经确认的。这部分就是你交代下属的,并且也做完了的,应该划掉的。
第二部分:发送了并且尚未确认的。这部分是你交代下属的,但是还没做完的,需要等待做完的回复之后,才能划掉。
第三部分:没有发送,但是已经等待发送的。这部分是你还没有交代给下属,但是马上就要交代的。
第四部分:没有发送,并且暂时还不会发送的。这部分是你还没有交代给下属,而且暂时还不会交代给下属的。
这里面为什么要区分第三部分和第四部分呢?没交代的,一下子全交代了不就完了吗?
这就是我们上一节提到的十个词口诀里的“流量控制,把握分寸”。作为项目管理人员,你应该根据以往的工作情况和这个员工反馈的能力、抗压力等,先在心中估测一下,这个人一天能做多少工作。如果工作布置少了,就会不饱和;如果工作布置多了,他就会做不完;如果你使劲逼迫,人家可能就要辞职了。
到底一个员工能够同时处理多少事情呢在TCP里接收端会给发送端报一个窗口的大小叫**Advertised window**。这个窗口的大小应该等于上面的第二部分加上第三部分,就是已经交代了没做完的加上马上要交代的。超过这个窗口的,接收端做不过来,就不能发送了。
于是,发送端需要保持下面的数据结构。
<img src="https://static001.geekbang.org/resource/image/dd/44/dd67ba62279a3849c11ffc1deea25d44.jpg" alt="">
<li>
LastByteAcked第一部分和第二部分的分界线
</li>
<li>
LastByteSent第二部分和第三部分的分界线
</li>
<li>
LastByteAcked + AdvertisedWindow第三部分和第四部分的分界线
</li>
对于接收端来讲,它的缓存里记录的内容要简单一些。
第一部分:接受并且确认过的。也就是我领导交代给我,并且我做完的。
第二部分:还没接收,但是马上就能接收的。也即是我自己的能够接受的最大工作量。
第三部分:还没接收,也没法接收的。也即超过工作量的部分,实在做不完。
对应的数据结构就像这样。<br>
<br>
<img src="https://static001.geekbang.org/resource/image/9d/be/9d597af268016f67caa14178627188be.jpg" alt="">
<li>
MaxRcvBuffer最大缓存的量
</li>
<li>
LastByteRead之后是已经接收了但是还没被应用层读取的
</li>
<li>
NextByteExpected是第一部分和第二部分的分界线。
</li>
第二部分的窗口有多大呢?
NextByteExpected和LastByteRead的差其实是还没被应用层读取的部分占用掉的MaxRcvBuffer的量我们定义为A。
AdvertisedWindow其实是MaxRcvBuffer减去A。
也就是AdvertisedWindow=MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)。
那第二部分和第三部分的分界线在哪里呢NextByteExpected加AdvertisedWindow就是第二部分和第三部分的分界线其实也就是LastByteRead加上MaxRcvBuffer。
其中第二部分里面,由于受到的包可能不是顺序的,会出现空档,只有和第一部分连续的,可以马上进行回复,中间空着的部分需要等待,哪怕后面的已经来了。
## 顺序问题与丢包问题
接下来我们结合一个例子来看。
还是刚才的图在发送端来看1、2、3已经发送并确认4、5、6、7、8、9都是发送了还没确认10、11、12是还没发出的13、14、15是接收方没有空间不准备发的。
在接收端来看1、2、3、4、5是已经完成ACK但是没读取的6、7是等待接收的8、9是已经接收但是没有ACK的。
发送端和接收端当前的状态如下:
<li>
1、2、3没有问题双方达成了一致。
</li>
<li>
4、5接收方说ACK了但是发送方还没收到有可能丢了有可能在路上。
</li>
<li>
6、7、8、9肯定都发了但是8、9已经到了但是6、7没到出现了乱序缓存着但是没办法ACK。
</li>
根据这个例子,我们可以知道,顺序问题和丢包问题都有可能发生,所以我们先来看**确认与重发的机制**。
假设4的确认到了不幸的是5的ACK丢了6、7的数据包丢了这该怎么办呢
一种方法就是**超时重试**也即对每一个发送了但是没有ACK的包都有设一个定时器超过了一定的时间就重新尝试。但是这个超时的时间如何评估呢这个时间不宜过短时间必须大于往返时间RTT否则会引起不必要的重传。也不宜过长这样超时时间变长访问就变慢了。
估计往返时间需要TCP通过采样RTT的时间然后进行加权平均算出一个值而且这个值还是要不断变化的因为网络状况不断地变化。除了采样RTT还要采样RTT的波动范围计算出一个估计的超时时间。由于重传时间是不断变化的我们称为**自适应重传算法****Adaptive Retransmission Algorithm**)。
如果过一段时间5、6、7都超时了就会重新发送。接收方发现5原来接收过于是丢弃56收到了发送ACK要求下一个是77不幸又丢了。当7再次超时的时候有需要重传的时候TCP的策略是**超时间隔加倍**。**每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍**。**两次超时,就说明网络环境差,不宜频繁反复发送。**
超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?
有一个可以快速重传的机制当接收方收到一个序号大于下一个所期望的报文段时就会检测到数据流中的一个间隔于是它就会发送冗余的ACK仍然ACK的是期望接收的报文段。而当客户端收到三个冗余的ACK后就会在定时器过期之前重传丢失的报文段。
例如接收方发现6收到了8也收到了但是7还没来那肯定是丢了于是发送6的ACK要求下一个是7。接下来收到后续的包仍然发送6的ACK要求下一个是7。当客户端收到3个重复ACK就会发现7的确丢了不等超时马上重发。
还有一种方式称为**Selective Acknowledgment** **SACK**。这种方式需要在TCP头里加一个SACK的东西可以将缓存的地图发送给发送方。例如可以发送ACK6、SACK8、SACK9有了地图发送方一下子就能看出来是7丢了。
## 流量控制问题
我们再来看流量控制机制,在对于包的确认中,同时会携带一个窗口的大小。
我们先假设窗口不变的情况窗口始终为9。4的确认来的时候会右移一个这个时候第13个包也可以发送了。
<img src="https://static001.geekbang.org/resource/image/af/87/af16ecdfabf97f696d8133a20818fd87.jpg" alt="">
这个时候假设发送端发送过猛会将第三部分的10、11、12、13全部发送完毕之后就停止发送了未发送可发送部分为0。
<img src="https://static001.geekbang.org/resource/image/e0/35/e011cb0e56f43bae942f0b7ab7407b35.jpg" alt="">
当对于包5的确认到达的时候在客户端相当于窗口再滑动了一格这个时候才可以有更多的包可以发送了例如第14个包才可以发送。
<img src="https://static001.geekbang.org/resource/image/f5/c2/f5a4fcc035d1bb2d7e11c38391d768c2.jpg" alt="">
如果接收方实在处理的太慢导致缓存中没有空间了可以通过确认信息修改窗口的大小甚至可以设置为0则发送方将暂时停止发送。
我们假设一个极端情况接收端的应用一直不读取缓存中的数据当数据包6确认后窗口大小就不能再是9了就要缩小一个变为8。
<img src="https://static001.geekbang.org/resource/image/95/9d/953e6706cfb5083e1f25b267505f5c9d.jpg" alt="">
这个新的窗口8通过6的确认消息到达发送端的时候你会发现窗口没有平行右移而是仅仅左面的边右移了窗口的大小从9改成了8。
<img src="https://static001.geekbang.org/resource/image/0a/1f/0a9265c63d5e0fb08c442ea0a7cffa1f.jpg" alt="">
如果接收端还是一直不处理数据则随着确认的包越来越多窗口越来越小直到为0。
<img src="https://static001.geekbang.org/resource/image/c2/4a/c24c414c31bd5deb346f98417ecdb74a.jpg" alt="">
当这个窗口通过包14的确认到达发送端的时候发送端的窗口也调整为0停止发送。
<img src="https://static001.geekbang.org/resource/image/89/cb/89fe7b73e40363182b13e3d9c9aa2acb.jpg" alt=""><br>
如果这样的话,发送方会定时发送窗口探测数据包,看是否有机会调整窗口的大小。当接收方比较慢的时候,要防止低能窗口综合征,别空出一个字节来就赶快告诉发送方,然后马上又填满了,可以当窗口太小的时候,不更新窗口,直到达到一定大小,或者缓冲区一半为空,才更新窗口。
这就是我们常说的流量控制。
## 拥塞控制问题
最后我们看一下拥塞控制的问题也是通过窗口的大小来控制的前面的滑动窗口rwnd是怕发送方把接收方缓存塞满而拥塞窗口cwnd是怕把网络塞满。
这里有一个公式 LastByteSent - LastByteAcked &lt;= min {cwnd, rwnd} ,是拥塞窗口和滑动窗口共同控制发送的速度。
那发送方怎么判断网络是不是慢呢这其实是个挺难的事情因为对于TCP协议来讲他压根不知道整个网络路径都会经历什么对他来讲就是一个黑盒。TCP发送包常被比喻为往一个水管里面灌水而TCP的拥塞控制就是在不堵塞不丢包的情况下尽量发挥带宽。
水管有粗细,网络有带宽,也即每秒钟能够发送多少数据;水管有长度,端到端有时延。在理想状态下,水管里面水的量=水管粗细 x 水管长度。对于到网络上,通道的容量 = 带宽 × 往返延迟。
如果我们设置发送窗口,使得发送但未确认的包为为通道的容量,就能够撑满整个管道。
<img src="https://static001.geekbang.org/resource/image/db/e6/db8510541662281175803c7f9d1fcae6.jpg" alt="">
如图所示假设往返时间为8s去4s回4s每秒发送一个包每个包1024byte。已经过去了8s则8个包都发出去了其中前4个包已经到达接收端但是ACK还没有返回不能算发送成功。5-8后四个包还在路上还没被接收。这个时候整个管道正好撑满在发送端已发送未确认的为8个包正好等于带宽也即每秒发送1个包乘以来回时间8s。
如果我们在这个基础上再调大窗口,使得单位时间内更多的包可以发送,会出现什么现象呢?
我们来想原来发送一个包从一端到达另一端假设一共经过四个设备每个设备处理一个包时间耗费1s所以到达另一端需要耗费4s如果发送的更加快速则单位时间内会有更多的包到达这些中间设备这些设备还是只能每秒处理一个包的话多出来的包就会被丢弃这是我们不想看到的。
这个时候我们可以想其他的办法例如这个四个设备本来每秒处理一个包但是我们在这些设备上加缓存处理不过来的在队列里面排着这样包就不会丢失但是缺点是会增加时延这个缓存的包4s肯定到达不了接收端了如果时延达到一定程度就会超时重传也是我们不想看到的。
于是TCP的拥塞控制主要来避免两种现象**包丢失**和**超时重传**。一旦出现了这些现象就说明,发送速度太快了,要慢一点。但是一开始我怎么知道速度多快呢,我怎么知道应该把窗口调整到多大呢?
如果我们通过漏斗往瓶子里灌水,我们就知道,不能一桶水一下子倒进去,肯定会溅出来,要一开始慢慢的倒,然后发现总能够倒进去,就可以越倒越快。这叫作慢启动。
一条TCP连接开始cwnd设置为一个报文段一次只能发送一个当收到这一个确认的时候cwnd加一于是一次能够发送两个当这两个的确认到来的时候每个确认cwnd加一两个确认cwnd加二于是一次能够发送四个当这四个的确认到来的时候每个确认cwnd加一四个确认cwnd加四于是一次能够发送八个。可以看出这是**指数性的增长**。
涨到什么时候是个头呢有一个值ssthresh为65535个字节当超过这个值的时候就要小心一点了不能倒这么快了可能快满了再慢下来。
每收到一个确认后cwnd增加1/cwnd我们接着上面的过程来一次发送八个当八个确认到来的时候每个确认增加1/8八个确认一共cwnd增加1于是一次能够发送九个变成了线性增长。
但是线性增长还是增长,还是越来越多,直到有一天,水满则溢,出现了拥塞,这时候一般就会一下子降低倒水的速度,等待溢出的水慢慢渗下去。
拥塞的一种表现形式是丢包需要超时重传这个时候将sshresh设为cwnd/2将cwnd设为1重新开始慢启动。这真是一旦超时重传马上回到解放前。但是这种方式太激进了将一个高速的传输速度一下子停了下来会造成网络卡顿。
前面我们讲过**快速重传算法**。当接收端发现丢了一个中间包的时候发送三次前一个包的ACK于是发送端就会快速地重传不必等待超时再重传。TCP认为这种情况不严重因为大部分没丢只丢了一小部分cwnd减半为cwnd/2然后sshthresh = cwnd当三个包返回的时候cwnd = sshthresh + 3也就是没有一夜回到解放前而是还在比较高的值呈线性增长。
<img src="https://static001.geekbang.org/resource/image/19/d2/1910bc1a0048d4de7b2128eb0f5dbcd2.jpg" alt="">
就像前面说的一样正是这种知进退使得时延很重要的情况下反而降低了速度。但是如果你仔细想一下TCP的拥塞控制主要来避免的两个现象都是有问题的。
**第一个问题**是丢包并不代表着通道满了,也可能是管子本来就漏水。例如公网上带宽不满也会丢包,这个时候就认为拥塞了,退缩了,其实是不对的。
**第二个问题**是TCP的拥塞控制要等到将中间设备都填充满了才发生丢包从而降低速度这时候已经晚了。其实TCP只要填满管道就可以了不应该接着填直到连缓存也填满。
为了优化这两个问题,后来有了**TCP BBR拥塞算法**。它企图找到一个平衡点,就是通过不断地加快发送速度,将管道填满,但是不要填满中间设备的缓存,因为这样时延会增加,在这个平衡点可以很好的达到高带宽和低时延的平衡。
<img src="https://static001.geekbang.org/resource/image/a2/4c/a2b3a5df5eca52e302b75824e4bbbd4c.jpg" alt="">
## 小结
好了,这一节我们就到这里,总结一下:
<li>
顺序问题、丢包问题、流量控制都是通过滑动窗口来解决的,这其实就相当于你领导和你的工作备忘录,布置过的工作要有编号,干完了有反馈,活不能派太多,也不能太少;
</li>
<li>
拥塞控制是通过拥塞窗口来解决的,相当于往管道里面倒水,快了容易溢出,慢了浪费带宽,要摸着石头过河,找到最优值。
</li>
最后留两个思考题:
<li>
TCP的BBR听起来很牛你知道他是如何达到这个最优点的嘛
</li>
<li>
学会了UDP和TCP你知道如何基于这两种协议写程序吗这样的程序会有什么坑呢
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,146 @@
<audio id="audio" title="第13讲 | 套接字SocketTalk is cheap, show me the code" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/6f/75b47bb7d89a675b096c50ed84d2da6f.mp3"></audio>
前面讲完了TCP和UDP协议还没有上手过这一节咱们讲讲基于TCP和UDP协议的Socket编程。
在讲TCP和UDP协议的时候我们分客户端和服务端在写程序的时候我们也同样这样分。
Socket这个名字很有意思可以作插口或者插槽讲。虽然我们是写软件程序但是你可以想象为弄一根网线一头插在客户端一头插在服务端然后进行通信。所以在通信之前双方都要建立一个Socket。
在建立Socket的时候应该设置什么参数呢Socket编程进行的是端到端的通信往往意识不到中间经过多少局域网多少路由器因而能够设置的参数也只能是端到端协议之上网络层和传输层的。
在网络层Socket函数需要指定到底是IPv4还是IPv6分别对应设置为AF_INET和AF_INET6。另外还要指定到底是TCP还是UDP。还记得咱们前面讲过的TCP协议是基于数据流的所以设置为SOCK_STREAM而UDP是基于数据报的因而设置为SOCK_DGRAM。
## 基于TCP协议的Socket程序函数调用过程
两端创建了Socket之后接下来的过程中TCP和UDP稍有不同我们先来看TCP。
TCP的服务端要先监听一个端口一般是先调用bind函数给这个Socket赋予一个IP地址和端口。为什么需要端口呢要知道你写的是一个应用程序当一个网络包来的时候内核要通过TCP头里面的这个端口来找到你这个应用程序把包给你。为什么要IP地址呢有时候一台机器会有多个网卡也就会有多个IP地址你可以选择监听所有的网卡也可以选择监听一个网卡这样只有发给这个网卡的包才会给你。
当服务端有了IP和端口号就可以调用listen函数进行监听。在TCP的状态图里面有一个listen状态当调用这个函数之后服务端就进入了这个状态这个时候客户端就可以发起连接了。
在内核中为每个Socket维护两个队列。一个是已经建立了连接的队列这时候连接三次握手已经完毕处于established状态一个是还没有完全建立连接的队列这个时候三次握手还没完成处于syn_rcvd的状态。
接下来服务端调用accept函数拿出一个已经完成的连接进行处理。如果还没有完成就要等着。
在服务端等待的时候客户端可以通过connect函数发起连接。先在参数中指明要连接的IP地址和端口号然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功服务端的accept就会返回另一个Socket。
这是一个经常考的知识点就是监听的Socket和真正用来传数据的Socket是两个一个叫作**监听Socket**,一个叫作**已连接Socket**。
连接建立成功之后双方开始通过read和write函数来读写数据就像往一个文件流里面写东西一样。
这个图就是基于TCP协议的Socket程序函数调用过程。
<img src="https://static001.geekbang.org/resource/image/78/2d/78d145a72f9473fc1fyy0847d9b8212d.jpg" alt="">
说TCP的Socket就是一个文件流是非常准确的。因为Socket在Linux中就是以文件的形式存在的。除此之外还存在文件描述符。写入和读出也是通过文件描述符。
在内核中Socket是一个文件那对应就有文件描述符。每一个进程都有一个数据结构task_struct里面指向一个文件描述符数组来列出这个进程打开的所有文件的文件描述符。文件描述符是一个整数是这个数组的下标。
这个数组中的内容是一个指针指向内核中所有打开的文件的列表。既然是一个文件就会有一个inode只不过Socket对应的inode不像真正的文件系统一样保存在硬盘上的而是在内存中的。在这个inode中指向了Socket在内核中的Socket结构。
在这个结构里面,主要的是两个队列,一个是**发送队列**,一个是**接收队列**。在这两个队列里面保存的是一个缓存sk_buff。这个缓存里面能够看到完整的包的结构。看到这个是不是能和前面讲过的收发包的场景联系起来了
整个数据结构我也画了一张图。<br>
<img src="https://static001.geekbang.org/resource/image/60/13/604f4cb37576990b3f836cb5d7527b13.jpg" alt="">
## 基于UDP协议的Socket程序函数调用过程
对于UDP来讲过程有些不一样。UDP是没有连接的所以不需要三次握手也就不需要调用listen和connect但是UDP的交互仍然需要IP和端口号因而也需要bind。UDP是没有维护连接状态的因而不需要每对连接建立一组Socket而是只要有一个Socket就能够和多个客户端通信。也正是因为没有连接状态每次通信的时候都调用sendto和recvfrom都可以传入IP地址和端口。
这个图的内容就是基于UDP协议的Socket程序函数调用过程。<br>
<img src="https://static001.geekbang.org/resource/image/6b/31/6bbe12c264f5e76a81523eb8787f3931.jpg" alt="">
## 服务器如何接更多的项目?
会了这几个基本的Socket函数之后你就可以轻松地写一个网络交互的程序了。就像上面的过程一样在建立连接后进行一个while循环。客户端发了收服务端收了发。
当然这只是万里长征的第一步,因为如果使用这种方法,基本上只能一对一沟通。如果你是一个服务器,同时只能服务一个客户,肯定是不行的。这就相当于老板成立一个公司,只有自己一个人,自己亲自上来服务客户,只能干完了一家再干下一家,这样赚不来多少钱。
那作为老板你就要想了,我最多能接多少项目呢?当然是越多越好。
我们先来算一下理论值,也就是**最大连接数**系统会用一个四元组来标识一个TCP连接。
```
{本机IP, 本机端口, 对端IP, 对端端口}
```
服务器通常固定在某个本地端口上监听等待客户端的连接请求。因此服务端端TCP连接四元组中只有对端IP, 也就是客户端的IP和对端的端口也即客户端的端口是可变的因此最大TCP连接数=客户端IP数×客户端端口数。对IPv4客户端的IP数最多为2的32次方客户端的端口数最多为2的16次方也就是服务端单机最大TCP连接数约为2的48次方。
当然服务端最大并发TCP连接数远不能达到理论上限。首先主要是**文件描述符限制**按照上面的原理Socket都是文件所以首先要通过ulimit配置文件描述符的数目另一个限制是**内存**按上面的数据结构每个TCP连接都要占用一定内存操作系统是有限的。
所以,作为老板,在资源有限的情况下,要想接更多的项目,就需要降低每个项目消耗的资源数目。
### 方式一:将项目外包给其他公司(多进程方式)
这就相当于你是一个代理在那里监听来的请求。一旦建立了一个连接就会有一个已连接Socket这时候你可以创建一个子进程然后将基于已连接Socket的交互交给这个新的子进程来做。就像来了一个新的项目但是项目不一定是你自己做可以再注册一家子公司招点人然后把项目转包给这家子公司做以后对接就交给这家子公司了你又可以去接新的项目了。
这里有一个问题是,如何创建子公司,并如何将项目移交给子公司呢?
在Linux下创建子进程使用fork函数。通过名字可以看出这是在父进程的基础上完全拷贝一个子进程。在Linux内核中会复制文件描述符的列表也会复制内存空间还会复制一条记录当前执行到了哪一行程序的进程。显然复制的时候在调用fork复制完毕之后父进程和子进程都会记录当前刚刚执行完fork。这两个进程刚复制完的时候几乎一模一样只是根据fork的返回值来区分到底是父进程还是子进程。如果返回值是0则是子进程如果返回值是其他的整数就是父进程。
进程复制过程我画在这里。<br>
<img src="https://static001.geekbang.org/resource/image/18/d0/18070c00ff5d0082yy1fbc32b84e73d0.jpg" alt="">
因为复制了文件描述符列表而文件描述符都是指向整个内核统一的打开文件列表的因而父进程刚才因为accept创建的已连接Socket也是一个文件描述符同样也会被子进程获得。
接下来子进程就可以通过这个已连接Socket和客户端进行互通了当通信完毕之后就可以退出进程那父进程如何知道子进程干完了项目要退出呢还记得fork返回的时候如果是整数就是父进程吗这个整数就是子进程的ID父进程可以通过这个ID查看子进程是否完成项目是否需要退出。
### 方式二:将项目转包给独立的项目组(多线程方式)
上面这种方式你应该也能发现问题,如果每次接一个项目,都申请一个新公司,然后干完了,就注销掉这个公司,实在是太麻烦了。毕竟一个新公司要有新公司的资产,有新的办公家具,每次都买了再卖,不划算。
于是你应该想到了,我们可以使用**线程**。相比于进程来讲,这样要轻量级的多。如果创建进程相当于成立新公司,购买新办公家具,而创建线程,就相当于在同一个公司成立项目组。一个项目做完了,那这个项目组就可以解散,组成另外的项目组,办公家具可以共用。
在Linux下通过pthread_create创建一个线程也是调用do_fork。不同的是虽然新的线程在task列表会新创建一项但是很多资源例如文件描述符列表、进程空间还是共享的只不过多了一个引用而已。
<img src="https://static001.geekbang.org/resource/image/a3/64/a36537201678e08ac83e5410562d5f64.jpg" alt="">
新的线程也可以通过已连接Socket处理请求从而达到并发处理的目的。
上面基于进程或者线程模型的其实还是有问题的。新到来一个TCP连接就需要分配一个进程或者线程。一台机器无法创建很多进程或者线程。有个**C10K**它的意思是一台机器要维护1万个连接就要创建1万个进程或者线程那么操作系统是无法承受的。如果维持1亿用户在线需要10万台服务器成本也太高了。
其实C10K问题就是你接项目接的太多了如果每个项目都成立单独的项目组就要招聘10万人你肯定养不起那怎么办呢
### 方式三一个项目组支撑多个项目IO多路复用一个线程维护多个Socket
当然,一个项目组可以看多个项目了。这个时候,每个项目组都应该有个项目进度墙,将自己组看的项目列在那里,然后每天通过项目墙看每个项目的进度,一旦某个项目有了进展,就派人去盯一下。
由于Socket是文件描述符因而某个线程盯的所有的Socket都放在一个文件描述符集合fd_set中这就是**项目进度墙**然后调用select函数来监听文件描述符集合是否有变化。一旦有变化就会依次查看每个文件描述符。那些发生变化的文件描述符在fd_set对应的位都设为1表示Socket可读或者可写从而可以进行读写操作然后再调用select接着盯着下一轮的变化。
### 方式四一个项目组支撑多个项目IO多路复用从“派人盯着”到“有事通知”
上面select函数还是有问题的因为每次Socket所在的文件描述符集合中有Socket发生变化的时候都需要通过轮询的方式也就是需要将全部项目都过一遍的方式来查看进度这大大影响了一个项目组能够支撑的最大的项目数量。因而使用select能够同时盯的项目数量由FD_SETSIZE限制。
如果改成事件通知的方式,情况就会好很多,项目组不需要通过轮询挨个盯着这些项目,而是当项目进度发生变化的时候,主动通知项目组,然后项目组再根据项目进展情况做相应的操作。
能完成这件事情的函数叫epoll它在内核中的实现不是通过轮询的方式而是通过注册callback函数的方式当某个文件描述符发送变化的时候就会主动通知。<br>
<img src="https://static001.geekbang.org/resource/image/d6/b1/d6efc5c5ee8e48dae0323de380dcf6b1.jpg" alt="">
如图所示假设进程打开了Socket m, n, x等多个文件描述符现在需要通过epoll来监听是否这些Socket都有事件发生。其中epoll_create创建一个epoll对象也是一个文件也对应一个文件描述符同样也对应着打开文件列表中的一项。在这项里面有一个红黑树在红黑树里要保存这个epoll要监听的所有Socket。
当epoll_ctl添加一个Socket的时候其实是加入这个红黑树同时红黑树里面的节点指向一个结构将这个结构挂在被监听的Socket的事件列表中。当一个Socket来了一个事件的时候可以从这个列表中得到epoll对象并调用call back通知它。
这种通知方式使得监听的Socket数据增加的时候效率不会大幅度降低能够同时监听的Socket的数目也非常的多了。上限就为系统定义的、进程打开的最大文件描述符个数。因而**epoll被称为解决C10K问题的利器**。
## 小结
好了,这一节就到这里了,我们来总结一下:
<li>
你需要记住TCP和UDP的Socket的编程中客户端和服务端都需要调用哪些函数
</li>
<li>
写一个能够支撑大量连接的高并发的服务端不容易需要多进程、多线程而epoll机制能解决C10K问题。
</li>
最后,给你留两个思考题:
<li>
epoll是Linux上的函数那你知道Windows上对应的机制是什么吗如果想实现一个跨平台的程序你知道应该怎么办吗
</li>
<li>
自己写Socket还是挺复杂的写个HTTP的应用可能简单一些。那你知道HTTP的工作机制吗
</li>
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,145 @@
<audio id="audio" title="第18讲 | DNS协议网络世界的地址簿" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c5/34/c56a67b3e6e791b6677d9d327b3fd834.mp3"></audio>
前面我们讲了平时常见的看新闻、支付、直播、下载等场景现在网站的数目非常多常用的网站就有二三十个如果全部用IP地址进行访问恐怕很难记住。于是就需要一个地址簿根据名称就可以查看具体的地址。
例如,我要去西湖边的“外婆家”,这就是名称,然后通过地址簿,查看到底是哪条路多少号。
## DNS服务器
在网络世界也是这样的。你肯定记得住网站的名称但是很难记住网站的IP地址因而也需要一个地址簿就是**DNS服务器**。
由此可见DNS在日常生活中多么重要。每个人上网都需要访问它但是同时这对它来讲也是非常大的挑战。一旦它出了故障整个互联网都将瘫痪。另外上网的人分布在全世界各地如果大家都去同一个地方访问某一台服务器时延将会非常大。因而**DNS服务器一定要设置成高可用、高并发和分布式的**。
于是,就有了这样**树状的层次结构**。
<img src="https://static001.geekbang.org/resource/image/89/4d/890ff98fde625c6a60fb71yy22d8184d.jpg" alt="">- 根DNS服务器 返回顶级域DNS服务器的IP地址
<li>
顶级域DNS服务器返回权威DNS服务器的IP地址
</li>
<li>
权威DNS服务器 返回相应主机的IP地址
</li>
## DNS解析流程
为了提高DNS的解析性能很多网络都会就近部署DNS缓存服务器。于是就有了以下的DNS解析流程。
<li>
电脑客户端会发出一个DNS请求问www.163.com的IP是啥啊并发给本地域名服务器 (本地DNS)。那本地域名服务器 (本地DNS) 是什么呢如果是通过DHCP配置本地DNS由你的网络服务商ISP如电信、移动等自动分配它通常就在你网络服务商的某个机房。
</li>
<li>
本地DNS收到来自客户端的请求。你可以想象这台服务器上缓存了一张域名与之对应IP地址的大表格。如果能找到 www.163.com它就直接返回IP地址。如果没有本地DNS会去问它的根域名服务器“老大能告诉我www.163.com的IP地址吗”根域名服务器是最高层次的全球共有13套。它不直接用于域名解析但能指明一条道路。
</li>
<li>
根DNS收到来自本地DNS的请求发现后缀是 .com“哦www.163.com啊这个域名是由.com区域管理我给你它的顶级域名服务器的地址你去问问它吧。”
</li>
<li>
本地DNS转向问顶级域名服务器“老二你能告诉我www.163.com的IP地址吗”顶级域名服务器就是大名鼎鼎的比如 .com、.net、 .org这些一级域名它负责管理二级域名比如 163.com所以它能提供一条更清晰的方向。
</li>
<li>
顶级域名服务器说:“我给你负责 www.163.com 区域的权威DNS服务器的地址你去问它应该能问到。”
</li>
<li>
本地DNS转向问权威DNS服务器“您好www.163.com 对应的IP是啥呀”163.com的权威DNS服务器它是域名解析结果的原出处。为啥叫权威呢就是我的域名我做主。
</li>
<li>
权威DNS服务器查询后将对应的IP地址X.X.X.X告诉本地DNS。
</li>
<li>
本地DNS再将IP地址返回客户端客户端和目标建立连接。
</li>
至此我们完成了DNS的解析过程。现在总结一下整个过程我画成了一个图。
<img src="https://static001.geekbang.org/resource/image/71/e8/718e3a1a1a7927302b6a0f836409e8e8.jpg" alt="">
## 负载均衡
站在客户端角度,这是一次**DNS递归查询过程。<strong>因为本地DNS全权为它效劳它只要坐等结果即可。在这个过程中DNS除了可以通过名称映射为IP地址它还可以做另外一件事就是**负载均衡</strong>
还是以访问“外婆家”为例,还是我们开头的“外婆家”,但是,它可能有很多地址,因为它在杭州可以有很多家。所以,如果一个人想去吃“外婆家”,他可以就近找一家店,而不用大家都去同一家,这就是负载均衡。
DNS首先可以做**内部负载均衡**。
例如一个应用要访问数据库在这个应用里面应该配置这个数据库的IP地址还是应该配置这个数据库的域名呢显然应该配置域名因为一旦这个数据库因为某种原因换到了另外一台机器上而如果有多个应用都配置了这台数据库的话一换IP地址就需要将这些应用全部修改一遍。但是如果配置了域名则只要在DNS服务器里将域名映射为新的IP地址这个工作就完成了大大简化了运维。
在这个基础上我们可以再进一步。例如某个应用要访问另外一个应用如果配置另外一个应用的IP地址那么这个访问就是一对一的。但是当被访问的应用撑不住的时候我们其实可以部署多个。但是访问它的应用如何在多个之间进行负载均衡只要配置成为域名就可以了。在域名解析的时候我们只要配置策略这次返回第一个IP下次返回第二个IP就可以实现负载均衡了。
另外一个更加重要的是DNS还可以做**全局负载均衡**。
为了保证我们的应用高可用往往会部署在多个机房每个地方都会有自己的IP地址。当用户访问某个域名的时候这个IP地址可以轮询访问多个数据中心。如果一个数据中心因为某种原因挂了只要在DNS服务器里面将这个数据中心对应的IP地址删除就可以实现一定的高可用。
另外,我们肯定希望北京的用户访问北京的数据中心,上海的用户访问上海的数据中心,这样,客户体验就会非常好,访问速度就会超快。这就是全局负载均衡的概念。
## 示例DNS访问数据中心中对象存储上的静态资源
我们通过DNS访问数据中心中对象存储上的静态资源为例看一看整个过程。
假设全国有多个数据中心托管在多个运营商每个数据中心三个可用区Available Zone。对象存储通过跨可用区部署实现高可用性。在每个数据中心中都至少部署两个内部负载均衡器内部负载均衡器后面对接多个对象存储的前置服务器Proxy-server
<img src="https://static001.geekbang.org/resource/image/0b/b6/0b241afef775a1c942c5728364b302b6.jpg" alt="">
<li>
当一个客户端要访问object.yourcompany.com的时候需要将域名转换为IP地址进行访问所以它要请求本地DNS解析器。
</li>
<li>
本地DNS解析器先查看看本地的缓存是否有这个记录。如果有则直接使用因为上面的过程太复杂了如果每次都要递归解析就太麻烦了。
</li>
<li>
如果本地无缓存则需要请求本地的DNS服务器。
</li>
<li>
本地的DNS服务器一般部署在你的数据中心或者你所在的运营商的网络中本地DNS服务器也需要看本地是否有缓存如果有则返回因为它也不想把上面的递归过程再走一遍。
</li>
<li>
至 7. 如果本地没有本地DNS才需要递归地从根DNS服务器查到.com的顶级域名服务器最终查到 yourcompany.com 的权威DNS服务器给本地DNS服务器权威DNS服务器按说会返回真实要访问的IP地址。
</li>
对于不需要做全局负载均衡的简单应用来讲yourcompany.com的权威DNS服务器可以直接将 object.yourcompany.com这个域名解析为一个或者多个IP地址然后客户端可以通过多个IP地址进行简单的轮询实现简单的负载均衡。
但是对于复杂的应用,尤其是跨地域跨运营商的大型应用,则需要更加复杂的全局负载均衡机制,因而需要专门的设备或者服务器来做这件事情,这就是**全局负载均衡器****GSLB****Global Server Load Balance**)。
在yourcompany.com的DNS服务器中一般是通过配置CNAME的方式给 object.yourcompany.com起一个别名例如 object.vip.yourcomany.com然后告诉本地DNS服务器让它请求GSLB解析这个域名GSLB就可以在解析这个域名的过程中通过自己的策略实现负载均衡。
图中画了两层的GSLB是因为分运营商和地域。我们希望不同运营商的客户可以访问相同运营商机房中的资源这样不跨运营商访问有利于提高吞吐量减少时延。
<li>
第一层GSLB通过查看请求它的本地DNS服务器所在的运营商就知道用户所在的运营商。假设是移动通过CNAME的方式通过另一个别名 object.yd.yourcompany.com告诉本地DNS服务器去请求第二层的GSLB。
</li>
<li>
第二层GSLB通过查看请求它的本地DNS服务器所在的地址就知道用户所在的地理位置然后将距离用户位置比较近的Region里面六个**内部负载均衡****SLB**S**erver Load Balancer**的地址返回给本地DNS服务器。
</li>
<li>
本地DNS服务器将结果返回给本地DNS解析器。
</li>
<li>
本地DNS解析器将结果缓存后返回给客户端。
</li>
<li>
客户端开始访问属于相同运营商的距离较近的Region 1中的对象存储当然客户端得到了六个IP地址它可以通过负载均衡的方式随机或者轮询选择一个可用区进行访问。对象存储一般会有三个备份从而可以实现对存储读写的负载均衡。
</li>
## 小结
好了,这节内容就到这里了,我们来总结一下:
<li>
DNS是网络世界的地址簿可以通过域名查地址因为域名服务器是按照树状结构组织的因而域名查找是使用递归的方法并通过缓存的方式增强性能
</li>
<li>
在域名和IP的映射过程中给了应用基于域名做负载均衡的机会可以是简单的负载均衡也可以根据地址和运营商做全局的负载均衡。
</li>
最后,给你留两个思考题:
<li>
全局负载均衡为什么要分地址和运营商呢?
</li>
<li>
全局负载均衡使用过程中,常常遇到失灵的情况,你知道具体有哪些情况吗?对应应该怎么来解决呢?
</li>
我们的专栏更新过半了,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从**已发布的文章中选出一批认真留言的同学**,赠送**学习奖励礼券**和我整理的**独家网络协议知识图谱**。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,166 @@
<audio id="audio" title="第19讲 | HttpDNS网络世界的地址簿也会指错路" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c5/8e/c5878b08f79096cdad80d9f9e656818e.mp3"></audio>
上一节我们知道了DNS的两项功能第一是根据名称查到具体的地址另外一个是可以针对多个地址做负载均衡而且可以在多个地址中选择一个距离你近的地方访问。
然而有时候这个地址簿也经常给你指错路明明距离你500米就有个吃饭的地方非要把你推荐到5公里外。为什么会出现这样的情况呢
还记得吗当我们发出请求解析DNS的时候首先会先连接到运营商本地的DNS服务器由这个服务器帮我们去整棵DNS树上进行解析然后将解析的结果返回给客户端。但是本地的DNS服务器作为一个本地导游往往有自己的“小心思”。
## 传统DNS存在哪些问题
### 1.域名缓存问题
它可以在本地做一个缓存也就是说不是每一个请求它都会去访问权威DNS服务器而是访问过一次就把结果缓存到自己本地当其他人来问的时候直接就返回这个缓存数据。
这就相当于导游去过一个饭店,自己脑子记住了地址,当有一个游客问的时候,他就凭记忆回答了,不用再去查地址簿。这样经常存在的一个问题是,人家那个饭店明明都已经搬了,结果作为导游,他并没有刷新这个缓存,结果你辛辛苦苦到了这个地点,发现饭店已经变成了服装店,你是不是会非常失望?
另外,有的运营商会把一些静态页面,缓存到本运营商的服务器内,这样用户请求的时候,就不用跨运营商进行访问,这样既加快了速度,也减少了运营商之间流量计算的成本。在域名解析的时候,不会将用户导向真正的网站,而是指向这个缓存的服务器。
很多情况下是看不出问题的,但是当页面更新,用户会访问到老的页面,问题就出来了。例如,你听说一个餐馆推出了一个新菜,你想去尝一下。结果导游告诉你,在这里吃也是一样的。有的游客会觉得没问题,但是对于想尝试新菜的人来说,如果导游说带你去,但其实并没有吃到新菜,你是不是也会非常失望呢?
再就是本地的缓存,往往使得全局负载均衡失败,因为上次进行缓存的时候,缓存中的地址不一定是这次访问离客户最近的地方,如果把这个地址返回给客户,那肯定就会绕远路。
就像上一次客户要吃西湖醋鱼的事,导游知道西湖边有一家,因为当时游客就在西湖边,可是,下一次客户在灵隐寺,想吃西湖醋鱼的时候,导游还指向西湖边的那一家,那这就绕得太远了。
<img src="https://static001.geekbang.org/resource/image/8b/eb/8b5e670e22c459e859293db1bed643eb.jpg" alt="">
### 2.域名转发问题
缓存问题还是说本地域名解析服务还是会去权威DNS服务器中查找只不过不是每次都要查找。可以说这还是大导游、大中介。还有一些小导游、小中介有了请求之后直接转发给其他运营商去做解析自己只是外包了出去。
这样的问题是如果是A运营商的客户访问自己运营商的DNS服务器如果A运营商去权威DNS服务器查询的话权威DNS服务器知道你是A运营商的就返回给一个部署在A运营商的网站地址这样针对相同运营商的访问速度就会快很多。
但是A运营商偷懒将解析的请求转发给B运营商B运营商去权威DNS服务器查询的话权威服务器会误认为你是B运营商的那就返回给你一个在B运营商的网站地址吧结果客户的每次访问都要跨运营商速度就会很慢。
<img src="https://static001.geekbang.org/resource/image/cf/f1/cfde4b3bc5c0aabaaa81f3a26cd99cf1.jpg" alt="">
### 3.出口NAT问题
前面讲述网关的时候,我们知道,出口的时候,很多机房都会配置**NAT**,也即**网络地址转换**使得从这个网关出去的包都换成新的IP地址当然请求返回的时候在这个网关再将IP地址转换回去所以对于访问来说是没有任何问题。
但是一旦做了网络地址的转换权威的DNS服务器就没办法通过这个地址来判断客户到底是来自哪个运营商而且极有可能因为转换过后的地址误判运营商导致跨运营商的访问。
### 4.域名更新问题
本地DNS服务器是由不同地区、不同运营商独立部署的。对域名解析缓存的处理上实现策略也有区别有的会偷懒忽略域名解析结果的TTL时间限制在权威DNS服务器解析变更的时候解析结果在全网生效的周期非常漫长。但是有的时候在DNS的切换中场景对生效时间要求比较高。
例如双机房部署的时候跨机房的负载均衡和容灾多使用DNS来做。当一个机房出问题之后需要修改权威DNS将域名指向新的IP地址但是如果更新太慢那很多用户都会出现访问异常。
这就像有的导游比较勤快、敬业时时刻刻关注酒店、餐馆、交通的变化问他的时候往往会得到最新情况。有的导游懒一些8年前背的导游词就没换过问他的时候指的路往往就是错的。
### 5.解析延迟问题
从上一节的DNS查询过程来看DNS的查询过程需要递归遍历多个DNS服务器才能获得最终的解析结果这会带来一定的时延甚至会解析超时。
## HttpDNS的工作模式
既然DNS解析中有这么多问题那怎么办呢难不成退回到直接用IP地址这样显然不合适所以就有了** HttpDNS**。
HttpDNS其实就是不走传统的DNS解析而是自己搭建基于HTTP协议的DNS服务器集群分布在多个地点和多个运营商。当客户端需要DNS解析的时候直接通过HTTP协议进行请求这个服务器集群得到就近的地址。
这就相当于每家基于HTTP协议自己实现自己的域名解析自己做一个自己的地址簿而不使用统一的地址簿。但是默认的域名解析都是走DNS的因而使用HttpDNS需要绕过默认的DNS路径就不能使用默认的客户端。使用HttpDNS的往往是手机应用需要在手机端嵌入支持HttpDNS的客户端SDK。
通过自己的HttpDNS服务器和自己的SDK实现了从依赖本地导游到自己上网查询做旅游攻略进行自由行爱怎么玩怎么玩。这样就能够避免依赖导游而导游又不专业你还不能把他怎么样的尴尬。
下面我来解析一下** HttpDNS的工作模式**。
在客户端的SDK里动态请求服务端获取HttpDNS服务器的IP列表缓存到本地。随着不断地解析域名SDK也会在本地缓存DNS域名解析的结果。
当手机应用要访问一个地址的时候首先看是否有本地的缓存如果有就直接返回。这个缓存和本地DNS的缓存不一样的是这个是手机应用自己做的而非整个运营商统一做的。如何更新、何时更新手机应用的客户端可以和服务器协调来做这件事情。
如果本地没有就需要请求HttpDNS的服务器在本地HttpDNS服务器的IP列表中选择一个发出HTTP的请求会返回一个要访问的网站的IP列表。
请求的方式是这样的。
```
curl http://106.2.xxx.xxx/d?dn=c.m.163.com
{&quot;dns&quot;:[{&quot;host&quot;:&quot;c.m.163.com&quot;,&quot;ips&quot;:[&quot;223.252.199.12&quot;],&quot;ttl&quot;:300,&quot;http2&quot;:0}],&quot;client&quot;:{&quot;ip&quot;:&quot;106.2.81.50&quot;,&quot;line&quot;:269692944}}
```
手机客户端自然知道手机在哪个运营商、哪个地址。由于是直接的HTTP通信HttpDNS服务器能够准确知道这些信息因而可以做精准的全局负载均衡。
<img src="https://static001.geekbang.org/resource/image/aa/75/aa45cf8a07b563ccea376f712b2e8975.jpg" alt="">
当然当所有这些都不工作的时候可以切换到传统的LocalDNS来解析慢也比访问不到好。那HttpDNS是如何解决上面的问题的呢
其实归结起来就是两大问题。一是解析速度和更新速度的平衡问题二是智能调度的问题对应的解决方案是HttpDNS的缓存设计和调度设计。
### HttpDNS的缓存设计
解析DNS过程复杂通信次数多对解析速度造成很大影响。为了加快解析因而有了缓存但是这又会产生缓存更新速度不及时的问题。最要命的是这两个方面都掌握在别人手中也即本地DNS服务器手中它不会为你定制你作为客户端干着急没办法。
而HttpDNS就是将解析速度和更新速度全部掌控在自己手中。一方面解析的过程不需要本地DNS服务递归的调用一大圈一个HTTP的请求直接搞定要实时更新的时候马上就能起作用另一方面为了提高解析速度本地也有缓存缓存是在客户端SDK维护的过期时间、更新时间都可以自己控制。
HttpDNS的缓存设计策略也是咱们做应用架构中常用的缓存设计模式也即分为客户端、缓存、数据源三层。
<li>
对于应用架构来讲就是应用、缓存、数据库。常见的是Tomcat、Redis、MySQL。
</li>
<li>
对于HttpDNS来讲就是手机客户端、DNS缓存、HttpDNS服务器。
</li>
<img src="https://static001.geekbang.org/resource/image/9e/56/9e0141a2939e7c194689e990859ed456.jpg" alt="">
只要是缓存模式,就存在缓存的过期、更新、不一致的问题,解决思路也是很像的。
例如DNS缓存在内存中也可以持久化到存储上从而APP重启之后能够尽快从存储中加载上次累积的经常访问的网站的解析结果就不需要每次都全部解析一遍再变成缓存。这有点像Redis是基于内存的缓存但是同样提供持久化的能力使得重启或者主备切换的时候数据不会完全丢失。
SDK中的缓存会严格按照缓存过期时间如果缓存没有命中或者已经过期而且客户端不允许使用过期的记录则会发起一次解析保障记录是更新的。
解析可以**同步进行**也就是直接调用HttpDNS的接口返回最新的记录更新缓存也可以**异步进行**添加一个解析任务到后台由后台任务调用HttpDNS的接口。
**同步更新**的**优点**是实时性好缺点是如果有多个请求都发现过期的时候同时会请求HttpDNS多次其实是一种浪费。
同步更新的方式对应到应用架构中缓存的**Cache-Aside机制**,也即先读缓存,不命中读数据库,同时将结果写入缓存。
**异步更新**的**优点**是可以将多个请求都发现过期的情况合并为一个对于HttpDNS的请求任务只执行一次减少HttpDNS的压力。同时可以在即将过期的时候就创建一个任务进行预加载防止过期之后再刷新称为**预加载**
它的**缺点**是当前请求拿到过期数据的时候,如果客户端允许使用过期数据,需要冒一次风险。如果过期的数据还能请求,就没问题;如果不能请求,则失败一次,等下次缓存更新后,再请求方能成功。
<img src="https://static001.geekbang.org/resource/image/df/98/df65059e9b66c32bbbca6febf6ecb298.jpg" alt="">
异步更新的机制对应到应用架构中缓存的**Refresh-Ahead机制**即业务仅仅访问缓存当过期的时候定期刷新。在著名的应用缓存Guava Cache中有个RefreshAfterWrite机制对于并发情况下多个缓存访问不命中从而引发并发回源的情况可以采取只有一个请求回源的模式。在应用架构的缓存中也常常用**数据预热**或者**预加载**的机制。
<img src="https://static001.geekbang.org/resource/image/16/28/161a14f41b3547739982ec551aa26928.jpg" alt="">
### HttpDNS的调度设计
由于客户端嵌入了SDK因而就不会因为本地DNS的各种缓存、转发、NAT让权威DNS服务器误会客户端所在的位置和运营商而可以拿到第一手资料。
在**客户端**可以知道手机是哪个国家、哪个运营商、哪个省甚至哪个市HttpDNS服务端可以根据这些信息选择最佳的服务节点访问。
如果有多个节点,还会考虑错误率、请求时间、服务器压力、网络状况等,进行综合选择,而非仅仅考虑地理位置。当有一个节点宕机或者性能下降的时候,可以尽快进行切换。
要做到这一点需要客户端使用HttpDNS返回的IP访问业务应用。客户端的SDK会收集网络请求数据如错误率、请求时间等网络请求质量数据并发送到统计后台进行分析、聚合以此查看不同的IP的服务质量。
在**服务端**应用可以通过调用HttpDNS的管理接口配置不同服务质量的优先级、权重。HttpDNS会根据这些策略综合地理位置和线路状况算出一个排序优先访问当前那些优质的、时延低的IP地址。
HttpDNS通过智能调度之后返回的结果也会缓存在客户端。为了不让缓存使得调度失真客户端可以根据不同的移动网络运营商WIFI的SSID来分维度缓存。不同的运营商或者WIFI解析出来的结果会不同。
<img src="https://static001.geekbang.org/resource/image/3b/ae/3b368yy7a4f2319bd6491bc4e66e55ae.jpg" alt="">
## 小结
好了,这节就到这里了,我们来总结一下,你需要记住这两个重点:
<li>
传统的DNS有很多问题例如解析慢、更新不及时。因为缓存、转发、NAT问题导致客户端误会自己所在的位置和运营商从而影响流量的调度。
</li>
<li>
HttpDNS通过客户端SDK和服务端通过HTTP直接调用解析DNS的方式绕过了传统DNS的这些缺点实现了智能的调度。
</li>
最后,给你留两个思考题。
<li>
使用HttpDNS需要向HttpDNS服务器请求解析域名可是客户端怎么知道HttpDNS服务器的地址或者域名呢
</li>
<li>
HttpDNS的智能调度主要是让客户端选择最近的服务器而有另一种机制使得资源分发到离客户端更近的位置从而加快客户端的访问你知道是什么技术吗
</li>
我们的专栏更新过半了,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从已发布的文章中选出一批认真留言的同学,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="第20讲 | CDN你去小卖部取过快递么" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8d/a0/8dafced847a138ec2914b8c5f45b5ea0.mp3"></audio>
上一节,我们看到了网站的一般访问模式。
当一个用户想访问一个网站的时候指定这个网站的域名DNS就会将这个域名解析为地址然后用户请求这个地址返回一个网页。就像你要买个东西首先要查找商店的位置然后去商店里面找到自己想要的东西最后拿着东西回家。
**那这里面还有没有可以优化的地方呢?**
例如你去电商网站下单买个东西,这个东西一定要从电商总部的中心仓库送过来吗?原来基本是这样的,每一单都是单独配送,所以你可能要很久才能收到你的宝贝。但是后来电商网站的物流系统学聪明了,他们在全国各地建立了很多仓库,而不是只有总部的中心仓库才可以发货。
电商网站根据统计大概知道,北京、上海、广州、深圳、杭州等地,每天能够卖出去多少书籍、卫生纸、包、电器等存放期比较长的物品。这些物品用不着从中心仓库发出,所以平时就可以将它们分布在各地仓库里,客户一下单,就近的仓库发出,第二天就可以收到了。
这样,用户体验大大提高。当然,这里面也有个难点就是,生鲜这类东西保质期太短,如果提前都备好货,但是没有人下单,那肯定就坏了。这个问题,我后文再说。
**我们先说,我们的网站访问可以借鉴“就近配送”这个思路。**
全球有这么多的数据中心,无论在哪里上网,临近不远的地方基本上都有数据中心。是不是可以在这些数据中心里部署几台机器,形成一个缓存的集群来缓存部分数据,那么用户访问数据的时候,就可以就近访问了呢?
当然是可以的。这些分布在各个地方的各个数据中心的节点,就称为**边缘节点**。
由于边缘节点数目比较多,但是每个集群规模比较小,不可能缓存下来所有东西,因而可能无法命中,这样就会在边缘节点之上。有区域节点,规模就要更大,缓存的数据会更多,命中的概率也就更大。在区域节点之上是中心节点,规模更大,缓存数据更多。如果还不命中,就只好回源网站访问了。
<img src="https://static001.geekbang.org/resource/image/5f/25/5fbe602d9b85966d9a1748d2e6aa6425.jpeg" alt="">
这就是**CDN分发系统的架构**。CDN系统的缓存也是一层一层的能不访问后端真正的源就不打扰它。这也是电商网站物流系统的思路北京局找不到找华北局华北局找不到再找北方局。
有了这个分发系统之后,接下来,**客户端如何找到相应的边缘节点进行访问呢?**
还记得我们讲过的基于DNS的全局负载均衡吗这个负载均衡主要用来选择一个就近的同样运营商的服务器进行访问。你会发现CDN分发网络也是一个分布在多个区域、多个运营商的分布式系统也可以用相同的思路选择最合适的边缘节点。
<img src="https://static001.geekbang.org/resource/image/c4/24/c4d826188e664605d6f8dfb82e348824.jpeg" alt="">
**在没有CDN的情况下**用户向浏览器输入www.web.com这个域名客户端访问本地DNS服务器的时候如果本地DNS服务器有缓存则返回网站的地址如果没有递归查询到网站的权威DNS服务器这个权威DNS服务器是负责web.com的它会返回网站的IP地址。本地DNS服务器缓存下IP地址将IP地址返回然后客户端直接访问这个IP地址就访问到了这个网站。
然而**有了CDN之后情况发生了变化**。在web.com这个权威DNS服务器上会设置一个CNAME别名指向另外一个域名 www.web.cdn.com返回给本地DNS服务器。
当本地DNS服务器拿到这个新的域名时需要继续解析这个新的域名。这个时候再访问的就不是web.com的权威DNS服务器了而是web.cdn.com的权威DNS服务器这是CDN自己的权威DNS服务器。在这个服务器上还是会设置一个CNAME指向另外一个域名也即CDN网络的全局负载均衡器。
接下来本地DNS服务器去请求CDN的全局负载均衡器解析域名全局负载均衡器会为用户选择一台合适的缓存服务器提供服务选择的依据包括
<li>
根据用户IP地址判断哪一台服务器距用户最近
</li>
<li>
用户所处的运营商;
</li>
<li>
根据用户所请求的URL中携带的内容名称判断哪一台服务器上有用户所需的内容
</li>
<li>
查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。
</li>
基于以上这些条件进行综合分析之后全局负载均衡器会返回一台缓存服务器的IP地址。
本地DNS服务器缓存这个IP地址然后将IP返回给客户端客户端去访问这个边缘节点下载资源。缓存服务器响应用户请求将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容那么这台服务器就要向它的上一级缓存服务器请求内容直至追溯到网站的源服务器将内容拉到本地。
**CDN可以进行缓存的内容有很多种。**
保质期长的日用品比较容易缓存,因为不容易过期,对应到就像电商仓库系统里,就是静态页面、图片等,因为这些东西也不怎么变,所以适合缓存。
<img src="https://static001.geekbang.org/resource/image/ca/1d/caec3ba1086557cbf694c621e7e01e1d.jpeg" alt="">
还记得这个**接入层缓存的架构**吗在进入数据中心的时候我们希望通过最外层接入层的缓存将大部分静态资源的访问拦在边缘。而CDN则更进一步将这些静态资源缓存到离用户更近的数据中心。越接近客户访问性能越好时延越低。
但是静态内容中有一种特殊的内容也大量使用了CDN这个就是前面讲过的[流媒体](https://time.geekbang.org/column/article/9688)。
CDN支持**流媒体协议**例如前面讲过的RTMP协议。在很多情况下这相当于一个代理从上一级缓存读取内容转发给用户。由于流媒体往往是连续的因而可以进行预先缓存的策略也可以预先推送到用户的客户端。
对于静态页面来讲,内容的分发往往采取**拉取**的方式,也即当发现未命中的时候,再去上一级进行拉取。但是,流媒体数据量大,如果出现**回源**,压力会比较大,所以往往采取主动**推送**的模式,将热点数据主动推送到边缘节点。
对于流媒体来讲很多CDN还提供**预处理服务**,也即文件在分发之前,经过一定的处理。例如将视频转换为不同的码流,以适应不同的网络带宽的用户需求;再如对视频进行分片,降低存储压力,也使得客户端可以选择使用不同的码率加载不同的分片。这就是我们常见的,“我要看超清、标清、流畅等”。
对于流媒体CDN来讲有个关键的问题是**防盗链**问题。因为视频是要花大价钱买版权的,为了挣点钱,收点广告费,如果流媒体被其他的网站盗走,在人家的网站播放,那损失可就大了。
最常用也最简单的方法就是**HTTP头的referer字段** 当浏览器发送请求的时候一般会带上referer告诉服务器是从哪个页面链接过来的服务器基于此可以获得一些信息用于处理。如果refer信息不是来自本站就阻止访问或者跳到其它链接。
**referer的机制相对比较容易破解所以还需要配合其他的机制。**
一种常用的机制是**时间戳防盗链**。使用CDN的管理员可以在配置界面上和CDN厂商约定一个加密字符串。
客户端取出当前的时间戳要访问的资源及其路径连同加密字符串进行签名算法得到一个字符串然后生成一个下载链接带上这个签名字符串和截止时间戳去访问CDN。
在CDN服务端根据取出过期时间和当前 CDN 节点时间进行比较确认请求是否过期。然后CDN服务端有了资源及路径时间戳以及约定的加密字符串根据相同的签名算法计算签名如果匹配则一致访问合法才会将资源返回给客户。
然而比如在电商仓库中,我在前面提过,有关生鲜的缓存就是非常麻烦的事情,这对应着就是动态的数据,比较难以缓存。怎么办呢?现在也有**动态CDN主要有两种模式**。
<li>
一种为**生鲜超市模式**,也即**边缘计算的模式**。既然数据是动态生成的,所以数据的逻辑计算和存储,也相应的放在边缘的节点。其中定时从源数据那里同步存储的数据,然后在边缘进行计算得到结果。就像对生鲜的烹饪是动态的,没办法事先做好缓存,因而将生鲜超市放在你家旁边,既能够送货上门,也能够现场烹饪,也是边缘计算的一种体现。
</li>
<li>
另一种是**冷链运输模式**,也即**路径优化的模式**。数据不是在边缘计算生成的而是在源站生成的但是数据的下发则可以通过CDN的网络对路径进行优化。因为CDN节点较多能够找到离源站很近的边缘节点也能找到离用户很近的边缘节点。中间的链路完全由CDN来规划选择一个更加可靠的路径使用类似专线的方式进行访问。
</li>
对于常用的TCP连接在公网上传输的时候经常会丢数据导致TCP的窗口始终很小发送速度上不去。根据前面的TCP流量控制和拥塞控制的原理在CDN加速网络中可以调整TCP的参数使得TCP可以更加激进地传输数据。
可以通过多个请求复用一个连接,保证每次动态请求到达时。连接都已经建立了,不必临时三次握手或者建立过多的连接,增加服务器的压力。另外,可以通过对传输数据进行压缩,增加传输效率。
所有这些手段就像冷链运输,整个物流优化了,全程冷冻高速运输。不管生鲜是从你旁边的超市送到你家的,还是从产地送的,保证到你家是新鲜的。
## 小结
好了,这节就到这里了。咱们来总结一下,你记住这两个重点就好。
<li>
CDN和电商系统的分布式仓储系统一样分为中心节点、区域节点、边缘节点而数据缓存在离用户最近的位置。
</li>
<li>
CDN最擅长的是缓存静态数据除此之外还可以缓存流媒体数据这时候要注意使用防盗链。它也支持动态数据的缓存一种是边缘计算的生鲜超市模式另一种是链路优化的冷链运输模式。
</li>
最后,给你留两个思考题:
<li>
这一节讲了CDN使用DNS进行全局负载均衡的例子CDN如何使用HttpDNS呢
</li>
<li>
客户端对DNS、HttpDNS、CDN访问了半天还没进数据中心你知道数据中心里面什么样吗
</li>
我们的专栏更新到第20讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,145 @@
<audio id="audio" title="第21讲 | 数据中心:我是开发商,自己拿地盖别墅" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/66/f4/66330fd4f7fb45367b333fee9775faf4.mp3"></audio>
无论你是看新闻、下订单、看视频、下载文件,最终访问的目的地都在数据中心里面。我们前面学了这么多的网络协议和网络相关的知识,你是不是很好奇,数据中心究竟长啥样呢?
数据中心是一个大杂烩,几乎要用到前面学过的所有知识。
前面讲办公室网络的时候,我们知道办公室里面有很多台电脑。如果要访问外网,需要经过一个叫**网关**的东西,而网关往往是一个路由器。
数据中心里面也有一大堆的电脑,但是它和咱们办公室里面的笔记本或者台式机不一样。数据中心里面是服务器。服务器被放在一个个叫作**机架****Rack**)的架子上面。
数据中心的入口和出口也是路由器,由于在数据中心的边界,就像在一个国家的边境,称为**边界路由器****Border Router**)。为了高可用,边界路由器会有多个。
一般家里只会连接一个运营商的网络,而为了高可用,为了当一个运营商出问题的时候,还可以通过另外一个运营商来提供服务,所以数据中心的边界路由器会连接多个运营商网络。
既然是路由器就需要跑路由协议数据中心往往就是路由协议中的自治区域AS。数据中心里面的机器要想访问外面的网站数据中心里面也是有对外提供服务的机器都可以通过BGP协议获取内外互通的路由信息。这就是我们常听到的**多线BGP**的概念。
如果数据中心非常简单,没几台机器,那就像家里或者宿舍一样,所有的服务器都直接连到路由器上就可以了。但是数据中心里面往往有非常多的机器,当塞满一机架的时候,需要有交换机将这些服务器连接起来,可以互相通信。
这些交换机往往是放在机架顶端的,所以经常称为**TOR****Top Of** **Rack****交换机**。这一层的交换机常常称为**接入层****Access Layer**)。注意这个接入层和原来讲过的应用的接入层不是一个概念。
<img src="https://static001.geekbang.org/resource/image/8f/f8/8fdfb8d4e1cd5a9a086f99b98a7555f8.jpg" alt="">
当一个机架放不下的时候,就需要多个机架,还需要有交换机将多个机架连接在一起。这些交换机对性能的要求更高,带宽也更大。这些交换机称为**汇聚层交换机****Aggregation Layer**)。
数据中心里面的每一个连接都是需要考虑高可用的。这里首先要考虑的是如果一台机器只有一个网卡上面连着一个网线接入到TOR交换机上。如果网卡坏了或者不小心网线掉了机器就上不去了。所以需要至少两个网卡、两个网线插到TOR交换机上但是两个网卡要工作得像一张网卡一样这就是常说的**网卡绑定****bond**)。
这就需要服务器和交换机都支持一种协议**LACP****Link Aggregation Control Protocol**)。它们互相通信,将多个网卡聚合称为一个网卡,多个网线聚合成一个网线,在网线之间可以进行负载均衡,也可以为了高可用作准备。
<img src="https://static001.geekbang.org/resource/image/84/94/84196dedd044cc135bfc28ede4687d94.jpg" alt="">
网卡有了高可用保证但交换机还有问题。如果一个机架只有一个交换机它挂了那整个机架都不能上网了。因而TOR交换机也需要高可用同理接入层和汇聚层的连接也需要高可用性也不能单线连着。
最传统的方法是部署两个接入交换机、两个汇聚交换机。服务器和两个接入交换机都连接接入交换机和两个汇聚都连接当然这样会形成环所以需要启用STP协议去除环但是这样两个汇聚就只能一主一备了。STP协议里我们学过只有一条路会起作用。
<img src="https://static001.geekbang.org/resource/image/11/50/116a168c0eb55fabd7786fca728bd850.jpg" alt="">
交换机有一种技术叫作**堆叠**,所以另一种方法是,将多个交换机形成一个逻辑的交换机,服务器通过多根线分配连到多个接入层交换机上,而接入层交换机多根线分别连接到多个交换机上,并且通过堆叠的私有协议,形成**双活**的连接方式。
<img src="https://static001.geekbang.org/resource/image/10/3a/10aa7eac3fd38dfc2a09d6475ff4d93a.jpg" alt="">
由于对带宽要求更大,而且挂了影响也更大,所以两个堆叠可能就不够了,可以就会有更多的,比如四个堆叠为一个逻辑的交换机。
汇聚层将大量的计算节点相互连接在一起,形成一个集群。在这个集群里面,服务器之间通过二层互通,这个区域常称为一个**POD****Point Of Delivery**),有时候也称为一个**可用区****Available Zon**e
当节点数目再多的时候,一个可用区放不下,需要将多个可用区连在一起,连接多个可用区的交换机称为**核心交换机**。
<img src="https://static001.geekbang.org/resource/image/08/13/080ce3bbe673de38e196b5b741a86313.jpg" alt="">
核心交换机吞吐量更大,高可用要求更高,肯定需要堆叠,但是往往仅仅堆叠,不足以满足吞吐量,因而还是需要部署多组核心交换机。核心和汇聚交换机之间为了高可用,也是全互连模式的。
这个时候还存在一个问题,出现环路怎么办?
一种方式是,不同的可用区在不同的二层网络,需要分配不同的网段。汇聚和核心之间通过三层网络互通的,二层都不在一个广播域里面,不会存在二层环路的问题。三层有环是没有问题的,只要通过路由协议选择最佳的路径就可以了。那为啥二层不能有环路,而三层可以呢?你可以回忆一下二层环路的情况。
<img src="https://static001.geekbang.org/resource/image/be/yy/be86f9a94002cf8d849a229ce5993cyy.jpeg" alt="">
如图核心层和汇聚层之间通过内部的路由协议OSPF找到最佳的路径进行访问而且还可以通过ECMP等价路由在多个路径之间进行负载均衡和高可用。
但是随着数据中心里面的机器越来越多,尤其是有了云计算、大数据,集群规模非常大,而且都要求在一个二层网络里面。这就需要二层互连从**汇聚层**上升为**核心层**,也即在核心以下,全部是二层互连,全部在一个广播域里面,这就是常说的**大二层**。
<img src="https://static001.geekbang.org/resource/image/2a/2c/2aa3787c31c52defc7614c53f0a71d2c.jpg" alt="">
如果大二层横向流量不大,核心交换机数目不多,可以做堆叠,但是如果横向流量很大,仅仅堆叠满足不了,就需要部署多组核心交换机,而且要和汇聚层进行全互连。由于堆叠只解决一个核心交换机组内的无环问题,而组之间全互连,还需要其他机制进行解决。
如果是STP那部署多组核心无法扩大横向流量的能力因为还是只有一组起作用。
于是大二层就引入了**TRILL****Transparent Interconnection of Lots of Link**),即**多链接透明互联协议**。它的基本思想是,二层环有问题,三层环没有问题,那就把三层的路由能力模拟在二层实现。
运行TRILL协议的交换机称为**RBridge**,是**具有路由转发特性的网桥设备**只不过这个路由是根据MAC地址来的不是根据IP来的。
Rbridage之间通过**链路状态协议**运作。记得这个路由协议吗通过它可以学习整个大二层的拓扑知道访问哪个MAC应该从哪个网桥走还可以计算最短的路径也可以通过等价的路由进行负载均衡和高可用性。
<img src="https://static001.geekbang.org/resource/image/b6/af/b6487347ebc5ec8f019e7ac82dd5d4af.jpeg" alt="">
TRILL协议在原来的MAC头外面加上自己的头以及外层的MAC头。TRILL头里面的Ingress RBridge有点像IP头里面的源IP地址Egress RBridge是目标IP地址这两个地址是端到端的在中间路由的时候不会发生改变。而外层的MAC可以有下一跳的Bridge就像路由的下一跳也是通过MAC地址来呈现的一样。
如图中所示的过程有一个包要从主机A发送到主机B中间要经过RBridge 1、RBridge 2、RBridge X等等直到RBridge 3。在RBridge 2收到的包里面分内外两层内层就是传统的主机A和主机B的MAC地址以及内层的VLAN。
在外层首先加上一个TRILL头里面描述这个包从RBridge 1进来的要从RBridge 3出去并且像三层的IP地址一样有跳数。然后再外面目的MAC是RBridge 2源MAC是RBridge 1以及外层的VLAN。
当RBridge 2收到这个包之后首先看MAC是否是自己的MAC如果是要看自己是不是Egress RBridge也即是不是最后一跳如果不是查看跳数是不是大于0然后通过类似路由查找的方式找到下一跳RBridge X然后将包发出去。
RBridge 2发出去的包内层的信息是不变的外层的TRILL头里面。同样描述这个包从RBridge 1进来的要从RBridge 3出去但是跳数要减1。外层的目标MAC变成RBridge X源MAC变成RBridge 2。
如此一直转发直到RBridge 3将外层解出来发送内层的包给主机B。
这个过程是不是和IP路由很像
对于大二层的广播包也需要通过分发树的技术来实现。我们知道STP是将一个有环的图通过去掉边形成一棵树而分发树是一个有环的图形成多棵树不同的树有不同的VLAN有的广播包从VLAN A广播有的从VLAN B广播实现负载均衡和高可用。
<img src="https://static001.geekbang.org/resource/image/bf/f2/bfdd48216f55bdf674fd8638e61d4ff2.jpeg" alt="">
核心交换机之外,就是边界路由器了。至此从服务器到数据中心边界的层次情况已经清楚了。
在核心交换上面往往会挂一些安全设备例如入侵检测、DDoS防护等等。这是整个数据中心的屏障防止来自外来的攻击。核心交换机上往往还有负载均衡器原理前面的章节已经说过了。
在有的数据中心里面对于存储设备还会有一个存储网络用来连接SAN和NAS。但是对于新的云计算来讲往往不使用传统的SAN和NAS而使用部署在x86机器上的软件定义存储这样存储也是服务器了而且可以和计算节点融合在一个机架上从而更加有效率也就没有了单独的存储网络了。
于是整个数据中心的网络如下图所示。
<img src="https://static001.geekbang.org/resource/image/dd/b3/dd39a95064b2132d78e3a2efb0723bb3.jpg" alt="">
这是一个典型的三层网络结构。这里的三层不是指IP层而是指接入层、汇聚层、核心层三层。这种模式非常有利于外部流量请求到内部应用。这个类型的流量是从外到内或者从内到外对应到上面那张图里就是从上到下从下到上上北下南所以称为**南北流量**。
但是随着云计算和大数据的发展,节点之间的交互越来越多,例如大数据计算经常要在不同的节点将数据拷贝来拷贝去,这样需要经过交换机,使得数据从左到右,从右到左,左西右东,所以称为**东西流量**。
为了解决东西流量的问题,演进出了**叶脊网络****Spine/Leaf**)。
<li>
**叶子交换机****leaf**直接连接物理服务器。L2/L3网络的分界点在叶子交换机上叶子交换机之上是三层网络。
</li>
<li>
**脊交换机****spine switch**相当于核心交换机。叶脊之间通过ECMP动态选择多条路径。脊交换机现在只是为叶子交换机提供一个弹性的L3路由网络。南北流量可以不用直接从脊交换机发出而是通过与leaf交换机并行的交换机再接到边界路由器出去。
</li>
<img src="https://static001.geekbang.org/resource/image/99/92/99f86d113a629d81bb52786d80ca5c92.jpg" alt="">
传统的三层网络架构是垂直的结构,而叶脊网络架构是扁平的结构,更易于水平扩展。
## 小结
好了,复杂的数据中心就讲到这里了。我们来总结一下,你需要记住这三个重点。
<li>
数据中心分为三层。服务器连接到接入层,然后是汇聚层,再然后是核心层,最外面是边界路由器和安全设备。
</li>
<li>
数据中心的所有链路都需要高可用性。服务器需要绑定网卡交换机需要堆叠三层设备可以通过等价路由二层设备可以通过TRILL协议。
</li>
<li>
随着云和大数据的发展,东西流量相对于南北流量越来越重要,因而演化为叶脊网络结构。
</li>
最后,给你留两个思考题:
<li>
对于数据中心来讲,高可用是非常重要的,每个设备都要考虑高可用,那跨机房的高可用,你知道应该怎么做吗?
</li>
<li>
前面说的浏览新闻、购物、下载、看视频等行为都是普通用户通过公网访问数据中心里面的资源。那IT管理员应该通过什么样的方式访问数据中心呢
</li>
我们的专栏更新到第21讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,262 @@
<audio id="audio" title="第22讲 | VPN朝中有人好做官" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0b/c8/0b3436bfb8557d895cf50828f73fb5c8.mp3"></audio>
前面我们讲到了数据中心,里面很复杂,但是有的公司有多个数据中心,需要将多个数据中心连接起来,或者需要办公室和数据中心连接起来。这该怎么办呢?
<li>
第一种方式是走公网,但是公网太不安全,你的隐私可能会被别人偷窥。
</li>
<li>
第二种方式是租用专线的方式把它们连起来,这是土豪的做法,需要花很多钱。
</li>
<li>
第三种方式是用VPN来连接这种方法比较折中安全又不贵。
</li>
<img src="https://static001.geekbang.org/resource/image/9f/68/9f797934cb5cf40543b716d97e214868.jpg" alt="">
**VPN**,全名**Virtual Private Network****虚拟专用网**,就是利用开放的公众网络,建立专用数据传输通道,将远程的分支机构、移动办公人员等连接起来。
## VPN是如何工作的
VPN通过隧道技术在公众网络上仿真一条点到点的专线是通过利用一种协议来传输另外一种协议的技术这里面涉及三种协议**乘客协议**、**隧道协议**和**承载协议**。
我们以IPsec协议为例来说明。
<img src="https://static001.geekbang.org/resource/image/52/7f/52c72a2a29c6b8526a243125e101f77f.jpeg" alt="">
你知道如何通过自驾进行海南游吗?这其中,你的车怎么通过琼州海峡呢?这里用到轮渡,其实这就用到**隧道协议**。
在广州这边开车是有“协议”的,例如靠右行驶、红灯停、绿灯行,这个就相当于“被封装”的**乘客协议**。当然在海南那面,开车也是同样的协议。这就相当于需要连接在一起的一个公司的两个分部。
但是在海上坐船航行,也有它的协议,例如要看灯塔、要按航道航行等。这就是外层的**承载协议**。
那我的车如何从广州到海南呢?这就需要你遵循开车的协议,将车开上轮渡,所有通过轮渡的车都关在船舱里面,按照既定的规则排列好,这就是**隧道协议**。
在大海上,你的车是关在船舱里面的,就像在隧道里面一样,这个时候内部的乘客协议,也即驾驶协议没啥用处,只需要船遵从外层的承载协议,到达海南就可以了。
到达之后,外部承载协议的任务就结束了,打开船舱,将车开出来,就相当于取下承载协议和隧道协议的头。接下来,在海南该怎么开车,就怎么开车,还是内部的乘客协议起作用。
在最前面的时候说了直接使用公网太不安全所以接下来我们来看一种十分安全的VPN**IPsec VPN**。这是基于IP协议的**安全隧道协议**,为了保证在公网上面信息的安全,因而采取了一定的机制保证安全性。
<li>
<p>机制一:**私密性**,防止信息泄露给未经授权的个人,通过加密把数据从明文变成无法读懂的密文,从而确保数据的私密性。<br>
前面讲HTTPS的时候说过加密可以分为对称加密和非对称加密。对称加密速度快一些。而VPN一旦建立需要传输大量数据因而我们采取对称加密。但是同样对称加密还是存在加密密钥如何传输的问题这里需要用到因特网密钥交换IKEInternet Key Exchange协议。</p>
</li>
<li>
机制二:**完整性**数据没有被非法篡改通过对数据进行hash运算产生类似于指纹的数据摘要以保证数据的完整性。
</li>
<li>
机制三:**真实性**,数据确实是由特定的对端发出,通过身份认证可以保证数据的真实性。
</li>
那如何保证对方就是真正的那个人呢?
<li>
第一种方法就是**预共享密钥**,也就是双方事先商量好一个暗号,比如“天王盖地虎,宝塔镇河妖”,对上了,就说明是对的。
</li>
<li>
另外一种方法就是**用数字签名来验证**。咋签名呢?当然是使用私钥进行签名,私钥只有我自己有,所以如果对方能用我的数字证书里面的公钥解开,就说明我是我。
</li>
基于以上三个特性,组成了**IPsec VPN的协议簇**。这个协议簇内容比较丰富。
<img src="https://static001.geekbang.org/resource/image/e4/c2/e43f13e5c68c9a5455b3793fb530a4c2.jpeg" alt="">
在这个协议簇里面,有两种协议,这两种协议的区别在于封装网络包的格式不一样。
<li>
一种协议称为**AH****Authentication Header**),只能进行数据摘要 ,不能实现数据加密。
</li>
<li>
还有一种**ESP****Encapsulating Security Payload**),能够进行数据加密和数据摘要。
</li>
在这个协议簇里面,还有两类算法,分别是**加密算法**和**摘要算法**。
这个协议簇还包含两大组件一个用于VPN的双方要进行对称密钥的交换的**IKE组件**另一个是VPN的双方要对连接进行维护的**SASecurity Association组件**。
## IPsec VPN的建立过程
下面来看IPsec VPN的建立过程这个过程分两个阶段。
**第一个阶段建立IKE自己的SA**。这个SA用来维护一个通过身份认证和安全保护的通道为第二个阶段提供服务。在这个阶段通过DHDiffie-Hellman算法计算出一个对称密钥K。
DH算法是一个比较巧妙的算法。客户端和服务端约定两个公开的质数p和q然后客户端随机产生一个数a作为自己的私钥服务端随机产生一个b作为自己的私钥客户端可以根据p、q和a计算出公钥A服务端根据p、q和b计算出公钥B然后双方交换公钥A和B。
到此客户端和服务端可以根据已有的信息各自独立算出相同的结果K就是**对称密钥**。但是这个过程,对称密钥从来没有在通道上传输过,只传输了生成密钥的材料,通过这些材料,截获的人是无法算出的。
<img src="https://static001.geekbang.org/resource/image/24/ea/24c89b7703ffcb7ce966f9d259b06eea.jpeg" alt="">
有了这个对称密钥K接下来是**第二个阶段建立IPsec SA**。在这个SA里面双方会生成一个随机的对称密钥M由K加密传给对方然后使用M进行双方接下来通信的数据。对称密钥M是有过期时间的会过一段时间重新生成一次从而防止被破解。
IPsec SA里面有以下内容
<li>
SPISecurity Parameter Index用于标识不同的连接
</li>
<li>
双方商量好的加密算法、哈希算法和封装模式;
</li>
<li>
生存周期超过这个周期就需要重新生成一个IPsec SA重新生成对称密钥。
</li>
<img src="https://static001.geekbang.org/resource/image/4d/75/4dd02eaa9a0242252371248a1b455275.jpeg" alt="">
当IPsec建立好接下来就可以开始打包封装传输了。
<img src="https://static001.geekbang.org/resource/image/15/65/158af554e14dd93237b136a99d07d165.jpg" alt="">
左面是原始的IP包在IP头里面会指定上一层的协议为TCP。ESP要对IP包进行封装因而IP头里面的上一层协议为ESP。在ESP的正文里面ESP的头部有双方商讨好的SPI以及这次传输的序列号。
接下来全部是加密的内容。可以通过对称密钥进行解密解密后在正文的最后指明了里面的协议是什么。如果是IP则需要先解析IP头然后解析TCP头这是从隧道出来后解封装的过程。
有了IPsec VPN之后客户端发送的明文的IP包都会被加上ESP头和IP头在公网上传输由于加密可以保证不被窃取到了对端后去掉ESP的头进行解密。
<img src="https://static001.geekbang.org/resource/image/48/d2/487d8dde5ec7758b4c169509fba59cd2.jpeg" alt="">
这种点对点的基于IP的VPN能满足互通的要求但是速度往往比较慢这是由底层IP协议的特性决定的。IP不是面向连接的是尽力而为的协议每个IP包自由选择路径到每一个路由器都自己去找下一跳丢了就丢了是靠上一层TCP的重发来保证可靠性。
<img src="https://static001.geekbang.org/resource/image/29/8e/29f4dff1f40a252dda2ac6f9d1d4088e.jpg" alt="">
因为IP网络从设计的时候就认为是不可靠的所以即使同一个连接也可能选择不同的道路这样的好处是一条道路崩溃的时候总有其他的路可以走。当然带来的代价就是不断的路由查找效率比较差。
和IP对应的另一种技术称为ATM。这种协议和IP协议的不同在于它是面向连接的。你可以说TCP也是面向连接的啊。这两个不同ATM和IP是一个层次的和TCP不是一个层次的。
另外TCP所谓的面向连接是不停地重试来保证成功其实下层的IP还是不面向连接的丢了就丢了。ATM是传输之前先建立一个连接形成一个虚拟的通路一旦连接建立了所有的包都按照相同的路径走不会分头行事。
<img src="https://static001.geekbang.org/resource/image/af/48/afea2a2cecb8bfe6c1c40585000f1c48.jpg" alt="">
好处是不需要每次都查路由表的虚拟路径已经建立打上了标签后续的包傻傻的跟着走就是了不用像IP包一样每个包都思考下一步怎么走都按相同的路径走这样效率会高很多。
但是一旦虚拟路径上的某个路由器坏了,则这个连接就断了,什么也发不过去了,因为其他的包还会按照原来的路径走,都掉坑里了,它们不会选择其他的路径走。
ATM技术虽然没有成功但其屏弃了繁琐的路由查找改为简单快速的标签交换将具有全局意义的路由表改为只有本地意义的标签表这些都可以大大提高一台路由器的转发功力。
有没有一种方式将两者的优点结合起来呢?这就是**多协议标签交换****MPLS****Multi-Protocol Label Switching**。MPLS的格式如图所示在原始的IP头之外多了MPLS的头里面可以打标签。
<img src="https://static001.geekbang.org/resource/image/c4/96/c45a0a2f11678f15e126449e64644596.jpeg" alt="">
在二层头里面有类型字段0x0800表示IP0x8847表示MPLS Label。
在MPLS头里面首先是标签值占20位接着是3位实验位再接下来是1位栈底标志位表示当前标签是否位于栈底了。这样就允许多个标签被编码到同一个数据包中形成标签栈。最后是8位TTL存活时间字段如果标签数据包的出发TTL值为0那么该数据包在网络中的生命期被认为已经过期了。
有了标签,还需要设备认这个标签,并且能够根据这个标签转发,这种能够转发标签的路由器称为**标签交换路由器**LSRLabel Switching Router
这种路由器会有两个表格一个就是传统的FIB也即路由表另一个就是LFIB标签转发表。有了这两个表既可以进行普通的路由转发也可以进行基于标签的转发。
<img src="https://static001.geekbang.org/resource/image/66/97/66df10af27dbyycee673dc47667dbb97.jpeg" alt="">
有了标签转发表,转发的过程如图所示,就不用每次都进行普通路由的查找了。
这里我们区分MPLS区域和非MPLS区域。在MPLS区域中间使用标签进行转发非MPLS区域使用普通路由转发在边缘节点上需要有能力将对于普通路由的转发变成对于标签的转发。
例如图中要访问114.1.1.1在边界上查找普通路由发现马上要进入MPLS区域了进去了对应标签1于是在IP头外面加一个标签1在区域里面标签1要变成标签3标签3到达出口边缘将标签去掉按照路由发出。
这样一个通过标签转换而建立的路径称为LSP标签交换路径。在一条LSP上沿数据包传送的方向相邻的LSR分别叫**上游LSR****upstream LSR**)和**下游LSR****downstream LSR**)。
有了标签转发是很简单的事但是如何生成标签却是MPLS中最难修炼的部分。在MPLS秘笈中这部分被称为**LDP****Label Distribution Protocol**),是一个动态的生成标签的协议。
其实LDP与IP帮派中的路由协议十分相像通过LSR的交互互相告知去哪里应该打哪个标签称为标签分发往往是从下游开始的。
<img src="https://static001.geekbang.org/resource/image/91/67/91cd0038877da8933365494625280b67.jpeg" alt="">
如果有一个边缘节点发现自己的路由表中出现了新的目的地址,它就要给别人说,我能到达一条新的路径了。
如果此边缘节点存在上游LSR并且尚有可供分配的标签则该节点为新的路径分配标签并向上游发出标签映射消息其中包含分配的标签等信息。
收到标签映射消息的LSR记录相应的标签映射信息在其标签转发表中增加相应的条目。此LSR为它的上游LSR分配标签并继续向上游LSR发送标签映射消息。
当入口LSR收到标签映射消息时在标签转发表中增加相应的条目。这时就完成了LSP的建立。有了标签转发轻松多了但是这个和VPN什么关系呢
可以想象如果我们VPN通道里面包的转发都是通过标签的方式进行效率就会高很多。所以要想个办法把MPLS应用于VPN。
<img src="https://static001.geekbang.org/resource/image/eb/ae/eb01c7d748b99bc94017f667eb74caae.jpeg" alt="">
在MPLS VPN中网络中的路由器分成以下几类
<li>
PEProvider Edge运营商网络与客户网络相连的边缘网络设备
</li>
<li>
CECustomer Edge客户网络与PE相连接的边缘设备
</li>
<li>
PProvider这里特指运营商网络中除PE之外的其他运营商网络设备。
</li>
为什么要这样分呢因为我们发现在运营商网络里面也即P Router之间使用标签是没有问题的因为都在运营商的管控之下对于网段路由都可以自己控制。但是一旦客户要接入这个网络就复杂得多。
首先是客户地址重复的问题。客户所使用的大多数都是私网的地址(192.168.X.X;10.X.X.X;172.X.X.X),而且很多情况下都会与其它的客户重复。
比如机构A和机构B都使用了192.168.101.0/24网段的地址这就发生了地址空间重叠Overlapping Address Spaces
首先困惑的是BGP协议既然VPN将两个数据中心连起来应该看起来像一个数据中心一样那么如何到达另一端需要通过BGP将路由广播过去传统BGP无法正确处理地址空间重叠的VPN的路由。
假设机构A和机构B都使用了192.168.101.0/24网段的地址并各自发布了一条去往此网段的路由BGP将只会选择其中一条路由从而导致去往另一个VPN的路由丢失。
所以PE路由器之间使用特殊的MP-BGP来发布VPN路由在相互沟通的消息中在一般32位IPv4的地址之前加上一个客户标示的区分符用于客户地址的区分这种称为VPN-IPv4地址族这样PE路由器会收到如下的消息机构A的192.168.101.0/24应该往这面走机构B的192.168.101.0/24则应该去另外一个方向。
另外困惑的是**路由表**当两个客户的IP包到达PE的时候PE就困惑了因为网段是重复的。
如何区分哪些路由是属于哪些客户VPN内的如何保证VPN业务路由与普通路由不相互干扰
在PE上可以通过VRFVPN Routing&amp;Forwarding Instance建立每个客户一个路由表与其它VPN客户路由和普通路由相互区分。可以理解为专属于客户的小路由器。
远端PE通过MP-BGP协议把业务路由放到近端PE近端PE根据不同的客户选择出相关客户的业务路由放到相应的VRF路由表中。
VPN报文转发采用两层标签方式
<li>
第一层外层标签在骨干网内部进行交换指示从PE到对端PE的一条LSP。VPN报文利用这层标签可以沿LSP到达对端PE
</li>
<li>
第二层内层标签在从对端PE到达CE时使用在PE上通过查找VRF表项指示报文应被送到哪个VPN用户或者更具体一些到达哪一个CE。这样对端PE根据内层标签可以找到转发报文的接口。
</li>
<img src="https://static001.geekbang.org/resource/image/43/1f/43e9ee00e3db2b0cd8315058d38d701f.jpeg" alt="">
我们来举一个例子看MPLS VPN的包发送过程。
<li>
机构A和机构B都发出一个目的地址为192.168.101.0/24的IP报文分别由各自的CE将报文发送至PE。
</li>
<li>
PE会根据报文到达的接口及目的地址查找VPN实例表项VRF匹配后将报文转发出去同时打上内层和外层两个标签。假设通过MP-BGP配置的路由两个报文在骨干网走相同的路径。
</li>
<li>
MPLS网络利用报文的外层标签将报文传送到出口PE报文在到达出口PE 2前一跳时已经被剥离外层标签仅含内层标签。
</li>
<li>
出口PE根据内层标签和目的地址查找VPN实例表项VRF确定报文的出接口将报文转发至各自的CE。
</li>
<li>
CE根据正常的IP转发过程将报文传送到目的地。
</li>
## 小结
好了,这一节就到这里了,我们来总结一下:
<li>
VPN可以将一个机构的多个数据中心通过隧道的方式连接起来让机构感觉在一个数据中心里面就像自驾游通过琼州海峡一样
</li>
<li>
完全基于软件的IPsec VPN可以保证私密性、完整性、真实性、简单便宜但是性能稍微差一些
</li>
<li>
MPLS-VPN综合和IP转发模式和ATM的标签转发模式的优势性能较好但是需要从运营商购买。
</li>
接下来,给你留两个思考题:
<li>
当前业务的高可用性和弹性伸缩很重要,所以很多机构都会在自建私有云之外,采购公有云,你知道私有云和公有云应该如何打通吗?
</li>
<li>
前面所有的上网行为,都是基于电脑的,但是移动互联网越来越成为核心,你知道手机上网都需要哪些协议吗?
</li>
我们的专栏更新到第22讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,217 @@
<audio id="audio" title="第23讲 | 移动网络:去巴塞罗那,手机也上不了脸书" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1b/b7/1bf7c2014dcac41bd61657729dea4bb7.mp3"></audio>
前面讲的都是电脑上网的场景,那使用手机上网有什么不同呢?
## 移动网络的发展历程
你一定知道手机上网有2G、3G、4G的说法究竟这都是什么意思呢有一个通俗的说法就是用2G看txt用3G看jpg用4G看avi。
### 2G网络
手机本来是用来打电话的不是用来上网的所以原来在2G时代上网使用的不是IP网络而是电话网络走模拟信号专业名称为公共交换电话网PSTNPublic Switched Telephone Network
那手机不连网线,也不连电话线,它是怎么上网的呢?
手机是通过收发无线信号来通信的专业名称是Mobile Station简称MS需要嵌入SIM。手机是客户端而无线信号的服务端就是基站子系统BSSBase Station SubsystemBSS。至于什么是基站你可以回想一下你在爬山的时候是不是看到过信号塔我们平时城市里面的基站比较隐蔽不容易看到所以只有在山里才会注意到。正是这个信号塔通过无线信号让你的手机可以进行通信。
但是你要知道一点,**无论无线通信如何无线,最终还是要连接到有线的网络里**。前面讲[数据中心](https://time.geekbang.org/column/article/10098)的时候我也讲过,电商的应用是放在数据中心的,数据中心的电脑都是插着网线的。
因而基站子系统分两部分一部分对外提供无线通信叫作基站收发信台BTSBase Transceiver Station另一部分对内连接有线网络叫作基站控制器BSCBase Station Controller。基站收发信台通过无线收到数据后转发给基站控制器。
这部分属于无线的部分统称为无线接入网RANRadio Access Network
基站控制器通过有线网络连接到提供手机业务的运营商的数据中心这部分称为核心网CNCore Network。核心网还没有真的进入互联网这部分还是主要提供手机业务是手机业务的有线部分。
首先接待基站来的数据的是移动业务交换中心MSCMobile Service Switching Center它是进入核心网的入口但是它不会让你直接连接到互联网上。
因为在让你的手机真正进入互联网之前提供手机业务的运营商需要认证是不是合法的手机接入。你别自己造了一张手机卡就连接上来。鉴权中心AUCAuthentication Center和设备识别寄存器EIREquipment Identity Register主要是负责安全性的。
另外需要看你是本地的号还是外地的号这个牵扯到计费的问题异地收费还是很贵的。访问位置寄存器VLRVisit Location Register是看你目前在的地方归属位置寄存器HLRHome Location Register是看你的号码归属地。
当你的手机卡既合法又有钱的时候才允许你上网这个时候需要一个网关连接核心网和真正的互联网。网关移动交换中心GMSC Gateway Mobile Switching Center就是干这个的然后是真正的互连网。在2G时代还是电话网络PSTN。
数据中心里面的这些模块统称为网络子系统NSSNetwork and Switching Subsystem
<img src="https://static001.geekbang.org/resource/image/2e/72/2e29d99cd977eaa41231a2abd1017c72.jpg" alt="">
因而2G时代的上网如图所示我们总结一下有这几个核心点
<li>
**手机通过无线信号连接基站;**
</li>
<li>
**基站一面朝前接无线,一面朝后接核心网;**
</li>
<li>
**核心网一面朝前接到基站请求,一是判断你是否合法,二是判断你是不是本地号,还有没有钱,一面通过网关连接电话网络。**
</li>
### 2.5G网络
后来从2G到了2.5G也即在原来电路交换的基础上加入了分组交换业务支持Packet的转发从而支持IP网络。
在上述网络的基础上基站一面朝前接无线一面朝后接核心网。在朝后的组件中多了一个分组控制单元PCUPacket Control Unit用以提供分组交换通道。
在核心网里面有个朝前的接待员SGSNService GPRS Supported Node和朝后连接IP网络的网关型GPRS支持节点GGSNGateway GPRS Supported Node
<img src="https://static001.geekbang.org/resource/image/0d/d8/0d2f378ace0fbfa2e4ece8e1e7893cd8.jpg" alt="">
### 3G网络
到了3G时代主要是无线通信技术有了改进大大增加了无线的带宽。
以W-CDMA为例理论最高2M的下行速度因而基站改变了一面朝外的是Node B一面朝内连接核心网的是无线网络控制器RNCRadio Network Controller。核心网以及连接的IP网络没有什么变化。
<img src="https://static001.geekbang.org/resource/image/60/57/60e2b3701de683f1424ec6a77816c957.jpg" alt="">
### 4G网络
然后就到了今天的4G网络基站为eNodeB包含了原来Node B和RNC的功能下行速度向百兆级别迈进。另外核心网实现了控制面和数据面的分离这个怎么理解呢
在前面的核心网里面有接待员MSC或者SGSN你会发现检查是否合法是它负责转发数据也是它负责也即控制面和数据面是合二为一的这样灵活性比较差因为控制面主要是指令多是小包往往需要高的及时性数据面主要是流量多是大包往往需要吞吐量。
于是有了下面这个架构。
<img src="https://static001.geekbang.org/resource/image/a6/61/a645919dfb484542350af84d76076d61.jpg" alt="">
HSS用于存储用户签约信息的数据库其实就是你这个号码归属地是哪里的以及一些认证信息。
MME是核心控制网元是控制面的核心当手机通过eNodeB连上的时候MME会根据HSS的信息判断你是否合法。如果允许连上来MME不负责具体的数据的流量而是MME会选择数据面的SGW和PGW然后告诉eNodeB我允许你连上来了你连接它们吧。
于是手机直接通过eNodeB连接SGW连上核心网SGW相当于数据面的接待员并通过PGW连到IP网络。PGW就是出口网关。在出口网关有一个组件PCRF称为策略和计费控制单元用来控制上网策略和流量的计费。
## 4G网络协议解析
我们来仔细看一下4G网络的协议真的非常复杂。我们将几个关键组件放大来看。
<img src="https://static001.geekbang.org/resource/image/e4/50/e40f7cb5754e01ed82ae120d8d571f50.jpg" alt="">
### 控制面协议
其中虚线部分是控制面的协议。当一个手机想上网的时候先要连接eNodeB并通过S1-MME接口请求MME对这个手机进行认证和鉴权。S1-MME协议栈如下图所示。
<img src="https://static001.geekbang.org/resource/image/3b/92/3b40f3c06f95de2e261609b82163f392.jpg" alt="">
UE就是你的手机eNodeB还是两面派朝前对接无线网络朝后对接核心网络在控制面对接的是MME。
eNodeB和MME之间的连接就是很正常的IP网络但是这里面在IP层之上却既不是TCP也不是UDP而是SCTP。这也是传输层的协议也是面向连接的但是更加适合移动网络。 它继承了TCP较为完善的拥塞控制并改进TCP的一些不足之处。
SCTP的第一个特点是**多宿主**。一台机器可以有多个网卡而对于TCP连接来讲虽然服务端可以监听0.0.0.0,也就是从哪个网卡来的连接都能接受,但是一旦建立了连接,就建立了四元组,也就选定了某个网卡。
SCTP引入了联合association的概念将多个接口、多条路径放到一个联合中来。当检测到一条路径失效时协议就会通过另外一条路径来发送通信数据。应用程序甚至都不必知道发生了故障、恢复从而提供更高的可用性和可靠性。
SCTP的第二个特点是**将一个联合分成多个流**。一个联合中的所有流都是独立的但均与该联合相关。每个流都给定了一个流编号它被编码到SCTP报文中通过联合在网络上传送。在TCP的机制中由于强制顺序导致前一个不到达后一个就得等待SCTP的多个流不会相互阻塞。
SCTP的第三个特点是**四次握手防止SYN攻击**。在TCP中是三次握手当服务端收到客户的SYN之后返回一个SYN-ACK之前就建立数据结构并记录下状态等待客户端发送ACK的ACK。当恶意客户端使用虚假的源地址来伪造大量SYN报文时服务端需要分配大量的资源最终耗尽资源无法处理新的请求。
SCTP可以通过四次握手引入Cookie的概念来有效地防止这种攻击的产生。在SCTP中客户机使用一个INIT报文发起一个连接。服务器使用一个INIT-ACK报文进行响应其中就包括了Cookie。然后客户端就使用一个COOKIE-ECHO报文进行响应其中包含了服务器所发送的Cookie。这个时候服务器为这个连接分配资源并通过向客户机发送一个COOKIE-ACK报文对其进行响应。
SCTP的第四个特点是**将消息分帧**。TCP是面向流的也即发送的数据没头没尾没有明显的界限。这对于发送数据没有问题但是对于发送一个个消息类型的数据就不太方便。有可能客户端写入10个字节然后再写入20个字节。服务端不是读出10个字节的一个消息再读出20个字节的一个消息而有可能读入25个字节再读入5个字节需要业务层去组合成消息。
SCTP借鉴了UDP的机制在数据传输中提供了消息分帧功能。当一端对一个套接字执行写操作时可确保对等端读出的数据大小与此相同。
SCTP的第五个特点是**断开连接是三次挥手**。在TCP里面断开连接是四次挥手允许另一端处于半关闭的状态。SCTP选择放弃这种状态当一端关闭自己的套接字时对等的两端全部需要关闭将来任何一端都不允许再进行数据的移动了。
当MME通过认证鉴权同意这个手机上网的时候需要建立一个数据面的数据通路。建立通路的过程还是控制面的事情因而使用的是控制面的协议GTP-C。
建设的数据通路分两段路其实是两个隧道。一段是从eNodeB到SGW这个数据通路由MME通过S1-MME协议告诉eNodeB它是隧道的一端通过S11告诉SGW它是隧道的另一端。第二端是从SGW到PGWSGW通过S11协议知道自己是其中一端并主动通过S5协议告诉PGW它是隧道的另一端。
GTP-C协议是基于UDP的这是[UDP的“城会玩”](https://time.geekbang.org/column/article/8924)中的一个例子。如果看GTP头我们可以看到这里面有隧道的ID还有序列号。
<img src="https://static001.geekbang.org/resource/image/77/cd/77eb34d092948fbf8773ef9041305fcd.jpg" alt="">
通过序列号不用TCPGTP-C自己就可以实现可靠性为每个输出信令消息分配一个依次递增的序列号以确保信令消息的按序传递并便于检测重复包。对于每个输出信令消息启动定时器在定时器超时前未接收到响应消息则进行重发。
### 数据面协议
当两个隧道都打通接在一起的时候PGW会给手机分配一个IP地址这个IP地址是隧道内部的IP地址可以类比为IPsec协议里面的IP地址。这个IP地址是归手机运营商管理的。然后手机可以使用这个IP地址连接eNodeB从eNodeB经过S1-U协议通过第一段隧道到达SGW再从SGW经过S8协议通过第二段隧道到达PGW然后通过PGW连接到互联网。
数据面的协议都是通过GTP-U如图所示。
<img src="https://static001.geekbang.org/resource/image/03/29/0397a72d0c5d76d4e3591cbe61eef729.jpg" alt="">
手机每发出的一个包都由GTP-U隧道协议封装起来格式如下。
<img src="https://static001.geekbang.org/resource/image/15/11/15c48f78ed74d62cdeda7cf2d3c66711.jpg" alt="">
和IPsec协议很类似分为乘客协议、隧道协议、承载协议。其中乘客协议是手机发出来的包IP是手机的IP隧道协议里面有隧道ID不同的手机上线会建立不同的隧道因而需要隧道ID来标识。承载协议的IP地址是SGW和PGW的IP地址。
### 手机上网流程
接下来,我们来看一个手机开机之后上网的流程,这个过程称为**Attach**。可以看出来移动网络还是很复杂的。因为这个过程要建立很多的隧道分配很多的隧道ID所以我画了一个图来详细说明这个过程。
<img src="https://static001.geekbang.org/resource/image/4d/d2/4d3e9282b00410a28e77f91f9375f4d2.jpg" alt="">
<li>
手机开机以后在附近寻找基站eNodeB找到后给eNodeB发送Attach Request说“我来啦我要上网”。
</li>
<li>
eNodeB将请求发给MME说“有个手机要上网”。
</li>
<li>
MME去请求手机一是认证二是鉴权还会请求HSS看看有没有钱看看是在哪里上网。
</li>
<li>
当MME通过了手机的认证之后开始分配隧道先告诉SGW说要创建一个会话Create Session。在这里面会给SGW分配一个隧道ID t1并且请求SGW给自己也分配一个隧道ID。
</li>
<li>
SGW转头向PGW请求建立一个会话为PGW的控制面分配一个隧道ID t2也给PGW的数据面分配一个隧道ID t3并且请求PGW给自己的控制面和数据面分配隧道ID。
</li>
<li>
PGW回复SGW说“创建会话成功”使用自己的控制面隧道ID t2回复里面携带着给SGW控制面分配的隧道ID t4和控制面的隧道ID t5至此SGW和PGW直接的隧道建设完成。双方请求对方都要带着对方给自己分配的隧道ID从而标志是这个手机的请求。
</li>
<li>
接下来SGW回复MME说“创建会话成功”使用自己的隧道ID t1访问MME回复里面有给MME分配隧道ID t6也有SGW给eNodeB分配的隧道ID t7。
</li>
<li>
当MME发现后面的隧道都建设成功之后就告诉eNodeB“后面的隧道已经建设完毕SGW给你分配的隧道ID是t7你可以开始连上来了但是你也要给SGW分配一个隧道ID”。
</li>
<li>
eNodeB告诉MME自己给SGW分配一个隧道ID为t8。
</li>
<li>
MME将eNodeB给SGW分配的隧道ID t8告知SGW从而前面的隧道也建设完毕。
</li>
这样,手机就可以通过建立的隧道成功上网了。
## 异地上网问题
接下来我们考虑异地上网的事情。
为什么要分SGW和PGW呢一个GW不可以吗SGW是你本地的运营商的设备而PGW是你所属的运营商的设备。
如果你在巴塞罗那一下飞机手机开机周围搜寻到的肯定是巴塞罗那的eNodeB。通过MME去查询国内运营商的HSS看你是否合法是否还有钱。如果允许上网你的手机和巴塞罗那的SGW会建立一个隧道然后巴塞罗那的SGW和国内运营商的PGW建立一个隧道然后通过国内运营商的PGW上网。
<img src="https://static001.geekbang.org/resource/image/06/38/0628bd6923ca91dc63ba36ca7ab74e38.jpg" alt="">
因此判断你是否能上网的是国内运营商的HSS控制你上网策略的是国内运营商的PCRF给手机分配的IP地址也是国内运营商的PGW负责的给手机分配的IP地址也是国内运营商里统计的。运营商由于是在PGW里面统计的这样你的上网流量全部通过国内运营商即可只不过巴塞罗那运营商也要和国内运营商进行流量结算。
由于你的上网策略是由国内运营商在PCRF中控制的因而你还是上不了脸书。
## 小结
好了,这一节就到这里了,我们来总结一下:
<li>
移动网络的发展历程从2G到3G再到4G逐渐从打电话的功能为主向上网的功能为主转变
</li>
<li>
请记住4G网络的结构有eNodeB、MME、SGW、PGW等分控制面协议和数据面协议你可以对照着结构试着说出手机上网的流程
</li>
<li>
即便你在国外的运营商下上网,也是要通过国内运营商控制的,因而也上不了脸书。
</li>
最后,给你留两个思考题:
<li>
咱们上网都有套餐,有交钱多的,有交钱少的,你知道移动网络是如何控制不同优先级的用户的上网流量的吗?
</li>
<li>
前面讲过的所有的网络都是基于物理机的,随着云计算兴起,无论是电商,还是移动网络都要部署在云中了,你知道云中网络的设计有哪些要点吗?
</li>
我们的专栏更新到第23讲不知你掌握得如何每节课后我留的思考题你都有没有认真思考并在留言区写下答案呢我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,93 @@
<audio id="audio" title="第37讲 | 知识串讲:用双十一的故事串起碎片的网络协议(上)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7c/6a/7c5687c9e0486d2435efe8f0d779e76a.mp3"></audio>
基本的网络知识我们都讲完了,还记得最初举的那个“双十一”下单的例子吗?这一节开始,我们详细地讲解这个过程,用这个过程串起我们讲过的网络协议。
我把这个过程分为十个阶段从云平台中搭建一个电商开始到BGP路由广播再到DNS域名解析从客户看商品图片到最终下单的整个过程每一步我都会详细讲解。这节我们先来看前三个阶段。
## 1.部署一个高可用高并发的电商平台
首先,咱们**要有个电商平台**。假设我们已经有了一个特别大的电商平台,这个平台应该部署在哪里呢?假设我们用公有云,一般公有云会有多个位置,比如在华东、华北、华南都有。毕竟咱们的电商是要服务全国的,当然到处都要部署了。我们把主站点放在华东。
<img src="https://static001.geekbang.org/resource/image/ed/20/eddde5929de2a72b197321e5ad87e120.jpg" alt="" />
为了每个点都能“雨露均沾”也为了高可用性往往需要有多个机房形成多个可用区Available Zone。由于咱们的应用是分布在两个可用区的所以假如任何一个可用区挂了都不会受影响。
我们来回想[数据中心](https://time.geekbang.org/column/article/10098)那一节,每个可用区里有一片一片的机柜,每个机柜上有一排一排的服务器,每个机柜都有一个接入交换机,有一个汇聚交换机将多个机柜连在一起。
这些服务器里面部署的都是计算节点每台上面都有Open vSwitch创建的虚拟交换机将来在这台机器上创建的虚拟机都会连到Open vSwitch上。
<img src="https://static001.geekbang.org/resource/image/d6/a7/d66c01c39e911e784525a118c37b50a7.jpg" alt="" />
接下来,你**在云计算的界面上创建一个VPC**Virtual Private Cloud虚拟私有网络指定一个IP段这样以后你部署的所有应用都会在这个虚拟网络里使用你分配的这个IP段。为了不同的VPC相互隔离每个VPC都会被分配一个VXLAN的ID。尽管不同用户的虚拟机有可能在同一个物理机上但是不同的VPC二层压根儿是不通的。
由于有两个可用区在这个VPC里面要为每一个可用区分配一个Subnet也就是在大的网段里分配两个小的网段。当两个可用区里面网段不同的时候就可以配置路由策略访问另外一个可用区走某一条路由了。
接下来,应该**创建数据库持久化层**。大部分云平台都会提供PaaS服务也就是说不需要你自己搭建数据库而是采用直接提供数据库的服务并且单机房的主备切换都是默认做好的数据库也是部署在虚拟机里面的只不过从界面上你看不到数据库所在的虚拟机而已。
云平台会给每个Subnet的数据库实例分配一个域名。创建数据库实例的时候需要你指定可用区和Subnet这样创建出来的数据库实例可以通过这个Subnet的私网IP进行访问。
为了分库分表实现高并发的读写,在创建的多个数据库实例之上,会**创建一个分布式数据库的实例**也需要指定可用区和Subnet还会为分布式数据库分配一个私网IP和域名。
对于数据库这种高可用性比较高的,需要进行跨机房高可用,因而两个可用区都要部署一套,但是只有一个是主,另外一个是备,云平台往往会提供数据库同步工具,将应用写入主的数据同步给备数据库集群。
接下来是**创建缓存集**群。云平台也会提供PaaS服务也需要每个可用区和Subnet创建一套缓存的数据在内存中由于读写性能要求高一般不要求跨可用区读写。
再往上层就是**部署咱们自己写的程序**了。基础服务层、组合服务层、Controller层以及Nginx层、API网关等等这些都是部署在虚拟机里面的。它们之间通过RPC相互调用需要到注册中心进行注册。
它们之间的网络通信是虚拟机和虚拟机之间的。如果是同一台物理机则那台物理机上的OVS就能转发过去如果是不同的物理机这台物理机的OVS和另一台物理机的OVS中间有一个VXLAN的隧道将请求转发过去。
再往外就是**负载均衡**了负载均衡也是云平台提供的PaaS服务也是属于某个VPC的部署在虚拟机里面的但是负载均衡有个外网的IP这个外网的IP地址就是在网关节点的外网网口上的。在网关节点上会有NAT规则将外网IP地址转换为VPC里面的私网IP地址通过这些私网IP地址访问到虚拟机上的负载均衡节点然后通过负载均衡节点转发到API网关的节点。
网关节点的外网网口是带公网IP地址的里面有一个虚拟网关转发模块还会有一个OVS将私网IP地址放到VXLAN隧道里面转发到虚拟机上从而实现外网和虚拟机网络之间的互通。
不同的可用区之间,通过核心交换机连在一起,核心交换机之外是边界路由器。
在华北、华东、华南同样也部署了一整套每个地区都创建了VPC这就需要有一种机制将VPC连接到一起。云平台一般会提供硬件的VPC互连的方式当然也可以使用软件互连的方式也就是使用VPN网关通过IPsec VPN将不同地区的不同VPC通过VPN连接起来。
对于不同地区和不同运营商的用户我们希望他能够就近访问到网站而且当一个点出了故障之后我们希望能够在不同的地区之间切换这就需要有智能DNS这个也是云平台提供的。
对于一些静态资源可以保持在对象存储里面通过CDN下发到边缘节点这样客户端就能尽快加载出来。
## 2.大声告诉全世界,可以到我这里买东西
当电商应用搭建完毕之后,接下来需要将如何访问到这个电商网站广播给全网。
刚才那张图画的是一个可用区的情况,对于多个可用区的情况,我们可以隐去计算节点的情况,将外网访问区域放大。
<img src="https://static001.geekbang.org/resource/image/e1/24/e132bc3ba500b1197139f30c02e20124.jpg" alt="" />
外网IP是放在虚拟网关的外网网口上的这个IP如何让全世界知道呢当然是通过BGP路由协议了。
每个可用区都有自己的汇聚交换机如果机器数目比较多可以直接用核心交换机每个Region也有自己的核心交换区域。
在核心交换外面是安全设备然后就是边界路由器。边界路由器会和多个运营商连接从而每个运营商都能够访问到这个网站。边界路由器可以通过BGP协议将自己数据中心里面的外网IP向外广播也就是告诉全世界如果要访问这些外网IP都来我这里。
每个运营商也有很多的路由器、很多的点于是就可以将如何到达这些IP地址的路由信息广播到全国乃至全世界。
## 3.打开手机来上网,域名解析得地址
这个时候不但你的这个网站的IP地址全世界都知道了你打的广告可能大家也都看到了于是有客户下载App来买东西了。
<img src="https://static001.geekbang.org/resource/image/85/fc/85c125c225faba29c0f374e18ea8c6fc.jpg" alt="" />
客户的手机开机以后在附近寻找基站eNodeB发送请求申请上网。基站将请求发给MMEMME对手机进行认证和鉴权还会请求HSS看有没有钱看看是在哪里上网。
当MME通过了手机的认证之后开始建立隧道建设的数据通路分两段路其实是两个隧道。一段是从eNodeB到SGW第二段是从SGW到PGW在PGW之外就是互联网。
PGW会为手机分配一个IP地址手机上网都是带着这个IP地址的。
当在手机上面打开一个App的时候首先要做的事情就是解析这个网站的域名。
在手机运营商所在的互联网区域里有一个本地的DNS手机会向这个DNS请求解析DNS。当这个DNS本地有缓存则直接返回如果没有缓存本地DNS才需要递归地从根DNS服务器查到.com的顶级域名服务器最终查到权威DNS服务器。
如果你使用云平台的时候配置了智能DNS和全局负载均衡在权威DNS服务中一般是通过配置CNAME的方式我们可以起一个别名例如 [vip.yourcomany.com](http://vip.yourcomany.com) 然后告诉本地DNS服务器让它请求GSLB解析这个域名GSLB就可以在解析这个域名的过程中通过自己的策略实现负载均衡。
GSLB通过查看请求它的本地DNS服务器所在的运营商和地址就知道用户所在的运营商和地址然后将距离用户位置比较近的Region里面三个负载均衡SLB的公网IP地址返回给本地DNS服务器。本地DNS解析器将结果缓存后返回给客户端。
对于手机App来说可以绕过刚才的传统DNS解析机制直接只要HTTPDNS服务通过直接调用HTTPDNS服务器得到这三个SLB的公网IP地址。
经过了如此复杂的过程咱们的万里长征还没迈出第一步刚刚得到IP地址包还没发呢话说手机App拿到了公网IP地址接下来应该做什么呢
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="第38讲 | 知识串讲:用双十一的故事串起碎片的网络协议(中)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6d/e8/6d61c5d70872758beb9c2b1fa3e434e8.mp3"></audio>
上一节我们讲到手机App经过了一个复杂的过程终于拿到了电商网站的SLB的IP地址是不是该下单了
别忙,俗话说的好,买东西要货比三家。大部分客户在购物之前要看很多商品图片,比来比去,最后好不容易才下决心,点了下单按钮。下单按钮一按,就要开始建立连接。建立连接这个过程也挺复杂的,最终还要经过层层封装,才构建出一个完整的网络包。今天我们就来看这个过程。
## 4.购物之前看图片静态资源CDN
客户想要在购物网站买一件东西的时候,一般是先去详情页看看图片,是不是想买的那一款。
<img src="https://static001.geekbang.org/resource/image/70/69/7023762edeaf4d481bc90331f60db769.jpg" alt="">
我们部署电商应用的时候一般会把静态资源保存在两个地方一个是接入层nginx后面的varnish缓存里面一般是静态页面对于比较大的、不经常更新的静态图片会保存在对象存储里面。这两个地方的静态资源都会配置CDN将资源下发到边缘节点。
配置了CDN之后权威DNS服务器上会为静态资源设置一个CNAME别名指向另外一个域名 cdn.com 返回给本地DNS服务器。
当本地DNS服务器拿到这个新的域名时需要继续解析这个新的域名。这个时候再访问的时候就不是原来的权威DNS服务器了而是 cdn.com 的权威DNS服务器。这是CDN自己的权威DNS服务器。
在这个服务器上还是会设置一个CNAME指向另外一个域名也即CDN网络的全局负载均衡器。
本地DNS服务器去请求CDN的全局负载均衡器解析域名全局负载均衡器会为用户选择一台合适的缓存服务器提供服务将IP返回给客户端客户端去访问这个边缘节点下载资源。缓存服务器响应用户请求将用户所需内容传送到用户终端。
如果这台缓存服务器上并没有用户想要的内容,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器,将内容拉到本地。
## 5.看上宝贝点下单,双方开始建连接
当你浏览了很多图片,发现实在喜欢某个商品,于是决定下单购买。
电商网站会对下单的情况提供RESTful的下单接口而对于下单这种需要保密的操作需要通过HTTPS协议进行请求。
在所有这些操作之前,首先要做的事情是**建立连接**。
<img src="https://static001.geekbang.org/resource/image/70/83/705e4cc5acc7b364bdf015f333d40783.jpg" alt="">
HTTPS协议是基于TCP协议的因而要先**建立TCP的连接**。在这个例子中TCP的连接是在手机上的App和负载均衡器SLB之间的。
尽管中间要经过很多的路由器和交换机但是TCP的连接是端到端的。TCP这一层和更上层的HTTPS无法看到中间的包的过程。尽管建立连接的时候所有的包都逃不过在这些路由器和交换机之间的转发转发的细节我们放到那个下单请求的发送过程中详细解读这里只看端到端的行为。
对于TCP连接来讲需要通过三次握手建立连接为了维护这个连接双方都需要在TCP层维护一个连接的状态机。
一开始客户端和服务端都处于CLOSED状态。服务端先是主动监听某个端口处于LISTEN状态。然后客户端主动发起连接SYN之后处于SYN-SENT状态。服务端收到发起的连接返回SYN并且ACK客户端的SYN之后处于SYN-RCVD状态。
客户端收到服务端发送的SYN和ACK之后发送ACK的ACK之后处于ESTABLISHED状态。这是因为它一发一收成功了。服务端收到ACK的ACK之后也会处于ESTABLISHED状态因为它的一发一收也成功了。
当TCP层的连接建立完毕之后接下来轮到**HTTPS层建立连接**了在HTTPS的交换过程中TCP层始终处于ESTABLISHED。
对于HTTPS客户端会发送Client Hello消息到服务器用明文传输TLS版本信息、加密套件候选列表、压缩算法候选列表等信息。另外还会有一个随机数在协商对称密钥的时候使用。
然后服务器会返回Server Hello消息告诉客户端服务器选择使用的协议版本、加密套件、压缩算法等。这也有一个随机数用于后续的密钥协商。
然后服务器会给你一个服务器端的证书然后说“Server Hello Done我这里就这些信息了。”
客户端当然不相信这个证书于是从自己信任的CA仓库中拿CA的证书里面的公钥去解密电商网站的证书。如果能够成功则说明电商网站是可信的。这个过程中你可能会不断往上追溯CA、CA的CA、CA的CA的CA反正直到一个授信的CA就可以了。
证书验证完毕之后觉得这个服务端是可信的于是客户端计算产生随机数字Pre-master发送Client Key Exchange用证书中的公钥加密再发送给服务器服务器可以通过私钥解密出来。
接下来无论是客户端还是服务器都有了三个随机数分别是自己的、对端的以及刚生成的Pre-Master随机数。通过这三个随机数可以在客户端和服务器产生相同的对称密钥。
有了对称密钥客户端就可以说“Change Cipher Spec咱们以后都采用协商的通信密钥和加密算法进行加密通信了。”
然后客户端发送一个Encrypted Handshake Message将已经商定好的参数等采用协商密钥进行加密发送给服务器用于数据与握手验证。
同样服务器也可以发送Change Cipher Spec“没问题咱们以后都采用协商的通信密钥和加密算法进行加密通信了”并且也发送Encrypted Handshake Message的消息试试。
当双方握手结束之后,就可以通过对称密钥进行加密传输了。
真正的下单请求封装成网络包的发送过程,我们先放一放,我们来接着讲这个网络包的故事。
## 6.发送下单请求网络包,西行需要出网关
当客户端和服务端之间建立了连接后,接下来就要发送下单请求的网络包了。
在用户层发送的是HTTP的网络包因为服务端提供的是RESTful API因而HTTP层发送的就是一个请求。
```
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/json; charset=utf-8
Content-Length: nnn
{
&quot;order&quot;: {
&quot;date&quot;: &quot;2018-07-01&quot;,
&quot;className&quot;: &quot;趣谈网络协议&quot;,
&quot;Author&quot;: &quot;刘超&quot;,
&quot;price&quot;: &quot;68&quot;
}
}
```
HTTP的报文大概分为三大部分。第一部分是**请求行**,第二部分是**请求的首部**,第三部分才是**请求的正文实体**。
在请求行中URL就是 www.geektime.com/purchaseOrder 版本为HTTP 1.1。
请求的类型叫作POST它需要主动告诉服务端一些信息而非获取。需要告诉服务端什么呢一般会放在正文里面。正文可以有各种各样的格式常见的格式是JSON。
请求行下面就是我们的首部字段。首部是key value通过冒号分隔。
Content-Type是指正文的格式。例如我们进行POST的请求如果正文是JSON那么我们就应该将这个值设置为JSON。
接下来是正文这里是一个JSON字符串里面通过文本的形式描述了要买一个课程作者是谁多少钱。
这样HTTP请求的报文格式就拼凑好了。接下来浏览器或者移动App会把它交给下一层传输层。
怎么交给传输层呢也是用Socket进行程序设计。如果用的是浏览器这些程序不需要你自己写有人已经帮你写好了如果在移动APP里面一般会用一个HTTP的客户端工具来发送并且帮你封装好。
HTTP协议是基于TCP协议的所以它使用面向连接的方式发送请求通过Stream二进制流的方式传给对方。当然到了TCP层它会把二进制流变成一个个报文段发送给服务器。
在TCP头里面会有源端口号和目标端口号目标端口号一般是服务端监听的端口号源端口号在手机端往往是随机分配一个端口号。这个端口号在客户端和服务端用于区分请求和返回发给那个应用。
在IP头里面都需要加上自己的地址即源地址和它想要去的地方即目标地址。当一个手机上线的时候PGW会给这个手机分配一个IP地址这就是源地址而目标地址则是云平台的负载均衡器的外网IP地址。
在IP层客户端需要查看目标地址和自己是否是在同一个局域网计算是否是同一个网段往往需要通过CIDR子网掩码来计算。
对于这个下单场景目标IP和源IP不会在同一个网段因而需要发送到默认的网关。一般通过DHCP分配IP地址的时候同时配置默认网关的IP地址。
但是客户端不会直接使用默认网关的IP地址而是发送ARP协议来获取网关的MAC地址然后将网关MAC作为目标MAC自己的MAC作为源MAC放入MAC头发送出去。
一个完整的网络包的格式是这样的。
<img src="https://static001.geekbang.org/resource/image/99/49/99c282efaca15deb79c7821c9c577349.jpg" alt="">
真不容易啊,本来以为上篇就发送下单包了,结果到中篇这个包还没发送出去,只是封装了一个如此长的网络包。别着急,你可以自己先预想一下,接下来该做什么了?
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,301 @@
<audio id="audio" title="第39讲 | 知识串讲:用双十一的故事串起碎片的网络协议(下)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5f/94/5fe9b9e65108cda410e851c60897ea94.mp3"></audio>
上一节,我们封装了一个长长的网络包,“大炮”准备完毕,开始发送。
发送的时候可以说是重重关隘从手机到移动网络、互联网还要经过多个运营商才能到达数据中心到了数据中心就进入第二个复杂的过程从网关到VXLAN隧道到负载均衡到Controller层、组合服务层、基础服务层最终才下单入库。今天我们就来看这最后一段过程。
## 7.一座座城池一道道关,流控拥塞与重传
网络包已经组合完毕接下来我们来看如何经过一道道城关到达目标公网IP。
对于手机来讲默认的网关在PGW上。在移动网络里面从手机到SGW到PGW是有一条隧道的。在这条隧道里面会将上面的这个包作为隧道的乘客协议放在里面外面SGW和PGW在核心网机房的IP地址。网络包直到PGWPGW是隧道的另一端才将里面的包解出来转发到外部网络。
所以,从手机发送出来的时候,网络包的结构为:
<li>
源MAC手机也即UE的MAC
</li>
<li>
目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
源IPUE的IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
进入隧道之后,要封装外层的网络地址,因而网络包的格式为:
<li>
外层源MACE-NodeB的MAC
</li>
<li>
外层目标MACSGW的MAC
</li>
<li>
外层源IPE-NodeB的IP
</li>
<li>
外层目标IPSGW的IP
</li>
<li>
内层源MAC手机也即UE的MAC
</li>
<li>
内层目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
内层源IPUE的IP地址
</li>
<li>
内层目标IPSLB的公网IP地址。
</li>
当隧道在SGW的时候切换了一个隧道会从SGW到PGW的隧道因而网络包的格式为
<li>
外层源MACSGW的MAC
</li>
<li>
外层目标MACPGW的MAC
</li>
<li>
外层源IPSGW的IP
</li>
<li>
外层目标IPPGW的IP
</li>
<li>
内层源MAC手机也即UE的MAC
</li>
<li>
内层目标MAC网关PGW上面的隧道端点的MAC
</li>
<li>
内层源IPUE的IP地址
</li>
<li>
内层目标IPSLB的公网IP地址。
</li>
在PGW的隧道端点将包解出来转发出去的时候一般在PGW出外部网络的路由器上会部署NAT服务将手机的IP地址转换为公网IP地址当请求返回的时候再NAT回来。
因而在PGW之后相当于做了一次[欧洲十国游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式为:
<li>
源MACPGW出口的MAC
</li>
<li>
目标MACNAT网关的MAC
</li>
<li>
源IPUE的IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
在NAT网关相当于做了一次[玄奘西游型](https://time.geekbang.org/column/article/8590)的转发,网络包的格式变成:
<li>
源MACNAT网关的MAC
</li>
<li>
目标MACA2路由器的MAC
</li>
<li>
源IPUE的公网IP地址
</li>
<li>
目标IPSLB的公网IP地址。
</li>
<img src="https://static001.geekbang.org/resource/image/58/a0/582c9515a877a6fde85a6180186ba8a0.jpg" alt="">
出了NAT网关就从核心网到达了互联网。在网络世界每一个运营商的网络成为自治系统AS。每个自治系统都有边界路由器通过它和外面的世界建立联系。
对于云平台来讲它可以被称为Multihomed AS有多个连接连到其他的AS但是大多拒绝帮其他的AS传输包。例如一些大公司的网络。对于运营商来说它可以被称为Transit AS有多个连接连到其他的AS并且可以帮助其他的AS传输包比如主干网。
如何从出口的运营商到达云平台的边界路由器在路由器之间需要通过BGP协议实现BGP又分为两类eBGP和iBGP。自治系统之间、边界路由器之间使用eBGP广播路由。内部网络也需要访问其他的自治系统。
边界路由器如何将BGP学习到的路由导入到内部网络呢通过运行iBGP使内部的路由器能够找到到达外网目的地最好的边界路由器。
网站的SLB的公网IP地址早已经通过云平台的边界路由器让全网都知道了。于是这个下单的网络包选择的下一跳是A2也即将A2的MAC地址放在目标MAC地址中。
到达A2之后从路由表中找到下一跳是路由器C1于是将目标MAC换成C1的MAC地址。到达C1之后找到下一跳是C2将目标MAC地址设置为C2的MAC。到达C2后找到下一跳是云平台的边界路由器于是将目标MAC设置为边界路由器的MAC地址。
你会发现这一路都是只换MAC不换目标IP地址。这就是所谓下一跳的概念。
在云平台的边界路由器会将下单的包转发进来经过核心交换汇聚交换到达外网网关节点上的SLB的公网IP地址。
我们可以看到手机到SLB的公网IP是一个端到端的连接连接的过程发送了很多包。所有这些包无论是TCP三次握手还是HTTPS的密钥交换都是要走如此复杂的过程到达SLB的当然每个包走的路径不一定一致。
网络包走在这个复杂的道路上很可能一不小心就丢了怎么办这就需要借助TCP的机制重新发送。
既然TCP要对包进行重传就需要维护Sequence Number看哪些包到了哪些没到哪些需要重传传输的速度应该控制到多少这就是**TCP的滑动窗口协议**。
<img src="https://static001.geekbang.org/resource/image/8a/3d/8af90ec349e69f2bf13a565e4179903d.jpg" alt="">
整个TCP的发送一开始会协商一个Sequence Number从这个Sequence Number开始每个包都有编号。滑动窗口将接收方的网络包分成四个部分
<li>
已经接收已经ACK已经交给应用层的包
</li>
<li>
已经接收已经ACK未发送给应用层
</li>
<li>
已经接收尚未发送ACK
</li>
<li>
未接收,尚有空闲的缓存区域。
</li>
对于TCP层来讲每一个包都有ACK。ACK需要从SLB回复到手机端将上面的那个过程反向来一遍当然路径不一定一致可见ACK也不是那么轻松的事情。
如果发送方超过一定的时间没有收到ACK就会重新发送。只有TCP层ACK过的包才会发给应用层并且只会发送一份对于下单的场景应用层是HTTP层。
你可能会问了TCP老是重复发送会不会导致一个单下了两遍是否要求服务端实现幂等从TCP的机制来看是不会的。只有收不到ACK的包才会重复发发到接收端在窗口里面只保存一份所以在同一个TCP连接中不用担心重传导致二次下单。
但是TCP连接会因为某种原因断了例如手机信号不好这个时候手机把所有的动作重新做一遍建立一个新的TCP连接在HTTP层调用两次RESTful API。这个时候可能会导致两遍下单的情况因而RESTful API需要实现幂等。
当ACK过的包发给应用层之后TCP层的缓存就空了出来这会导致上面图中的大三角也即接收方能够容纳的总缓存整体顺时针滑动。小的三角形也即接收方告知发送方的窗口总大小也即还没有完全确认收到的缓存大小如果把这些填满了就不能再发了因为没确认收到所以一个都不能扔。
## 8.从数据中心进网关公网NAT成私网
包从手机端经历千难万险终于到了SLB的公网IP所在的公网网口。由于匹配上了MAC地址和IP地址因而将网络包收了进来。
<img src="https://static001.geekbang.org/resource/image/e0/95/e070a0bcbb249c36c80ee1300003f395.jpg" alt="">
在虚拟网关节点的外网网口上会有一个NAT规则将公网IP地址转换为VPC里面的私网IP地址这个私网IP地址就是SLB的HAProxy所在的虚拟机的私网IP地址。
当然为了承载比较大的吞吐量虚拟网关节点会有多个物理网络会将流量分发到不同的虚拟网关节点。同样HAProxy也会是一个大的集群虚拟网关会选择某个负载均衡节点将某个请求分发给它负载均衡之后是Controller层也是部署在虚拟机里面的。
当网络包里面的目标IP变成私有IP地址之后虚拟路由会查找路由规则将网络包从下方的私网网口发出来。这个时候包的格式为
<li>
源MAC网关MAC
</li>
<li>
目标MACHAProxy虚拟机的MAC
</li>
<li>
源IPUE的公网IP
</li>
<li>
目标IPHAProxy虚拟机的私网IP。
</li>
## 9.进入隧道打标签RPC远程调用下单
在虚拟路由节点上也会有OVS将网络包封装在VXLAN隧道里面VXLAN ID就是给你的租户创建VPC的时候分配的。包的格式为
<li>
外层源MAC网关物理机MAC
</li>
<li>
外层目标MAC物理机A的MAC
</li>
<li>
外层源IP网关物理机IP
</li>
<li>
外层目标IP物理机A的IP
</li>
<li>
内层源MAC网关MAC
</li>
<li>
内层目标MACHAProxy虚拟机的MAC
</li>
<li>
内层源IPUE的公网IP
</li>
<li>
内层目标IPHAProxy虚拟机的私网IP。
</li>
在物理机A上OVS会将包从VXLAN隧道里面解出来发给HAProxy所在的虚拟机。HAProxy所在的虚拟机发现MAC地址匹配目标IP地址匹配就根据TCP端口将包发给HAProxy进程因为HAProxy是在监听这个TCP端口的。因而HAProxy就是这个TCP连接的服务端客户端是手机。对于TCP的连接状态、滑动窗口等都是在HAProxy上维护的。
在这里HAProxy是一个四层负载均衡也即它只解析到TCP层里面的HTTP协议它不关心就将请求转发给后端的多个Controller层的一个。
HAProxy发出去的网络包就认为HAProxy是客户端了看不到手机端了。网络包格式如下
<li>
源MACHAProxy所在虚拟机的MAC
</li>
<li>
目标MACController层所在虚拟机的MAC
</li>
<li>
源IPHAProxy所在虚拟机的私网IP
</li>
<li>
目标IPController层所在虚拟机的私网IP。
</li>
当然这个包发出去之后还是会被物理机上的OVS放入VXLAN隧道里面网络包格式为
<li>
外层源MAC物理机A的MAC
</li>
<li>
外层目标MAC物理机B的MAC
</li>
<li>
外层源IP物理机A的IP
</li>
<li>
外层目标IP物理机B的IP
</li>
<li>
内层源MACHAProxy所在虚拟机的MAC
</li>
<li>
内层目标MACController层所在虚拟机的MAC
</li>
<li>
内层源IPHAProxy所在虚拟机的私网IP
</li>
<li>
内层目标IPController层所在虚拟机的私网IP。
</li>
在物理机B上OVS会将包从VXLAN隧道里面解出来发给Controller层所在的虚拟机。Controller层所在的虚拟机发现MAC地址匹配目标IP地址匹配就根据TCP端口将包发给Controller层的进程因为它在监听这个TCP端口。
在HAProxy和Controller层之间维护一个TCP的连接。
Controller层收到包之后它是关心HTTP里面是什么的于是解开HTTP的包发现是一个POST请求内容是下单购买一个课程。
## 10.下单扣减库存优惠券,数据入库返回成功
下单是一个复杂的过程因而往往在组合服务层会有一个专门管理下单的服务Controller层会通过RPC调用这个组合服务层。
假设我们使用的是Dubbo则Controller层需要读取注册中心将下单服务的进程列表拿出来选出一个来调用。
Dubbo中默认的RPC协议是Hessian2。Hessian2将下单的远程调用序列化为二进制进行传输。
Netty是一个非阻塞的基于事件的网络传输框架。Controller层和下单服务之间使用了Netty的网络传输框架。有了Netty就不用自己编写复杂的异步Socket程序了。Netty使用的方式就是咱们讲[Socket编程](https://time.geekbang.org/column/article/9293)的时候一个项目组支撑多个项目IO多路复用从派人盯着到有事通知这种方式。
Netty还是工作在Socket这一层的发送的网络包还是基于TCP的。在TCP的下层还是需要封装上IP头和MAC头。如果跨物理机通信还是需要封装的外层的VXLAN隧道里面。当然底层的这些封装Netty都不感知它只要做好它的异步通信即可。
在Netty的服务端也即下单服务中收到请求后先用Hessian2的格式进行解压缩。然后将请求分发到线程中进行处理在线程中会调用下单的业务逻辑。
下单的业务逻辑比较复杂往往要调用基础服务层里面的库存服务、优惠券服务等将多个服务调用完毕才算下单成功。下单服务调用库存服务和优惠券服务也是通过Dubbo的框架通过注册中心拿到库存服务和优惠券服务的列表然后选一个调用。
调用的时候统一使用Hessian2进行序列化使用Netty进行传输底层如果跨物理机仍然需要通过VXLAN的封装和解封装。
咱们以库存为例子的时候讲述过幂等的接口实现的问题。因为如果扣减库存仅仅是谁调用谁减一。这样存在的问题是如果扣减库存因为一次调用失败而多次调用这里指的不是TCP多次重试而是应用层调用的多次重试就会存在库存扣减多次的情况。
这里常用的方法是使用乐观锁Compare and Set简称CAS。CAS要考虑三个方面当前的库存数、预期原来的库存数和版本以及新的库存数。在操作之前查询出原来的库存数和版本真正扣减库存的时候判断如果当前库存的值与预期原值和版本相匹配则将库存值更新为新值否则不做任何操作。
这是一种基于状态而非基于动作的设计符合RESTful的架构设计原则。这样的设计有利于高并发场景。当多个线程尝试使用CAS同时更新同一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知这次竞争中失败并可以再次尝试。
最终,当下单更新到分布式数据库中之后,整个下单过程才算真正告一段落。
好了,经过了十个过程,下单终于成功了,你是否对这个过程了如指掌了呢?如果发现对哪些细节比较模糊,可以回去看一下相应的章节,相信会有更加深入的理解。
到此,我带着你用下单过程把网络协议的知识都复习了一遍。授人以鱼不如授人以渔。下一节,我将会带你来搭建一个网络实验环境,配合实验来说明理论。
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,214 @@
<audio id="audio" title="第40讲 | 搭建一个网络实验环境:授人以鱼不如授人以渔" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/6a/680a1b85738d87a7208e9e386d52286a.mp3"></audio>
因为这门课是基础课程,而且配合音频的形式发布,所以我多以理论为主来进行讲解。在专栏更新的过程中,不断有同学让我推荐一些网络方面的书籍,还有同学说能不能配合一些实验来说明理论。
的确,网络是一门实验性很强的学科,就像我在开篇词里面说的一样:**一看觉得懂,一问就打鼓,一用就糊涂。** 在写专栏的过程中,我自己也深深体会到了。这个时候,我常常会拿一个现实的环境,上手操作一下,抓个包看看,这样心里就会有定论。
## 《TCP/IP详解》实验环境搭建
对于网络方面的书籍我当然首推Rechard Stevens的《[TCP/IP illustrated](https://book.douban.com/subject/1741925/)》《TCP/IP详解》。这本书把理论讲得深入浅出还配有大量的上手实践和抓包看到这些抓包原来不理解的很多理论一下子就能懂了。
这本书里有个拓扑图,书上的很多实验都是基于这个图的,但是这个拓扑图还是挺复杂的。我这里先不说,一会儿详细讲。
Rechard Stevens因为工作中有这么一个环境很方便做实验最终才写出了这样一本书而我们一般人学习网络没有这个环境应该怎么办呢
时代不同了咱们现在有更加强大的工具了。例如这里这么多的机器我们可以用Docker来实现多个网络可以用Open vSwitch来实现。你甚至不需要一台物理机只要一台1核2G的虚拟机就能将这个环境搭建起来。
搭建这个环境的时候,需要一些脚本。我把脚本都放在了[Github](https://github.com/popsuper1982/tcpipillustrated)里面,你可以自己取用。
### 1.创建一个Ubuntu虚拟机
在你的笔记本电脑上用VirtualBox创建就行。1核2G随便一台电脑都能搭建起来。
首先我们先下载一个Ubuntu的镜像。我是从[Ubuntu官方网站](https://www.ubuntu.com/download/alternative-downloads)下载的。
<img src="https://static001.geekbang.org/resource/image/39/87/390a5bfb153fc95bdac0939194bc7387.jpg" alt="" />
然后在VirtualBox里面安装Ubuntu。安装过程网上一大堆教程你可以自己去看我这里就不详细说了。
这里我需要说明的是网络的配置。
对于这个虚拟机我们创建两个网卡一个是Host-only只有你的笔记本电脑上能够登录进去。这个网卡上的IP地址也只有在你的笔记本电脑上管用。这个网卡的配置比较稳定用于在SSH上做操作。这样你的笔记本电脑就可以搬来搬去在公司里安装一半回家接着安装另一半都没问题。
<img src="https://static001.geekbang.org/resource/image/ab/ce/ab1974fc25fe902ff8eeda598eb9c3ce.jpg" alt="" />
这里有一个虚拟的网桥,这个网络可以在管理&gt;主机网络管理里面进行配置。
<img src="https://static001.geekbang.org/resource/image/49/13/49eea1f000c9a538271d46c25c0a7b13.jpg" alt="" />
在这里可以虚拟网桥的的IP地址同时启用一个DHCP服务器为新创建的虚拟机配置IP地址。
另一个网卡配置为NAT网络用于访问互联网。配置了NAT网络之后只要你的笔记本电脑能上网虚拟机就能上网。由于咱们在Ubuntu里面要安装一些东西因而需要联网。
你可能会问了,这个配置复杂吗?一点儿都不复杂。咱们讲[虚拟机网络](https://time.geekbang.org/column/article/10742)的时候,讲过这个。
<img src="https://static001.geekbang.org/resource/image/2d/c4/2dd447992fbf4901f92a3dfdf8086bc4.jpg" alt="" />
安装完了Ubuntu之后需要对Ubuntu里面的网卡进行配置。对于Ubuntu来讲网卡的配置在/etc/network/interfaces这个文件里面。在我的环境里NAT的网卡名称为enp0s3Host-only的网卡的名称为enp0s8都可以配置为自动配置。
```
auto lo
iface lo inet loopback
auto enp0s3
iface enp0s3 inet dhcp
auto enp0s8
iface enp0s8 inet dhcp
```
这样重启之后IP就配置好了。
### 2.安装Docker和Open vSwitch
接下来在Ubuntu里面以root用户安装Docker和Open vSwitch。
你可以按照Docker的[官方安装](https://docs.docker.com/install/linux/docker-ce/ubuntu/)[文档](https://docs.docker.com/install/linux/docker-ce/ubuntu/)来做。我这里也贴一下我的安装过程。
```
apt-get remove docker docker-engine docker.io
apt-get -y update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg &gt; gpg
apt-key add gpg
apt-key fingerprint 0EBFCD88
add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;
apt-get -y update
apt-cache madison docker-ce
apt-get -y install docker-ce=18.06.0~ce~3-0~ubuntu
```
之后还需要安装Open vSwitch和Bridge。
```
apt-get -y install openvswitch-common openvswitch-dbg openvswitch-switch python-openvswitch openvswitch-ipsec openvswitch-pki openvswitch-vtep
apt-get -y install bridge-utils
apt-get -y install arping
```
### 3.准备一个Docker的镜像
<img src="https://static001.geekbang.org/resource/image/4c/1e/4c0e633ddc53f3feb98946518c0cf21e.jpg" alt="" />
每个节点都是一个Docker对应要有一个Docker镜像。这个镜像我已经打好了你可以直接使用。
```
docker pull hub.c.163.com/liuchao110119163/ubuntu:tcpip
```
当然你也可以自己打这个镜像。Dockerfile就像这样
```
FROM hub.c.163.com/public/ubuntu:14.04
RUN apt-get -y update &amp;&amp; apt-get install -y iproute2 iputils-arping net-tools tcpdump curl telnet iputils-tracepath traceroute
RUN mv /usr/sbin/tcpdump /usr/bin/tcpdump
ENTRYPOINT /usr/sbin/sshd -D
```
### 4.启动整个环境
启动这个环境还是比较复杂的我写成了一个脚本。在Git仓库里面有一个文件 [setupenv.sh](http://setupenv.sh) 可以执行这个脚本里面有两个参数一个参数是NAT网卡的名字一个是镜像的名称。
```
git clone https://github.com/popsuper1982/tcpipillustrated.git
cd tcpipillustrated
docker pull hub.c.163.com/liuchao110119163/ubuntu:tcpip
chmod +x setupenv.sh
./setupenv.sh enp0s3 hub.c.163.com/liuchao110119163/ubuntu:tcpip
```
这样整个环境就搭建起来了所有的容器之间都可以ping通而且都可以上网。
不过,我写的这个脚本对一些人来说可能会有点儿复杂,我这里也解释一下。
首先**每一个节点,都启动一个容器**。使用privileged=true方式网络先不配置net none。有两个二层网络使用ovs-vsctl的add-br命令创建两个网桥。
pipework是一个很好的命令行工具可以将容器连接到两个二层网络上。
但是我们上面那个图里有两个比较特殊的网络一个是从slip到bsdi的P2P网络需要创建一个peer的两个网卡然后两个Docker的网络namespace里面各塞进去一个。
有关操作Docker的网络namespace的方式咱们在[容器网络](https://time.geekbang.org/column/article/11465)那一节讲过ip netns命令。
这里需要注意的是P2P网络和下面的二层网络不是同一个网络。P2P网络的CIDR是140.252.13.64/27而下面的二层网络的CIDR是140.252.13.32/27。如果按照/24看起来是一个网络但是/27就不是了。至于[CIDR的计算方法](https://time.geekbang.org/column/article/7772),你可以回去复习一下。
另外需要**配置从sun到netb的点对点网络**方法还是通过peer网卡和ip netns的方式。
这里有个特殊的地方对于netb来讲不是一个普通的路由器因为netb两边是同一个二层网络所以需要配置arp proxy。
为了所有的节点之间互通要配置一下路由策略这里需要通过ip route命令。
<li>
对于slip来讲bsdi左面13.66这个网口是网关。
</li>
<li>
对于bsdi和svr4来讲如果去外网sun下面的网口13.33是网关。
</li>
<li>
对于sun来讲上面的网口1.29属于上面的二层网络了它如果去外网gateway下面的网口1.4就是外网网关。
</li>
<li>
对于aixsolarisgemini来讲如果去外网网关也是gateway下面的网口1.4。如果去下面的二层网口网关是sun上面的网口1.29。
</li>
配置完了这些,图中的所有的节点都能相互访问了,最后还要解决**如何访问外网**的问题。
我们还是需要创建一个peer网卡对。一个放在gateway里面一个放在gateway外面。外面的网卡去外网的网关。
在虚拟机上面还需要配置一个iptables的地址伪装规则MASQUERADE其实就是一个SNAT。因为容器里面要访问外网因为外网是不认的所以源地址不能用容器的地址需要SNAT成为虚拟机的地址出去回来的时候再NAT回来。
配置这个环境还是挺复杂的,要用到咱们学到的很多知识。如果没有学习前面那些知识,直接就做这个实验,你肯定会很晕。但是只学理论也不行,要把理论都学过一遍,再做一遍实验,这才是一个不断迭代、更新知识库的过程。
有了这个环境《TCP/IP详解》里面的所有实验都能做了而且我打的这个Docker镜像里面tcpdump等网络工具都安装了你可以“为所欲为”了。
## Open vSwitch的实验
做了TCP/IP详解的实验之后网络程序设计这部分你就有了坚实的基础。但是涉及到数据中心内部的一些网络技术什么VLAN、VXLAN、STP等偏运维方向的学习还是会比较困难。好在我们有Open vSwitch也可以做大量的实验。
Open vSwitch门槛比较高里面的概念也非常多可谓千头万绪。不过通过我这么多年研究的经验可以告诉你这里面有一个很好的线索那就是Open vSwitch会将自己对于网络的配置保存在一个本地库里面。这个库的表结构之间的关系就像这样
<img src="https://static001.geekbang.org/resource/image/43/d9/439a996b7f8de9477cb00876f13a28d9.jpg" alt="" />
这个库其实是一个JSON如果把这个JSON打印出来能够看到更加详细的特性。按照这些特性一一实验可以逐渐把Open vSwitch各个特性都掌握。
<img src="https://static001.geekbang.org/resource/image/f0/a0/f00657f044e5afb07332f7236054b8a0.jpg" alt="" />
这里面最重要的概念就是**网桥**。一个网桥会有流表控制网络包的处理过程会有控制器下发流表一个网桥上会有多个端口可以对端口进行流控一个端口可以设置VLAN一个端口可以包含多个网卡可以做绑定网卡可以设置成为GRE和VXLAN。
我写过一个Open vSwitch的实验教程也放在了Github里面。这里面有这么几个比较重要的实验你可以看一看。
<li>
实验一查看Open vSwitch的架构。我们在讲Open vSwitch的时候提过Open vSwitch的架构在这个实验中我们可以查看Open vSwitch的各个模块以及启动的参数。
</li>
<li>
实验五配置使用OpenFlow Controller体验一把作为小区物业在监控室里面管控整个小区道路的样子。
</li>
<li>
实验八测试Port的VLAN功能。看一下VLAN隔离究竟是什么样的。
</li>
<li>
实验十QoS功能。体验一把如果使用HTB进行网卡限流。
</li>
<li>
实验十一GRE和VXLAN隧道功能看虚拟网络如何进行租户隔离。
</li>
<li>
实验十五对Flow Table的操作体验流表对网络包随心所欲的处理。
</li>
好了,关于整个环境的搭建我就讲到这里了。
其实到这里,对于网络世界的探索才刚刚开始,只有经过你自己动手和思考产生的内容,才是真正属于你的知识!打开你的电脑,上手去实验吧!
欢迎你留言和我讨论。趣谈网络协议,我们下期见!

View File

@@ -0,0 +1,74 @@
我用将近半年的时间在“极客时间”写了一个专栏“趣谈网络协议”。对于我自己来讲,这真的是个非常特殊而又难忘的经历。
很多人都很好奇,这个专栏究竟是怎么一步步创作出来的,每一篇文章是怎么写出来的?自己录音频又是什么样的感受?写完整个专栏之后,我终于有时间回顾、整理一下这半年的所感所想。对我来说,这是一次难得的体验,也是一次与“极客时间”的深度沟通。
## 专栏是写给谁的?
和极客时间的编辑谈妥主题之后他们首先要求我基于约定的主题写一个36节至50节的大纲之后会以每周三篇的频率文字加音频的方式发布。每篇文章的体量要求在3000字左右录成音频大约就是10分钟。
我本来觉得写这么一个专栏根本就不是个事儿。毕竟咱也是在IT圈摸爬滚打了许多年的“老司机”干货积累得也不少。只要是熟悉的领域不用准备聊个把小时都没啥问题。况且我原来还写过书、写过博客、写过公众号。所以我对自己文字方面的能力很有自信。
至于语言方面咱常年出入各大技术论坛什么场子没趟过。一个两天的线下培训咱都能扛过来。每篇10分钟总共36篇那不才是6个小时嘛肯定没问题。
但是,写了之后我发现,**自己会是一回事儿,能讲给别人是另一回事儿**,而能讲给“看不见的陌生人”听,是这世上最难的事儿。
我知道,很多技术人都有这样一个“毛病”,就是觉得掌握技术本身是最重要的,其他什么产品、市场、销售,都没技术含量。这种思维导致很多技术比较牛的人会以自我为中心,仅站在自己的角度思考问题。所以,**常常是自己讲得很爽,完全不管听的人是不是真的接受了**。写专栏的时候,这绝对是个大忌。
除此之外,这种思维对职业发展的影响也是很大的。单打独斗,一个人搞定一个软件的时代已经过去了。学会和别人合作,才是现代社会的生存法则,而良好的合作源于沟通。
但沟通不易,高质量的沟通更难。面对的人越多,沟通的难度就越大。因为每个人的背景、知识、基础都不同,想听的内容肯定更是千差万别。况且不是每个人都能准确地表达出自己的需求,加之需求的表达、转述都会因表达方式和传递媒介而发生变形,这样一来,接收信息的一方自然很难把握真实的需求。
写专栏的时候,“极客时间”的编辑不断地告诉我,我的受众只有一个人,就是“你”。我心想,这个简单啊,因为面对的人最少嘛!可是,事实上证明,我又“错”了。
这个抽象的“你”看起来只有一个其实却是看不到、摸不着的许许多多的人。所以这个其实是最难的。协议专栏上线10天就有10000多人订阅而订阅专栏的用户里只有少数人会留言。所以对于很多读者的真实情况我都无从得知你可能每天都听但是没有留言的习惯也可能买了之后觉得我讲得不好骂一句“这钱白花了”然后再也不听。
所以,**如何把控内容,写给广大未知受众,是我写这个专栏面临的最大挑战**。而这里面,文章的深度、广度,音频的语调、语气,每一个细节都非常重要。
## 专栏文章是怎么写的?
经过大纲和前几篇文稿的打磨,我对“极客时间”和专栏创作也有了更深的了解。我私下和很多人交流过一个问题,那就是,咱们平时聊一个话题的时候,有很多话可以说。但是真正去写一篇文章的时候,好像又没有什么可讲的,尤其是那些看起来很基础的内容。
我在写专栏的过程中,仔细思考过这样一个问题:很多人对某一领域或者行业研究得很深入,也有自己长期的实践,但是**有多少人可以<strong><strong>从感性认识上升到理性**</strong>认知**<strong>的**</strong>高度呢?</strong>
现在技术变化这么快,我们每个人的精力都是有限的,不少人学习新知识的方式就是看看书,看看博客、技术文章,或者听同事讲一下,了解个大概就觉得可以直接上手去做了。我也是这样的。可是一旦到写专栏的时候,**基础掌握不扎实的问题一下子全都“暴露”出来了。**
落到文字上的东西一定要是严谨的。所以在写到很多细节的时候我查了大量的资料找到权威的书籍、官方文档、RFC里面的具体描述有时候我甚至要做个实验或者打开代码再看一下才放心下笔。
尽管我对自己写文章有很多“完美倾向”的要求,但是这其实依旧是站在我自己的角度去看的。读者究竟想要看什么内容呢?
太深入了,看不懂;太浅显了,也不行。太长了,负担太重;太短了,没有干货;同时,每篇文字还要自成一体,所有文章要是一个完整的知识体系。我发现,原来我不仅是对知识的了解没那么全面、具体,对用户阅读和倾听场景也没有过多的考虑。
除了写文字,专栏还要录音频,所以为了方便“听”,文章内不能放大量代码、实验。如果很多人在通勤路上听,而我把一张图片讲得天花乱坠,听的人却根本看不到,那肯定是不行的,所以写文章的时候,我还要把故事性、画面感都考虑进去,尽量详尽而不啰嗦。
把这些限制条件加起来之后,我发现,写专栏这件事儿,真的太不容易了。每篇文章看起来内容不多,但是都是费了很多心思的,这也是为什么很多老师说,写完专栏就像是过了火焰山。
## 专栏音频是怎么录的?
说完写文章,我来说说录音频。我平时听播音员说话,感觉非常轻松,所以当时我毫不犹豫地就说,“我要自己录”。但是在录开篇词的时候,我就觉得这完全不是我想的那么回事啊!
专栏的文章在录音的时候一定会有个“音频稿”,我一开始很不理解,我对着发布的稿件直接讲就好了啊,为什么还要特意准备一个供录音频的稿件啊?
我在没有音频稿的情况下,自己试着“发挥”了几次,结果,我发现我的嘴会“吃”字,会反复讲一个内容而且表达不清,但是自己却经常毫无察觉,还会自己讲着讲着就收不住等等。
咱们平时说话的时候,会有很多口头语和重复的词语。面对面交流的时候,我们为什么没有注意这个问题呢?因为我们会更注重对方的表情、手势,但是一旦录成音频,这些“啰嗦”的地方就特别明显。
而有了音频稿之后,整个过程就严谨很多。如果哪句话说错了,看着稿件再说一遍就好了。而且,你会发现录音的时间大大缩短了,原来需要用十分钟,现在五分钟就可以很精炼地讲出来了。
有了稿子,那我是不是对着念就好了?这不是很容易吗?不,我又遇到了新的难题。
录音频的时候,我常常一个人关在密闭的房间里,对着显示器“读”,这和公共演讲肯定是不一样的。加上因为有写好的音频稿,我常常感觉束手束脚,找不到演讲那种有激情的感觉,很容易就变成了念课文。
为了同时满足自然和严谨,一方面我会先熟记“台词”;另一方面,每次录的时候,我都假想对面有个人,我在对着他缓缓地讲出来。讲到某些地方,我还会假想他对这个知识点是不是有疑问,这样就更加有互动感。
录音频这件事对我的改变非常大。我说话、演讲的时候变得更加严谨了。我会下意识地不去重复已经说过的话。一旦想重复,也闭嘴不发音,等想好了下一句再说。后面,我的录音也越来越顺利,一开始要录五六遍才能成功,后面基本一遍就过了。
创作专栏的过程还有许多事情,都是我很难得的记忆。我很佩服“极客时间”的编辑做专栏时的专业和认真。我也很庆幸,我没有固执地按照自己认为正确的方向和方式来做,而是尊重了他们的专业。很显然,**他们没有我懂技术,但是他们比我<strong><strong>更**</strong>懂“你”。</strong>
专栏结束后,我回看这半年的准备和努力,我发现,**无论对自己的领域多么熟悉,写这个专栏都让我又上升了一个新高度**。
我知道很多技术人都喜欢分享,而写文章又是最容易实现的方式。写文章的时候,可以检验你对基础知识的掌握是否扎实,是不是有换位思考能力,能不能从感性认识上升到理性认知。
除此之外,我觉得最重要的一点是,在创作专栏文章的过程中,我学到了很多技术之外的东西,比如换位思考能力和细节把控的能力。
我在这里记下与“极客时间”的相识和相知。希望看到更多人在极客时间,分享自己的知识和见解。

View File

@@ -0,0 +1,86 @@
你好,我是刘超。
“趣谈网络协议”专栏现在已经全部更新完毕。这里有一份「食用指南」,可以帮你找到学习本专栏的最佳姿势。
在这份指南中,我为你整理了**专栏的所有学习资料**,并告诉你**如何更高效地使用这些资料**,从而帮助你消化吸收,以期获得更好的学习效果。
不管你是刚刚打开这个专栏,还是进入温故的阶段,我的这份指南,都可以帮你更上一个台阶。一起加油吧!
## 1. 能力测试
我从常用的网络协议中精心筛选了核心知识点编成了10道题。这里面的题目和答案都是我精心设计的。希望你一定要先拿出纸笔认真思考记录下自己的答案之后再和文末的详细解析进行对照。
刚刚打开这个专栏的你,可以据此寻找自己的薄弱点,**对症下药**;已经学习了一段时间的你,可以检测学习成果,**查漏补缺**。
点击查看:[网络协议能力测试题](https://time.geekbang.org/column/article/14384)
## 2. 答疑解惑
每篇文章后,我都会留两个思考题,其中第一个问题意在启发你的思考,是对本节内容的延伸学习;第二个问题是为引出下一节,下一节的内容其实就是答案(所以我就不单独解答啦)。
我希望你能够好好地利用这些思考题,毕竟所有的“知”,只有经过了自己的思考之后,才能成为“识”。
如果你是刚刚加入学习,你可以继续在思考题后的“留言区”写下你的答案,学习过程中遇到的问题和思考也欢迎多多分享,我依然会在这里回复你的留言,和你一起讨论。
我知道你肯定也很好奇我对这些问题的思考是怎样的,因此,我针对每一节课后的第一道思考题及留言区比较有代表性的、有深度的问题,特意写了一系列答疑文章。
我再强调一遍,对于这一系列的答疑文章,你一定要在自己进行深度思考之后,再来看文章对比答案,这样可以更有效地拓展你的知识边界。
点击查看:
[第一期第1讲至第2讲答疑解惑合辑](https://time.geekbang.org/column/article/13520)
[第二期第3讲至第6讲答疑解惑合辑](https://time.geekbang.org/column/article/13847)
[第三期第7讲至第13讲答疑解惑合辑](https://time.geekbang.org/column/article/14028)
[第四期第14讲至第21讲答疑解惑合辑](https://time.geekbang.org/column/article/14194)
[第五期第22讲至第36讲答疑解惑合辑](https://time.geekbang.org/column/article/14381)
## 3. 知识串讲
在学习完前面36讲的内容之后我详细讲解了一个“下单”的过程。我把这个过程分为十个阶段从云平台中搭建一个电商开始到BGP路由广播再到DNS域名解析从客户看商品图片到最终下单的整个过程每个步骤我都画了详细的分解图。
你可以用这个过程,串起我们讲过的所有网络协议,还原真实的使用场景,学以致用。我相信,学完前面的详细内容之后,再来看这个串讲内容,你对网络协议一定会有一个全面、深入的把握。
点击查看:
[知识串讲(上篇)](https://time.geekbang.org/column/article/12991)
[知识串讲(中篇)](https://time.geekbang.org/column/article/12996)
[知识串讲(下篇)](https://time.geekbang.org/column/article/13099)
## 4. 知识图谱
专栏中最精华的内容,我都整理在这张图上了。
点击查看:[网络协议知识图谱](https://time.geekbang.org/column/article/14905)
## 5. 实验环境
纸上得来终觉浅。网络是一门实验性很强的学科,我在写专栏的过程中也深深体会到了。有时候,遇到疑问,我常常会拿一个现实的环境,上手操作一下,抓个包看看,这样心里就会有定论。
网络方面最权威的书籍《TCP/IP详解》[TCP/IP illustrated](https://book.douban.com/subject/1741925/)的作者斯蒂文森W. Richard Stevens也是经过无数次实验才完成这本巨著。
因此,我在这本书中的实验基础上,带你搭建一个实验环境,希望你能够上手操作一下学过的知识。毕竟,只有经过你自己动手和思考产生的内容,才是真正属于你的知识。
点击查看我搭建实验环境时候的具体操作,希望给你的思维晋升指路:[《搭建一个网络实验环境:授人以鱼不如授人以渔》](https://time.geekbang.org/column/article/13124)
## 6. 专栏音频
我在这里想特别提一下专栏音频。我的每篇专栏文章都包含了很多图片,为了帮助你更好地理解文章内容,我在录音的时候,常常会对图片做一些补充解释和说明,所以音频和文字稿并非完全一一对应。不知道你具体的学习习惯是怎样的,我建议你除了阅读文字以外,一定要听一下音频,可以利用“**倍速播放**”的功能,还可以自由把控播放速度,更高效地学习。
## 7. 记录,高效;分享,快乐
我们专栏还有不少功能,提醒你好好利用起来,成为高效的学习者。
比如,在学习的过程中,遇到自己不懂的地方,或者是有深刻感受的地方,一定要及时利用“**划线笔记**”的功能,记录下自己当时的想法。这样在过程中点滴积累,等学完后,还可以回过头来再过一遍。如果有可能,你可以把自己的这些思考梳理成文。相信我,这样做,你的提升速度会快到让自己意外。
再比如“**请朋友读**”功能。如果你觉得某篇内容对自己很有帮助,不妨把它推荐给身边有同样需求的朋友,这一个动作或许就能帮他解决一个手边的问题。最重要的是,通过这些分享,你会找到那些和你一样热爱学习的伙伴,一起学习更快乐。
最后,还有一个小小的彩蛋。我把自己这半年写专栏的经历,写成了一篇文章。我是如何写专栏中每一篇文章的?每一篇音频又是如何录出来的?创作专栏给我带来了哪些改变?带你走进“**极客时间万人专栏**”背后的创作故事。
点击查看:[我是如何创作“趣谈网络协议”专栏的?](https://time.geekbang.org/column/article/17846)

View File

@@ -0,0 +1,209 @@
<audio id="audio" title="协议专栏特别福利 | 答疑解惑第一期" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/5b/757ec1a4f577ec091171aeffda08885b.mp3"></audio>
你好,我是刘超。
首先感谢大家关注并在留言区写下近3000条留言。留言太多没有及时回复一是每周写三篇文章压力真的挺大的。为了保质保量地产出晚上和周末的时间基本上都搭进去了。二是很多人的留言非常有深度水平很高提的问题一两句话解释不清楚。
每一节结尾我基本都会留两个思考题,其中第一个问题是启发思考的,是对本节内容的延伸学习;第二个问题是为了引出下一节,下一节的内容其实就是答案。
所以我会回答一下每一节的第一个问题,并列出第一个同我的思路最相近的同学,并对留言中比较有代表性的问题,做一个统一的回答,顺便也实现之前要送知识图谱和奖励礼券的承诺。
当然,这并不能说明我的回答就是一定是正确的或者全面的,有很多同学的留言有非常大的信息量,甚至更广的思路,也对这些同学表示感谢。还有些同学指出了我的错误,也感谢你们。
### [《第1讲 | 为什么要学习网络协议?》](https://time.geekbang.org/column/article/7581)
### 课后思考题
当网络包到达一个城关的时候,可以通过路由表得到下一个城关的 IP 地址,直接通过 IP地址找就可以了为什么还要通过本地的MAC地址呢
<img src="https://static001.geekbang.org/resource/image/16/69/16e0b76fe90ce10d8e5c16cad0010e69.png" alt="">
徐良红同学说的比较接近。在网络包里有源IP地址和目标IP地址、源MAC地址和目标MAC地址。从路由表中取得下一跳的IP地址后应该把这个地址放在哪里呢如果放在目标IP地址里面到了城关谁知道最终的目标在哪里呢所以要用MAC地址。
所谓的下一跳看起来是IP地址其实是要通过ARP得到MAC地址将下一跳的MAC地址放在目标MAC地址里面。
### 留言问题
1.MAC地址可以修改吗
<img src="https://static001.geekbang.org/resource/image/b3/1b/b3591518b266a3558f9dd61061c6271b.png" alt="">
<img src="https://static001.geekbang.org/resource/image/3a/5d/3abde3f5607b0278cc87ccfadb88875d.png" alt="">
我查了一下MACMedia Access Control介质访问控制地址也叫硬件地址长度是48比特6字节由16进制的数字组成分为前24位和后24位。
前24位叫作**组织唯一标志符**Organizationally Unique IdentifierOUI是由IEEE的注册管理机构给不同厂家分配的代码用于区分不同的厂家。后24位是厂家自己分配的称为**扩展标识符**。同一个厂家生产的网卡中MAC地址后24位是不同的。
也就是说MAC本来设计为唯一性的但是后来设备越来越多而且还有虚拟化的设备和网卡有很多工具可以修改就很难保证不冲突了。但是至少应该保持一个局域网内是唯一的。
MAC的设计使得即便不能保证绝对唯一但是能保证一个局域网内出现冲突的概率很小。这样一台机器启动的时候就能够在没有IP地址的情况下先用MAC地址进行通信获得IP地址。
好在MAC地址是工作在一个局域网中的因而即便出现了冲突网络工程师也能够在自己的范围内很快定位并解决这个问题。这就像我们生成UUID或者哈希值大部分情况下是不会冲突的但是如果碰巧出现冲突了采取一定的机制解决冲突就好。
2.TCP重试有没有可能导致重复下单
<img src="https://static001.geekbang.org/resource/image/ce/71/ced92fc92921a47918704f1657fb2771.png" alt="">
答案是不会的。这个在[TCP](https://time.geekbang.org/column/article/8975)那一节有详细的讲解。因为TCP层收到了重复包之后TCP层自己会进行去重发给应用层、HTTP层。还是一个唯一的下单请求所以不会重复下单。
那什么时候会导致重复下单呢因为网络原因或者服务端错误导致TCP连接断了这样会重新发送应用层的请求也即HTTP的请求会重新发送一遍。
如果服务端设计的是无状态的,它记不住上一次已经发送了一次请求。如果处理不好,就会导致重复下单,这就需要服务端除了实现无状态,还需要根据传过来的订单号实现幂等,同一个订单只处理一次。
还会有的现象是请求被黑客拦截发送多次这在HTTPS层可以有很多种机制例如通过 Timestamp和Nonce随机数联合起来然后做一个不可逆的签名来保证。
3.TCP报平安的包是原路返回吗
<img src="https://static001.geekbang.org/resource/image/36/ac/361fee74d932bac74f0b26bb280bf2ac.png" alt="">
谢谢语鬼同学的指正。这里的比喻不够严谨,容易让读者产生误会,这里的原路返回的意思是原样返回,也就是返回也是这个过程,不一定是完全一样的路径。
4.IP地址和MAC地址的关系
<img src="https://static001.geekbang.org/resource/image/9c/e3/9c359d2c20ab3f77f5a7e87dcb6fd7e3.png" alt="">
芒果同学的理解非常准确,讲[IP和MAC的关系](https://time.geekbang.org/column/article/7772)的时候说了这个问题。IP是有远程定位功能的MAC是没有远程定位功能的只能通过本地ARP的方式找到。
我个人认为即便有了IPv6也不会改变当前的网络分层模式还是IP层解决远程定位问题只不过改成IPv6了到了本地还是通过MAC。
5.如果最后一跳的时候IP改变了怎么办
<img src="https://static001.geekbang.org/resource/image/08/c1/08029e8c3dce6413558c4819b380c5c1.png" alt="">
对于IP层来讲当包到达最后一跳的时候原来的IP不存在了。比如网线拔掉了或者服务器直接宕机了则ARP就找不到了所以这个包就会发送失败了。对于IP层的工作就结束了。
但是IP层之上还有TCP层TCP会重试的包还是会重新发送但是如果服务器没有启动起来超过一定的次数最终放弃。
如果服务器重启了IP还是原来的IP地址这个时候TCP重新发送的一个包的时候ARP是能够得到这个地址的因而会发到这台机器上来但是机器上面没有启动服务端监听那个端口于是会发送ICMP端口不可达。
如果服务器重启了服务端也重新启动了也在监听那个端口了这个时候TCP的服务端由于是新的Sequence Number根本对不上说明不是原来的连接会发送RST。
那有没有可能有特殊的场景Sequence Number也能对的上呢按照Sequence Number的生成算法是不可能的。
但是有一个非常特殊的方式就是虚拟机的热迁移从一台物理机迁移到另外一台物理机IP不变MAC不变内存也拷贝过去Sequence Number在内存里面也保持住了在迁移的过程中会丢失一两个包但是从TCP来看最终还是能够连接成功的。
6.TCP层报平安怎么确认浏览器收到呢
<img src="https://static001.geekbang.org/resource/image/f3/48/f39ceca92d67db24b42ef9d31f9bff48.png" alt="">
TCP报平安只能保证TCP层能够收到不保证浏览器能够收到。但是可以想象如果浏览器是你写的一个程序你也是通过socket编程写的你是通过socket建立一个TCP的连接然后从这个连接里面读取数据读取的数据就是TCP层确认收到的。
这个读取的动作是本地系统调用大部分情况下不会失败的。如果读取失败呢当然本地会报错你的socket读取函数会返回错误如果你是浏览器程序的实现者你有两种选择一个是将错误报告给用户另一个是重新发送一次请求获取结果显示给用户。
7.ARP协议属于哪一层
<img src="https://static001.geekbang.org/resource/image/e7/9f/e77d85e7260df7611b90a45b7e77ce9f.png" alt="">
ARP属于哪个层一直是有争议的。比如《TCP/IP详解》把它放在了二层和三层之间但是既然是协议只要大家都遵守相同的格式、流程就可以了在实际应用的时候不会有歧义的唯一有歧义的是参加各种考试让你做选择题ARP属于哪一层平时工作中咱不用纠结这个。
## [《第2讲 | 网络分层的真实含义是什么?》](https://time.geekbang.org/column/article/7724)
### 课后思考题
如果你也觉得总经理和员工的比喻不恰当,你有更恰当的比喻吗?
<img src="https://static001.geekbang.org/resource/image/21/27/21cd2dba241413bcfc5bb74fc8dd2527.png" alt="">
<img src="https://static001.geekbang.org/resource/image/3b/3e/3b69f685c893bfd6746ef03c395e8e3e.png" alt="">
我觉得,寄快递和寄信这两个比喻都挺好的。关键是有了封装和解封装的过程。有的同学举了爬楼,或者公司各层之间的沟通,都无法体现封装和解封装的过程。
### 留言问题
1.为什么要分层?
<img src="https://static001.geekbang.org/resource/image/4d/92/4d6c7f391ca5a55fdde3dbf62d69ab92.png" alt="">
是的,仅仅用复杂性来解释分层,太过牵强了。
<img src="https://static001.geekbang.org/resource/image/85/78/856b8040cd0a9f2206ad6f27ad3e9078.png" alt="">
其实这是一个架构设计的通用问题,不仅仅是网络协议的问题。一旦涉及到复杂的逻辑,或者软件需求需要经常变动,一般都会通过分层来解决问题。
假如我们将所有的代码都写在一起但是产品经理突然想调整一下界面这背后的业务逻辑变不变那要不要一起修改呢所以会拆成两层把UI层从业务逻辑中分离出来调用API来进行组合。API不变仅仅界面变是不是就不影响后台的代码了
为什么要把一些原子的API放在基础服务层呢将数据库、缓存、搜索引擎等屏蔽到基础服务层以下基础服务层之上的组合逻辑层、API层都只能调用基础服务层的API不能直接访问数据库。
比如我们要将Oracle切换成MySQL。MySQL有一个库分库分表成为4个库。难道所有的代码都要修改吗当然只要把基础服务层屏蔽提供一致的接口就可以了。
网络协议也是这样的。有的想基于TCP自己不操心就能够保证到达有的想自己实现可靠通信不基于TCP而使用UDP。一旦分了层就好办了定制化后要依赖于下一层的接口只要实现自己的逻辑就可以了。如果TCP的实现将所有的逻辑耦合在了整个七层不用TCP的可靠传输机制都没有办法。
2.层级之间真实的调用方式是什么样的?
<img src="https://static001.geekbang.org/resource/image/95/57/95510f25eb463c4302fb58bd518a8957.png" alt="">
如果文中是一个逻辑图这个问题其实已经到实现层面上来了需要看TCP/IP的协议栈代码了。这里首先推荐一本书《深入理解Linux网络技术内幕》。
其实下层的协议知道上层协议的,因为在每一层的包头里面,都会有上一层是哪个协议的标识,所以不是一个回调函数,每一层的处理函数都会在操作系统启动的时候,注册到内核的一个数据结构里面,但是到某一层的时候,是通过判断到底是哪一层的哪一个协议,然后去找相应的处理函数去调用。
调用的大致过程我这里再讲一下。由于TCP比较复杂我们以UDP为例子其实发送的包就是一个sk_buff结构。这个在[Socket](https://time.geekbang.org/column/article/9293)那一节讲过。
```
int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
```
接着UDP层会调用IP层的函数。
```
int ip_send_skb(struct net *net, struct sk_buff *skb)
```
然后IP层通过路由判断最终将包发给下一层。
```
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
```
发送的时候要进行ARP。如果有MAC则调用二层的函数neigh其实就是邻居系统是二层的意思。
```
int neigh_output(struct neighbour *n, struct sk_buff *skb)
```
接收的时候,会调用这里的接收函数。
```
int netif_receive_skb(struct sk_buff *skb)
```
这个函数会根据是ARP或者IP等选择调用不同的函数。如果是IP协议的话就调用这里的函数。
```
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
```
这里也有路由判断。如果是本地的,则继续往上提交这个结构。
```
int ip_local_deliver(struct sk_buff *skb)
```
接着还是根据IP头里面的协议号来判断是什么协议从而调用什么函数。下面这个是对UDP的调用。
```
int udp_rcv(struct sk_buff *skb)
```
3.什么情况下会有下层没上层?
<img src="https://static001.geekbang.org/resource/image/f6/c3/f6414085ec89ba0dfae79436aad2b2c3.png" alt="">
有时候我们自己写应用的时候不一定是直接调用应用层协议的接口例如HTTP等而是自己写Socket编程来约定应用层的协议。再如ping也是一个应用但是它没有用传输层的协议而是用了ICMP的协议。
最后感谢留言次数前15名的同学谢谢你们持之以恒的学习相信你们一定有自己的收获。统计数据截止到2018年8月8日
<img src="https://static001.geekbang.org/resource/image/ca/91/caeec16c4e959e250da1db6c6543b791.jpg" alt="">
同时感谢第1讲、第2讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
欢迎你继续提问!
<img src="https://static001.geekbang.org/resource/image/00/5b/0057c2d7c99924366c94c5ed58e3385b.jpg" alt="">

View File

@@ -0,0 +1,368 @@
<audio id="audio" title="协议专栏特别福利 | 答疑解惑第三期" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6a/17/6a6883051cf676be036547a1f2b47f17.mp3"></audio>
你好,我是刘超。
第三期答疑涵盖第7讲至第13讲的内容。我依旧对课后思考题和留言中比较有代表性的问题作出回答。你可以点击文章名回到对应的章节复习也可以继续在留言区写下你的疑问我会持续不断地解答。希望对你有帮助。
## [《](https://time.geekbang.org/column/article/8445)[第7讲 | ICMP与ping投石问路的侦察兵](https://time.geekbang.org/column/article/8445)[》](https://time.geekbang.org/column/article/8445)
### 课后思考题
当发送的报文出问题的时候会发送一个ICMP的差错报文来报告错误但是如果 ICMP 的差错报文也出问题了呢?
我总结了一下不会导致产生ICMP差错报文的有
<li>
ICMP差错报文ICMP查询报文可能会产生ICMP差错报文
</li>
<li>
目的地址是广播地址或多播地址的IP数据报
</li>
<li>
作为链路层广播的数据报;
</li>
<li>
不是IP分片的第一片
</li>
<li>
源地址不是单个主机的数据报。这就是说,源地址不能为零地址、环回地址、广播地址或多播地址。
</li>
### 留言问题
1.ping使用的是什么网络编程接口
<img src="https://static001.geekbang.org/resource/image/3b/e6/3b2b3f4abaed8a485e8933efbcc304e6.png" alt="">
咱们使用的网络编程接口是Socket对于ping来讲使用的是ICMP创建Socket如下
```
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
```
SOCK_RAW就是基于IP层协议建立通信机制。
如果是TCP则建立下面的Socket
```
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
```
如果是UDP则建立下面的Socket
```
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
```
2.ICMP差错报文是谁发送的呢
我看留言里有很多人对这个问题有疑惑。ICMP包是由内核返回的在内核中有一个函数用于发送ICMP的包。
```
void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info);
```
例如,目标不可达,会调用下面的函数。
```
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
```
当IP大小超过MTU的时候发送需要分片的ICMP。
```
if (ip_exceeds_mtu(skb, mtu)) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
goto drop;
}
```
## [《第8讲 | 世界这么大,我想出网关:欧洲十国游与玄奘西行》](https://time.geekbang.org/column/article/8590)
### 课后思考题
当在你家里要访问 163 网站的时候,你的包需要 NAT 成为公网 IP返回的包又要 NAT 成你的私有 IP返回包怎么知道这是你的请求呢它怎么能这么智能地 NAT 成了你的 IP 而非别人的 IP 呢?
这是个比较复杂的事情。在讲云中网络安全里的iptables时我们讲过conntrack功能它记录了SNAT一去一回的对应关系。
如果编译内核时开启了连接跟踪选项那么Linux系统就会为它收到的每个数据包维持一个连接状态用于记录这条数据连接的状态。
<img src="https://static001.geekbang.org/resource/image/a9/fc/a924ccda5d54bcad6f67fdebe0a6c1fc.jpg" alt="">
根据咱们学过的Netfilter的流程图我们知道网络包有三种路径
<li>
发给我的从PREROUTING到INPUT我就接收了
</li>
<li>
我发给别人的从OUTPUT到POSTROUTING就发出去的
</li>
<li>
从我这里经过的从PREROUTING到FORWARD到POSTROUTING。
</li>
如果要跟踪一个网络包,对于每一种路径,都需要设置两个记录点,相当于打两次卡,这样内核才知道这个包的状态。
对于这三种路径,打卡的点是这样设置的:
<li>
发给我的在PREROUTING调用ipv4_conntrack_in创建连接跟踪记录在INPUT调用ipv4_confirm将这个连接跟踪记录挂在内核的连接跟踪表里面。为什么不一开始就挂在内核的连接跟踪表里面呢因为有filter表一旦把包过滤了也就是丢弃了那根本没必要记录这个连接了。
</li>
<li>
我发给别人的在OUTPUT调用ipv4_conntrack_local创建连接跟踪记录在POSTROUTING调用ipv4_confirm将这个连接跟踪记录挂在内核的连接跟踪表里面。
</li>
<li>
从我这里经过的在PREROUTING调用ipv4_conntrack_in创建连接跟踪记录在POSTROUTING调用ipv4_confirm将这个连接跟踪记录挂在内核的连接跟踪表里面。
</li>
网关主要做转发这里主要说的是NAT网关因而我们重点来看“从我这里经过的”这种场景再加上要NAT因而将NAT的过程融入到连接跟踪的过程中来
<li>
如果是PREROUTING的时候先调用ipv4_conntrack_in创建连接跟踪记录
</li>
<li>
如果是PREROUTING的时候有NAT规则则调用nf_nat_ipv4_in进行地址转换
</li>
<li>
如果是POSTROUTING的时候有NAT规则则调用nf_nat_ipv4_out进行地址转换
</li>
<li>
如果是POSTROUTING的时候调用ipv4_confirm将这个连接跟踪记录挂在内核的连接跟踪表里面。
</li>
接下来,我们来看,在这个过程中涉及到的数据结构:连接跟踪记录、连接跟踪表。
在前面讲网络包处理的时候我们说过每个网络包都是一个struct sk_buff它有一个成员变量_nfct指向一个连接跟踪记录struct nf_conn。当然当一个网络包刚刚进来的时候是不会指向这么一个结构的但是这个网络包肯定属于某个连接因而会去连接跟踪表里面去查找之后赋值给sk_buff的这个成员变量。没找到的话就说明是一个新的连接然后会重新创建一个。
连接跟踪记录里面有几个重要的东西:
<li>
nf_conntrack其实才是_nfct变量指向的地址但是没有关系学过C++的话应该明白对于结构体来讲nf_conn和nf_conntrack的起始地址是一样的
</li>
<li>
tuplehash虽然是数组但是里面只有两个IP_CT_DIR_ORIGINAL为下标0表示连接的发起方向IP_CT_DIR_REPLY为下标1表示连接的回复方向。
</li>
```
struct nf_conn {
......
struct nf_conntrack ct_general;
......
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
......
unsigned long status;
......
}
```
在这里面最重要的是nf_conntrack_tuple_hash的数组。nf_conn是这个网络包对应的一去一回的连接追踪记录但是这个记录是会放在一个统一的连接追踪表里面的。
连接跟踪表nf_conntrack_hash是一个数组数组中的每一项都是一个双向链表的头每一项后面都挂着一个双向链表链表中的每一项都是这个结构。
这个结构的第一项是链表的链nf_conntrack_tuple是用来标识是否同一个连接。
从上面可以看出来,连接跟踪表是一个典型的链式哈希表的实现。
每当有一个网络包来了的时候会将网络包中sk_buff中的数据提取出来形成nf_conntrack_tuple并根据里面的内容计算哈希值。然后需要在哈希表中查找如果找到则说明这个连接出现过如果没找到则生成一个插入哈希表。
通过nf_conntrack_tuple里面的内容可以唯一地标识一个连接
<li>
src包含源IP地址如果是TCP或者UDP包含源端口如果是ICMP包含的是ID
</li>
<li>
dst包含目标IP地址如果是TCP或者UDP包含目标端口如果是ICMP包含的是type, code。
</li>
有了这些数据结构,我们接下来看这一去一回的过程。
当一个包发出去的时候到达这个NAT网关的时候首先经过PREROUTING的时候先调用ipv4_conntrack_in。这个时候进来的包sk_buff为 {源IP客户端IP源端口客户端port目标IP服务端IP目标端口服务端port}将这个转换为nf_conntrack_tuple然后经过哈希运算在连接跟踪表里面查找发现没有说明这是一个新的连接。
于是创建一个新的连接跟踪记录nf_conn这里面有两个nf_conntrack_tuple_hash
<li>
一去:{源IP客户端IP源端口客户端port目标IP服务端IP目标端口服务端port}
</li>
<li>
一回:{源IP服务端IP源端口服务端port目标IP客户端IP目标端口客户端port}。
</li>
接下来经过FORWARD过程假设包没有被filter掉于是要转发出去进入POSTROUTING的过程有NAT规则则调用nf_nat_ipv4_out进行地址转换。这个时候源地址要变成NAT网关的IP地址对于masquerade来讲会自动选择一个公网IP地址和一个随机端口。
为了让包回来的时候能找到连接跟踪记录需要修改两个nf_conntrack_tuple_hash中回来的那一项为{源IP服务端IP源端口服务端port目标IPNAT网关IP目标端口随机端口}。
接下来要将网络包真正发出去的时候除了要修改包里面的源IP和源端口之外还需要将刚才的一去一回的两个nf_conntrack_tuple_hash放入连接跟踪表这个哈希表中。
当网络包到达服务端然后回复一个包的时候这个包sk_buff为{源IP服务端IP源端口服务端port目标IPNAT网关IP目标端口随机端口}。
将这个转换为nf_conntrack_tuple后进行哈希运算在连接跟踪表里面查找是能找到相应的记录的找到nf_conntrack_tuple_hash之后Linux会提供一个函数。
```
static inline struct nf_conn *
nf_ct_tuplehash_to_ctrack(const struct nf_conntrack_tuple_hash *hash)
{
return container_of(hash, struct nf_conn,
tuplehash[hash-&gt;tuple.dst.dir]);
}
```
可以通过nf_conntrack_tuple_hash找到外面的连接跟踪记录nf_conn通过这个可以找到来方向的那个nf_conntrack_tuple_hash{源IP客户端IP源端口客户端port目标IP服务端IP目标端口服务端port}这样就能够找到客户端的IP和端口从而可以NAT回去。
### 留言问题
1.NAT能建立多少连接
<img src="https://static001.geekbang.org/resource/image/3d/d6/3d9249834f730926c2b2f350aba6e1d6.png" alt="">
SNAT多用于内网访问外网的场景鉴于conntrack是由{源IP源端口目标IP目标端口}hash后确定的。
如果内网机器很多但是访问的是不同的外网也即目标IP和目标端口很多这样内网可承载的数量就非常大可不止65535个。
但是如果内网所有的机器都一定要访问同一个目标IP和目标端口这样源IP如果只有一个这样的情况下才受65535的端口数目限制根据原理一种方法就是多个源IP另外的方法就是多个NAT网关来分摊不同的内网机器访问。
如果你使用的是公有云65535台机器应该放在一个VPC里面可以放在多个VPC里面每个VPC都可以有自己的NAT网关。
<img src="https://static001.geekbang.org/resource/image/d0/8d/d08e3a727681751037f715c3f5bd398d.png" alt="">
其实SNAT的场景是内网访问外网存在端口数量的问题也是所有的机器都访问一个目标地址的情况。
如果是微信这种场景应该是服务端在数据中心内部无论多少长连接作为服务端监听的都是少数几个端口是DNAT的场景是没有端口数目问题的只有一台服务器能不能维护这么多连接因而在NAT网关后面部署多个nginx来分摊连接即可。
2.公网IP和私网IP需要一一绑定吗
<img src="https://static001.geekbang.org/resource/image/2d/f1/2d134e61e8cc945c71969be7391b3ff1.png" alt="">
公网IP是有限的如果使用公有云需要花钱去买。但是不是每一个虚拟机都要有一个公网IP的只有需要对外提供服务的机器也即接入层的那些nginx需要公网IP没有公网IP使用SNAT大家共享SNAT网关的公网IP地址也是能够访问的外网的。
我看留言中的困惑点都在于,要区分内主动发起访问外,还是外主动发起访问内,是访问同一个服务端,还是访问一大批服务端。这里就很明白了。
## [《第9讲 | 路由协议:西出网关无故人,敢问路在何方》](https://time.geekbang.org/column/article/8729)
### 课后思考题
路由协议要在路由器之间交换信息,这些信息的交换还需要走路由吗?不是死锁了吗?
<img src="https://static001.geekbang.org/resource/image/b5/da/b5834f4f10c51cb8c91e570bf83f7eda.png" alt="">
OSPF是直接基于IP协议发送的而且OSPF的包都是发给邻居的也即只有一跳不会中间经过路由设备。BGP是基于TCP协议的在BGP peer之间交换信息。
### 留言问题
1.多线BGP机房是怎么回事儿
<img src="https://static001.geekbang.org/resource/image/18/cd/18040a74506276b23e672d2d818d37cd.png" alt="">
BGP主要用于互联网AS自治系统之间的互联BGP的最主要功能在于**控制路由的传播**和**选择最好的路由**。各大运营商都具有AS号全国各大网络运营商多数都是通过BGP协议与自身的AS来实现多线互联的。
使用此方案来实现多线路互联IDC需要在CNNIC中国互联网信息中心或APNIC亚太网络信息中心申请自己的IP地址段和AS号然后通过BGP协议将此段IP地址广播到其它的网络运营商的网络中。
使用BGP协议互联后网络运营商的所有骨干路由设备将会判断到IDC机房IP段的最佳路由以保证不同网络运营商用户的高速访问。
## [《第10讲 | UDP协议因性善而简单难免碰到“城会玩”》](https://time.geekbang.org/column/article/8924)
### 课后思考题
都说 TCP 是面向连接的,在计算机看来,怎么样才算一个连接呢?
赵强强在留言中回答的是正确的。这是TCP的两端为了维护连接所保持的数据结构。
<img src="https://static001.geekbang.org/resource/image/3c/e1/3cba74151564c129057b2cd246a332e1.png" alt="">
<img src="https://static001.geekbang.org/resource/image/95/e0/9507374c04e0908f29d5a3050d905fe0.png" alt="">
## [《第11讲 | TCP协议因性恶而复杂先恶后善反轻松》](https://time.geekbang.org/column/article/8975)
### 课后思考题
TCP 的连接有这么多的状态,你知道如何在系统中查看某个连接的状态吗?
<img src="https://static001.geekbang.org/resource/image/3c/d7/3c997ad09a1c72cbb32a992e7c9588d7.png" alt="">
### 留言问题
1.TIME_WAIT状态太多是怎么回事儿
<img src="https://static001.geekbang.org/resource/image/85/b8/8535df3de9f426b44def750330dcf2b8.png" alt="">
<img src="https://static001.geekbang.org/resource/image/1f/11/1f6a5e17b34f00d28722428b7b8ccb11.jpg" alt="">
如果处于TIMEWAIT状态说明双方建立成功过连接而且已经发送了最后的ACK之后才会处于这个状态而且是主动发起关闭的一方处于这个状态。
如果存在大量的TIMEWAIT往往是因为短连接太多不断的创建连接然后释放连接从而导致很多连接在这个状态可能会导致无法发起新的连接。解决的方式往往是
<li>
打开tcp_tw_recycle和tcp_timestamps选项
</li>
<li>
打开tcp_tw_reuse和tcp_timestamps选项
</li>
<li>
程序中使用SO_LINGER应用强制使用rst关闭。
</li>
当客户端收到Connection Reset往往是收到了TCP的RST消息RST消息一般在下面的情况下发送
<li>
试图连接一个未被监听的服务端;
</li>
<li>
对方处于TIMEWAIT状态或者连接已经关闭处于CLOSED状态或者重新监听seq num不匹配
</li>
<li>
发起连接时超时重传超时keepalive超时
</li>
<li>
在程序中使用SO_LINGER关闭连接时放弃缓存中的数据给对方发送RST。
</li>
2.起始序列号是怎么计算的,会冲突吗?
有同学在留言中问了几个问题。Ender0224的回答非常不错。
<img src="https://static001.geekbang.org/resource/image/af/47/afed5f0593647c0b64971c2fef7e4247.png" alt="">
<img src="https://static001.geekbang.org/resource/image/c3/e1/c39c723c9389c4414401366a32b69fe1.jpg" alt="">
起始ISN是基于时钟的每4毫秒加一转一圈要4.55个小时。
TCP初始化序列号不能设置为一个固定值因为这样容易被攻击者猜出后续序列号从而遭到攻击。 RFC1948中提出了一个较好的初始化序列号ISN随机生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
M是一个计时器这个计时器每隔4毫秒加1。F是一个Hash算法根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证Hash算法不能被外部轻易推算得出用MD5算法是一个比较好的选择。
## [《第12讲 | TCP协议西行必定多妖孽恒心智慧消磨难》](https://time.geekbang.org/column/article/9141)
### 课后思考题
TCP的BBR听起来很牛你知道它是如何达到这个最优点的吗
<img src="https://static001.geekbang.org/resource/image/33/03/33b035bd326e9c1f811d667104a54003.png" alt="">
## [《第13讲 | 套接字SocketTalk is cheap, show me the code》](https://time.geekbang.org/column/article/9293)
### 课后思考题
epoll是Linux上的函数那你知道Windows上对应的机制是什么吗如果想实现一个跨平台的程序你知道应该怎么办吗
<img src="https://static001.geekbang.org/resource/image/74/e9/74d6535a22f5dc8ab2f782b4484ca7e9.png" alt="">
epoll是异步通知当事件发生的时候通知应用去调用IO函数获取数据。IOCP异步传输当事件发生时IOCP机制会将数据直接拷贝到缓冲区里应用可以直接使用。
如果跨平台推荐使用libevent库它是一个事件通知库适用于Windows、Linux、BSD等多种平台内部使用select、epoll、kqueue、IOCP等系统调用管理事件机制。
感谢第7讲至第13讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
欢迎你继续提问!
<img src="https://static001.geekbang.org/resource/image/ed/57/edc42141381c0458ab65f70628e88557.jpg" alt="">

View File

@@ -0,0 +1,251 @@
<audio id="audio" title="协议专栏特别福利 | 答疑解惑第二期" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/c0/035030be01889cd02f5e909528650bc0.mp3"></audio>
你好,我是刘超。
第二期答疑涵盖第3讲至第6讲的内容。我依旧对课后思考题和留言中比较有代表性的问题作出回答。你可以点击文章名回到对应的章节复习也可以继续在留言区写下你的疑问我会持续不断地解答。希望对你有帮助。
## [《第3讲 | ifconfig最熟悉又陌生的命令行》](https://time.geekbang.org/column/article/7772)
### 课后思考题
你知道 net-tools 和 iproute2 的“历史”故事吗?
<img src="https://static001.geekbang.org/resource/image/02/ba/02ae5ca5ab1c87bf5fea29196725c0ba.png" alt="">
这个问题的答案,盖同学已经写的比较全面了。具体的对比,我这里推荐一篇文章[https://linoxide.com/linux-command/use-ip-command-linux/](https://linoxide.com/linux-command/use-ip-command-linux/),感兴趣的话可以看看。
### 留言问题
1.A、B、C类地址的有效地址范围是多少
<img src="https://static001.geekbang.org/resource/image/eb/c5/ebf13d11cb0bc03d520c6cc4796ee8c5.png" alt="">
我在写的时候,没有考虑这么严谨,平时使用地址的时候,也是看个大概的范围。所以这里再回答一下。
A类IP的地址第一个字段范围是0127但是由于全0和全1的地址用作特殊用途实际可指派的范围是1126。所以我仔细查了一下如果较真的话你在答考试题的时候可以说A类地址范围和A类有效地址范围。
2.网络号、IP地址、子网掩码和广播地址的先后关系是什么
<img src="https://static001.geekbang.org/resource/image/25/56/254f28a3623ca8b4ee368ac02f0cf656.png" alt="">
当在一个数据中心或者一个办公室规划一个网络的时候,首先是网络管理员规划网段,一般是根据将来要容纳的机器数量来规划,一旦定了,以后就不好变了。
假如你在一个小公司里总共就没几台机器对于私有地址一般选择192.168.0.0/24就可以了。
这个时候先有的是网络号。192.168.0就是网络号。有了网络号子网掩码同时也就有了就是前面都是网络号的是1其他的是0广播地址也有了除了网络号之外都是1。
当规划完网络的时候一般这个网络里面的第一个、第二个地址被默认网关DHCP服务器占用你自己创建的机器只要和其他的不冲突就可以了当然你也可以让DHCP服务自动配置。
规划网络原来都是网络管理员的事情。有了公有云之后一般有个概念虚拟网络VPC鼠标一点就能创建一个网络网络完全软件化了任何人都可以做网络规划。
3.组播和广播的意义和原理是什么?
<img src="https://static001.geekbang.org/resource/image/92/f1/92654ed9549bba6c4e609e256ba083f1.png" alt="">
C类地址的主机号8位去掉0和255就只有254个了。
在《TCP/IP详解》这本书里面有两章讲了广播、多播以及IGMP。广播和组播分为两个层面其中MAC层有广播和组播对应的地址IP层也有自己的广播地址和组播地址。
广播相对比较简单MAC层的广播为ff:ff:ff:ff:ff:ffIP层指向子网的广播地址为主机号为全1且有特定子网号的地址。
组播复杂一些MAC层中当地址中最高字节的最低位设置为1时表示该地址是一个组播地址用十六进制可表示为01:00:00:00:00:00。IP层中组播地址为D类IP地址当IP地址为组播地址的时候有一个算法可以计算出对应的MAC层地址。
多播进程将目的IP地址指明为多播地址设备驱动程序将它转换为相应的以太网地址然后把数据发送出去。这些接收进程必须通知它们的IP层它们想接收的发给定多播地址的数据报并且设备驱动程序必须能够接收这些多播帧。这个过程就是“加入一个多播组”。
当多播跨越路由器的时候需要通过IGMP协议告诉多播路由器多播数据包应该如何转发。
4.MTU 1500的具体含义是什么
<img src="https://static001.geekbang.org/resource/image/0b/3d/0b43a6d8c1b77daef705e6b08f40a43d.png" alt="">
MTUMaximum Transmission Unit最大传输单元是二层的一个定义。以以太网为例MTU为1500个Byte前面有6个Byte的目标MAC地址6个Byte的源MAC地址2个Byte的类型后面有4个Byte的CRC校验共1518个Byte。
在IP层一个IP数据报在以太网中传输如果它的长度大于该MTU值就要进行分片传输。如果不允许分片DF就会发送ICMP包这个在[ICMP](https://time.geekbang.org/column/article/8445)那一节讲过。
在TCP层有个MSSMaximum Segment Size最大分段大小它等于MTU减去IP头再减去TCP头。即在不分片的情况下TCP里面放的最大内容。
在HTTP层看来它的body没有限制而且在应用层看来下层的TCP是一个流可以一直发送但其实是会被分成一个个段的。
## [《第4讲 | DHCP与PXEIP是怎么来的又是怎么没的》](https://time.geekbang.org/column/article/8015)
### 课后思考题
PXE 协议可以用来安装操作系统,但是如果每次重启都安装操作系统,就会很麻烦。你知道如何使得第一次安装操作系统,后面就正常启动吗?
<img src="https://static001.geekbang.org/resource/image/8d/c2/8db1eca548b4eaacfd9fbbb544a467c2.png" alt="">
一般如果咱们手动安装一台电脑的时候,都是有启动顺序的,如果改为硬盘启动,就没有问题了。
<img src="https://static001.geekbang.org/resource/image/05/cc/053273db99e6ef2458fcf48a534ac4cc.png" alt="">
好在服务器一般都提供IPMI接口可以通过这个接口启动、重启、设置启动模式等等远程访问这样就可以批量管理一大批机器。
<img src="https://static001.geekbang.org/resource/image/8f/09/8f423b7215d40642f8c712250b65b709.png" alt="">
这里提到Cobbler这是一个批量安装操作系统的工具。在OpenStack里面还有一个Ironic也是用来管理裸机的。有兴趣的话可以研究一下。
### 留言问题
1.在DHCP网络里面手动配置IP地址会冲突吗?
<img src="https://static001.geekbang.org/resource/image/6c/bc/6cd736774a770fe0ca5e6d42ad3425bc.png" alt="">
<img src="https://static001.geekbang.org/resource/image/62/d0/62b7fbe964db964060d501e3ef5252d0.png" alt="">
在一个DHCP网络里面如果某一台机器手动配置了一个IP地址并且在DHCP管理的网段里的话DHCP服务器是会将这个地址分配给其他机器的。一旦分配了ARP的时候就会收到两个应答IP地址就冲突了。
当发生这种情况的时候应该怎么办呢DHCP的过程虽然没有明确如何处理但是DHCP的客户端和服务器都可以添加相应的机制来检测冲突。
如果由客户端来检测冲突一般情况是客户端在接受分配的IP之前先发送一个ARP看是否有应答有就说明冲突了于是发送一个DHCPDECLINE放弃这个IP地址。
如果由服务器来检测冲突DHCP服务器会发送ping来看某个IP是否已经被使用。如果被使用了它就不再将这个IP分配给其他的客户端了。
2.DHCP的Offer和ACK应该是单播还是广播呢
<img src="https://static001.geekbang.org/resource/image/2c/b0/2c3c71b16ffca3442ffb60c36de8dcb0.png" alt="">
<img src="https://static001.geekbang.org/resource/image/a6/68/a6745f6d648ce8819976fed6fdc82468.png" alt="">
没心没肺 回答得很正确。
这个我们来看[DHCP的RFC](http://www.faqs.org/rfcs/rfc2131.html),我截了个图放在这儿:
<img src="https://static001.geekbang.org/resource/image/5b/ba/5ba44ea86352594b78c30ce75cc123ba.jpg" alt="">
这里面说了几个问题。
正常情况下一旦有了IP地址DHCP Server还是希望通过单播的方式发送OFFER和ACK。但是不幸的是有的客户端协议栈的实现如果还没有配置IP地址就使用单播。协议栈是不接收这个包的因为OFFER和ACK的时候IP地址还没有配置到网卡上。
所以一切取决于客户端的协议栈的能力如果没配置好IP就不能接收单播的包那就将BROADCAST设为1以广播的形式进行交互。
如果客户端的协议栈实现很厉害即便是没有配置好IP仍然能够接受单播的包那就将BROADCAST位设置为0就以单播的形式交互。
3.DHCP如何解决内网安全问题?
<img src="https://static001.geekbang.org/resource/image/f7/8c/f7ff8bed68c94247a19772113d87e18c.png" alt="">
其实DHCP协议的设计是基于内网互信的基础来设计的而且是基于UDP协议。但是这里面的确是有风险的。例如一个普通用户无意地或者恶意地安装一台DHCP服务器发放一些错误或者冲突的配置再如有恶意的用户发出很多的DHCP请求让DHCP服务器给他分配大量的IP。
对于第一种情况DHCP服务器和二层网络都是由网管管理的可以在交换机配置只有来自某个DHCP服务器的包才是可信的其他全部丢弃。如果有SDN或者在云中非法的DHCP包根本就拦截到虚拟机或者物理机的出口。
对于第二种情况一方面进行监控对DHCP报文进行限速并且异常的端口可以关闭一方面还是SDN或者在云中除了被SDN管控端登记过的IP和MAC地址其他的地址是不允许出现在虚拟机和物理机出口的也就无法模拟大量的客户端。
## [《第5讲 | 从物理层到MAC层如何在宿舍里自己组网玩联机游戏》](https://time.geekbang.org/column/article/8094)
### 课后思考题
1.在二层中我们讲了 ARP 协议,即已知 IP 地址求 MAC还有一种 RARP 协议,即已知 MAC 求 IP 的,你知道它可以用来干什么吗?
<img src="https://static001.geekbang.org/resource/image/e3/51/e3f91712382b9322beb02ec3d12ce851.png" alt="">
2.如果一个局域网里面有多个交换机ARP 广播的模式会出现什么问题呢?
盖还说出了环路的问题。
<img src="https://static001.geekbang.org/resource/image/74/a8/74de5ae71807bd0ee3d95f44d7e82aa8.png" alt="">
没心没肺不但说明了问题,而且说明了方案。
<img src="https://static001.geekbang.org/resource/image/fa/a4/fa42fea4d9b4b8ed75008e3f1eadd7a4.png" alt="">
## [《第6讲 | 交换机与VLAN办公室太复杂我要回学校》](https://time.geekbang.org/column/article/8386)
### 课后思考题
STP 协议能够很好地解决环路问题,但是也有它的缺点,你能举几个例子吗?
<img src="https://static001.geekbang.org/resource/image/b4/61/b4cb0842468ab9bb16916e9fb29ccb61.png" alt="">
<img src="https://static001.geekbang.org/resource/image/ee/a1/eebc423ca8a37fbc3e7dfc06f07e4ea1.png" alt="">
STP的主要问题在于当拓扑发生变化新的配置消息要经过一定的时延才能传播到整个网络。
由于整个交换网络只有一棵生成树,在网络规模比较大的时候会导致较长的收敛时间,拓扑改变的影响面也较大,当链路被阻塞后将不承载任何流量,造成了极大带宽浪费。
## 留言问题
1.每台交换机的武力值是什么样的?
<img src="https://static001.geekbang.org/resource/image/ae/51/ae72cc32931fc72918dd2f5da0ea9e51.png" alt="">
<img src="https://static001.geekbang.org/resource/image/c2/d8/c24fe02c21f41449a3a896442bc3b4d8.png" alt="">
当一台交换机加入或者离开网络的时候,都会造成网络拓扑变化,这个时候检测到拓扑变化的网桥会通知根网桥,根网桥会通知所有的网桥拓扑发生变化。
网桥的ID是由网桥优先级和网桥MAC地址组成的网桥ID最小的将成为网络中的根桥。默认配置下网桥优先级都一样默认优先级是32768。这个时候MAC地址最小的网桥成为根网桥。但是如果你想设置某台为根网桥就配置更小的优先级即可。
在优先级向量里面Root Bridge ID就是根网桥的IDBridge ID是网桥的IDPort ID就是一个网桥上有多个端口端口的ID。
<img src="https://static001.geekbang.org/resource/image/c0/95/c04244bf0b91ae964871698e7811d695.png" alt="">
按照RFC的定义ROOT PATH COST是和出口带宽相关的具体的数据如下
<img src="https://static001.geekbang.org/resource/image/d6/b9/d606b2c9f636ea3bc5694ac95d51f7b9.jpg" alt="">
2.图中的LAN指的是什么
<img src="https://static001.geekbang.org/resource/image/48/83/48efc105d54eff1ca6099e44df5c5a83.png" alt="">
在这一节中,这两张图引起了困惑。
<img src="https://static001.geekbang.org/resource/image/a5/85/a57a66bf97d326fd0b1355a0ba844e85.png" alt="">
<img src="https://static001.geekbang.org/resource/image/92/17/92450a4c1d7f261a3fbf06a12d621d17.png" alt="">
本来是为了讲二层的原理,做了个抽象的图,结果引起了大家的疑问,所以这里需要重新阐述一下。
首先这里的LAN1、LAN2、LAN 3的说法的确不准确因为通过网桥或者交换机连接它们还是属于一个LAN其实这是三个物理网络通过网桥或者交换机连接起来形成一个二层的LAN。
对于一层也即物理层的设备主要使用集线器Hub这里我们就用Hub将物理层连接起来。
于是我新画了两个图。
<img src="https://static001.geekbang.org/resource/image/ea/48/ea499064396aa5078b9fe9cd5f4fcc48.png" alt="">
<img src="https://static001.geekbang.org/resource/image/7d/e5/7d0ec3b857e7c85bba8aefc340a86ce5.png" alt="">
在这里我用Hub将不同的机器连接在一起形成一个物理段而非LAN。
3.在MAC地址已经学习的情况下ARP会广播到没有IP的物理段吗
<img src="https://static001.geekbang.org/resource/image/1f/f4/1f0ae48921ea404705d93e8bcbf4daf4.png" alt="">
<img src="https://static001.geekbang.org/resource/image/0c/a8/0c5d8df850a2fff54c175789645bd7a8.png" alt="">
首先谢谢这两位同学指出错误这里ARP的目标地址是广播的所以无论是否进行地址学习都会广播而对于某个MAC的访问在没有地址学习的时候是转发到所有的端口的学习之后只会转发到有这个MAC的端口。
4.802.1Q VLAN 和Port-based VLAN有什么区别
<img src="https://static001.geekbang.org/resource/image/98/4d/988673f41c561228c017e96435ea264d.png" alt="">
所谓Port-based VLAN一般只在一台交换机上起作用比如一台交换机10个口1、3、5、7、9属于VLAN 10。1发出的包只有3、5、7、9能够收到但是从这些口转发出去的包头中并不带VLAN ID。
而802.1Q的VLAN出了交换机也起作用也就是说一旦打上某个VLAN则出去的包都带这个VLAN也需要链路上的交换机能够识别这个VLAN进行转发。
感谢第3讲至第6讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
欢迎你继续提问!
<img src="https://static001.geekbang.org/resource/image/c6/9c/c65176654fe6305665399da89cfe909c.jpg" alt="">

View File

@@ -0,0 +1,265 @@
<audio id="audio" title="协议专栏特别福利 | 答疑解惑第五期" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/64/18/64d2e85a551930e15c4537ba6e0d1518.mp3"></audio>
你好,我是刘超。
第五期答疑涵盖第22讲至第36讲的内容。我依旧对课后思考题和留言中比较有代表性的问题作出回答。你可以点击文章名回到对应的章节复习也可以继续在留言区写下你的疑问我会持续不断地解答。希望对你有帮助。
## [《第22讲 | VPN朝中有人好做官》](https://time.geekbang.org/column/article/10386)
### 课后思考题
当前业务的高可用性和弹性伸缩很重要,所以很多机构都会在自建私有云之外,采购公有云,你知道私有云和公有云应该如何打通吗?
<img src="https://static001.geekbang.org/resource/image/88/2b/882fbc4105dcfb68e9da055065ad0f2b.jpg" alt="" />
### 留言问题
DH算法会因为传输随机数被破解吗
<img src="https://static001.geekbang.org/resource/image/59/b5/5985bcc89e863897eecb18097e8430b5.jpg" alt="" />
这位同学的[笔记](https://mubu.com/doc/1cZYndRrAg)特别认真让人感动。DH算法的交换材料要分公钥部分和私钥部分公钥部分和其他非对称加密一样都是可以传输的所以对于安全性是没有影响的而且传输材料远比传输原始的公钥更加安全。私钥部分是谁都不能给的因此也是不会截获到的。
## [《第23讲 | 移动网络:去巴塞罗那,手机也上不了脸书》](https://time.geekbang.org/column/article/10534)
### 课后思考题
咱们上网都有套餐,有交钱多的,有交钱少的,你知道移动网络是如何控制不同优先级的用户的上网流量的吗?
这个其实是PCRF协议进行控制的它可以下发命令给PGW来控制上网的行为和特性。
## [《第24讲 | 云中网络:自己拿地成本高,购买公寓更灵活》](https://time.geekbang.org/column/article/10742)
### 课后思考题
为了直观这一节的内容我们以桌面虚拟化系统举例。在数据中心里面有一款著名的开源软件OpenStack这一节讲的网络连通方式对应OpenStack中的哪些模型呢
<img src="https://static001.geekbang.org/resource/image/dd/c2/dde85e3ca5c02dfcde75b8bb96a264c2.jpg" alt="" />
OpenStack的早期网络模式有Flat、Flat DHCP、VLAN后来才有了VPC用VXLAN和GRE进行隔离。
## [《第25讲 | 软件定义网络:共享基础设施的小区物业管理办法》](https://time.geekbang.org/column/article/10755)
### 课后思考题
在这一节中提到了通过VIP可以通过流表在不同的机器之间实现负载均衡你知道怎样才能做到吗
可以通过ovs-ofctl下发流表规则创建group并把端口加入group中所有发现某个地址的包在两个端口之间进行负载均衡。
```
sudo ovs-ofctl -O openflow11 add-group br-lb &quot;group_id=100 type=select selection_method=dp_hash bucket=output:1 bucket=output:2&quot;
sudo ovs-ofctl -O openflow11 add-flow br-lb &quot;table=0,ip,nw_dst=192.168.2.0/24,actions=group:100&quot;
```
### 留言问题
SDN控制器是什么东西
<img src="https://static001.geekbang.org/resource/image/e5/a4/e5401b93f60da5b95fdc0e060a0a51a4.jpg" alt="" />
SDN控制器是一个独立的集群主要是在管控面因为要实现一定的高可用性。
主流的开源控制器有OpenContrail、OpenDaylight等。当然每个网络硬件厂商都有自己的控制器而且可以实现自己的私有协议进行更加细粒度的控制所以江湖一直没有办法统一。
流表是在每一台宿主机上保存的,大小限制取决于内存,而集中存放的缺点就是下发会很慢。
## [《第26讲 | 云中的网络安全:虽然不是土豪,也需要基本安全和保障》](https://time.geekbang.org/column/article/10978)
### 课后思考题
这一节中重点讲了iptables的filter和nat功能iptables还可以通过QUEUE实现负载均衡你知道怎么做吗
我们可以在iptables里面添加下面的规则
```
-A PREROUTING -p tcp -m set --match-set minuteman dst,dst -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j NFQUEUE --queue-balance 50:58
-A OUTPUT -p tcp -m set --match-set minuteman dst,dst -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j NFQUEUE --queue-balance 50:58
```
NFQUEUE的规则表示将把包的处理权交给用户态的一个进程。queue-balance表示会将包发给几个queue。
libnetfilter_queue是一个用户态库用户态进程会使用libnetfilter_queue连接到这些queue中将包读出来根据包的内容做决策后再放回内核进行发送。
## [《第27讲 | 云中的网络QoS邻居疯狂下电影我该怎么办》](https://time.geekbang.org/column/article/10994)
### 课后思考题
这一节中提到,入口流量其实没有办法控制,出口流量是可以很好控制的,你能想出一个控制云中的虚拟机的入口流量的方式吗?
<img src="https://static001.geekbang.org/resource/image/0a/26/0ae24cd15cda3eb8aba91666650d1526.jpg" alt="" />
在云平台中,我们可以限制一个租户的默认带宽,我们仍然可以配置点对点的流量控制。
在发送方的OVS上我们可以统计发送方虚拟机的网络统计数据上报给管理面。在接收方的OVS上我们同样可以收集接收方虚拟机的网络统计数据上报给管理面。
当流量过大的时候,我们虽然不能控制接收方的入口流量,但是我们可以在管理面下发一个策略,控制发送方的出口流量。
### 留言问题
对于HTB借流量的情况借出去的流量能够抢回来吗
<img src="https://static001.geekbang.org/resource/image/ec/79/ec406e7e9502859e9b6b80c04d703979.jpg" alt="" />
首先,借出去的流量,当自己使用的时候,是能够抢回来的。
有一篇著名的文章[《HTB Linux queuing discipline manual》](http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm)里面很详细,你可以看看。
<img src="https://static001.geekbang.org/resource/image/2b/0b/2bf7de23e5bda856c606a73f66dd050b.jpg" alt="" />
很多人看不懂这是一棵HTB树有三个分支。A用户使用www访问网页SMTP协议发送邮件B用户不限协议。
在时间0的时候0、1、2都以90k的速度发送数据也即A用户在访问网页同时发送邮件B也在上网干啥都行。
在时间3的时候将0的发送停止A不再访问网页了红色的线归零A的总速率下来了剩余的流量按照比例分给了蓝色的和绿色的线也即分给了A发邮件和B上网。
在时间6的时候将0的发送重启为90k也即A重新开始访问网页则蓝色和绿色的流量返还给红色的流量。
在时间9的时候将1的发送停止A不再发送邮件了绿色的流量为零A的总速率也下来了剩余的流量按照比例分给了蓝色和红色。
在时间12将1的发送恢复A又开始发送邮件了红色和蓝色返还流量。
在时间15将2的发送停止B不再上网了啥也不干了蓝色流量为零剩余的流量按照比例分给红色和绿色。
在时间19将1的发送停止A不再发送邮件了绿色的流量为零所有的流量都归了红色所有的带宽都用于A来访问网页。
## [《第28讲 | 云中网络的隔离GRE、VXLAN虽然住一个小区也要保护隐私》](https://time.geekbang.org/column/article/11324)
### 课后思考题
虽然VXLAN可以支持组播但是如果虚拟机数目比较多在Overlay网络里面广播风暴问题依然会很严重你能想到什么办法解决这个问题吗
<img src="https://static001.geekbang.org/resource/image/c3/0e/c354402836976d9defcf67d36ba2ae0e.jpg" alt="" />
很多情况下物理机可以提前知道对端虚拟机的MAC地址因而当发起ARP请求的时候不用广播全网只要本地返回就可以了在Openstack里面称为L2Population。
## [《第29讲 | 容器网络:来去自由的日子,不买公寓去合租》](https://time.geekbang.org/column/article/11465)
### 课后思考题
容器内的网络和物理机网络可以使用NAT的方式相互访问如果这种方式用于部署应用有什么问题呢
<img src="https://static001.geekbang.org/resource/image/68/25/683db80f520ddc094817d5e780099025.jpg" alt="" />
_CountingStars对这个问题进行了补充。
<img src="https://static001.geekbang.org/resource/image/97/3f/978d279627a1388c54c2eb2466cd0d3f.jpg" alt="" />
其实就是性能损耗随机端口占用看不到真实IP。
## [《第30讲 | 容器网络之Flannel每人一亩三分地》](https://time.geekbang.org/column/article/11643)
### 课后思考题
通过 Flannel 的网络模型可以实现容器与容器直接跨主机的互相访问,那你知道如果容器内部访问外部的服务应该怎么融合到这个网络模型中吗?
<img src="https://static001.geekbang.org/resource/image/ac/83/acb3aef79240c8707f70491604dbde83.jpg" alt="" />
Pod内到外部网络是通过docker引擎在iptables的POSTROUTING中的MASQUERADE规则实现的将容器的地址伪装为node IP出去回来时再把包nat回容器的地址。
有的时候我们想给外部的一个服务使用一个固定的域名这就需要用到Kubernetes里headless service的ExternalName。我们可以将某个外部的地址赋给一个Service的名称当容器内访问这个名字的时候就会访问一个虚拟的IP。然后在容器所在的节点上由iptables规则映射到外部的IP地址。
## [《第31讲 | 容器网络之Calico为高效说出善意的谎言》](https://time.geekbang.org/column/article/11940)
### 课后思考题
将Calico部署在公有云上的时候经常会选择使用IPIP模式你知道这是为什么吗
_CountingStars的回答是部分正确的。
<img src="https://static001.geekbang.org/resource/image/17/a2/1769832623a4a14e4bc79a67b3e522a2.jpg" alt="" />
一个原因是中间有路由如果VPC网络是平的但是公有云经常会有一个限制那就是器的IP段是用户自己定义的一旦出虚拟机的时候云平台发现不是它分配的IP很多情况下直接就丢弃了。如果是IPIP出虚拟机之后IP还是虚拟机的IP就没有问题。
## [《第32讲 | RPC协议综述远在天边近在眼前》](https://time.geekbang.org/column/article/12230)
### 课后思考题
在这篇文章中mount的过程是通过系统调用最终调用到RPC层。一旦mount完毕之后客户端就像写入本地文件一样写入NFS了这个过程是如何触发RPC层的呢
_CountingStars的回答很有深度。
<img src="https://static001.geekbang.org/resource/image/7a/60/7ab3228c9cb73e7f0cf0b922332d1d60.jpg" alt="" />
是的是通过VFS实现的。
在讲[Socket](https://time.geekbang.org/column/article/9141)那一节的时候我们知道在Linux里面很多东西都是文件因而内核中有一个打开的文件列表File list每个打开的文件都有一项。
<img src="https://static001.geekbang.org/resource/image/60/8c/602d09290bd4f9e0183f530e9653348c.jpg" alt="" />
对于Socket来讲in-core inode就是内存中的inode。对于nfs来讲也有一个inode这个inode里面有一个成员变量file_operations这里面是这个文件系统的操作函数。对于nfs来讲因为有nfs_file_read、nfs_file_write所以在一个mount的路径中读取某个文件的时候就会调用这两个函数触发RPC远程调用服务端。
## [《第33讲 | 基于XML的SOAP协议不要说NBA请说美国职业篮球联赛》](https://time.geekbang.org/column/article/12388)
### 课后思考题
对于HTTP协议来讲有多种方法但是SOAP只用了POST这样会有什么问题吗
<img src="https://static001.geekbang.org/resource/image/8a/19/8a889ea32a5a404c432a1102b90a4819.jpg" alt="" />
## [《第34讲 | 基于JSON的RESTful接口协议我不关心过程请给我结果》](https://time.geekbang.org/column/article/12512)
### 课后思考题
在讨论RESTful模型的时候举了一个库存的例子但是这种方法有很大问题那你知道为什么要这样设计吗
<img src="https://static001.geekbang.org/resource/image/4a/a2/4ab1e3cb5b48a843d6dc5967cc1238a2.jpg" alt="" />
这个我在双十一包的例子中已经分析过了。
## [《第35讲 | 二进制类RPC协议还是叫NBA吧总说全称多费劲》](https://time.geekbang.org/column/article/12521)
### 课后思考题
对于微服务模式下的RPC框架的选择Dubbo和SpringCloud各有优缺点你能做个详细的对比吗
<img src="https://static001.geekbang.org/resource/image/49/33/49d571110301eab5cf61560bfee2f333.jpg" alt="" />
## [《第36讲 | 跨语言类RPC协议交流之前双方先来个专业术语表》](https://time.geekbang.org/column/article/12819)
### 课后思考题
在讲述Service Mesh的时候我们说了希望Envoy能够在服务不感知的情况下将服务之间的调用全部代理了你知道怎么做到这一点吗
<img src="https://static001.geekbang.org/resource/image/0b/d3/0b13bbed598d0d9b9a783d1a3fa881d3.jpg" alt="" />
<img src="https://static001.geekbang.org/resource/image/56/f8/56e282315a2ef361aa0a8bb1cf0b38f8.jpg" alt="" />
iptables规则可以这样来设置
首先定义的一条规则是ISTIO_REDIRECT转发链。这条链不管三七二十一都将网络包转发给envoy的15000端口。但是一开始这条链没有被挂到iptables默认的几条链中所以不起作用。
接下来在PREROUTING规则中使用这个转发链。从而进入容器的所有流量都被先转发到envoy的15000端口。而envoy作为一个代理已经被配置好了将请求转发给productpage程序。
当productpage往后端进行调用的时候就碰到了output链。这个链会使用转发链将所有出容器的请求都转发到envoy的15000端口。
这样无论是入口的流量还是出口的流量全部用envoy做成了“汉堡包”。envoy根据服务发现的配置做最终的对外调用。
这个时候iptables规则会对从envoy出去的流量做一个特殊处理允许它发出去不再使用上面的output规则。
感谢第22讲至第36讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
<img src="https://static001.geekbang.org/resource/image/90/99/90874109328ea2f88d097f58dddc4e99.jpg" alt="" />
咱们的答疑环节暂时告一段落了,如果你在学习过程中有什么问题,欢迎你继续留言给我!

View File

@@ -0,0 +1,207 @@
<audio id="audio" title="协议专栏特别福利 | 答疑解惑第四期" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ad/d4/ad9d6ddf788e6c9a1d7cb9b771901fd4.mp3"></audio>
你好,我是刘超。
第四期答疑涵盖第14讲至第21讲的内容。我依旧对课后思考题和留言中比较有代表性的问题作出回答。你可以点击文章名回到对应的章节复习也可以继续在留言区写下你的疑问我会持续不断地解答。希望对你有帮助。
## [《第14讲 | HTTP协议看个新闻原来这么麻烦》](https://time.geekbang.org/column/article/9410)
### 课后思考题
QUIC是一个精巧的协议所以它肯定不止今天我提到的四种机制你知道还有哪些吗
云学讲了一个QUIC的特性。
<img src="https://static001.geekbang.org/resource/image/4e/02/4eb79fc072ad11cf3e938616259f0502.png" alt="">
QUIC还有其他特性一个是**快速建立连接**。这个我放在下面HTTPS的时候一起说。另一个是**拥塞控制**QUIC协议当前默认使用了TCP协议的CUBIC拥塞控制算法
你还记得TCP的拥塞控制算法吗每当收到一个ACK的时候就需要调整拥塞窗口的大小。但是这也造成了一个后果那就是RTT比较小的窗口增长快。
然而这并不符合当前网络的真实状况因为当前的网络带宽比较大但是由于遍布全球RTT也比较长因而基于RTT的窗口调整策略不仅不公平而且由于窗口增加慢有时候带宽没满数据就发送完了因而巨大的带宽都浪费掉了。
CUBIC进行了不同的设计它的窗口增长函数仅仅取决于连续两次拥塞事件的时间间隔值窗口增长完全独立于网络的时延RTT。
CUBIC的窗口大小以及变化过程如图所示。
<img src="https://static001.geekbang.org/resource/image/fe/5f/fe9fb6d6e9deea8c3543f54572ec765f.jpg" alt="">
当出现丢包事件时CUBIC会记录这时的拥塞窗口大小把它作为Wmax。接着CUBIC会通过某个因子执行拥塞窗口的乘法减小然后沿着立方函数进行窗口的恢复。
从图中可以看出一开始恢复的速度是比较快的后来便从快速恢复阶段进入拥塞避免阶段也即当窗口接近Wmax的时候增加速度变慢立方函数在Wmax处达到稳定点增长速度为零之后在平稳期慢慢增长沿着立方函数的开始探索新的最大窗口。
### 留言问题
HTTP的keepalive模式是什么样
<img src="https://static001.geekbang.org/resource/image/77/7c/77df27b180a8d0390322bd9137d21d7c.png" alt="">
在没有keepalive模式下每个HTTP请求都要建立一个TCP连接并且使用一次之后就断开这个TCP连接。
使用keepalive之后在一次TCP连接中可以持续发送多份数据而不会断开连接可以减少TCP连接建立次数减少TIME_WAIT状态连接。
然而长时间的TCP连接容易导致系统资源无效占用因而需要设置正确的keepalive timeout时间。当一个HTTP产生的TCP连接在传送完最后一个响应后还需要等待keepalive timeout秒后才能关闭这个连接。如果这个期间又有新的请求过来可以复用TCP连接。
## [《第15讲 | HTTPS协议点外卖的过程原来这么复杂》](https://time.geekbang.org/column/article/9492)
### 课后思考题
HTTPS 协议比较复杂,沟通过程太繁复,这样会导致效率问题,那你知道有哪些手段可以解决这些问题吗?
通过HTTPS访问的确复杂至少经历四个阶段DNS查询、TCP连接建立、TLS连接建立最后才是HTTP发送数据。我们可以一项一项来优化这个过程。
首先如果使用基于UDP的QUIC可以省略掉TCP的三次握手。至于TLS的建立如果按文章中基于TLS 1.2的双方要交换key经过两个来回也即两个RTT才能完成握手。但是咱们讲IPSec的时候讲过通过共享密钥、DH算法进行握手的场景。
在TLS 1.3中握手过程中移除了ServerKeyExchange和ClientKeyExchangeDH参数可以通过key_share进行传输。这样只要一个来回就可以搞定RTT了。
对于QUIC来讲也可以这样做。当客户端首次发起QUIC连接时会发送一个client hello消息服务器会回复一个消息里面包括server config类似于TLS1.3中的key_share交换。当客户端获取到server config以后就可以直接计算出密钥发送应用数据了。
### 留言问题
1.HTTPS的双向认证流程是什么样的
<img src="https://static001.geekbang.org/resource/image/93/39/934a64552c3c8c04c629383db0d6b939.jpg" alt="">
2.随机数和premaster的含义是什么
<img src="https://static001.geekbang.org/resource/image/28/78/28a3938ab8efaf9fa7f313a46d0e1478.jpg" alt="">
## [《第16讲 | 流媒体协议:如何在直播里看到美女帅哥?》](https://time.geekbang.org/column/article/9688)
### 课后思考题
你觉得基于 RTMP 的视频流传输的机制存在什么问题?如何进行优化?
Jason的回答很对。
<img src="https://static001.geekbang.org/resource/image/38/90/38d8dda78c277a01f85cbf7371639c90.png" alt="">
Jealone的回答更加具体。
<img src="https://static001.geekbang.org/resource/image/c3/d9/c384054368a1f491a34ac7f6f0641dd9.png" alt="">
当前有基于自研UDP协议传输的也有基于QUIC协议传输的。
### 留言问题
RTMP建立连接的序列是什么样的
<img src="https://static001.geekbang.org/resource/image/ad/53/ada59a46a4c5d5194c322ddb34250d53.png" alt="">
的确,这个图我画错了,我重新画了一个。
<img src="https://static001.geekbang.org/resource/image/5a/75/5a45685536eb1c1ae354c3cc1e037275.jpg" alt="">
不过文章中这部分的文字描述是没问题的。
客户端发送C0、C1、 C2服务器发送S0、 S1、 S2。
首先客户端发送C0表明自己的版本号不必等对方的回复然后发送C1表明自己的时间戳。
服务器只有在收到C0的时候才能返回S0表明自己的版本号。如果版本不匹配可以断开连接。
服务器发送完S0后也不用等什么就直接发送自己的时间戳S1。客户端收到S1的时候发一个知道了对方时间戳的ACK C2。同理服务器收到C1的时候发一个知道了对方时间戳的ACK S2。
于是,握手完成。
## [《第17讲 | P2P协议我下小电影99%急死你》](https://time.geekbang.org/column/article/9707)
### 课后思考题
除了这种去中心化分布式哈希的算法,你还能想到其他的应用场景吗?
<img src="https://static001.geekbang.org/resource/image/fe/eb/fe12f0f41edbb4f46c3df513988760eb.png" alt="">
### 留言问题
99%卡住的原因是什么?
<img src="https://static001.geekbang.org/resource/image/4e/da/4e52188839de11d46050935c2a3f12da.png" alt="">
## [《第18讲 | DNS协议网络世界的地址簿》](https://time.geekbang.org/column/article/9895)
### 课后思考题
全局负载均衡使用过程中,常常遇到失灵的情况,你知道具体有哪些情况吗?对应应该怎么来解决呢?
<img src="https://static001.geekbang.org/resource/image/e3/28/e3ce7238d7fa1d7e5d9568def5832b28.jpeg" alt="">
### 留言问题
如果权威DNS连不上怎么办
<img src="https://static001.geekbang.org/resource/image/92/06/92579a426910452422521f6bce71be06.png" alt="">
一般情况下DNS是基于UDP协议的。在应用层设置一个超时器如果UDP发出没有回应则会进行重试。
DNS服务器一般也是高可用的很少情况下会挂。即便挂了也会很快切换重试一般就会成功。
对于客户端来讲为了DNS解析能够成功也会配置多个DNS服务器当一个不成功的时候可以选择另一个来尝试。
## [《第19讲 | HttpDNS网络世界的地址簿也会指错路》](https://time.geekbang.org/column/article/9938)
### 课后思考题
使用 HttpDNS需要向 HttpDNS 服务器请求解析域名,可是客户端怎么知道 HttpDNS 服务器的地址或者域名呢?
<img src="https://static001.geekbang.org/resource/image/c3/e1/c3ca8a189ac6ad10afaca2da185c5ce1.png" alt="">
## [《第20讲 | CDN你去小卖部取过快递么》](https://time.geekbang.org/column/article/10085)
### 课后思考题
这一节讲了CDN使用DNS进行全局负载均衡的例子CDN如何使用HttpDNS呢
<img src="https://static001.geekbang.org/resource/image/c8/d9/c86f4fa153ff64ebaa1335933779cad9.png" alt="">
## [《第21讲 | 数据中心:我是开发商,自己拿地盖别墅》](https://time.geekbang.org/column/article/10098)
### 课后思考题
对于数据中心来讲,高可用是非常重要的,每个设备都要考虑高可用,那跨机房的高可用,你知道应该怎么做吗?
其实跨机房的高可用分两个级别,分别是**同城双活**和**异地灾备**。
<img src="https://static001.geekbang.org/resource/image/8a/71/8af9a0a7c1d24b73d26ab0ef5f54b071.jpg" alt="">
**同城双活**就是在同一个城市距离大概30km到100km的两个数据中心之间通过高速专线互联的方式让两个数据中心形成一个大二层网络。
同城双活最重要的是,数据如何从一个数据中心同步到另一个数据中心,并且在一个数据中心故障的时候,实现存储设备的切换,保证状态能够快速切换到另一个数据中心。在高速光纤互联情况下,主流的存储厂商都可以做到在一定距离之内的两台存储设备的近实时同步。数据双活是一切双活的基础。
基于双数据中心的数据同步可以形成一个统一的存储池从而数据库层在共享存储池的情况下可以近实时地切换例如Oracle RAC。
虚拟机在统一的存储池的情况下也可以实现跨机房的HA在一个机房切换到另一个机房。
SLB负载均衡实现同一机房的各个虚拟机之间的负载均衡。GSLB可以实现跨机房的负载均衡实现外部访问的切换。
如果在两个数据中心距离很近并且大二层可通的情况下也可以使用VRRP协议通过VIP方式进行外部访问的切换。
下面我们说**异地灾备**。
<img src="https://static001.geekbang.org/resource/image/e5/94/e561d8a5ec5842a67513ba42bda09494.jpg" alt="">
异地灾备的第一大问题还是数据的问题,也即生产数据中心的数据如何备份到容灾数据中心。由于异地距离比较远,不可能像双活一样采取近同步的方式,只能通过异步的方式进行同步。可以预见的问题是,容灾切换的时候,数据会丢失一部分。
由于容灾数据中心平时是不用的,不是所有的业务都会进行容灾,否则成本太高。
对于数据的问题,我比较建议从业务层面进行容灾。由于数据同步会比较慢,可以根据业务需求高优先级同步重要的数据,因而容灾的层次越高越好。
例如,有的用户完全不想操心,直接使用存储层面的异步复制。对于存储设备来讲,它是无法区分放在存储上的虚拟机,哪台是重要的,哪台是不重要的,只会完全根据块进行复制,很可能就会先复制了不重要的虚拟机。
如果用户想对虚拟机做区分,则可以使用虚拟机层面的异步复制。用户知道哪些虚拟机更重要一些,哪些虚拟机不重要,则可以先同步重要的虚拟机。
对业务来讲,如果用户可以根据业务层情况,在更细的粒度上区分数据是否重要。重要的数据,例如交易数据,需要优先同步;不重要的数据,例如日志数据,就不需要优先同步。
在有异地容灾的情况下,可以平时进行容灾演练,看容灾数据中心是否能够真正起作用,别容灾了半天,最后用的时候掉链子。
感谢第14讲至第21讲中对内容有深度思考和提出问题的同学。我会为你们送上奖励礼券和知识图谱。稍后运营同学会发送短信通知。
欢迎你继续提问!
<img src="https://static001.geekbang.org/resource/image/7e/7e/7e172a2e3bb3cbc926d138dd2bd9297e.jpg" alt="">

View File

@@ -0,0 +1,21 @@
<audio id="audio" title="第2季回归 | 这次我们来“趣谈Linux操作系统”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3a/69/3abcd0c589a74d612cdd5ff5c1c18169.mp3"></audio>
你好我是你的老朋友刘超。在“趣谈网络协议”结课半年之后我又给你带来了一个新的基础课程“趣谈Linux操作系统”。
在咱们“趣谈网络协议”的留言里,我和同学们进行了很多互动,同时,我也和其他做基础知识专栏的作者有了不少交流,我发现,无论是从个人的职业发展角度,还是从公司招聘候选人的角度来看,扎实的基础知识是很多人的诉求。这让我更加坚信,我应该在“趣谈基础知识”这条道路上走下去。
**在设计“趣谈Linux操作系统”专栏的时候我仍然秉承“趣谈”和“故事化”的方式将枯燥的基础知识结合某个场景给你生动、具象地讲述出来帮你加深理解、巩固记忆、夯实基础。**
在我看来,操作系统在计算机中承担着“大管家”的角色,这个“大管家”就好比一家公司的老板,我们的目标就是把这家公司做上市,具体的过程,我用了一张图来表示:
<img src="https://static001.geekbang.org/resource/image/7d/a5/7d7b2f705d4877bb331b4ea3ff3450a5.jpg" alt="">
Linux操作系统中的概念非常多数据结构也很多流程也复杂一般人在学习的过程中很容易迷路。我希望能够将这些复杂的概念、数据结构、流程通俗地讲解出来争取每篇文章都用一张图串起这篇的知识点。
最终,整个专栏下来,你如果能把这些图都掌握了,你的知识就会形成体系和连接。在此基础上再进行深入学习,就会如鱼得水、易如反掌。
一段新的征途即将开始期待与你继续同行。为了感谢老同学我为你送上一张10元专属优惠券可以与限时优惠同享优惠券有效期仅5天建议你抓紧使用。点击下方图片可试读专栏最新文章。
我们新专栏见!
[<img src="https://static001.geekbang.org/resource/image/57/44/57f047be7ebb1f4aba7e8064e1c11544.jpg" alt="">](https://time.geekbang.org/column/intro/164?utm_term=zeusOLMNR&amp;utm_source=app&amp;utm_medium=geektime&amp;utm_campaign=164-presell&amp;utm_content=qutanwangluoxieyi)

View File

@@ -0,0 +1,63 @@
<audio id="audio" title="结束语 | 放弃完美主义,执行力就是限时限量认真完成" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2b/52/2ba45030569a8525f83b916545dd5952.mp3"></audio>
你好,我是刘超。
从筹备、上线到今天专栏完结过去了将近半年的时间。200多天弹指一挥间。
我原本计划写36篇最后愣是写到了45篇。原本编辑让我一篇写两三千字结果几乎每篇都是四五千字。这里面涉及图片张数我没具体数过但是据说多到让编辑上传到吐。编辑一篇我的稿件的工作量相当于别的专栏的两倍。
人常说,有多少付出,就有多少回报。但是,写这个“趣谈网络协议”专栏,我收获的东西远超过我的想象。我希望你的收获也是如此。为什么这么说呢?我们把时间放回到这个专栏最开始的时候,我慢慢跟你讲。
## 我不是最懂的人,但我想尝试成为这样的人
今年年初,极客时间来找我,希望我讲一些偏重基础的知识,比如网络协议。
他们一提到这个主题,我就很兴奋,因为这也触动了我心中长期以来的想法,因为网络这个东西学起来实在是太痛苦。
但是,说实话,接下这个重任,我心里其实是有点“怕”的。我怕自己不够专业,毕竟业内有这么多网络工程师和研究网络理论的教授。我讲这个课会不会贻笑大方啊?
我知道,很多技术人员不敢写博客、写公众号,其实都有这种“怕”的心理:我又不牛,没啥要分享的,要是误导了别人怎么办?
如果你想做出一些成绩,这个心理一定要克服。其实每个人都有自己的**相对优势**。对于某个东西,你研究的时间不一定是最长的,但是你可能有特殊的角度、表达方式和应用场景。坚定了这个想法之后,我就开始投入热火朝天的专栏写作了。
一旦开始写,我发现,这个事情远没有看上去那么简单。它会花费你非常多的个人时间。写专栏这几个月,晚上两点之后睡,周末全在写专栏,基本成为我的生活常态。但是**我想挑战一下自己,我觉得,只要咬牙挺过去,自己的技术就会上升一个层次。**
## 放弃完美主义,执行力就是限时限量认真完成
技术人都有完美主义倾向,觉得什么事情都要钻研个底儿朝天,才拿出来见人。我也一样。
我曾经答应某出版社写一本搜索引擎的书。这本书分为原理篇和实践篇。我总觉得我还没把原理篇写完,就不能写实践篇。但是,仅原理篇我就写了一年。搜索引擎就火了一两年,最后时间窗口过了,书稿没有完成,这件事儿也就这么搁浅了。
所以,完美主义虽然是个很好听的词,但是它往往是和拖延症如影随形的,它常常会给拖延症披上一个华丽的外衣,说,我是因为追求完美嘛。但是,最终的结果往往是,理论研究半天还没动手,执行力很差。时间点过了,就心安理得地说,反正现在也不需要了,那就算了吧。久而久之,你就会发现,自己好像陷入了瓶颈。
我慢慢明白过来,我们**不是为了做技术而做技术,做技术是为了满足人类需求的**。完美主义是好事儿,但是,坚持完美主义的同时要限时限量地完成,才能形成执行力。
写这个专栏之后,我更加深刻地体会到这一点。每周都要写三篇文章,压力很大,根本容不得任何拖延。如果我还是坚持以前完美主义的做法,读完十本书,用三年时间把网络协议都研究透再来写,那现在就没有这个专栏了。
如果我们要强调执行力时间点这个因素就至关重要。在固定的时间点上就要把控范围不能顾虑太多要勇于放弃。就像给产品做排期先做最小闭环的功能集合其他的放在以后再补充。在这个前提下以自己最大的限度往完美的方向上努力。比如我觉得每天2点睡是我的身体极限努力到这个程度我也就无愧于心了。
所以说,我们做事情的目的并不是完美,而是在固定的时间点,以固定的数量和质量,尽可能认真地满足当时的客户需求,这才是最重要的。
这样做肯定会有不满意的地方比如很多同学在留言区指出我的错误甚至有的同学提的问题我原来都没思考过。但是我觉得这些都不是事儿。我可以再查资料再补充、再完善。所以后来时间宽裕了我还增加了5期答疑回答了一下之前没来得及回答地问题。高手在民间咱们一起来讨论和进步。这个过程已经让我受益良多。
## 保持饥渴,不怕被“鄙视”,勇于脱离舒适区
有人可能会问了,你看你既不是最专业的,还不追求完美,真的不怕被人“鄙视”吗?
被“鄙视”,谁都怕,这也是为什么越大的会议,参加人数越多的演讲,越是没有人提出具体的问题。大家都怕丢人,看上去好像大家都听懂了,就我啥都不懂,我要是问,被大家笑话怎么办?我想很多人都有这样的经历吧?我也来给你讲讲我的亲身经历。
我从Windows开发去做Linux的存储系统开发时连Linux man都不会看我在惠普从事OpenStack实施工作的时候对于网络的了解一塌糊涂一直被甲方骂我在华为做云计算支撑运营商项目的时候面对一大堆核心网词汇一脸懵我在网易云对内支撑考拉的时候在微服务架构方面也是小白……被“鄙视”了这么多次之后,我不怕了。因为这每一次“鄙视”都可以让我发现自己的短板,然后啃下这些东西,这不就是最大的收获吗?
我就是这样一直被“鄙视”着成长起来的人,我就是常常在别人分享的时候坐第一排问很傻的那种问题的人,我就是常常一知半解还愿意和别人讨论的人……
**怕被“鄙视”,说明你还不够饥渴,还没有勇气脱离你的舒适区。** 在你熟悉的领域里面,你是最最权威的,但是,天下之大,你真的只满足于眼前这一亩三分地吗?
很多人因为怕被“鄙视”,不敢问、不敢做,因而与很多美好的东西都擦肩而过了。直到有一天你用到了,你才后悔,当时自己怎么没去多问一句。
所以,当你看到一个特别好的、突破自己的学习机会,别犹豫,搭上这辆车。等过了十年,你会发现,当年那些嘲笑、轻视,甚至谩骂,都算不了什么,进步本身才是最最重要的。
今天,咱们没有谈具体的知识,我只表达了一下我的观点。我就是那个你在直播里看到的,那个邋遢、搞笑、不装,同时做事认真,愿意和你一起进步的技术大叔。
脱离舒适区吧,希望我们可以一起成长!
最后,我在这里放了一个[毕业调查问卷](http://cn.mikecrm.com/aeGIhYd)。如果你对这个专栏或者我本人有什么建议,可以通过这个问卷进行反馈,我一定会认真查看每一封的内容。期待你的反馈!

View File

@@ -0,0 +1,8 @@
经过三个多月的学习相信你对网络协议的基础概念和使用场景有了更深入的了解。我从专栏中精心筛选了核心知识点编成了这20道测试题。希望可以帮助你学习自检消化吸收以期获得更好的学习效果。
如果你刚刚打开这个专栏可以用这20道题找到自己的薄弱点**对症下药**如果你已经学习了一段时间可以用这20道题检测一下学习成果**查漏补缺**。
还等什么,点击下面按钮开始测试吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=151&amp;exam_id=335)