文章目录
- 前言
- 应用层
-
- [DNS: 因特网的目录服务](#DNS: 因特网的目录服务)
-
- [DNS 提供的服务](#DNS 提供的服务)
- [DNS 工作机理概述](#DNS 工作机理概述)
-
- 分布式、层次数据库
- [DNS 缓存](#DNS 缓存)
- [DNS 记录和报文](#DNS 记录和报文)
-
- [DNS 报文](#DNS 报文)
- [在 DNS 数据库中插入记录](#在 DNS 数据库中插入记录)
- [P2P 文件分发](#P2P 文件分发)
-
- [P2P 体系结构的扩展性](#P2P 体系结构的扩展性)
- BitTorrent
- 视频流和内容分发网
-
- 因特网视频
- [HTTP 流和 DASH](#HTTP 流和 DASH)
- 内容分发网
-
- [CDN 操作](#CDN 操作)
- 集群选择策略(不作介绍,书中的太过简略)
- 套接字编程:生成网络应用
-
- [UDP 套接字编程](#UDP 套接字编程)
- [TCP 套接字编程](#TCP 套接字编程)
- 参考目录
前言
阅读本文前请注意最后编辑时间,文章内容可能与目前最新的技术发展情况相去甚远。欢迎各位评论与私信,指出错误或是进行交流等。
本文是关于《计算机网络:自顶向下方法(第七版)》的学习分享,内容书写顺序也是按照书中的顺序。本文并不会提及书中的所有内容,主要写重点的知识,以及自己感兴趣的内容。会对原文中的内容进行一定的精简,或者加上个人的理解。
应用层
DNS: 因特网的目录服务
人类能以很多方式来标识。例如,我们能够通过出生证书上的名字来标识;能够通过社会保险号码来标识;也能够通过驾驶执照上的号码来标识。
因特网上的主机和人类一样,可以使用多种方式进行标识。主机的一种标识方法是用它的主机名 (hostname) , 如 www. facebook. com、www. google. com。然而,主机名几乎没有提供(即使有也很少)关于主机在因特网中位置的信息。(一个名为www. eurecom.fr的主机以 国家码 .fr结束,告诉我们该主机很可能在法国,仅此而已。)况且,主机名由不定长的字母数字组成,路由器难以处理。由于这些原因,主机可以使用 IP地址进行标识。
关于IP地址会在网络层更详细地讨论,现在简略地介绍一下。一个IP 地址由 4个字节组成,并有着严格的层次结构。 例如121.7. 106. 83 这样一个IP地址,其中的每个字节都被句点分隔开来。IP地址具有层次结构,当我们从左至右扫描它时,会得到越来越具体的关于主机位于因特网何处的信息(即在众多网络的哪个网络里)。
DNS 提供的服务
刚刚看到了识别主机有两种方式,通过主机名或者IP地址。人们喜欢便于记忆的主机名标识方式,而路由器则喜欢定长的、有着层次结构的 IP地址。为了折中这些不同的偏好,我们需要一种能进行主机名到 IP 地址转换的目录服务。这就是域名系统( Domain Name System, DNS) 的主要任务。DNS是由分层的 DNS 服务器实现的分布式数据库,并且还是一个使得主机能够查询分布式数据库的应用层协议。DNS 协议运行在 UDP 之上,使用53 号端口。
DNS 通常是由其他应用层协议所使用的,包括HTTP、 SMTP和FTP, 将用户提供的主机名解析为IP地址。举一个例子,考虑运行在某用户主机上的一个浏览器请求 URL www. someschool.edu/index. html 页面时会发生什么现象。为了使用户的主机能够将一个HTTP请求报文发送到Web 服务器www. someschool. edu, 该用户主机必须 获得www. someschool. edu 的 IP 地址。其做法如下:
1 ) 用户主机上还运行着DNS应用软件的客户端
-
浏览器从上述 URL 中抽取出主机名 www.someschool. edu,并将主机名传给 DNS 应用软件的客户端。
-
DNS 客户端向 DNS 服务器发送一个包含主机名的请求。
-
DNS 客户端最终会收到一份响应报文,其中含有对应于该主机名的IP地址。
5 ) 浏览器从响应报文中提取该主机名所对应的IP 地址,它能够向位于该IP地址 80 端口的 HTTP 服务器进程发起一个TCP连接。
除了进行主机名到 IP地址的转换外, DNS还提供了一些重要的服务:(此处不作详细介绍
)1.主机别名、2邮件服务器别名、3. 负载分配
DNS 工作机理概述
假设运行在用户主机上的某些应用程序(如 Web浏览器或邮件阅读器)需要将主机名转换为 IP地址。这些应用程序将调用 DNS 的客户端,并指明需要被转换的主机名。用户主机上的 DNS客户端接收到后,向网络中发送一个 DNS查询报文。 所有的 DNS请求和回答报文使用 UDP数据报经端口 53 发送。经过若干毫秒到若干秒的时延后,用户主机上的 DNS 接收到一个所希望映射的 DNS 回答报文。这个映射结果则被传递到 调用 DNS 的应用程序。因此,从调用DNS的应用程序的角度看, DNS是一个提供简单、直接的转换服务的黑盒子。 但事实上,实现这个服务的黑盒子非常复杂,它由分布于全球的大量DNS服务器以及定义了 DNS服务器与查询主机通信方式的应用层协议组成。
DNS 的一种简单设计是在因特网上只使用一个 DNS服务器,该服务器包含所有的映射。 在这种集中式设计中,客户直接将所有查询直接发往单一的 DNS 服务器,同时该 DNS 服务器直接对所有的查询客户做出响应。尽管这种设计的简单性非常具有吸引力,但它不适用于当今的因特网,因为因特网有着数量巨大(并持续增长)的主机。 这种集中式设计的问题包括:
- 单点故障:如果该 DNS 服务器崩溃,整个因特网随之瘫痪
- 通信容量:单个 DNS 服务器不得不处理所有的 DNS查询
- 远距离的集中式数据库:单个 DNS 服务器不可能 邻近所有查询客户。
- 维护:单个 DNS 服务器将不得不为所有的因特网主机保留记录。这不仅将使这个中央数据库庞大,而且它还不得不为解决每个新添加的主机而频繁更新。
总的来说,在单一DNS服务器上运行集中式数据库完全没有可扩展能力。 因此, DNS 采用了分布式的设计方案。
分布式、层次数据库
为了处理扩展性问题, DNS使用了大量的 DNS服务器,它们以层次方式组织, 并 且分布在全世界范围内。 没有一台 DNS 服务器拥有因特网上所有主机的映射。大致说来,有3 种类型的 DNS服务器:根 DNS 服务器、顶级域DNS 服务器和权威 DNS 服务器。这些服务 器如下图中所示的层次结构组织起来。

根 DNS 服务器:400 多个根服务器遍及全世界。这些根服务器由 13个不同的组织管理。根服务器提供顶级域服务器的 IP地址。
顶级域 (TLD)DNS服务器:对于每个顶级域(如com、 org、 net、 edu 和 gov) 和所有国家的顶级域(如uk、fr) 都有 顶级域服务器 (或服务器集群)。
权威 DNS服务器:在因特网上公共可访问主机(如 Web服务器和邮件服务器)的每个组织机构必须提供公共可访问的 DNS记录,这些记录将主机的名字映射为IP地址。权威DNS服务器直接持有域名的权威信息,并负责向其他服务器提供这些信息。
还有另一类重要的DNS服务器,称为本地DNS服务器。严格说来,一个 本地DNS 服务器并不属于上述的层次结构,但它对DNS层次结构是至关重要的。每个ISP (如一个居民区的 ISP或一个机构的 ISP) 都有一台本地 DNS 服务器。当主机与某个ISP连接时,该ISP为主机分配一个IP地址,以及为该主机分配一台或多台DNS服务器的地址(一般通过 DHCP服务分配,关于DHCP将在网络层进行讨论)。主机的本地DNS 服务器通常邻近本主机。 对某机构 ISP 而言,本地 DNS 服务器可能就与主机在同一个局域网中;对于某居民区ISP 来说, 本地 DNS 服务器通常与主机相隔不超过几台路由器。当主机发出DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 服务器层次结构中。

来看一个简单的例子,假设主机cse. nyu. edu 想知道主机 gaia. cs. umass. edu的 IP 地址。 本地 DNS 服务器为dns. nyu. edu,并且 gaia. cs. umass. edu 的权威DNS 服务器为 dns. umass. edu(也就表示gaia. cs. umass. edu的IP地址,可以从该权威DNS服务器中获取)。
如图所示,主机 cse. nyu. edu 首先向它的本地 DNS 服务器 dns. nyu. edu 发送一个 DNS 查询报文。该查询报文含有需要被转换为IP地址的主机名gaia cs. umass. edu 。本地 DNS 服务器将该报文转发到根DNS 服务器。该根 DNS 服务器注意到主机名中的edu 并向本地 DNS 服务器返回负责edu 的 TLD 的 IP 地址列表。该本地DNS 服务器则再次向这些 TLD 服务器之一发送查询报文。该 TLD 服务器注意到umass. edu,并向本地DNS服务器返回权威 DNS 服务器的 IP 地址。最后,本地 DNS 服务器直接向权威 DNS 服务器发查询报文,权威服务器dns. umass. edu 用 gaia. cs. umass. edu 的 IP 地址进行响应。
注意!在本例中,为了获得一台主机名的映射,共发送了8份DNS报文: 4份查询报文和4 份响应报文。
前面的例子假设了TLD服务器知道要查询主机的权威DNS服务器的 IP地址。一般而言,这种假设并不总是正确的。 相反,TLD 服务器只是知道中间的某个 DNS 服务器, 随后依靠中间 DNS 服务器直至查询到该主机的权威DNS 服务器。
DNS 缓存
实际上,为了改善时延性能并减少在因特网上到处传输的 DNS 报文数量, DNS 广泛使用了缓存技术。在一次DNS查询中,当某 DNS服务器接收一个DNS 响应报文(包含某主机名到IP地址的映射),它能将映射缓存在本地存储器中。例如,在上图本地 DNS 服务器dns. nyu. edu 从某个 DNS 服务器接收到一个回答,它能够缓存该响应报文中的任何信息。当一次DNS查询报文发送到本地DNS服务器,且请求的主机名刚好是已经缓存了的,则该本地DNS服务器直接返回所对应的IP地址。由于主机名与 IP地址间的映射并不是永久的, DNS服务器在一段时间后(通常设置为两天)将丢弃缓存的信息。
另外地,本地 DNS 服务器也能够缓存TLD 服务器的 IP地址,因而允许本地DNS绕过查询链中的根 DNS 服务器。
DNS 记录和报文
实现 DNS 分布式数据库的所有 DNS 服务器存储了资源记录,资源记录提供了主机名到 IP 地址的映射。每个 DNS 回答报文包含了一条或多条资源记录。资源记录是一个包含了下列字段的4元组:(Name, Value, Type, TTL)
TTL 是该记录的生存时间,它决定了资源记录应当从缓存中删除的时间。
Name 和 Value 的值取决于Type。
如果Type= A, 则 Name 是主机名, Value 是该主机名对应的 IP 地址。
如果Type=NS,则 Name 是个域(如 foo. com) , 而 Value 是个知道如何获得该域中主机IP地址的权威DNS服务器的主机名。
如果Type=CNAME,则 Value 是Name 的主机对应的规范主机名,此时的Name是主机的别名。
如果 Type= MX,则 Value 是Name 的邮件服务器的规范主机名,此时的Name是该邮件服务器的别名。
DNS 报文
DNS有查询和回答报文,并且他们有着相同的格式。

标识符:用于标识该查询。这个标识符会被复制到对查询的回答报文中,以便让客户用它来匹配发送的请求和接收到的回答。
标志:指出报文是查询报文 (0) 还是回答报文 (1)
问题(问题的变量数):包含着正在进行的查询信息,包括:1. 名字字段:正在被查询的主机名字;2. 类型字段:有关该名字的正被询问的问题类型,例如主机地址与一个名字相关联(类型A)
回答(资源记录的变量数):包含了对最初请求的名字的资源记录。在回答报文的回答区域中可以包含多条资源记录,一个主机名能够有多个IP地址。
权威(资源记录的变量数)包含了其他权威服务器的记录
附加信息区域包含了其他有帮助的记录。 例如,对于一个MX请求的回答报文的回答区域包含了一条资源记录,该记录提供了邮件服务器的规范主机名。
在 DNS 数据库中插入记录
上面的讨论关注如何从 DNS数据库中取数据。但这些数据最初是怎么进入数据库中的。通过一个特定的例子看看这是如何完成的。假定你刚创建一个名为网络乌托邦的公司。你要做的第一件事是在注册登记机构注册域名networkutopia.com。注册登记机构是一个商业实体,它验证该域名的唯一性,将该域名输入DNS数据库, 并对提供的服务收取少量费用。
向某注册登记机构注册域名时,需要向该机构提供你的基本和辅助权威DNS服务器的名字和IP地址。对这两个权威 DNS 服务器 的每一个,该注册登记机构确保将一个类型 NS 和一个类型A 的记录输入顶级域DNS服务器(如下所示)。
(networkutopia. com, dns1. networkutopia.com, NS)
(dns1.networkutopia. com, 212 .212.212.1, A)
你还需确保用于Web服务器www. networkutopia. com的类型 A 资源记录和用于邮件服务器mail.networkutopia.com的类型 MX 资源记录被输入你的权威 DNS 服务器中。完成所有这些步骤,人们将能够访问你的Web站点,并向你公司的雇员发送电子邮件。
P2P 文件分发
Web、 电子邮件和DNS都采用了客户-服务器体系结构,依赖于总是打开的服务器。使用 P2P 体系结构,对总是打开的服务器有最小的(或者没有)依赖。与之相反,成对连接的主机(称为对等方)彼此直接通信。这些对等方并不为服务提供商所拥有,而是受用户控制的主机。
本节研究一个非常自然的P2P应用,即从单一服务器向大量主机分发一个大文件。在客户-服务器文件分发中,该服务器必须向每个主机发送该文件的一个副本,即服务器承受了极大的负担,并且消耗了大量的服务器带宽。在P2P文件分发中,每个对等方能够向任何其他对等方重新分发它已经收到的该文件的任何部分,从而在分发过程中协助该服务器。
P2P 体系结构的扩展性
为了将服务器体系结构与P2P体系结构进行比较,考虑一个简单定量模型。如图所示

服务器和主机使用接入链路与因特网相连。其中 us表示服务器接入链路的上载速率,ui表示第i个主机接入链路的上载速率, di表示了第i个主机接入链路的下载速率。F表示被分发的文件长度(以比特计),一共有N个主机。分发时间是所有 N 个主机得到该文件的副本所需要的时间。在下面分析分发时间的过程中,我们对客户-服务器和P2P体系结构做了简化的假设, 即因特网核心具有足够的带宽,这意味着所有瓶颈都在网络接入链路。还假设服务器和客户没有参与任何其他网络应用,因此它们的所有上传和下载访问带宽能被全部用于分发该文件。
首先来确定对定客户-服务器体系结构的分发时间,在该体系结构中,没有对等方参与来帮助分发文件。服务器必须向 N个主机都传输该文件的一个副本。 因此该服务器必须传输 NF 比特。 因为该服务器的上载速率是 us , 分发该文件的时间必定是至少为 NF/us。令dmin表示最小下载速率的对等方的下载速率,dmin=min{d1, d2,...,dn}。具有最小下载速率的对等方不可能在少于 F/dmin秒时间内获得该文件的所有
F 比特。 因此最小分发时间至少为F/dmin。将最小上载时间与最小下载时间结合在一起,假设分发时间为Dcs,得到如下式子
D c s ⩾ max { N F u s , F d m i n } D_{\mathrm{cs}}\geqslant\max\left\{\frac{NF}{u_s},\frac{F}{d_{\mathrm{min}}}\right\} Dcs⩾max{usNF,dminF}
现在来对P2P体系结构进行简单的分析,其中每个对等方能够帮助服务器分发该文件。 特别是, 当一个对等方接收到某些文件数据,它能够使用自己的上载能力重新将数据分发给其他对等方。计算P2P体系结构的分发时间在某种程度上比计算客户-服务器体 系结构的更为复杂,因为分发时间取决于每个对等方如何向其他对等方分发该文件的各个部分。
在分发的开始,只有服务器具有文件。为了使这些对等方得到该文件,该服务器必须经其接入链路至少发送该文件的每个比特一次。 因此,最小分发时间至少是F/us。(与客户-服务器方案不同,由服务器发送过一次的比特可能不必 由该服务器再次发送,因为对等方在它们之间可以重新分发这些比特)
与客户-服务器体系结构相同,具有最低下载速率的对等方不能够以小于F/dmin 秒的分发时间获得所有F比特。 因此最小分发时间至少为F/dmin。
最后,观察到系统整体的总上载能力等于服务器的上载速率加上每个单独的对等方的上载速率,即 utotal = us + u1 + ... + un。 系统必须向这 N 个对等方的每个交付 (上载) F 比特,因此总共交付NF 比特。这不能以快于utotal的速率完成。 因此, 最小的分发时间也至少是NF/(us +u1 +...+ uN)。
将这三个观察放在一起,我们获得了对P2P的最小分发时间,表示为Dp2p
D P 2 P ⩾ max { F u s , F d m i n , N F u s + ∑ i = 1 N u i } D_{\mathrm{P2P}}\geqslant\max\left\{\frac{F}{u_{s}},\frac{F}{d_{\mathrm{min}}},\frac{NF}{u_{s}+\sum_{i=1}^{N}u_{i}}\right\} DP2P⩾max{usF,dminF,us+∑i=1NuiNF}
BitTorrent
BitTorrent 是一种用于文件分发的流行P2P 协议。用 BitTorrent 的术语来讲,参与一个特定文件分发的所有对等方的集合被称为一个洪流 (torrent)。在一个洪流中的对等方彼此下载等长度的文件块 (chunk) , 典型的块长度为256KB。当一个对等方首次加入一个洪流时,它没有块。 随着时间的流逝,它累积了越来越多的块。 当它下载块时,也为其他对等方上载了多个块。 一旦某对等方获得了整个文件,它也许(自私地)离开洪流,或(大公无私地)留在该洪流中并继续向其他对等方上载块。同时,任何对等方可能在任何时候仅具有一些块就离开该洪流,并在以后重新加入该洪流中。
每个洪流具有一个基础设施节点,称为追踪器 (tracker)。 当一个对等方加入某洪流时,它向追踪器注册自己,并周期性地通知追踪器它仍在该洪流中。以这种方式,追踪器跟踪参与在洪流中的对等方。 一个给定的洪流可能在任何时刻具有数以百计或数以千计的对等方。

如图所示,当一个新的对等方Alice加入该洪流时,追踪器随机地从对等方的集合中选择一个子集(假设选50个对等方作为子集),并将这50个对等方的IP 地址发送给Alice。Alice持有对等方的IP地址列表,试图与该列表上的所有对等方创建并行的TCP连接。称所有这样与Alice成功地创建一个TCP连接的对等方为邻近对等方(邻居)。随着时间的流逝,这些对等方中的某些可能离开,其他对等方可能试图与Alice 创建TCP连接。 因此一个对等方的邻近对等方将随时间而波动。
在任何给定的时间,每个对等方将具有该文件的一部分块(或者说是具有该文件所有块的子集),并且不同的对等方具有不同的子集。Alice 周期性地(经TCP连接) 询问每个邻近对等方它们所具有的块列表。如果Alice 具有 L个不同的邻居,她将获得L个块列表。 有了这个信息, Alice 将对她当前还没有的块发出请求。
因此在任何给定的时刻,Alice具有块的子集并知道它的邻居具有哪些块。利用这些信息, Alice 将做出两个重要决定。 第一,她应当从邻居请求哪些块?第二,她应当向哪些向她请求块的邻居发送块?
第一,在决定请求哪些块的过程中,Alice使用一种称为 最稀缺优先的技术。这种技术的思路是,某一个她没有的块,并在她的邻居中副本数量最少,请求这些最稀缺的块。
第二,为了决定她响应哪个邻居的请求,BitTorrent 使用了一种对换算法。其基本想法是,Alice 根据当前能够以最高速率向她提供数据的邻居,给出其优先权。Alice 对于她的每个邻居都持续地测量接收到比特的速率,并确定以最高速率流入的4个邻居。每过 10 秒,她重新计算该速率并可能修改这4个对等方的集合。此外,每过30 秒,她也要随机地选择另外一个邻居并发送块。假设该邻居称为Bob,她可能成为Bob前4位上载者之一,这样的话 Bob将开始向 Alice 发送数据。如果 Bob 向 Alice 发送数据的速率足够高, Bob 接下来也能成为Alice 的前4 位上载者。这种效果是对等方能够以趋向于找到彼此的协调的速率上载。
关于BitTorrent还有一些机制没有提到,包括片(小块)、流水线、随机优先选择、 残局模型和反怠慢。顺带提一下另一种P2P应用一一分布式散列表 (DHT)
视频流和内容分发网
因特网视频
在流式存储视频应用中,基础的媒体是预先录制的视频,例如电影、电视节目、录制好的体育事件或录制好的用户生成的视频(如在 YouTube 上可见的那些)。这些预先录制好的视频放置在服务器上,用户按需向这些服务器发送请求来观看视频。
视频是一系列的图像,通常以一种恒定的速率(如每秒24或30张图像)来展现。一幅未压缩、 数字编码的图像由像素阵列组成,其中每个像素是由一些比特编码来表示亮度和颜色。 视频的一个重要特征是它能够被压缩,因而可用比特率来权衡视频质量。
压缩的因特网视频的比特率范围通常从用于低质量视频的100kbps, 到用于流式高分辨率电影的超过3Mbps, 再到用于4K流式的超过 10Mbps。 粗略地说,比特率越高,图像质量越好,用户的总体视觉感受越好。
我们也能使用压缩生成相同视频的多个版本,每个版本有不同的质量等级。 例如,我们能够使用压缩生成相同视频的3个版本,比特率分别为300kbps、1Mbps和3Mbps。 用户则能够根据他们当前可用带宽来决定观看哪个版本。
HTTP 流和 DASH
在 HTTP 流中,视频只是存储在HTTP服务器中作为一个普通的文件,每个文件有一个特定的 URL。 当用户要看该视频时, 客户与服务器创建一个TCP连接并发送对该 URL的 HTTP GET请求。 服务器则以底层网络协议和流量条件允许的尽可能快的速率, 在一个HTTP 响应报文中发送该视频文件。在客户一侧,视频文件字节被收集在客户应用缓存中, 一旦该缓存中的字节数量超过预先设定的门限,客户应用程序就开始播放,特别是,流式视频应用程序周期性地从客户应用程序缓存中抓取帧,对这些帧解压缩并且在用户屏幕上展现。因此,流式视频应用接收到视频就进行播放,同时缓存该视频后面部分的帧。
尽管HTTP流在实践中已经得到广泛部署,但它具有严重缺陷,即所有客户接收到相同编码(这里指比特率)的视频。不同时间,不同客户可用的带宽大小有很大不同。这导致了一种新型基于HTTP 的流的研发, 它被称为经 HTTP 的动态适应性流(DASH)。在 DASH 中, 视频编码为几个不同的版本,其中每个版本具有不同的比特率,对应于不同的质量水平。 客户动态地请求来自不同版本且长度为几秒的视频段数据块。 当可用带宽量较高时,客户自然地选择来自高速率版本的块;当可用带宽量较低时,客户自然地选择来自低速率版本的块。
DASH 允许客户使用不同的以太网接入速率流式播放具有不同编码速率的视频。使用低速连接的客户能够接收一个低比特率(和低质量)的版本,使用光纤连接的客户能够接收高质量的版本。 如果端到端带宽在会话过程中改变的话, DASH允许客户适应可用带宽。
使用 DASH后,每个视频版本存储在 HTTP 服务器中,每个版本都有一个不同的URL。 HTTP 服务器也有一个告示文件 , 为每个版本提供了一个 URL 及其比特率。 客户首先请求该告示文件并且得知各种各样的版本。 然后客户通过在HTTP GET 请求报文中对每块指定一个 URL 和一个字节范闱, 一次选择一块。在下载块的同时, 客户也测量接收带宽并运行一个速率决定算法来选择下次请求的块。 自然地、如果客户缓存的视频很多,并且测量的接收带宽较高, 它将选择一个高速率的版本。 同样,如果客户缓存的视频很少,并且测量的接收带宽较低,它将选择一个低速率的版本。因此 DASH允许客户自由地在不同的质量等级之间切换。
内容分发网
今天,许多因特网视频公司日复一日地向数以百万计的用户按需分发每秒数兆比特的流。对于一个因特网视频公司,或许提供流式视频服务最为直接的方法是建立单一的大规模数据中心,在数据中心中存储其所有视频, 并直接从该数据中心向世界范围的客户传输流式视频。 但是这种方法存在三个问题。 首先,如果客户远离数据中心,服务器到客户的分组将跨越许多通信链路并很可能通过许多 ISP, 其中某些 ISP 可能位于不同的大洲。 如果这些链路之一提供的吞吐量小于视频消耗速率,端到端吞吐量也将小于该消耗速率,给用户带来恼人的时延。
第二个缺陷是流行的视频很可能经过相同的通信链路发送许多次。 这不仅浪费了网络带宽,因特网视频公司自己也将为向因特网反复发送相同的字节而向其ISP运营商(连接到数据中心)支付费用。第三个问题是单个数据中心代表一个单点故障,如果数据中心或其通向因特网的链路崩溃,它将不能够分发任何视频流了。
为了应对向分布千全世界的用户分发巨量视频数据的挑战,几乎所有主要的视频流公司都利用内容分发网(CDN)。CDN 管理分布在多个地理位置上的服务器, 在它的服务器中存储视频(和其他类型的 Web 内容,包括文档、图片和音频)的副本,并且所有试图将每个用户请求定向到一个将提供最好的用户体验的 CDN 位置。
CDN 通常采用两种不同的服务器安置原则
- 深入。 第一个原则由 Akamai 首创,该原则是通过在遍及全球的接入ISP 中部署服务器集群来深入到ISP 的接入网中。其目标是靠近端用户,通过减少端用户和CDN 集群之间链路和路由器的数量,从而改善了用户感受的时延。 因为这种高度分布式设计,维护和管理集群的任务成为挑战。
- 邀请做客。 第二个设计原则由 Limelight 和许多其他 CDN 公司所采用,该原则是通过在少量(例如10个)关键位置建造大集群。不是将集群放在接入ISP 中,这些 CDN 通常将它们的集群放置在因特网交换点 (IXP) 。 与深入设计原则相比,邀请做客设计通常产生较低的维护和管理开销,可能以对端用户的较高时延和较低吞吐量为代价。
一旦CDN 的集群准备就绪,它就可以跨集群复制内容。CDN 可能不希望将每个视频的副本放置在每个集群中,因为某些视频很少观看或仅在某些国家中流行。 事实上,许多CDN 没有将视频推入它们的集群,而是使用一种简单的拉策略:如果客户向一个未存储该视频的集群请求某视频,则该集群检索该视频(从某中心仓库或者从另一个集群),向客户流式传输该视频,并同时在本地存储一个副本。
CDN 操作
当用户主机中的一个浏览器指令检索一个特定的视频(由 URL标识)时,CDN 必须截获该请求,以便能够: 1.确定此时适合用于该客户的 CDN 服务器集群; 2.将客户的请求重定向到该集群的某台服务器。
大多数CDN利用 DNS 来截获和重定向请求。用一个简单的例子来说明通常是怎样涉及DNS的。

假定一个内容提供商 NetCinema,雇佣了第三方CDN 公司 KingCDN 来向其客户分发视频。在NetCinema 的 Web网页上(www.NetCinema.com),它的每个视频都被指派了一个URL。该 URL也许包括了字符串video以及该视频本身的独特标识符;例如,http://www. Netcinema. com/video/6Y7B23V 接下来出现如图所示的6个步骤:
- 用户访问位于 NetCinema 的Web 网页。
- 当用户点击链接http://www. Netcinema. com/video/6Y7B23V时(相当于点击了某视频),该用户主机发送了一个对于该http://www. Netcinema. com/video的DNS请求。(这是由于该内容提供商NetCinema的视频内容没有放在他们自己的服务器上,而是由第三方CDN公司来分发视频内容。那么就要重新获取到该CDN公司的服务器IP地址)
- 用户的本地DNS服务器将该 DNS 请求中继到一台用于NetCinema 的权威DNS 服务器,该服务器观察到主机名中的字符串 ''video"。 为了将该 DNS请求移交给 KingCDN, NetCinema 权威 DNS 服务器并不返回一个 lP 地址,而是向本地DNS服务器返回一个 KingCDN 域的主机名。
- 从这时起, DNS请求进入了 KingCON专用 DNS 基础设施。 用户的本地DNS服务器发送第二个请求,KingCDN 的 DNS 系统最终返回 KingCDN 内容服务器的 IP地址。所以正是在这里,在 KingCDN 的 DNS 系统中,指定了 CDN 服务器、客户将能够从这台服务器接收到它的内容。
- 本地DNS服务器向用户主机转发内容服务 CDN 节点的 IP地址。
- 客户与内容服务 CDN 节点的服务器创建了一条直接的TCP连接,并且发出对该视频的 HTTP GET请求。
集群选择策略(不作介绍,书中的太过简略)
任何CDN 部署,其核心是集群选择策略,, 即动态地将客户定向到 CDN 中的某个服务器集群或数据中心的机制。一种简单的策略是指派客户到地理上最为邻近的集群。
套接字编程:生成网络应用
之前讲过,典型的网络应用是由一对程序(即客户程序和服务器程序)组成的,它们位于两个不同的端系统中。 当运行这两个程序时,创建了一个客户进程和一个服务器进程,同时它们通过从套接字读出和写入数据在彼此之间进行通信。 开发者创建一个网络应用时,其主要任务就是编写客户程序和服务器程序的代码。客户端和服务器端通信协议可能是由RFC 2616 (或其他某种开放的标准文档)明确定义,那么如果一个开发者编写客户程序的代码, 另一个开发者编写服务器程序的代码,并且两者都完全遵从该 RFC 的各种规则,那么这两个程序将能够互相通信。
部分网络应用程序是专用的网络应用程序。在这种情况下,客户和服务器程序应用的应用层协议没有公开发布。 则其他开发者将不能开发出和该应用程序交互的代码。
在本节中,将实现一个非常简单的客户-服务器应用程序代码。在研发阶段,开发者必须做的一个决定是, 应用程序是运行在TCP上还是运行在 UDP上。 我们通过一个简单的 UDP 应用程序和一个简单的TCP应用程序来介绍 UDP和TCP套接字编程。
UDP 套接字编程
运行在不同机器上的进程彼此通过向套接字发送报文来进行通信。在发送进程将分组推入套接字,当使用 UDP时,必须先将目的地址附在该分组之上。通过在分组中包括目的地的 IP地址,因特网中的路由器将能够通过因特网将分组选路到目的主机。 当分组到达接收套接字时,接收进程将通过该套接字取回分组, 然后检查分组的内容并采取适当的动作。但是因为一台主机可能运行许多网络应用进程,每个进程具有一个或多个套接字,所以在目的主机指定特定的套接字也是必要的。 当生成一个套接字时,就为它分配一个称为端口号的标识符。因此,分组的目的地址也包括该套接字的端口号。发送方的源地址也是由源主机的 IP地址和源套接字的端口号组成,该源地址也要附在分组之上(由底层操作系统自动完成)。
我们将使用下列简单的客户-服务器应用程序来演示对于UDP和TCP的套接字编程:
- 客户从其键盘输入一行字符(数据)并将该数据向服务器发送。
- 服务器接收该数据并将这些字符转换为大写。
- 服务器将修改的数据发送给客户。
- 客户接收修改的数据并在其显示器上将该行显示出来。

如上图所示,显示了客户和服务器的主要与套接字相关的活动,两者通过 UDP运输服务进行通信。
我们将以 UDP客户开始,该程序将向服务器发送一个简单的报文。 服务器为了能够接收并回答该客户的报文,它必须准备好并已经在运行,这就是说,在客户发送其报文之前,服务器必须作为一个进程正在运行。
csharp
# UDPClient. py
from socket import * # 导入socket的包
serverName = 'hostname' # 目的主机名(会启用DNS服务解析主机名获取IP地址),也可以直接是目的主机IP地址。
serverPort = 12000 # 端口号
clientSocket = socket(AF_INET, SOCK_DGRAM) # 创建套接字对象,第一个参数表明网络使用了IPv4,第二个参数表明是UDP
message = raw_input(' lnput lowercase sentence:') # 从键盘输入
clientSocket.sendto(message.encode(), (serverName, serverPort)) # 发送
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) # 获取响应数据
print(modifiedMessage.decode ()) # 打印响应数据
clientSocket.close() # 关闭套接字
csharp
# UDPServer. py
from socket import * # 导包
serverPort = 12000 # 设置端口号
serverSocket = socket(AF_INET, SOCK_DGRAM) # 创建套接字
serverSocket.bind (('', serverPort)) # 将套接字与端口号绑定
print (''The server is ready to receive")
while True:
message, clientAddress= serverSocket.recvfrom(2048) # 获取分组中的数据,源地址
modified.Message= message.decode().upper() # 将数据转换成大写
serverSocket.sendto{modifiedMessage.encode(), clientAddress) # 将转换后的数据发给源地址
TCP 套接字编程
与 UDP 不同, TCP 是一个面向连接的协议。 这意味着在客户和服务器能够开始互相发送数据之前,它们先要握手和创建一个TCP连接。 TCP连接的一端与客户套接字相联系,另一端与服务器套接字相联系。 当创建该TCP连接时,我们将其与客户套接字地址(lP 地址和端口号)和服务器套接字地址 (IP地址和端口号)关联起来。 使用创建的TCP连接,当一侧要向另一侧发送数据时,它只需经过其套接字将数据丢进TCP连接。
现在我们仔细观察一下TCP 中客户程序和服务器程序的交互。服务器为了能够对客户的连接做出反应, 服务器进程必须已经在运行。随后,该客户生成TCP套接字,并指定了服务器中的套接字的地址,即服务器主机的IP地址及其套接字的端口号。生成其套接字后,该客户发起了一个三次握手并创建与服务器的一个TCP连接。在三次握手期间,客户进程初次与serverSocket 的 TCP套接字对象接触,该TCP套接字允许建立连接后,会再创建一个连接套接字(connectionSocket) 与客户进行通信。
csharp
# TCPClient. py
from socket import * # 导包
serverName = 'servername' # 服务器主机名或IP地址
serverPort = 12000 # 端口号
clientSocket = socket(AF_INET, SOCK_STREAM) # 套接字声明 第二个参数表示为TCP
clientSocket.connect((serverName, serverPort)) # 发起TCP连接 参数为主机名和端口号
sentence = raw_input('Input lowercase sencence:') # 键盘输入数据
clientSocket.send(sentence.encode()) # 发送数据
modifiedSentence = clientSocket.recv(1024} # 接收回复
print('From Server:', modifiedSentence.decode()) # 打印数据
clientSocket. close()
csharp
# TCPServer. py
from socket import * # 导包
serverPort = 12000 #设置端口号
serverSocket = socket(AF_INET, SOCK_STREAM} # 创建TCP套接字
serverSocket.bind(('', server Port)) # 套接字关联端口
serverSocket.listen(1) # 等待连接
print(' The server is ready to receive ' )
while True:
connectionSocket, addr = serverSocket.accept() # 允许连接,创建连接套接字
sentence = connectionSocket.recv(1024).decode () # 接收数据
capitalizedSentence = sentence.upper () # 转换数据
connectionSocket.send(capitalizedSentence.encode()) # 发送转换后的数据
connectionSocket.close()
参考目录
书籍:《计算机网络:自顶向下方法(第七版)》
https://blog.csdn.net/weixin_48024605/article/details/132426862