《计算机网络·自顶向下方法》第 2 章 应用层
网络应用是计算机网络存在的理由:构想出任何有用的应用——>设计支持它们的网络协议。
在本章中,我们学习有关网络应用的原理和实现方面的知识。
我们从定义关键的应用层概念开始——包括应用程序所需要的网络服务、客户和服务器、进程、运输层接口。
我们详细考察几种网络应用程序——包括 Web、电子邮件、DNS、对等文件分发和视频流。
然后我们将讨论运行在TCP和UDP上的网络应用程序开发。特别是,我们学习套接字接口,并浮光掠影地学习用Python语言写的一些简单的客户-服务器应用程序。在本章结尾,我们也将提供几个有趣、有意义的套接字编程作业。
应用层是我们学习协议非常好的起点,它最为我们所熟悉。我们熟悉的很多应用就是建立在这些将要学习的协议基础上的。通过对应用层的学习,我们将很好地感受到协议的方方面面,了解到很多问题,这些问题在我们学习运输层、网络层及链路层协议时也同样会碰到。
2.1 网络应用原理
如何将想法转变为一种真实世界的网络应用?
研发网络应用程序的核心是写出能够运行在不同的端系统和通过网络彼此通信的程序。例如,在Web应用程序中,有两个互相通信的不同的程序——一个是运行在用户主机(桌面机、膝上机、平板电脑、智能电话等)上的浏览器程序;另一个是运行在Web服务器主机上的Web服务器程序。
服务器常常(但无疑并不总是)托管在数据中心中,如图2-1所示。
因此,当研发新应用程序时,你需要编写将在多个端系统上运行的软件。该软件能够用如C、Java或Python来编写。重要的是,你不需要写在网络核心设备如路由器或链路层交换机上运行的软件。即使你要为网络核心设备写应用程序软件,你也没有能力做到这一点。如我们在第1章所知,以及如图1-24所显示的那样,网络核心设备并不在应用层上起作用,而仅在较低层起作用,特别是在网络层及下面层次起作用。这种基本设计,即将应用软件限制在端系统(如图2-1所示)的方法,促进了大量的网络应用程序的迅速研发和部署。
2.1.1 网络应用体系结构——C&S、P2P
当进行软件编码之前,应当对应用程序有一个宽泛的体系结构计划。记住应用程序的体系结构明显不同于网络的体系结构(例如在第1章中所讨论的5层因特网体系结构)。
从应用程序研发者的角度看,网络体系结构是固定的,并为应用程序提供了特定的服务集合。
而应用体系结构(application architecture)由应用程序研发者设计,规定了如何在各种端系统上组织该应用程序。在选择应用程序体系结构时,现代网络应用程序中使用两种主流体系结构之一:
客户-服务器体系结构、对等(P2P)体系结构。
客户-服务器体系结构(client-server architecture):有一个总是打开的主机,称为服务器,它服务于来自许多其他称为客户的主机的请求。
一个典型的例子是Web应用程序,其中总是打开的Web服务器处理来自浏览器(运行在客户主机上)的请求。当Web服务器接收到来自某客户对某对象的请求时,它向该客户发送所请求的对象作为响 应。
客户-服务器体系结构的特征:
① 客户相互之间不直接通信;
② 另一个特征是该服务器具有固定的、周知的地址,该地址称为IP地址。
因为该服务器具有固定的、周知的地址,并且因为该服务器总是打开的,所以客户总是能够通过向该服务器的IP地址发送分组来与其联系。具有客户-服务器体系结构的非常著名的应用程序包括Web、FTP、Telnet和电子邮件。图2-2a中显示了这种客户-服务器体系结构。
客户-服务器应用中:常常会出现一台单独的服务器主机跟不上它所有客户请求的情况。为此,托管大量主机的数据中心(data center)常被用于创建强大的虚拟服务器。最为流行的因特网服务——如搜索引擎(如谷歌、Bing和百度)、因特网商务(如亚马逊、e-Bay和阿里巴巴)、基于Web的电子邮件(如Cmail和雅虎邮件)、社交网络(如脸书、Insagam、推特和微信),就运行在一个或多个数据中心中。一个数据中心可以有数十万台服务器,它们必须要供电和维护。此外,服务提供商必须支付不断出现的互联和带宽费用,以发送和接收到达/来自数据中心的数据。
P2P体系结构(P2P architecture)中:对位于数据中心的专用服务器有最小的(或者没有)依赖。相反,应用程序在间断连接的主机对之间使用直接通信,这些主机对被称为对等方。这些对等方并不为服务提供商所有,而是为用户的台式机和笔记本电脑所控制,大多数对等方驻留在家庭、大学和办公室。因为这种对等方通信不必通过专门的服务器,该体系结构被称为对等方到对等方的。
流行的P2P应用的例子是文件共享应用BitTorent。
P2P体系结构的最引人入胜的特性之一——自扩展性(self-scalability)。
例如,在一个P2P文件共享应用中,尽管每个对等方都由于请求文件产生工作负载,但每个对等方通过向其他对等方分发文件也为系统增加服务能力。P2P体系结构也是有成本效率的,因为它通常不需要庞大的服务器基础设施和服务器带宽(这与具有数据中心的客户-服务器设计形成鲜明对比)。然而,未来P2P应用由于高度非集中式结构,面临安全性、性能和可靠性等挑战。
2.1.2 进程通信
在构建网络应用程序前,还需要对程序如何运行在多个端系统上以及程序之间如何相互通信有基本了解。
进程(process):用操作系统的术语来说,进行通信的实际上是进程(process)而不是程序。一个进程可以被认为是运行在端系统中的一个程序。
当多个进程运行在相同的端系统上时,它们使用进程间通信机制相互通信——由端系统上的操作系统确定,且在本书中,我们并不特别关注同一台主机上的进程间通信,而关注运行在不同端系统上的进程间通信。
在两个不同端系统上的进程,通过跨越计算机网络交换报文而相互通信。发送进程生成并向网络中发送报文;接收进程接收这些报文并可能通过回送报文进行响应。图2-1显示了驻留在5层协议栈的应用层进程相互通信的情况。
(1)客户和服务器进程
网络应用程序由成对的进程组成,这些进程通过网络相互发送报文。
例如,在 Web应用程序中,一个客户浏览器进程与一个Web服务器进程交换报文。在一个 P2P文件共享系统中,文件从一个对等方中的进程传输到另一个对等方中的进程。
对每对通信进程,我们通常将这两个进程之一标识为客户,而另一个进程标识为服务器。
对于Web而言,浏览器是一个客户进程,Web服务器是一个服务器进程。对于P2P文件共享,下载文件的对等方标识为客户,上载文件的对等方标识为服务器。你或许已经观察到,如在P2P文件共享的某些应用中,一个进程能够既是客户又是服务器。在 P2P文件共享系统中,一个进程的确既能上载文件又能下载文件。无论如何,在任何给定的一对进程之间的通信会话场景中,我们仍能将一个进程标识为客户,另一个进程标识为服务器。
我们定义客户和服务器进程如下:
在一对进程之间的通信会话场景中,
客户(client):发起通信(即在该会话开始时发起与其他进程的联系)的进程被标识为客户;
服务器(server):在会话开始时等待联系的进程是服务器。
在Web中,一个浏览器进程向一个Web服务器进程发起联系,因此该浏览器进程是客户,而该Web服务器进程是服务器。在 P2P文件共享中,当对等方A请求对等方B发送一个特定的文件时,在这个特定的通信会话中对等方A是客户,而对等方B是服务器。
在不致混淆的情况下,我们有时也使用术语“应用程序的客户端和服务器端”。
在本章的结尾,我们将逐步讲解网络应用程序的客户端和服务器端的简单代码。
(2)进程与计算机网络之间的接口
如上所述,多数应用程序由通信进程对组成,每对中的两个进程相互发送报文。从一个进程向另一个进程发送的报文必须通过下面的网络。
套接字(socket)接口:进程通过一个称为套接字的软件接口向网络发送报文和从网络接收报文。
我们考虑一个类比来帮助我们理解进程和套接字。进程可类比一座房子、而它的套接字可以类比它的门。当一个进程想向位于另外一台主机上的另一个进程发送报文时,它把报文推出该门(套接字)。该发送进程假定该门到另外一侧之间有运输的基础设施,该设施将把报文传送到目的进程的门口。一旦该报文抵达目的主机,它通过接收进程的门(套接字)传递,然后接收进程对该报文进行 处理。
图2-3显示了两个经过因特网通信的进程之间的套接字通信(图2-3中假定由该进程使用的下面运输层协议是因特网的TCP协议)。
如该图所示,套接字是同一台主机内应用层与运输层之间的接口。由于该套接字是建立网络应用程序的可编程接口,因此套接字也称为应用程序和网络之间的应用编程接口(Applcation Programming Interface,API)。
应用程序开发者可以控制套接字在应用层端的一切,但是对该套接字的运输层端几乎没有控制 权。应用程序开发者对于运输层的控制仅限于:①选择运输层协议;②也许能设定几个运输层参数,如最大缓存和最大报文段长度等(将在第3章中涉及)。一旦应用程序开发者选择了一个运输层协议(如果可供选择的话),则应用程序就建立在由该协议提供的运输层服务之上。我们将在2.7节中对套接字进行更为详细的探讨。
(3)进程寻址
在一台主机上运行的进程为了向另一台主机上运行的进程发送分组,接收进程需要有一个地址。
为了标识该接收进程(destination),需要定义两种信息:
①目的主机的地址;②在目的主机中指定接收进程的标识符。
(目的主机:哪幢公寓;接收进程:哪个门牌号)
在因特网中,主机由其IP地址(IP address)标识——IP地址是一个32比特的量,且它能够唯一地标识该主机。
除了知道报文发送目的地的主机地址外,发送进程还必须指定运行在接收主机上的接收进程(更具体地说,接收套接字)。因为一般而言一台主机能够运行许多网络应用,所以这些信息是必要的
目的地端口号(port number)用于这个目的。
已经给流行的应用分配了特定的端口号。例如,Web服务器用端口号80来标识。邮件服务器进程(使用SMTP协议)用端口号25来标识。
用于所有因特网标准协议的周知端口号的列表能够在www.iana.org处找到。我们将在第3章中详细学习端口号。
2.1.3 可供应用程序使用的运输服务
前面讲过套接字是应用程序进程和运输层协议之间的接口。在发送端的应用程序将报文推进该套接字。在该套接字的另一侧,运输层协议负责从接收进程的套接字得到该报文。
包括因特网在内的很多网络提供了不止一种运输层协议。当开发一个应用时,必须选择一种可用的运输层协议。如何做出这种选择呢?最可能的方式是,通过研究这些可用的运输层协议所提供的服务,选择一个最能为你的应用需求提供恰当服务的协议。这种情况类似于……
(这一节介绍包括因特网在内的各种网络能够为应用层提供的各种服务——全集,下一节介绍因特网为应用层提供的服务——子集)
一个运输层协议能够为调用它的应用程序提供什么样的服务呢?
我们大体能够从四个方面对应用程序服务要求进行分类:
可靠数据传输、吞吐量、定时、安全性。
(1)可靠数据传输
如第1章讨论的那样,分组在计算机网络中可能丢失。例如,分组能够使路由器中的缓存溢出,或者当分组中的某些比特损坏后可能被丢弃。为了支持对数据完整度有高需求的应用,必须做一些工作以确保由应用程序的一端发送的数据正确并完全地交付给该应用程序的另一端。
如果一个协议提供了这样的确保数据交付服务,就认为提供了可靠数据传输(reliable data transfer)。
运输层协议能够潜在地向应用程序提供的一个重要服务是进程到进程的可靠数据传输。当一个运输协议提供这种服务时,发送进程只要将其数据传递进套接字,就可以完全相信该数据将能无差错地到达接收进程。
当一个运输层协议不提供可靠数据传输时,由发送进程发送的某些数据可能到达不了接收进程。这可能能被容忍丢失的应用(lost-tolerant application)所接受
在多媒体应用中,可以容忍小幅度的数据丢失,丢失的数据会引起播放的音频/视频出现小干扰,但不是致命的损伤。
(2)吞吐量
在第1章中我们引入了可用吞吐量的概念,
在沿着一条网络路径上的两个进程之间的通信会话场景中,
可用吞吐量就是发送进程能够向接收进程交付比特的速率。
因为其他会话将共享沿着该网络路径的带宽,并且因为这些会话将会到达和离开,该可用吞吐量将随时间波动。
这些观察导致另一种自然的服务,即:
运输层协议能够以某种特定的速率提供确保的可用吞吐量。
使用这种服务,应用程序能够请求 r bps的确保吞吐量,并且该运输协议能够确保可用吞吐量总是为至少 r bps。这样的确保吞吐量的服务将对许多应用程序有吸引力。
具有吞吐量要求的应用程序被称为带宽敏感的应用(bandwidth-sensitive application)。许多当前的多媒体应用是带宽敏感的,尽管某些多媒体应用可能采用自适应编码技术对数字语音或视频以与当前可用带宽相匹配的速率进行编码。
带宽敏感的应用具有特定的吞吐量要求,而弹性应用(elastic appication)能够根据当时可用的带宽或多或少地利用可供使用的吞吐量。电子邮件、文件传输以及Web传送都属于弹性应用。当然,我们永远不会嫌吞吐量太多的!
(3)定时
运输层协议也能提供定时保证。如同具有吞吐量保证那样,定时保证能够以多种形式实现。
一个保证的例子如:发送方注入套接字中的每个比特到达接收方的套接字不迟于100ms(最高时延)。
这种服务将对交互式实时应用程序有吸引力,所有这些服务为了有效性而要求数据交付有严格的时间限制。对于非实时的应用,较低的时延总比较高的时延好,但对端到端的时延没有严格的约束。
(4)安全性
最后,运输协议能够为应用程序提供一种或多种安全性服务。例如,在发送主机中运输协议能够加密由发送进程传输的所有数据,在接收主机中,运输协议能够在将数据交付给接收进程之前解密这些数据。这种服务将在发送和接收进程之间提供机密性,以防数据以某种方式在这两个进程之间被观察到。运输协议还能提供除了机密性以外的其他安全性服务——包括数据完整性、端点鉴别。
2.1.4 因特网提供的运输服务
至此,们已经考虑了计算机网络能够提供的通用运输服务。现在我们要更为具体地考察由因特网提供的运输服务类型。
因特网(更一般的是TCP/IP网络)为应用程序提供两个运输层协议——UDP、TCP。
TCP:Transmission Control Protocol
UDP:User Datagram Protocol
当你(作为一个软件开发者)为因特网创建一个新的应用时,首先要做出的决定是,选择UDP还是选择TCP。每个协议为调用它的应用程序提供了不同的服务集合。
图2-4显示了某些所选的应用程序的服务要求。
(1)TCP服务
TCP服务模型包括:面向连接服务、可靠数据传输服务。
当某个应用程序调用TCP作为其运输协议时,该应用程序就能获得来自TCP的这两种服务。
① 面向连接的服务:在应用层数据报文开始流动之前,TCP让客户和服务器相互交换运输层控制信息。这个所谓的握手过程(建立连接)提醒客户和服务器,让它们为大量分组的到来做好准备。在握手阶段后,一个TCP连接(TCP connection)就在两个进程的套接字之间建立了。
这条连接是全双工的——即连接双方的进程可以在此连接上同时进行报文收发。当应用程序结束报文发送时,必须拆除该连接。
② 可靠的数据传输服务:通信进程能够依靠TCP,无差错、按适当顺序交付所有发送的数据。当应用程序的一端将字节流传进套接字时,它能够依靠TCP将相同的字节流交付给接收方的套接字,而没有字节的丢失和冗余。
TCP还具有:拥塞控制机制。
这种服务不一定能为通信进程带来直接好处,但能为因特网带来整体好处。当发送方和接收方之间的网络出现拥塞时,TCP的拥塞控制机制会抑制发送进程(客户或服务器)。TCP拥塞控制也试图限制每个TCP连接,使它们达到公平共享网络带宽的目的。
---------------------------------------------------------------------------------------------------------------------------
TCP安全——无论TCP还是UDP都没有提供任何加密机制,这就是说发送进程传进其套接字的数据,与经网络传送到目的进程的数据相同(明文方式发送接收,未经加密),这就可能在任何中间链路被唤探和发现。因为隐私和其他安全问题对许多应用而言已经成为至关重要的问题,所以因特网界已经研制了TCP的加强版本——运输层安全(Transport Layer Securiy,TLS)。
用TLS加强后的TCP不仅能够做传统的TCP所能做的一切,而且提供了关键的进程到进程的安全性服务,包括加密、数据完整性、端点鉴别。
我们强调TLS不是与TCP和UDP在相同层次上的第三种因特网运输协议,而是一种对TCP的加强,这种强化是在应用层上实现的。特别是,如果一个应用程序要使用TLS的服务,它需要在该应用程序的客户端和服务器端包括TLS代码(利用现有的、高度优化的库和类)。
TLS有它自已的套接字API,这类似于传统的TCP套接字API。当一个应用使用TLS时,发送进程向TLS套接字传递明文数据;发送主机中的TLS则加密该数据,并将加密的数据传递给TCP套接字。加密的数据经因特网传送到接收进程中的TCP套接字。该接收套接字将加密数据传递给TLS,由其进行解密。最后,TLS通过它的TLS套接字将明文数据传递给接收进程。第8章详细讨论TLS
---------------------------------------------------------------------------------------------------------------------------
(2)UDP服务
UDP是一种不提供不必要服务的轻量级运输协议,它仅提供最低限度的服务。
UDP是无连接的,因此在两个进程通信前没有握手过程。
UDP提供一种不可靠数据传输服务,也就是说,当进程将一个报文发送进UDP套接字时,UDP并不保证该报文将到达接收进程。不仅如此,到达接收进程的报文也可能是乱序到达的。
UDP不包括拥塞控制机制,所以UDP的发送端可以用它选定的任何速率向其下层(网络层)注入数据。(然而,值得注意的是实际端到端吞吐量可能小于该速率,这可能是由中间链路的带宽受限或拥塞而造成的。)
(3)因特网运输协议不提供的服务
我们已经从4个方面组织了运输协议服务:可靠数据传输、吞吐量、定时、安全性。
TCP和UDP提供了这些服务中的哪些呢?我们已经注意到TCP提供了可靠的端到端数据传输。并且我们也知道TCP在应用层可以很容易地用TLS来加强以提供安全服务。但在我们对TCP和UDP的简要描述中,明显地漏掉了对吞吐量、定时保证的讨论,即目前的因特网运输协议并没有提供这些服务。这是否意味着诸如因特网电话这样的时间敏感应用不能运行在今天的因特网上呢?答案显然是否定的,因为在因特网上运行时间敏感应用已经有多年了。这些应用经常工作得相当好,因为它们已经被设计成尽最大可能应对这种保证的缺乏。无论如何,在时延过大或端到端吞吐量受限时,好的设计也是有限制的。总之,今天的因特网通常能够为时间敏感应用提供满意的服务,但它不能提供任何定时或吞吐量保证。
图2-5给出了一些流行的因特网应用所使用的运输协议。
可以看到,电子邮件、远程终端访问、Web、文件传输都使用了TCP。这些应用选择TCP的最主要原因是TCP提供了可靠数据传输服务,确保所有数据最终到达目的地。因为因特网电话应用通常能够容忍某些丢失但要求达到一定的最小速率才能有效工作,所以因特网电话应用的开发者通常愿意将该应用运行在UDP上,从而设法避开TCP的拥塞控制机制和分组开销。但因为许多防火墙被配置成阻挡(大多数类型的)UDP流量,所以因特网电话应用通常被设计成如果UDP通信失败就使用TCP作为备份。
2.1.5 应用层协议
我们刚刚学习了通过把报文发送进套接字实现网络进程间的相互通信。
但是如何构造这些报文?在这些报文中,各个字段的含义是什么?进程何时发送这些报文?这些问题将我们带进应用层协议的范围。
应用层协议(appication layer protocol)定义了运行在不同端系统上的应用程序进程如何相互传递报文。特别是,应用层协议定义了以下内容:
① 交换的报文类型:请求报文、响应报文。
② 各种报文类型的语法:报文中的各个字段、这些字段是如何描述的。
③ 字段的语义:这些字段中信息的含义。
④ 确定一个进程何时以及如何发送报文、对报文进行响应的规则。
有些应用层协议是由RFC文档定义的,因此它们位于公共域中。
例如,Web的应用层协议HTTP(超文本传输协议[RFC7230])就作为一个RFC可供使用。如果浏览器开发者遵从HTTP RFC规则,所开发出的浏览器就能访问任何遵从该文档标准的Web服务器并获取相应Web页面。还有很多别的应用层协议是专用的,有意不为公共域所用。
区分网络应用和应用层协议是很重要的。应用层协议只是网络应用的一部分(尽管从我们的角度看,它是应用非常重要的一部分)。我们来看一些例子。Web是一种客户-服务器应用,它允许客户按照需求从Web服务器获得文档。该Web应用有很多组成部分,包括文档格式的标准(即HTML)、Web浏览器(如Chrome和Microsoft Inteme Explorer)、Web服务器(如Apache、Microsoft服务器程序)、以及一个应用层协议。 Web的应用层协议是HTTP,它定义了在浏览器和Web服务器之间传输的报文格式和序列。因此,HTTP只是Web应用的一个部分(尽管是重要部分)。
举另外一个例子——Netflix的视频服务也具有多个组成部分,包括……,因此,DASH仅是Netflix应用程序的一小部分(尽管是重要部分)。
2.1.6 本书涉及的网络应用
在本章中我们详细讨论5种重要的应用——Web、电子邮件、目录服务、流式视频和P2P。
我们首先讨论Web应用,不仅因为它是极为流行的应用,而且因为它的应用层协议HTTP比较简单且易于理解。我们接下来讨论电子邮件,这是因特网上第一个招人喜爱的应用程序。说电子邮件比Web更复杂,是因为它使用了多个而不是一个应用层协议。
在电子邮件之后,我们学习DNS(域名系统,会增加时延),它为因特网提供目录服务。大多数用户不直接与DNS打交道,而是通过其他的应用(包括Web、文件传输和电子邮件)间接使用它。DNS很好地说明了一种核心的网络功能(网络名字到网络地址的转换)是怎样在因特网的应用层实现的。
然后我们讨论P2P文件共享应用,通过讨论包括经内容分发网分发存储的视频在内的按需流式视频,结束应用层的学习。
2.2 Web 和 HTTP
20世纪90年代以前,因特网的主要使用者是研究人员、学者和大学生,他们登录远程主机,在本地主机和远程主机之间传输文件、收发新闻、收发电子邮件。尽管这些应用非常有用(并且继续如此),但是因特网基本上不为学术界和研究界之外的世界所知。
到了20世纪90年代初期,一个主要的新型应用——万维网(Word Wide Web),登上了舞台。Web是第一个引起公众注意的因特网应用,它极大地改变了人们与工作环境内外交流的方式。它将因特网从只是很多数据网之一的地位提升为仅有的一个数据网。
也许对大多数用户来说,最具有吸引力的就是Web的按需操作。当用户需要时,就能得到想要的内容。这不同于传统的无线电广播和电视,它们迫使用户只能被动地收听、收看内容提供者提供的节目。除了可以按需操作以外,Web还有很多让人们喜欢的特性。任何人在Web上发布信息都非常简单,即只需要极低的费用就能成为信息传播者。Web及其协议作为平台,为YouTube、基于Web的电子邮件(如Gmail)和大多数移动因特网应用(包括Instagram和谷歌地图)服务。
2.2.1 HTTP概述
Web的应用层协议——超·文本·传输·协议(HyperText Transfer Protocol,HTTP)
HTTP是Web的核心。
HTTP由两个程序实现:一个客户程序和一个服务器程序。
客户程序和服务器程序运行在不同的端系统中,通过交换HTTP报文进行会话。
HTTP定义了这些报文的结构、客户和服务器进行报文交换的方式。
在详细解释HTTP之前,先回顾一些Web术语。
Web页面(Web page):也叫文档、网页,是由对象组成的。一个对象(object)只是一个文件,诸如一个HML文件、一个JPEG图形、一个JavScrpt文件、一个CCS样式表文件或一个视频片段,它们可通过一个URL寻址。
----------------------------------------------------------------------------------------------------------------------------
URL 统一·资源·定位符(Uniform Resource Locator):是一种用于标识互联网上资源位置的标识符,通常用于指定网页、图片、文件等资源的地址。
URL的结构通常包括以下几个部分:
① 协议:例如——http://(超文本传输协议)、https://(安全超文本传输协议)、ftp://(文件传输协议)等。
② 域名或IP地址:用于标识服务器的位置(用于寻址),例如 www.example.com、196.168.26.1
③ 端口号(可选):用于指定服务器上运行的服务端口,默认情况下,HTTP协议使用端口80,HTTPS协议使用端口443。
④ 路径:用于指定资源在服务器上的具体位置,例如 /index.html。
⑤ 查询参数(可选):用于向服务器传递额外的信息,通常以`?`开头,例如`?key=value`。
一个完整的URL示例:`https://www.example.com:8080/path/to/resource?query=123`。
----------------------------------------------------------------------------------------------------------------------------
多数Web页面含有一个HTML基本文件(base HTMLfile-HTM代码文件)以及几个引用对象。
例如,如果一个Web页面包含HTML文本和5个JPEG图形,那么这个Web页面有6个对象,HTML基本文件通过对象的URL引用页面中的其他对象。
每个URL由两部分组成:存放对象的服务器主机名、对象的路径名。
例如,URL http://www.someSchool.edu/someDepartmen/picture.grf,其中的www.someSchool.edu就是主机名,/someDepartmen/picture.grf就是路径名。
因为Web浏览器(Web browser)实现了HTTP的客户端,所以在Web环境中我们经常交换使用浏览器和客户这两个术语。
Web服务器(Web server)实现了HTTP的服务器端,它用于存储Web对象,每个对象由URL寻址。
HTTP定义了Web客户向Web服务器请求Web页面的方式,以及服务器向客户传送Web页面的方式。我们稍后详细讨论客户和服务器的交互过程,而其基本思想在图2-6中进行了图示。
当用户请求一个Web页面(如点击一个超链接)时,浏览器向服务器发出对该页面中所包含对象的HTTP请求报文,服务器接收到请求并用包含这些对象的HTTP响应报文进行响应。
HTTP使用TCP作为它的支撑运输协议——HTTP客户首先发起一个与服务器的TCP连接。一旦连接建立,该浏览器和服务器进程就可以通过套接字接口访问 TCP。 如同在2.1节中描述的那样,客户端的套接字接口是客户进程与TCP连接之间的门,服务器端的套接字接口则是服务器进程与TCP连接之间的门。客户向它的套接字接口发送 HTTP请求报文并从它的套接字接口接收HTTP响应报文。类似地,服务器从它的套接字接口接收HTTP请求报文并向它的套接字接口发送HTTP响应报文。一旦客户向它的套接字接口发送了一个请求报文,该报文就脱离了客户控制并进入TCP的控制。2.1节讲过,TCP为HTTP提供可靠数据传输服务。这意味着,一个客户进程发出的每个HTTP请求报文最终能完整地到达服务器;类似地,服务器进程发出的每个HTTP响应报文最终能完整地到达客户。这里我们看到了分层体系结构最大的优点——即HTTP不用担心数据丢失,也不关注TCP从网络的数据丢失和乱序故障中恢复的细节。那是TCP以及协议栈较低层协议的工作。
注意到下列现象很重要:服务器向客户发送被请求的文件,而不存储任何关于该客户的状态信息。假如某个特定的客户在短短的几秒内两次请求同一个对象,服务器并不会因为刚刚为该客户提供了该对象就不再做出反应,而是重新发送该对象,就像服务器已经完全忘记不久之前所做过的事一样。
因为HTTP服务器并不保存关于客户的任何信息,所以我们说HTTP是一个无状态协议(state-less protocol)。
我们同时也注意到Web使用了客户-服务器应用程序体系结构(C&S)。Web服务器总是打开的,具有一个固定的IP地址,且它服务于可能来自数以百万计的不同浏览器的请求。
HTTP的初始版本称为HTTP/1.0,其可追湖到20世纪90年代早期[RFC1945]。到2020年为止,绝大部分的HTP事务都采用HTTP/1.1[RFC7230]。然而,越来越多的浏览器和Web服务器也支持新版的HTTP,称为HTTP/2[RFC7540]。在本节结束时,我们将给出 HTTP/2的简介。
2.2.2 非持续连接和持续连接
在许多因特网应用程序中,客户和服务器在一个相当长的时间范围内通信,在此期间,客户发出一系列请求,并且服务器对每个请求进行响应。依据应用程序以及该应用程序的使用方式,这一系列请求可以以规则的间隔周期性地或者间断性地一个接一个发出。
当这种客户一服务器的交互是经TCP进行的时,应用程序的研制者就需要做一个重要决定,即每个请求/响应对是经一个单独的TCP连接发送,还是所有的请求及其响应经相同的TCP连接发送。
采用前一种方法,该应用程序被称为使用非持续连接(non-persistent connection);
采用后一种方法,该应用程序被称为使用持续连接(persistent connection)。
为了深入地理解该设计问题,我们研究在特定的应用程序即HTTP的情况下持续连接的优点和缺点。
HTTP既能够使用非持续连接,也能够使用持续连接。尽管HTTP默认使用持续连接,但HTTP客户和服务器也能配置成使用非持续连接。
(1)采用非持续连接的 HTTP
看看在非持续连接情况下从服务器向客户传送一个Web页面的步骤。
假设该页面含有1个HTML基本文件和10个JPEG图形,并且这11个对象位于同一台服务器上。
进一 步假设该HTML文件的URL为http://www.someSchool.edu/someDepatmen/home.index。
我们看看发生了什么情况:
① HTTP客户进程在端口号80发起一个到服务器www.someSchool.cdu的TCP连接,该端口号是HTTP的默认端口。在客户和服务器上分别有一个套接字与该连接相关联。(发起连接)
② HTTP客户经它的套接字向该服务器发送一个HTTP请求报文。请求报文中包含了路径名/someDepartmen/home.index。
③ HTTP服务器进程经它的套接字接收该请求报文,从其存储器(RAM或磁盘)中检索出对象someDepartmen/home.indox,在一个HTTP响应报文中封装对象,并通过其套接字向客户发送响应报文。
④ HTTP服务器进程通知TCP断开该TCP连接。(但是直到TCP确认客户已经完整地收到响应报文为止,它才会实际中断连接。)(断开连接)
⑤ HTTP客户接收响应报文,TCP连接关闭。该报文指出封装的对象是一个HTML文件,客户从响应报文中提取出该文件,检查该HTML文件,得到对10个PEG图形的引用。
⑥ 对每个引用的JPEG图形对象重复前4个步骤。
当浏览器收到Web页面后,向用户显示该页面。两个不同的浏览器也许会以不同的方式解释(即向用户显示)该页面。HTTP与客户如何解释一个Web页面毫无关系。 HTTP规范仅定义了在HTTP客户程序与HTTP服务器程序之间的通信协议。
上面的步骤举例说明了非持续连接的使用,其中每个TCP连接在服务器发送一个对象后关闭,即该连接并不为其他的对象而持续下来。
HTTP/1.0应用了非持续TCP连接。值得注意的是每个TCP连接只传输一个请求报文和一个响应报文。因此在本例中,当用户请求该Web页面时,要产生11个TCP连接。
在上面描述的步骤中,我们有意没有明确客户获得这10个PEG图形对象是使用10个串行的TCP连接,还是某些PEG对象使用了一些并行的TCP连接。事实上,用户能够配置现代浏览器来控制连接的并行度。浏览器打开多个TCP连接,并且请求经多个连接请求某Web页面的不同部分。我们在下一章会看到,使用并行连接可以缩短响应时间。
在继续讨论之前,我们来简单估算一下从客户请求HTML基本文件起到该客户收到整个文件止所花费的时间。为此,我们给出往返时间的定义:
往返时间(Round-TripTime,RTT):指一个短分组从客户到服务器然后再返回客户所花费的时间。
RTT包括分组传播时延、分组在中间路由器和交换机上的排队时延、分组处理时延。现在考虑当用户点击超链接时会发生什么现象。如图2-7所示,这引起浏览器在它和Web服务器之间发起一个TCP连接:这涉及一次“三次握手”过程——第1次客户向服务器发送一个小TCP报文段,第2次服务器用一个小TCP报文段做出确认和响应,第3次客户向服务器返回确认(同时发出正式通信内容)。
三次握手中前两个部分所耗费的时间占用了一个RTT。完成了三次握手的前两个部分后,客户结合三次握手的第三部分(确认)向该TCP连接发送一个HTTP请求报文。
一旦该请求报文到达服务器,服务器就在该 TCP连接上发送 HTML 文件。该HTTP请求/响应用去了另一个RTT。因此,粗略地讲,总的响应时间就是两个RTT加上服务器传输 HTML 文件的时间。
(2)采用持续连接的HTTP
非持续连接有一些缺点。第一,必须为每一个请求的对象建立和维护一个全新的连接。对于每个这样的连接,在客户和服务器中都要分配TCP的缓冲区和保持TCP变量,这给Web服务器来了严重的负担,因为一台Web服务器可能同时服务于数以百计不同的客户的请求。第二,就像我们刚描述的那样,每一个对象经受两倍RTT的交付时延,即一个RTT用于创建TCP,另一个RTT用于请求和接收一个对象。
在采用HTP1.1持续连接的情况下,服务器在发送响应后保持该TCP连接打开。在相同的客户与服务器之间,后续的请求和响应报文能够通过相同的连接进行传送。特别是,一个完整的Web页面(上例中的HTML基本文件加上10个图形)可以用单个持续TCP连接进行传送。更有甚者,位于同一台服务器的多个Web页面在从该服务器发送给同一个客户时,可以在单个持续TCP连接上进行。对对象的这些请求可以一个接一个地发出,而不必等待对未决请求(流水线)的回答。通常,如果一条连接经过一定时间间隔(一个可配置的超时间隔)仍未被使用,HTTP服务器就关闭该连接。
HTTP的默认模式是使用带流水线的持续连接。
2.2.3 HTTP报文格式
HTTP规范 [ RFC 1945;RFC 7230;RFC 7540]包含了对HTTP报文格式的定义。
HTTP报文有两种:请求报文、响应报文。
(1)HTTP请求报文
下面提供了一个典型的 HTTP请求报文:
首先,该报文是用普通的 ASCII 文本书写的,这样有一定计算机知识的人都能够阅读它。
其次,该报文由5行组成——每行由一个回车(Carriage Return,简称Retuen、CR,\r)和换行符(New Line、Line Feed,简称\n、LF)结束。最后一行后再附加一个回车和换行符。
------------------------------------------------------------------------------------------------------------------------------
回车 & 换行
carriage:马车;feed:喂养、进给(行进给)
在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符,但是它有一个问题,就是执行部分打完一行换行的时候,机械结构要用去0.2秒用于换行,正好可以打两个字符,要是在这0.2秒里面,接收部分又有新的字符传过来,那么这个字符将丟失。
于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符:一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做"换行”,告诉打字机把纸向下移一行。
这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。
后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。
于是,就出现了分歧:
Unix、Linux系统里,每行结尾只有“换行”(LF),即“\n";
Windows系统里面,每行结尾是“回车+换行”(CR+LF),即“\r\n";
早期Mac系统里,每行结尾是“回车”(CR),即“\r”。
现在Mac系统(兼容Unix),每行结尾只有“换行”(LF),即“\n";
一个直接后果是——跨平台兼容性问题:
Unix/Mac文件在Windows中打开:所有文字会变成一行,因为Windows系统需要同时识别CR和LF才能换行。
Windows文件在Unix/Mac中打开:在每行结尾可能会多出一个 ^M 符号,因为Unix/Mac系统只识别LF作为换行符,而Windows文件中的CR会被显示出来。
【转载】 Linux、Windows 和 Mac 中的换行符对比 - 猴子搬桃子 - 博客园
kimi-AI——^M
`^M`是一个特殊的符号,用来表示 ASCII 字符中的“回车符”(Carriage Return,CR),其 ASCII 码值为 13(十进制)或`\r`(在编程语言中表示回车符的转义字符)。
① 为什么会出现`^M`符号???
在文本文件中,不同操作系统使用不同的换行符组合:
• Windows 系统:使用“回车符+换行符”(`\r\n`)作为换行。
• Unix/Linux 系统:使用“换行符”(`\n`)作为换行。
• 早期 Mac 系统:使用“回车符”(`\r`)作为换行。
当将一个Windows 系统创建的文本文件(包含`\r\n`)直接在Unix/Linux 系统中打开时,Unix/Linux 系统只识别`\n`作为换行符,而`\r`(回车符)则被视为普通字符。因此,`\r`会被显示为`^M`符号。
② `^M`的显示原因
在 Unix/Linux 系统中,`^`是一个常见的表示控制字符的前缀,`M`的 ASCII 码值为 77(十进制),而回车符的 ASCII 码值为 13(十进制)。在控制字符的表示中,`M`的 ASCII 码值减去 64(控制字符的偏移量)正好等于 13,因此回车符`\r`被显示为`^M`。
---------------------------------------------------------------------------------------------------------------------
虽然这个特定的报文仅有5行,但一个请求报文能够具有更多的行或者至少为一行。
请求行(request line):HTTP请求报文的第一行叫作请求行。
首部行(header line):HTTP请求报文后继的行叫作首部行(≠第一行)
请求行有3个字段:方法字段、URL字段、HTTP版本字段。
方法字段——可以取几种不同的值:GET、POST、HEAD、PUT、DELETE。
绝大部分的HTTP请求报文使用GET方法——当测览器请求一个对象时,使用GET方法,在URL字段带有请求对象的标识。
在本例中,该浏览器正在请求对象/somedir/page.hml。其版本字段是自解释的,在本例中,浏览器实现的是HTTP/1.1版本。
现在我们看看本例的首部行。
① 首部行“Host:www.someschool.edu”指明了对象所在的主机。你也许认为该首部行是不必要的,因为在该主机中已经有一条TCP连接存在了。但是,如我们将在2.2.5节中所见,该首部行提供的信息是Web代理高速缓存所要求的。
② 首部行“Connection:close”,该浏览器告诉服务器不要麻烦地使用持续连接,它要求服务器在发送完被请求的对象后就关闭这条连接。
③ 首部行“User-agent:”,用来指明用户代理,即向服务器发送请求的浏览器的类型。这里浏览器类型是Mozilla/5.0,即Firefox浏览器。这个首部行是有用的,因为服务器可以有效地为不同类型的用户代理实际发送相同对象的不同版本。(每个版本都由相同的URL寻址。)
④ 首部行“Accept-language:”,表示用户想得到该对象的法语版本(如果服务器中有这样的对象的话);否则,服务器应当发送它的默认版本。“Acceptlanguage:”首部行仅是HTTP中可用的众多内容协商首部之一。
看过一个例子之后,我们再来看看如图2-8所示的一个请求报文的通用格式。
我们看到该通用格式与我们前面的例子密切对应。然而,你可能已经注意到了在首部行(与附加的回车和换行符)后有一个“实体体”(entity body)——使用GET方法时整个实体体为空,而使用POST方法时才使用该实体体。
POST方法——当用户提交表单时,HTTP客户常常使用POST方法,例如当用户向搜索引擎提供搜索关键词时。使用 POST 报文时,用户仍可以向服务器请求一个Web页面,但 Web页面的特定内容依赖于用户在表单字段中输入的内容。如果方法字段的值为POST,则实体体中包含的就是用户在表单字段中的输入值。
用表单生成的请求报文不是必须使用POST方法——HTML表单经常使用GET方法,并在(表单字段中)所请求的URL中包括输入的数据。例如,一个表单使用GET方法,它有两个字段,分别填写的是monkeys和bananas,该URL结构为www.somesic.com/animalscarch?monkeys&banans。在日复一日的网上冲浪中,你也许已经留意到了这种扩展的URL。
HEAD方法——类似于GET方法。当服务器收到一个使用HEAD方法的请求时,将会用一个HTTP报文进行响应,但是并不返回请求对象。应用程序开发者常用HEAD方法进行调试跟踪。
PUT方法——常与Web发行工具联合使用,它允许用户上传对象到指定的Web服务器上指定的路径(目录)。PUT方法也被那些需要向Web服务器上传对象的应用程序使用。
DELETE方法——允许用户或者应用程序删除Web服务器上的对象。
(2)HTTP响应报文
下面我们提供了一条典型的HTTP响应报文。该响应报文可以是对刚刚讨论的例子中请求报文的响应:
我们仔细看一下这个响应报文,它有三个部分:
1个初始状态行(satus line) + 6个首部行(headerline) + 实体体(cntiybody)。
实体体部分是报文的主要部分,即它包含了所请求的对象本身(表示为data data data data data…)。
状态行有3个字段:协议版本字段、状态码、状态码对应的状态信息。
在这个例子中,状态行指示服务器正在使用HTTP1.1,并且一切正常(即服务器已经找到并正在发送所请求的对象)。
首部行——
① “Connection:close”:告诉客户,发送完报文后将关闭该TCP连接。
② “Date:”:指示服务器产生并发送该响应报文的日期和时间——值得一提的是,这个时间不是指对象创建或者最后修改的时间,而是服务器从它的文件系统中检索到该对象,将该对象插入响应报文,并发送该响应报文的时间。
③ “Server:”:首部行指示该报文是由一台Apache Web服务器产生的,它类似于HTTP请求报文中的“User-agent:”首部行。
④ “Last-Modified:”:首部行指示该对象创建或最后修改的时间与日期。“Last-Modified:”首部行对既可能在本地客户也可能在网络缓存服务器(又称为代理服务器)上的对象缓存来说非常重要,下文将更为详细地讨论“Last-Modified:”首部行
⑤ “Content-Length:”:指示了被发送对象中的字节数。
⑥ “Content-Type:”:指示了实体体中的对象是HTML文本。
(该对象类型应该正式地用“Content-Type:”首部行而不是文件扩展名来指示。)
响应报文的通用格式如图 2-9所示。
该通用格式与前面例子中的响应报文相匹配。
我们补充说明一下状态码和它们对应的短语。
状态码及其相应的短语指示了请求的结果。一些常见的状态码和相关的短语包括:
2.2.4 用户与服务器的交互:cookie
我们前面提到了HTTP服务器是无状态的。这简化了服务器的设计,并且允许工程师去开发可以同时处理数千个TCP连接的高性能Web服务器。然而一个Web站点通常希望能够识别用户,可能是因为服务器希望限制用户的访问,或者因为它希望把内容与用户身份联系起来。
为此,HTTP使用了cookie——cookie在[RFC6265]中定义,它允许站点对用户进行跟踪,目前大多数商务Web站点都使用了cookie。
如图2-10所示,cookie技术有4个组件:
①在HTTP响应报文中的一个cookie首部行;
②在HTTP请求报文中的一个cookie首部行;
③在用户端系统中保留的一个cookie文件,并由用户的浏览器进行管理;
④位于Web站点的一个后端数据库。
使用图2-10,我们通过一个典型的例子看看cookie的工作过程。假设Susan总是从家中PC使用Internet Explorer上网,她首次与Amazon.com联系。我们假定过去她已经访问过eBay站点。当请 求报文到达Amazon Web服务器时,该Web站点将产生一个唯一识别码,并以此作为索引在它的后端数据库中产生一个表项。接下来Amazon Web服务器用一个包含“Set-cookie:”首部行的HTTP响应报文对Susan的浏览器进行响应,“Set-cookie:”首部含有该识别码。
例如,该首部行可能是Set-cookie: 1678,当Susan的浏览器收到了该HTTP响应报文时,它会看到“Set-cookie:”首部。该浏览器在它管理的特定cookie文件中添加一行,该行包含服务器的主机名和“Set-cookie:”首部中的识别码。
值得注意的是该cookie文件已经有了用于eBay的表项,因为Susan过去访问过该站点。当Susan继续浏览Amaon网站时,每请求一个Web页面,其浏览器就会查询该cookie文件并抽取她对这个网站的识别码,并放到HTTP请求报文中包括识别码的cookie首部行中。特别是,发往该Amazon服务器的每个HTTP请求报文都包括以下首部行:Cookie: 1678。
在这种方式下,Amazon服务器可以跟踪Susan在Amazon站点的活动。尽管Amazon Web站点不必知道Susan的名字,但它确切地知道用户1678按照什么顺序在什么时间访问了哪些页面!
Amzon使用cookie来提供它的购物车服务,即Amazon能够维护Susan希望购买的物品列表,这样在Susan结束会话时可以一起为它们付费。
如果Susan再次访问Amazon站点,比如说一个星期后,她的浏览器会在其请求报文中继续放入首部行“Cookie:1678”。Amazon将根据Susan过去在Amzon访问的网页向她推荐产品!
如果Susan也在Amazon注册过,即提供了她的全名、电子邮件地址、邮政地址和信用卡账号,则Amazon能在其数据库中包括这些信息,将Susan的名字与识别码相关联(以及她在过去访问过的本站点的所有页面)。这就解释了Amaon和其他一些电子商务网站实现“一键购物”(one-click shopping)的道理,即当Susan在后继的访问中选择购买某个物品时,她不必重新输入姓名、信用卡账号或者地址等信息了。
从上述讨论中我们看到,cookie可以用于标识一个用户。用户首次访问一个站点时,可能需要提供一个用户标识(可能是名字)。在后继会话中,浏览器向服务器传递一个cookie首部,从而向该服务器标识了用户。因此cookie可以在无状态的HTTP之上建立一个用户会话层。例如,当用户向一个基于Web的电子邮件系统注册时,浏览器向服务器发送cookie信息,允许该服务器在用户与应用程序会话的过程中标识该用户。
尽管cookie通常能够简化用户的因特网购物活动,但是其使用仍具有争议,因为它被认为是对用户隐私的一种侵害。如我们刚才所见,结合cookie和用户提供的账户信息,Web站点可以得知许多有关用户的信息,并可能将这些信息实给第三方。
2.2.5 Web缓存
Web缓存器(Web cache):它是能够代表初始Web服务器来满足HTTP请求的网络实体。——本地存储已访问过的web页面,加快响应时间
代理服务器(proxy server):Web缓存也叫代理服务器。
Web缓存器有自己的磁盘存储空间,并在存储空间中保存最近请求过的对象的副本。
如图2-11所示,可以配置用户的浏览器,使得用户的所有HTTP请求首先指向Web缓存器。一旦某浏览器被配置,每个对某对象的浏览器请求首先被定向到该Web缓存器。举例来说,假设浏览器正在请求对象hppt://www.someschool.edu/campus.gif,将会发生如下情况:
① 浏览器创建一个到Web缓存器的TCP连接,并向Web缓存器中的对象发送一个HTTP请求。
② Web缓存器进行检查,看看本地是否存储了该对象副本。如果有,Web缓存器就向客户浏览器用HTTP响应报文返回该对象。
③ 如果Web缓存器中没有该对象,它就打开一个与该对象的初始服务器(即www.someschool.edu)的TCP连接。Web缓存器则在这个缓存器到服务器的TCP连接上发送一个对该对象的HTTP请求。在收到该请求后,初始服务器向该Web缓存器发送具有该对象的HTTP 响应。
④ 当Web缓存器接收到该对象时,它在本地存储空间存储一份副本,并向客户的浏览器用HTTP响应报文发送该副本(通过客户浏览器和Web缓存器之间现有的TCP连接)。
值得注意的是,Web缓存器既是服务器又是客户——当它接收浏览器的请求并发回响应时,它是一个服务器。当它向初始服务器发出请求并接收响应时,它是一个客户。
Web缓存器通常由ISP购买并安装。例如,一所大学可能在它的校园网上安装一台缓存器,并且将所有校园网上的用户浏览器配置为指向它。或者,一个主要的住宅ISP(例如Comcast)可能在它的网络上安装一台或多台Web缓存器,并且预先配置其配套的浏览器指向这些缓存器。
在因特网上部署Web缓存器有两个原因。
① Web缓存器可以大大减少对客户请求的响应时间,特别是当客户与初始服务器之间的瓶颈带宽远低于客户与Web缓存器之间的瓶颈带宽时更是如此。
如果在客户与Web缓存器之间有一个高速连接(情况常常如此),并且如果用户所请求的对象在Web缓存器上,则Web缓存器可以迅速将该对象交付给用户。
② Web缓存器能够大大减少一个机构的接入链路到因特网的通信量。通过减少通信量,该机构(如一家公司或者一所大学)就不必急于增加带宽,因此降低了费用。
③ Web缓存器能从整体上大大减少因特网上的Web流量,从而改善了所有应用的性能。
为了深刻理解缓存器带来的好处,我们考虑在图2-12场景下的一个例子。该图显示了两个网络,即机构(内部)网络和公共因特网的一部分。机构网络是一个高速的局域网,它的一台路由器与因特网上的一台路由器通过一条15Mbps的链路连接。这些初始服务器与因特网相连但位于全世界各地。假设对象的平均长度为1Mb(≠1MB),从机构内的浏览器对这些初始服务器的平均访问速率为每秒15个请求。假设HTTP请求报文小到可以忽略,因而不会在网络中以及接入链路(从机构内部路由器到因特网路由器)上产生什么通信量。
我们还假设在图2-12中从因特网接入链路一侧的路由器转发HTTP请求报文(在一个IP数据报中)开始,到它收到其响应报文(通常在多个IP数据报中)为止的时间平均为2s。我们将该持续时延非正式地称为“因特网时延”。
总的响应时间:从浏览器请求一个对象到接收到该对象为止的时间。
接入时延:两台路由器之间的时延。
总的响应时间 = 局域网时延 + 接入时延 + 因特网时延之和。
局域网上的流量强度:
(15个请求/s)×(1Mb/请求)/(100Mbps)=0.15
然而接入链路上的流量强度:
(15个请求/s)×(1Mb/请求)/(15Mbps)=1
局域网上强度为0.15的通信量通常最多导致数十ms的时延,因此我们可以忽略局域网时延。然而,如果流量强度接近1,链路上的时延会变得非常大并且无限增长。因此,满足请求的平均响应时间将在分钟的量级上。显然,必须想办法来改进时间响应特性。
① 一个可能的解决办法就是增加接入链路的速率,如从15Mbps增加到100Mps。这可以将接入链路上的流量强度减少到0.15,这样一来,两台路由器之间的链路时延也可以忽略了。这时,总的响应时间将大约为2s,即为因特网时延。但这种解决方案也意味者该机构必须将它的接入链路由15Mbps升级为100Mbps,这是一种代价很高的方案。
② 另一种解决方案,即不升级链路带宽而是在机构网络中安装一个Web缓存器。这种解决方案如图2-13所示。现实中的命中率(即由一个缓存器所满足的请求的比率)通常在0.2~0.7之间。为了便于阐述,我们假设该机构的缓存命中率为0.4。因为客户和缓存连接在一个相同的高速局域网上,这样40%的请求将几乎立即会由缓存器得到响应,时延约在10ms以内。然而,剩下的60%的请求仍然要由初始服务器来满足。但是只有60%的被请求对象通过接入链路,接入链路上的流量强度从1.0减小到0.6。一般而言,在15Mbps链路上,当流量强度小于0.8时对应的时延较小,约为几十毫秒。这个时延与2s因特网时延相比是微不足道的。考虑这些之后,平均时延因此为
0.4×(0.010s)+0.6×(2.01s)
这略大于1.2s。因此,第二种解决方案提供的响应时延甚至比第一种解决方案更低,也不需要该机构升级它到因特网的链路。该机构理所当然地要购买和安装 Web缓存器。
除此之外其成本较低,很多缓存器使用了运行在廉价PC上的公共域软件。
通过使用内容分发网络(Conent Distribution Netwok,CDN),Web缓存器正在因特网中发挥着越来越重要的作用。CDN公司在因特网上安装了许多地理上分散的缓存器,因而使大量流量实现了本地化。有多个共享的CDN(例如Akami和Limelight)和专用的CDN(例如谷歌和Neflix)。我们将在2.6节中更为详细地讨论CDN。
条件GET方法
尽管高速缓存能减少用户感受到的响应时间,但也引入了一个新的问题,即存放在缓存器中的对象副本可能是陈旧的。换句话说,保存在服务器中的对象自该副本缓存在客户上以后可能已经被修改了。
幸运的是,HTTP有一种机制,允许缓存器证实它的对象是最新的。
这种机制就是条件GET(conditional GET)
---------------------------------------------------------------------------------------------------------------------
条件GET请求报文:
如果HTTP请求报文使用GET方法,并且请求报文中包含一个“IF-modified-since:”首部行,那么,这个HTTP请求报文就是一个条件GET请求报文。
---------------------------------------------------------------------------------------------------------------------
为了说明GET方法的操作方式,我们看一个例子。首先,一个代理缓存器(proxy cache)代表一个请求浏览器,向某Web服务器发送一个请求报文:
其次,该Web服务器向缓存器发送具有被请求的对象的响应报文:
该缓存器在将对象转发到请求的浏览器的同时,也在本地缓存了该对象。重要的是缓存器在存储该对象时也存储了最后修改日期。最后,一个星期后,另一个用户经过该缓存器请求同一个对象,该对象仍在这个缓存器中。由于在过去的一个星期中位于Web服务器上的该对象可能已经被修改了,该缓存器通过发送一个条件GET执行最新检查。具体来说,该缓存器发送:
值得注意的是“IF-modified-since:”首部行的值正好等于一星期前服务器发送的响应报文中的“Last-Modified:”首部行的值。该条件GET报文告诉服务器,仅当自指定日期之后该对象被修改过,才发送该对象。假设该对象自2015年9月9日09:23:24后没有被修改。接下来的第四步,Web服务器向该缓存器发送一个响应报文:
我们看到,作为对条件GET方法的响应,该Web服务器仍发送一个响应报文,但并没有在该响应报文中包含所请求的对象。包含该对象只会浪费带宽,并增加用户感受到的响应时间,特别是如果该对象很大更是如此。值得注意的是在最后的响应报文中,状态行中为304 Not Modiied,它告诉缓存器可以使用该对象,能向请求的浏览器转发它(该代理缓存器)缓存的对象副本。
2.2.6 HTTP/2
于2015年标准化的HTTP/2[RFC7540]是自HTTP/1.1以后的首个新版本,而HTTP/1.1是1997年标准化的。
HTTP/2的主要目标是减小感知时延,其手段是经单一TCP连接使请求与响应多路复用,提供请求优先次序和服务器推,并提供HTTP首部字段的有效压缩。
HTTP/2不改变HTTP方法、状态码、URL或首部字段,而是改变数据格式化方法以及客户和服务器之间的传输方式。
回想HTTP/1.1,其使用持续TCP连接,允许经单一TCP连接将一个Web页面从服务器发送到客户。由于每个Web页面仅用一个TCP连接,,服务器的套接字数量被压缩,并且所传送的每个Web页面平等共享网络带宽(如下面所讨论的)。
但Web浏览器的研发者很快就发现了经单一TCP连接发送一个Web页面中的所有对象存在队首阻塞(Head Of Line (HOL) blocking)问题。为了理解HOL阻塞,考虑一个Web页面,它包括一个HTML基本页面、靠近Web页面顶部的一个大视频片段和该视频下面的许多小对象。进一步假定在服务器和客户之间的通路上有一条低速/中速的瓶颈链路(例如一条低速的无线链路)。使用一条TCP连接,视频片段将花费很长时间来通过该瓶颈链路,与此同时,那些小对象将被延迟,因为它们在视频片段之后等待。也就是说,链路前面的视频片段阻塞了后面的小对象。
HTTP/1.1浏览器解决该问题的典型方法是打开多个并行的TCP连接,从而让同一Web页面的多个对象并行地发送给测览器。采用这种方法,小对象到达并呈现在浏览器上的速度要快得多,因此可减小用户感知时延。
TCP拥塞控制也使得浏览器倾向于使用多条并行TCP连接而非单一持续连接。粗略来说,TCP拥塞控制针对每条共享同一条瓶颈链路的TCP连接,给出一个平等共享该链路的可用带宽。如果有n条TCP连接运行在同一条瓶颈链路上,则每条连接大约得到1/n带宽。通过打开多条并行TCP连接来传送一个Web页面,浏览器能够“欺骗”并霸占该链路的大部分带宽。许多HTTP/1.1打开多达6条并行TCP连接并非为了避免HOL阻塞,而是为了获得更多的带宽。
HTTP/2的基本目标之一是摆脱(或至少减少其数量)传送单一Web页面时的并行TCP连接。
好处:
① 减少了需要服务器打开与维护的套接字数量;
② 允许TCP拥塞控制像设计的那样运行。
但与只用一个TCP连接来传送一个Web页面相比,HTTP/2要求仔细设计相关机制以避免HOL阻塞。
(1)HTTP/2 成帧
用于HOL阻塞的HTTP/2解决方案是将每个报文分成小帧,并且在相同TCP连接上交错发送请求和响应报文——为了理解这个问题,再次考虑由一个大视频片段和许多小对象(例如8个)组成的Web页面的例子。此时,服务器将从希望查看该Web页面的浏览器处接收到9个并行的请求。对于每个请求,服务器需要向浏览器发送9个相互竞争的报文。
假定所有帧具有固定长度,该视频片段由1000帧组成,并且每个较小的对象由2帧组成。
使用帧交错技术,在视频片段发送第一帧后,发送每个小对象的第一帧。然后在视频片段发送第二帧后,发送每个小对象的第二帧。因此,在发送18帧后,所有小对象就发送完成了。如果不采用交错,则发送完其他小对象共需要发送1016帧。因此HTTP/2成顿机制能够极大地减小用户感知时延。
将一个HTTP报文分成独立的帧、交错发送它们并在接收端将其装配起来的能力,是HTTP/2最为重要的改进。这一成帧过程是通过HTTP/2协议的成帧子层来完成的——
当某服务器要发送一个HTTP响应时,其响应由成帧子层来处理,即将响应划分为帧。响应的首部字段成为一帧,报文体被划分为一帧以用于更多的附加帧。通过服务器中的成帧子层,该响应的帧与其他响应的帧交错并经过单一持续TCP连接发送。当这些帧到达客户时,它们先在成帧子层装配成初始的响应报文,然后像以往一样由浏览器处理。类似地,客户的 HTTP请求也被划分成帧并交错发送。
除了将每个HTTP报文划分为独立的帧外,成帧子层也对这些帧进行二进制编码。二进制协议解析更为高效,会得到略小一些的帧,并且更不容易出错。
(2)响应报文的优先次序、服务器推
报文优先次序允许研发者根据用户要求安排请求的相对优先权,从而更好地优化应用的性能。如前文所述,成帧子层将报文组织为并行数据流发往相同的请求方。当某客户向服务器发送并发请求时,它能够为正在请求的响应确定优先次序,方法是为每个报文分配1到256之间的权重。较大的数字表明较高的优先权。通过这些权重,服务器能够为具有最高优先权的响应发送第一帧。此外,客户也可通过指明相关的报文段ID,来说明每个报文段与其他报文段的相关性。
HTTP/2的另一个特征是允许服务器为一个客户请求而发送多个响应。即除了对初始请求的响应外,服务器能够向该客户推额外的对象,而无须客户再进行任何请求。因为HTML基本页指示了需要在页面呈现的全部对象,所以这一点是可实现的。因此无须等待对这些对象的HTTP请求,服务器就能够分析该HTML页,识别需要的对象,并在接收到对这些对象的明确的请求前将它们发送到客户。服务器推消除了因等待这些请求而产生的额外时延。
(3)HTTP/3
QUIC(在第3章讨论)是一种新型的“运输”协议,它在应用层中最基本的UDP之上实现。QUIC具有几个能够满足HTTP的特征,例如报文复用(交错)、每流流控和低时延连接创建。HTTP/3是一种设计在QUIC之上运行的新HTTP。到2020年为止,HTTP/3处于因特网草案阶段,还没有全面标准化。许多HTTP/2特征(如报文交错)已被收入QUIC中,使得对HTTP/3的设计更为简单、合理。
2.3 因特网中的电子邮件
自从有了因特网,电子邮件就在因特网上流行起来。当因特网还在襁探中时,电子邮件已经成为最流行的应用程序,至今它仍然是当今因特网上最重要和实用的应用程序之一。
与普通邮件一样,电子邮件是一种异步通信媒介——即当人们方便时就可以收发邮件,不必与他人的计划进行协调。与普通邮件相比,电子邮件更为快速,易于分发,而且价格便宜。现代电子邮件具有许多强大的功能,包括添加附件、超链接、HTML格式文本、图片。
在本节中,我们将讨论处于因特网电子邮件核心地位的应用层协议。在深入讨论这些应用层协议之前,我们先总体上看看因特网电子邮件系统和它的关键组件。
图2-14给出了因特网电子邮件系统的总体情况。
从该图中我们可以看到电子邮件有3个主要组成部分:
用户代理(user agent)、邮件服务器(mail server)、简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)。
下面我们结合发送方Alice发电子邮件给接收方Bob的场景,对每个组成部分进行描述。
用户代理——允许用户阅读、回复、转发、保存和撰写报文。
与用户进行直接交互的前端平台,相当于客户端。
邮件服务器——当Alice完成邮件撰写时,她的邮件代理向其邮件服务器发送邮件,此时邮件放在邮件服务器的外出报文队列中。当Bob要阅读报文时,他的用户代理在其邮件服务器的邮箱中取得该报文。
邮件服务器形成了电子邮件体系结构的核心。
每个接收方在其中的某个邮件服务器上有一个邮箱(mail box)。
例如:Bob的邮箱——管理和维护着发送给他的报文。
一个典型的邮件发送过程是:
从发送方的用户代理开始→传输到发送方的邮件服务器→
再传输到接收方的邮件服务器→然后在这里被分发到接收方的邮箱中。
当Bob要在他的邮箱中读取该报文时,包含他邮箱的邮件服务器(使用用户名和口令)鉴别Bob。Alice的邮箱也必须能处理Bob的邮件服务器的故障。如果Alice的服务器不能将邮件交付给Bob的服务器,Alice的邮件服务器在一个报文队列(message queue)中保持该报文并在以后尝试再次发送。通常每30分钟左右进行一次尝试,如果几天后仍不能成功,服务器就删除该报文并以电子邮件的形式通知发送方(Alice)。
SMTP——是因特网电子邮件中主要的应用层协议。它使用TCP可靠数据传输服务,从发送方的邮件服务器向接收方的邮件服务器发送部件。
像大多数应用层协议一样,SMTP也有两个部分:
运行在发送方邮件服务器的客户端、运行在接收方邮件服务器的服务器端。
每台邮件服务器上既运行SMTP的客户端也运行SMTP的服务器端——当一个邮件服务器向其他邮件服务器发送邮件时,它就表现为SMTP的客户;当一个邮件服务器从其他邮件服务器上接收邮件时,它就表现为SMTP的服务器。
2.3.1 SMTP
RFC 5321给出了SMTP的定义。SMTP是因特网电子邮件的核心。如前所述,SMTP用于从发送方的邮件服务器发送报文到接收方的邮件服务器。SMTP问世的时间比HTTP要长得多(初始的SMTP的RFC可追溯到1982年,而SMTP在此之前很长一段时间就已经出现了)。尽管电子邮件应用在因特网上的独特地位可以证明SMTP有着众多非常出色的性质,但它所具有的某种陈旧特征表明它仍然是一种继承的技术。例如,它限制所有邮件报文的体部分(不只是其首部)只能采用简单的7比特ASCII表示。在20世纪80年代早期,这种限制是明智的,因为当时传输能力不足,没有人会通过电子邮件发送大的附件或大的图片、声音、视频文件。然而,在今天的多媒体时代,7比特ASCII的限制的确有点痛苦,即在用SMTP传送邮件之前,需要将二进制多媒体数据编码为ASCII码,并且在使用SMTP传输后要求将相应的ASCII码邮件解码还原为多媒体数据。2.2节讲过,使用HTTP传送前不需要将多媒体数据编码为ASCII码。
为了描述SMTP的基本操作,我们观察一种常见的情景。
假设Alice想给Bob发送一封简单的ASCII报文。
① Alice调用她的邮件代理程序并提供Bob的邮件地址(例如bob@someschool.edu),撰写报文,然后指示用户代理发送该报文。
② Alice的用户代理把报文发到她的邮件服务器,在那里该报文被放在报文队列中。
③ 运行在Alice的邮件服务器上的SMTP客户发现了报文队列中的这个报文,它创建一个到运行在Bob的邮件服务器上的SMTP服务器的TCP连接。
④ 在经过一些初始SMTP握手后,SMTP客户通过该TCP连接发送Alice的报文。
⑤ 在Bob的邮件服务器上,SMTP的服务器接收该报文。Bob的邮件服务器然后将该报文放入Bob的邮箱中。
⑥ 在Bob方便的时候,他调用用户代理阅读该报文。
图2-15 总结了上述这个情况。
观察到下述现象是重要的:SMTP一般不使用中间邮件服务器发送邮件,即使这两个邮件服务器位于地球的两端也是这样。假设Alice的邮件服务器在中国香港,而Bob的服务器在美国圣路易斯,那么这个TCP连接也是从香港服务器到圣路易斯服务器之间的直接相连。特别是,如果Bob的邮件服务器没有开机,该报文会保留在Aice的邮件服务器上并等待进行新的尝试,这意味着邮件并不在中间的某个邮件服务器中存留。
我们现在仔细观察一下,SMTP是如何将一个报文从发送邮件服务器传送到接收邮件服务器的。我们将看到,SMTP与人类面对面交往的行为方式有许多类似之处。
① 首先,客户SMTP(运行在发送邮件服务器主机上)在25号端口建立一个到服务器SMTP(运行在接收邮件服务器主机上)的TCP连接。如果服务器没有开机,客户会在稍后继续尝试连接。一旦连接建立,服务器和客户执行某些应用层的握手,就像人们在相互交流前先进行自我介绍一样。SMTP的客户和服务器在传输信息前先相互介绍。
② 在SMTP握手的阶段,SMTP客户指示发送方的邮件地址(产生报文的那个人)和接收方的邮件地址。
③ 一旦该SMTP客户和服务器彼此介绍之后,客户发送该报文。SMTP能依赖TCP提供的可靠数据传输无差错地将邮件投递到接收服务器。该客户如果有另外的报文要发送到该服务器,就在该相同的TCP连接上重复这种处理;否则,它指示TCP关闭连接。
接下来我们分析一个在SMTP客户(C)和SMTP服务器(S)之间交换报文文本的例子。客户的主机名为crepes.fr,服务器的主机名为hamburger.edu。
以“C:”开头的ASCII码文本行正是客户交给其TCP套接字的那些行;
以“S:”开头的ASCII码文本行则是服务器发送给其TCP套接字的那些行。
一旦创建了TCP连接,就开始了下列过程。
在上例中,客户从邮件服务器crepes.fr向邮件服务器hamburger.edu发送了一个报文 (“Do you like ketchup? How about pickles?”)。
作为对话的一部分,该客户发送了5条命令:
HELO(是HELLO的缩写)、MAIL FROM、RCPT TO、DATA、QUIT。
这些命令都是自解释的。该客户通过发送一个只包含一个句点的行,向服务器指示该报文结束了。(按照ASCII码的表示方法,每个报文以CRLF.CRLF结束,其中的CR和LF分别表示回车和换行。)
服务器对每条命令做出回答,其中每个回答含有一个回答码和一些(可选的)英文解释。我们在这里指出SMTP用的是持续连接:如果发送邮件服务器有几个报文发往同一个接收邮件服务器,它可以通过同一个TCP连接发送所有这些报文。对每个报文,该客户用一个新的“MAIL FROM:crepes.fr”开始,用一个独立的句点指示该邮件的结束,并且仅当所有邮件发送完后才发送QUIT。
你可以使用Telnet与一个SMTP服务器进行一次直接对话。使用的命令是
telnet serverName 25
其中sererName是本地邮件服务器的名称。当你这么做时,就直接在本地主机与邮件服务器之间建立了一个TCP连接。输完上述命令后,你立即会从该服务器收到220回答。接下来,在适当的时机发出HELO、MAIL FROM、RCPT TO、DATA、CRLF.CRLF以及QUIT等SMTP命令。
2.3.2 邮件报文格式
当Alice给Bob写一封邮寄时间很长的普通信件时,她可能要在信的上部包含各种各样的环境首部信息,如Bob的地址、她自己的回复地址以及日期等。类似地,当一个人给另一个人发送电子邮件时,一个包含环境信息的首部位于报文体前面。这些环境信息包括在一系列首部行中,这些行由RFC5322定义。首部行和该报文的体用空行(即回车和换行)进行分隔。
RFC 5322定义了邮件首部行和它们的语义解释的精确格式:
每个首部行包含了可读的文本,是由关键词后跟冒号及其值组成的。
某些关键词是必需的,另一些则是可选的。每个首部必须含有一个“From:”首部行和一个“To:”首部行,一个首部也许包含一个“Subject:”首部行以及其他可选的首部行。
重要的是注意到下列事实:这些首部行不同于我们在2.3.1节所学到的SMTP命令(即使那里包含了某些相同的词汇,如form和to)。那节中的命令是SMTP握手协议的一部分,本节中考察的首部行则是邮件报文自身的一部分。
一个典型的报文首部看起来如下:
在报文首部之后,紧接着一个空白行,然后是以ASCII格式表示的报文体。你应当用Telnet向邮件服务器发送包含一些首部行的报文,包括“Subject:”首部行。为此,输入命令telnet serverName 25,如在2.3.1节中讨论的那样。
2.3.3 邮件访问协议
一旦SMTP将邮件报文从Alice的邮件服务器交付给Bob的邮件服务器,该报文就被放入了Bob的邮箱中。假设Bob(接收方)在其本地主机(如智能手机或PC)上运行用户代理程序,考虑在他的本地PC上也放置一个邮件服务器是自然而然的事。在这种情况下,Alice的邮件服务器就能直接与Bob的PC进行对话了。然而这种方法会有一个问题。前面讲过邮件服务器管理用户的邮箱,并且运行SMTP的客户端和服务器端。如果Bob的邮件服务器位于他的PC上,那么为了能够及时接收可能在任何时候到达的新邮件,他的PC必须总是不间断地运行看并一直保持在线。这对于许多因特网用尸而言是个现实的。相反,典型的用户通常在本地PC上运行一个用户代理程序,它访问存储在总是保持开机的共享邮件服务器上的邮箱。该邮件服务器与其他用户共享。
现在我们考虑当从Alice向Bob发送一个电子邮件报文时所采取的路径。我们刚才已经知道,在沿着该路径的某些点上,需要将电子邮件报文存放在Bob的邮件服务器上。通过让Alice的用户代理直接向Bob的邮件服务器发送报文,就能够做到这一点。然而,通常Alice的用户代理和Bob的邮件服务器之间并没有一个直接的SMTP对话。相反,如图2-16所示,Alice的用户代理用SMTP或HTTP将电子邮件报文推入她的邮件服务器,接着她的邮件服务器(作为一个SMTP客户)再用SMTP将该邮件中继到Bob的邮件服务器。为什么该过程要分成两步呢?主要是因为不通过Alice的邮件服务器进行中继,Alice的用户代理将没有任何办法到达一个不可达的目的地邮件服务器。通过首先将邮件存放在自己的邮件服务器中,Alice的邮件服务器可以重复地尝试向Bob的邮件服务器发送该报文,如每30分钟一次,直到Bob的邮件服务器变得运行为止。(并且如果Alice的邮件服务器关机,则她能向系统管理员进行申告!)
但是对于该难题仍然有一个疏漏的环节!像Bob这样的接收方,是如何通过运行其本地PC上的用户代理,获得位于他的某ISP的邮件服务器上的邮件呢?值得注意的是Bob的用户代理不能使用SMTP得到报文,因为取报文是一个拉操作,而SMTP是一个推协议。
今天,Bob从邮件服务器取回邮件有两种常用方法:
① HTTP:如果Bob使用基于Web的电子邮件或智能手机上的客户端(如Gmail),则用户代理将使用HTTP来取回Bob的电子邮件。这种情况要求Bob的电子邮件服务器具有HTTP接口和SMTP接口(与Aice的邮件服务器通信)。
② IMAP:另一种方法是使用由RFC 3501定义的因特网邮件访问协议(Internet Mail Access Protocol,IMAP),这通常用于微软的Oulook等。HTTP和IMAP方法都支持Bob管理自己邮件服务器中的文件夹,包括将邮件移动到他创建的文件夹中,删除邮件,将邮件标记为重要邮件等。