到第二章我们就将进入具体5层协议栈的描述,更具体地说,是从顶层应用层开始,渐进地学习计算机网络。我们知道,应用层是在传输层提供的服务上实现应用进程通信的,也就是借助操作系统使用Socket API的方式实现的。那基于Socket API应用层都实现了服务呢?常用的有Web,Http,FTP,Email,DNS,CDN。在讲述这些具体应用之前,我们会先对通用的原理作以介绍。然后展开描述这些应用层的服务。
好啦,了解了概况,就让我们出发吧。。。
在开始正式的内容之前,我们有必要先建立有意识的学习习惯。
为什么要学习应用层呢?应用层或者更具体的说是应用程序,应用程序是互联网存在的理由,在其以下的四层,它们的确实现了很多功能,但这些功能如果不能被集成应用到一个应用程序上,那分层的结构就只是纸上谈兵罢了。
而应用层也是与我们日常生活最贴近的一层,我们与电脑上可以实现人机交互的绝大部分应用都是应用层的应用。
好啦,说了这么多,无非想表达一个观点,应用层很重要。
我们将从应用层协议原理起笔,浓墨重彩的描述Web,Http,Ftp,Email,DNS,这几个应用层协议,落笔于P2P应用与CDN。
任何复杂的系统实现都是从一个小的灵感异或想法开始的。
假如我们想做一个交易旧书的网站,并且让它实际的运营,那我们应该从哪里点亮这个火光呢?
研发网络应用程序的核心是写出能够运行在不同的端系统和通过网络彼此通信的程序。
也就是说我们重点关注如何用程序实现端系统间通信,而程序的书写首先必须是基于框架的,互联网应用的体系结构分为两种:CS架构和P2P架构。
CS服务体系指的是服务器和客户端组成的架构。
服务器
:服务器是一直运行着的,通过固定IP与端口号来访问的,可扩展性较差的终端设备;
客户端
:客户端在网络中以动态IP的方式标识,并主动与服务器建立通信。
在P2P体系结构中,任何端系统既可以是客户端,也可以是服务器,这也使得它们的可扩展性强,又因为没有限制,所以任意两台端设备之间可以进行通信。但这也导致难以有效的管理体系中的端系统,在现实中的应用场景包括迅雷,Gnutella。
我们发现,P2P的负载均衡能力强,而CS架构更容易维护,那可不可以取其所长,来做一种CS与P2P体系结构的混合体呢?
别说,还真有人实现了。一个大学生做了一个名为Napster的应用。这个应用是用来提供文件传输的。一个用户想查询该系统中是否存在某个文件,首先她的主机向中心服务器查询资源的位置,要实现这个功能就需要使用CS体系,即每台主机上线以后,会在中心服务器上注册它所拥有的的资源。查询到存在这个资源后,中心服务器会返回给主机这个资源所在的主机位置,最后使用P2P体系完成两个节点间的文件传输即可。
再回到核心问题,在确定使用哪种体系的前提下,我们还需要解决通信的问题。而通信的实体是进程,也就是要解决分布式进程间通信的问题。但这个问题是宽泛的,我们不妨分而治之——
换个角度来思考这三个问题,就是怎样找到要服务的用户,这个服务怎样提供给用户,用户怎样使用这个服务。带着这三个问题,我们出发吧——
我们通常使用32位的IP地址来唯一的标识一台主机,在主机之上,用端口号来标识特定的进程。比如HTTP,TCP的默认端口号就为80.进程可以使用TCP或者UDP的传输层协议进行通信。
前面提到,传输层通过接口向应用层提供服务。这个接口需要携带一些通信必须的信息,这种信息至少应该包含:要传输的报文,这个报文要传给谁,这个报文是从哪里传输来的。
官方地说,我们需要知道SDU,IP+TCP(UDP)端口,对方的IP+TCP(UDP)端口。
但每次通过接口通信使单独传输这些信息实在有点繁琐。所以,将这些信息封装变得很有必要。
应用进程通过一个称为套接字的软件接口向网络中发送报文和接收报文。
所谓套接字,也就是Socket .那如何来理解这个socket呢?
我们先从它的作用入手**,socket是用于指明应用进程会话的本地标识**,我们可以打个比方来形象地理解——
进程可类比于一座房子,而它的套接字可以类比于它的门。当一个进程想向位于另外一台主机上的另一个进程发送报文时,它把报文推出该门(套接字)。该发送进程假定该门到另外一侧之间有运输的基础设施,该设施将把报文传送到目的进程的门口。一旦该报文抵达目的主机,它通过接收进程的门(套接字)传递,然后接收进程对该报文进行处理。
再从它的含义来看,socket是操作系统生成的一个整数,这个整数可以理解为传输信息的引用。这些信息具体的说,即是包含源IP,源port,目标IP,目标port的四元组。
我们不必关注四元组的信息,而只需要关注socket的状态即可。操作系统的文件系统同样应用了这样的思想,在打开一个文件时,OS返回一个文件句柄,而不是使用这个文件的目录名,文件名,这样使用简单且易管理。
值得一提的是,四元组是针对TCP的socket信息,但对于使用UDP的服务来说,情况又有所不同。
使用UDP提供的服务时,两个进程之间的通信不需要建立连接,即不面向连接,每个报文都是独立传输的,它的前后报文也可能被送到不同的分布式进程,也就是说,对于UDP的socket,即只需指明报文将发往何处,而不必关心报文来自哪里。所以Socket中封装对方的IP及端口即可,
问题三需要回答的是怎样约定传输的格式,动作。这就需要定义应用层的协议,那这个协议的遵守是如何体现的呢?通过编制程序,使用API来调用网络基础设施来提供通信服务传报文,解析报文,实现应用时序等。
应用层的协议是如何定义的呢?它需要解决的具体包含哪些问题?
应用层的协议定义了运行在不同端系统上的应用进程如何相互交换报文
。
它需要包含报文的类型,报文类型对应的语法,报文中字段的语义,报文所导致的动作。
我们在学习工作时总会无可避免地访问web页面了,一个键盘,一声敲击,一个奇迹。这奇迹的背后似乎有迹可循
——
在我们浏览的网页中,包含图文,动画,视频等,为了更好地描述这些事物,它们有了通用的名字:对象。对象可以认为是HTML页面的最小组成单元。直观地看,在我们浏览一个页面时,不同的对象并非同时出现,显然它们有可能来自不同的目的地,那如何来标识由对象组成的HTML页面呢?
这就需要URL(统一资源定位符),URL表示资源的地点,即资源来自于互联网中哪个IP。如果只使用URL标识资源位置,未免有点宽泛。所以引入URI(统一资源标识符)就势在必行了。
URI用字符串标识资源的位置。则URL是URI的子集,具体看下符合RFC标准的URI格式——
协议方案名:
更多被使用的是http,但使用SSL安全传输层服务的https必然将取代http.登录信息:
即用户认证,默认是匿名访问。服务器地址:
即服务器的域名。服务器端口号:
这里的80端口指的就是TCP服务。文件路径:
不能再具体的描述了。查询字符串:
即参数,比如点开某件商品的详情页时需要附带该商品的ID与该客户的ID。片段标识符:
无题。了解了页面的组成,以及资源如何标识,我们再来看下页面是如何展示给用户的——
应用程序的URL
。HTML网页
。这么多的步骤,想来应该会比较耗时,这也是起初web被戏称World wide wait的原因,如今网络节点的增加与带宽的提高让网页的传输有了一丝飞一般的感觉~
我们知道应用层的应用程序都需要遵循规范,那web应用程序需要遵循什么规范呢?
自然是HTTP协议咯,HTTP协议全称是超文本传输协议。它的出现主要是为了解决文本传输的问题。一路走来经历了HTTP1.0,HTTP1.1,HTTP2.0.可见版本更迭异常缓慢。但这并不意味着HTTP不受人青睐。相反,由于它本身非常简单。开发者在它的基础上开发出了很多应用方法并投入使用,现在HTTP协议已经超出Web框架的局限,被用于各种各样的场景中。可见,简单也意味着丰富的可扩展性。
为了更好地理解HTTP,我们有必要先了解一下TCP/IP。
我们所用的网络都是在TCP/IP协议族的基础上运行的,它约定了端系统间相互通信的规则。因为后面的小结会涉及到具体的TCP/IP协议族,这里我们只给出一个框架——
在TCP/IP协议族提供的网络基础上,HTTP按部就班地传输着报文。接下来我们将剥茧抽丝地解析报文传输的过程:
在这张图中,可以看到HTTP协议的职责在于生成目标Web服务器的请求报文,及对Web服务器请求内容的处理。
HTTP报文都是以ASCII码的形式与人交互,请求报文和响应报文的格式是这样的——
请求行中包括请求的方法,使用的协议及版本。
Host:
请求资源所在的服务器,比如WWW.Baidu.comUser-Agent:
HTTP客户端程序的信息Accept:
用户代理可以处理的媒体类型。Accept-Language:
优先的语言。Accept-Encoding:
优先的内容编码。DNT:
请求追踪字段,即用户不希望被追踪。Connection:
主要有两个用途:控制不再转发给代理的首部字段和持久连接的状态管理Pragma:
报文的指令。Cache-Control:
控制浏览器端缓存的行为其他的首部还有很多,我们就不一一列举了。
同样的,通过一个小例子我们简要认识一下HTTP响应报文——
首先是状态行,它的信息包括协议类型,版本,状态码,状态码对应的解释。
再来看字段的解析:
Date:
服务器端创建报文的日期时间,必选项。Server:
用于告知客户端当前服务器上安装的HTTP服务器应用程序的信息。Last-Modified:
资源最终修改的时间。ETag:
资源的匹配信息,只有当If-Match的字段值与ETag值匹配时,服务器才会接收请求。Accept-Ranges:
是否接收字节范围请求,当我们只需要一部分数据时,可以加这个参数来减少网络链路的带宽压力。Content-Length:
实体主体的长度,它的单位是字节,TCP提供的是字节流的服务,他不会维护信息的边界,所以需要一些辅助信息。Connection:
连接的状态,可选Keep-alive和CloseConnect-Type:
实体类型我们再从层间接口的角度来看Web是如何工作的。。。
HTTP协议使用是基于TCP的传输层服务,则服务器的80号端口会始终处于等待状态,客户端发起请求后,服务器端的守护socket在接收到请求以后,会生成一个该请求独有的socket,这个socket表示服务器端与特定客户端的会话关系。
HTTP协议是一个无状态的协议,它不会维护历史信息,这使得它能够支持的用户数量多,但比如说在登录网页以后,维护登录状态又是必不可少的。
那如何使它可以记录状态呢?对HTTP协议本身做文章未免不是开发者的初衷,所以由客户端来维护历史信息,由于要维护的历史信息比较多,这里与用户交互的不是信息本身,而是指向这些信息的引用,小甜饼Cookie就这样应运而生了。
我们通过下图来认识一下Cookie机制——
HTTP1.0协议是不支持持久连接的,也就是每次发送报文时,都需要建立TCP连接——
这样每次请求时都会造成无谓的TCP连接和断开,增加通信量的消耗。
因此在HTTP1.1中引入了持久连接,持久连接是指如果没有一方明确要求断开连接时,这个连接会一直保存。也就是说,持久连接是双方端系统需要设置的,而不是在通信过程中设定。
持久连接使多数请求以管线化方式发送称为可能。这就可以实现高并发发送。类似于流水线。
我们通过搜索引擎来获取想要的web页面,那搜索引擎是如何找到web页面的呢?它会通过网络爬虫爬取到HTTP的首部字段,使用首部字段中的关键信息来建立索引,实现高效查找。
请求行:
这里我们主要描述用于告知服务器意图的方法:
状态行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEUWoAzS-1613959211494)(http://qnuez6z2b.hn-bkt.clouddn.com/typora/image-20210220190153920.png)]假设有这样一种需求,客户请求服务器资源时因为链路太长导致响应时间慢,老板要求你解决这个问题,这时根据你的经验有两种处理方式——
缓存应该是学习的一大热点了,但凡缓存,就需要注意缓存一致性问题。首先我们要能发现缓存是否一致,也就是通过加时间字段。如果字段值超时了Web代理就丢弃缓存内容。
在开发应用时,FTP的身影并不多,唯一一次见证它的背影是在使用Maven找包的时候,给我的直观感觉就是,这不就是文件系统嘛~
鉴于它太古老了,我们只是简要描述一下它工作的流程——
这是一个么得感情的分割线~
Email不同于FTP,它在工作交流,面试简历提交等方面应用非常广泛。Email由三部分构成:用户代理,也就是客户端软件,邮件服务器,协议。
我们同样复现EMail的工作流程——
Email使用7位的ASCII码编码,而中文需要两个字节描述,如果使用7位ASCII码传输时就会乱码。为了防止乱码,使用MIME多媒体扩展协议,这是基于Base64的编码,即将ASCII码序列化再传输。
而使用POP3的拉取协议,显然不应该记录邮件都经由谁手,在什么时间写好这种敏感信息,所以它被设计为无状态协议。
DNS不同于其他应用层协议,它是用来为其他应用协议提供服务的协议,DNS为人所知的功能是完成域名到IP地址的转换,且非常健壮。而这个转化实质上是由DNS系统来完成的。
系统的设计是为了解决复杂问题,而DNS系统解决的核心问题就是如何完成域名到IP地址的转换,我们再将这个问题拆解为三个小问题——
如果我们想访问一个网站,那就需要在地址输入栏中输入网站的URI,而URI中显然需要包含易记的用来标识端系统位置的东西,这就是域名,域名是一个端系统的别名,易于记忆,显然www.baidu.com比起纯数字的方式交互起来友好地多。
既然有别名,那就一定有正规名称,也就是IP地址.IP地址被用来表示一台主机,同时也用于寻址。IP地址分为IPV4和IPV6两种。IPV4使用点分十进制的书写习惯,共有32位,IPV6则有128位,可标识的主机数量非常多。
显然,日常使用时域名显然是更好的选择,域名在初期使用没有分层的结构,因为主机数量少,不会出现重名,只需要维护一张映射表就行了,维护起来游刃有余。
但当主机数指数级增多以后,重名变得不可避免,分层式的命名开始大势所趋。这时只维护一张表也显然不够了,分布式数据库被引入。而既然是应用层协议,自然会使用到传输层的服务,DNS使用的就是相对简单的UDP协议,可见网络核心的功能其实是在网络边沿实现的,网络核心只要保证传输就ok了。
再说回层次树状化的命名,首先从根开始,也就是顶级域名,顶级域名对应的是顶级服务器,全世界只有13台顶级,即根服务器。而且中国并没有,有人担忧万一根服务器被黑了,网络安全是否变的不可靠了。但实际上这对网络安全并没有什么大的影响。分布式足以解决这一看起来很棘手的问题。在这些顶级域下,又有很多子域,子域的类型分为两种,即通用的和国家的。最终演化到树叶,就是主机啦。
如果将DNS的命名规则平面化的话,它可以这样表示:
假如我们要访问耶鲁大学AI学院的网站。通俗的方式是首先找到顶级域名,再找到通用域名edu,然后找到edu下的yale,接着发现cs,最后定位到AI。从它宽泛的命名框架来看,域名适用于任何主机,这就意味着域名不代表地域,它只是一个逻辑的概念,而非物理上的。
解析关系直观地看,可以维护一张映射表,但考虑到性能,我们使用专门的名字服务器来存储这样的一张表,但当主机变多时,一个服务器容易宕机,且不好维护。
这时需要演化为分布式的名字服务器,而完成其功能的实际上是分布式的数据库。
在每一个区域Zone内需要维护一个名字服务器,它存储有这个区域内所有主机Ip与域名的映射关系,同时它也应该满足寻址的要求,这就需要维护指向路径的指针。因其完备的功能,因此也称其为权威服务器。
那权威服务器是如何维护转换关系的呢?通过数据库中的资源记录。
资源记录使用RR格式来维护域名到IP(或者其他标识)的映射关系。它包含如下字段——
Domain_name:
域名,没什么可说的~Ttl:
生存时间,假如我现在要访问中国以外的一个域名,我所在区域的权威服务器会沿路径获取到这个域名与IP的映射关系,并将其是作为缓存记录下来,但它显然不常用,所以存活时间仅有两天。而对于区域内的域名,考虑到作为老大的职责,会长期缓存区域内小弟的映射关系。Class:
只要是因特网范围内的,值都为IN,在物联网时代,NOT IN将成为一种可能。Value:
可以是数字,域名或者ASCII码。Type:
资源记录的类型,它包括:
A
:name为主机,Value为IP地址CNMAE
:name为规范名字的别名,value是规范名字。NS
:name为域名,value为该域名权威服务器的名字MX
:value为name对应的邮件服务器的名字。那名字服务器是如何获取到远端的IP映射关系呢?
这里要分有缓存和没缓存两种情况讨论——
存在缓存
:直接返回没有缓存
:
递归查询
,即从根服务器开始递,再从目标服务器的权威服务器开始归迭代查询
,即依次遍历每种可能。关系的维护无非增删改查,假如要在上级域的名字服务器中增加一个映射关系,那就往数据库中添加两条记录,分别指向新增的子域的域名和域名服务器的地址。这样在下次访问时会方便很多。但缓存有可能出现不一致的问题,因而在某些时候删除也是必要的。
因为我在毕业后所将从事的工作和P2P的体系架构大抵是没有太多交集的,所以这一小节的知识点我只简要描述原理。
先入为主地说,P2P的产生是为了解决CS体系的缺陷,即服务器压力大,负载均衡能力弱,可靠性不好。而相应的,P2P体系在这些方面是要略胜一筹的。
P2P体系常常被应用在文件分发,流媒体,在这些应用场景中,peer节点的压力比较轻。
为了形象描述P2P体系,我们来看下面这张图——
这张图描述了两种体系结构随着客户端数量的增加,下载文件所消耗的时间,可以看到,CS体系呈现出线性的关系,而P2P则是类似于对数关系。
如果单纯从下载性能方面来看的话,P2P早就风靡一时了。但因为它难以管理的缺陷,导致了它应用的并不多。
我们来了解通用的P2P的管理模式——
非结构化的P2P:
邻居节点间存在互通有无的关系,形成覆盖网。典型的应用是文件共享,即通过集中目录的方式,用户首先键入关键字,然后目录服务器告诉用户有哪些节点拥有该资源,用户建立TCP连接,再去请求某节点的资源。拥有该资源后,将自己拥有该资源的信息上报目录服务器,流程结束。
但集中式的目录,存在单点故障,性能瓶颈,侵犯版权的问题。此外,查询时采用洪泛的方式有可能导致请求无限放大,所以有必要设置TTL或由节点维护历史信息避免成环。因此除了集中式的目录以外,还存在完全分布式和混合体的机制。混合体机制利用节点的不均匀性,某个对等点要么是属于一个组或者组长,查询时,上报组长,如果组内有,直接去请求,组内没有的话,则交由组长传递给其他组长。
那如何找到唯一地那个文件呢,首先通过输入框的描述信息找到该文件的哈希值,然后将哈希值作为唯一Id查询返回结果
这里还有一个有意思的模式,BitTorrow。在这种模式下,文件被分为很多块,每个节点各持一块,且每个节点维护一个位图,即BitMap,0标识不拥有此资源,1表示拥有此资源。为了使所有节点都拥有相同的资源,需要定期洪泛。
结构化的P2P:
节点与节点之间的关系是往往成网状或者环状的。在更新节点时始终维护节点间的关系,即根据哈希值更新环。
在互联网中,视频类的流量占据所有流量的大部分,这也使得带宽的增加有用武之地。那视频的流量为什么这么大呢?
视频是固定速度显示的图片序列,而图片是由许多像素点集成得到的。所以视频就会比较大,编码也会相应复杂一些。
那针对太大的视频,应该做如何的处理呢?这就首先要看网络视频的特点——
所以降低视频流量的方式就是降低码率并做时空上的压缩,比如我国的AVS编码方式。
我们再从整体的框架看,显然只用一台超级服务器来提供流式服务是不可能的,因为规模太大,并且存在异构性。因此提供流式服务的分布式服务器被广泛应用。
我们在平时观看视频时,有时会发生卡顿,并且提示我们缓存一会再看,如何解释它的原理呢?
互联网提供给我们的是存储视频的流化服务,它可以实现辺下载辺看,就像河流一般,我们在从缓冲区中取视频内容,实现了高并发的视频传输。
但不同的用户对于视频的要求不同,这可能受限于带宽,或者是设备。那如何解决这个问题呢?
采用动态自适应的流化技术DASH,服务器端并不会有选择性的发送内容,但它会将视频分块,每块有不同的解析度,不同的编码标准,为了为客户端标识不同块的URL,服务器还维护一个告示文件用以提供不同块的URL。
客户端首先获取到告示文件,然后周期性的测试服务器到客户端的带宽,根据带宽来实时地请求某一块,最后解析观看视频。
假如视频流量出现超高并发,比如春晚直播,如果仅从央视的服务器去请求资源的话,那你将体会到什么叫做超高频延迟。但我们实际观看时,貌似很流畅,这是为什么呢?
因为使用了CDN服务商的加速服务,加速的原理也很简洁,无非部署缓存节点。但如何部署呢?CDN服务商使用的部署策略通常有两种——
策略1
:在本地ISP的区域内部署缓存节点,这样离用户最近,但未免投入太高。策略2
:靠近ISP的数据中心部署,效果也很不错,而且部署的节点相对较少,减少了成本。步骤:
ICP预先部署资源到CDN节点
用户访问Netflix的注册网站;
重定向到部署在亚马逊云端的网站;
使用域名解析重定向的机制从最近的CDN节点中获取资源;
将动态自适应的资源返回给用户,
日拱一卒,功不唐捐。