一、HTTP
HTTP(Hypertext Transfer Protocol,超文本传输协议)是万维网上进行通信时所使用的协议方案。HTTP使用的是可靠的数据传输协议。HTTP有很多应用,但最著名的是用于Web浏览器和Web服务器之间的双工通信。
注:HTTP被译为“超文本传输协议”,其中transfer使用了“传输”的含义,但依据HTTP制定者之一Roy Fielding博士的论文,transfer表示的是“(状态的)转移”,而不是“传输”。
二、Web客户端和服务器
Web内容都是存储在Web服务器上的。Web服务器所使用的是HTTP协议,因此经常会被称为HTTP服务器。这些HTTP服务器存储了因特网中的数据,如果HTTP客户端发出请求的话,它们会提供数据。客户端向服务器发送HTTP请求,服务器会在HTTP响应中回送所请求的数据。HTTP客户端和HTTP服务器共同构成了万维网的基本组件。
三、资源
Web服务器是Web资源(Web resource)的宿主。Web资源是Web内容的源头。最简单的Web资源就是Web服务器文件系统中的静态文件。这些文件可以包含任意内容:文本文件、HTML文件、Word文件、JPEG图片文件等等。
但资源不一定非得是静态文件。资源还可以是根据需要生成内容的软件程序。这些动态内容资源可以根据你的身份、所请求的信息或每天的不同时段来产生内容。
四、媒体类型
因特网上有数千种不同的数据类型,HTTP仔细地给每种要通过Web传输的对象都打上了名为MIME类型(MIME type)的数据格式标签。最初设计MIME(Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。MIME在电子邮件系统中工作得非常好,因此HTTP也采纳了它,用它来描述并标记多媒体内容。
Web服务器会为所有HTTP对象数据附加一个MIME类型。当Web浏览器从服务器中取回一个对象时,会去查看相关的MIME类型,看看它是否知道应该如何处理这个对象。大多数浏览器都可以处理数百种常见的对象类型。
MIME类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间由一条斜杠来分隔。
- HTML格式的文本文档由text/html类型来标记。
- 普通的ASCII文本文档由text/plain类型来标记。
- JPEG格式的图片为image/jpeg类型。
- GIF格式的图片为 image/gif类型。
常见的MIME类型有数百个,实验性或用途有限的MIME类型则更多。
五、URI
每个Web 服务器资源都有一个名字,这样客户端就可以说明它们感兴趣的资源是什么了。服务器资源名被称为:统一资源标识符(Uniform Resource Identifier,URI)。URI就像因特网上的邮政地址一样,在世界范围内唯一标识并定位信息资源。URI有两种形式,分别称为URL和URN。
5.1、URL
统一资源定位符(URL)是资源标识符最常见的形式。URL描述了一台特定服务器上某资源的特定位置。它们可以明确说明如何从一个精确、固定的位置获取资源。大部分URL都遵循一种标准格式,这种格式包含三个部分,即:
方案://服务器位置/路径。
- URL 的第一部分被称为方案(scheme),说明了访问资源所使用的协议类型。这部分通常就是HTTP协议(http://)。
- 第二部分给出了服务器的因特网地址(比如:www.joes-hardware.com)
- 其余部分指定了Web服务器上的某个资源(比如:/specials/saw-blade.gif)
现在,几乎所有的URI都是URL。URL可以通过HTTP之外的其他协议类访问资源。如个人的E-mail账户:
mailto:president@whitehouse.gov
或ftp访问文件:
ftp://ftp.lots-o-books.com/pub.xls
5.2、URN
URI的第二种形式就是:统一资源名(URN)。URN是作为特定内容的唯一名称使用的。与目前的资源所在地无关。使用这些与位置无关的URN,就可以将资源四处搬移。通过URN,还可以用同一个名字通过多种网络访问协议来访问资源。比如,不论因特网标准文档RFC 2141 位于何处(甚至可以将其复制到多个地方),都可以用下列URN来命名它:
urn:item:roc:2141
六、事务
一个HTTP事务由一条从客户端发往服务器的请求命令和一个从服务器发回客户端的响应结果组成。这种通信是通过名为HTTP报文(HTTP message)的格式化数据块进行的。从Web客户端发往Web服务器的HTTP报文称为请求报文,从服务器发往客户端的报文称为响应报文,此外没有其他类型的HTTP报文。
HTTP请求和响应报文的格式很类似。HTTP报文包括以下三个部分:
起始行:报文的第一行就是起始行,在请求报文中用来说明要做什么,比如:GET /tools.html HTTP/1.0;在响应报文中说明出现了什么情况,如:HTTP/1.0 200 OK 。
首部字段(headers):首部字段时键值对,首部以一个空行结束。例如, Content-Type:text/html 说明了文档的MIME类型。
主体:空行之后就是可选的报文主体了,其中包含了所有类型的数据。主体中可以包含任意的二进制数据(比如图片、视频、音频、软件程序),当然,主体中也可以包含文本。
七、TCP/IP
HTTP 是个应用层协议。HTTP无需操心网络通信的具体细节;它把联网的细节都交给了通用、可靠的因特网传输协议TCP/IP。
TCP提供了:
1、无差错的数据传输;
2、按序传输(数据总是会按照发送的顺序到达);
3、未分段的数据流。
因特网自身就是基于TCP/IP 的,TCP/IP 是全世界的计算机和网络设备常用的层次化分组交换网络协议集。TCP/IP 隐藏了各种网络和硬件的特点及弱点,使各种类型的计算机和网络都能够进行可靠的通信。HTTP网络协议栈如下:
7.1、TCP的可靠数据管道
世界上几乎所有的HTTP通信都是由TCP/IP承载的,TCP/IP是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。
HTTP连接实际上就是TCP连接及其使用规则。TCP连接是因特网上的可靠连接。TCP为HTTP提供了一条可靠的比特传输管道。从TCP连接一端填入的字节会从另一端以原有的顺序、正确地传送出来。
在浏览器中访问地址步骤如下:
http://www.joes-hardware.com:80/power-tools.html
- 1、浏览器解析出主机名:www.joes-hardware.com
- 2、浏览器查询这个主机名的IP地址(DNS):202.43.78.3
- 3、浏览器获得端口号(80)
- 4、浏览器发起到202.43.78.3端口80的连接
- 5、浏览器向服务器发送一条HTTP GET报文
- 6、浏览器从服务器读取HTTP响应报文
- 7、浏览器关闭连接
7.2、TCP流是分段的、由IP分组传送
TCP的数据是通过名为IP分组(或IP数据报)的小数据块来发送的。这样的话,HTTP就是“HTTP over TCP over IP”这个协议栈中的最顶层了。其安全版本HTTPS就是在HTTP和TCP之间插入了一个(称为TLS或SSL的)密码加密层。
HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在IP分组中,通过英特网进行传输。所有这些工作都是由TCP/IP 软件来处理的,HTTP程序员什么都看不到。
每个TCP段都是由IP分组承载,从一个IP地址发送到另一个IP地址的。每个IP分组中都包括:
- 一个IP分组首部(通常为20字节)
- 一个TCP段首部(通常为20字节)
- 一个TCP数据块(0个或多个字节)
IP首部包含了源和目的IP地址、长度和其他一些标记。TCP段的首部包含了TCP端口号、TCP控制标记,以及用于数据排序和完整性检查的一些数字值。
7.3、保持TCP连接持续不断地运行
在任意时刻计算机都可以有几条TCP连接处于打开状态。TCP是通过端口号来保持所有这些连接持续不断地运行。
IP地址可以将你连接到正确的计算机,而端口号则可以将你连接到正确的应用程序上去。TCP连接是通过4个值来识别的:
<源IP地址、源端口号、目的IP地址、目的端口号>
这4个值一起唯一地定义了一条连接。两条不同的TCP连接不能拥有4个完全相同的地址组件值。
7.4、用TCP套接字编程
操作系统提供了一些操纵其TCP连接的工具。为了更具体地说明问题,我们来看一下TCP编程接口。如下:
// 创建一个新的、未命名、未关联的套接字
s = socket(<parameters>)
// 向套接字赋一个本地端口号和接口
bind(s,<local IP:port>)
// 创建一条连接本地套接字与远程主机及端口的连接
connect(s,<remote IP:port>)
// 标识一个本地套接字,使其可以合法接受连接
listen(s,...)
// 等待某人建立一条道本地端口的连接
s2 = accept(s)
// 尝试从套接字向缓冲区读取n个字节
n = read(s,buffer,n)
// 尝试从缓冲区向套接字写入n个字节
n = write(s,buffer,n)
// 完全关闭TCP连接
close(s)
这个套接字API向HTTP程序员隐藏了TCP和IP的所有细节。套接字API最初是为Unix操作系统开发的,但现在几乎所有的操作系统和语言中都有其变体存在。
套接字API允许用户创建TCP的端点数据结构,将这些端点与远程服务器的TCP端点进行连接,并对数据流进行读写。TCP API隐藏了所有底层网络协议的握手细节,以及TCP数据流与IP分组之间的分段和重装细节。
TCP客户端和服务器是如何通过TCP套接字接口进行通信的?
我们从Web服务器等待连接开始。客户端根据URL判断出IP地址和端口号,并建立一条到服务器的TCP连接。建立连接可能需要花费一些时间,时间长短取决于服务器距离的远近、服务器的负载情况,以及英特网的拥挤程度。
一旦建立了连接,客户端就会发送HTTP请求,服务器则会读取请求。一旦服务器获取了整条请求报文,就会对请求进行处理,执行所请求的动作,并将数据写回客户端。客户端读取数据,并对响应数据进行处理。
7.5、对TCP性能的考虑
HTTP紧挨着TCP,位于其上层,所以HTTP事务的性能在很大程度上取决于底层TCP通道的性能。
7.5.1、HTTP事务的时延
与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则HTTP时延就是由TCP网络时延构成的。
HTTP事务时延有以下几种主要原因。
1、客户端首先根据URI确定Web服务器的IP地址和端口号。如果最近没有对URI中的主机名进行访问,通过DNS解析系统将URI中的主机名转换成一个IP地址可能需要花费数十秒的时间。
2、接下来,客户端会向服务器发送一条TCP连接请求,并等待服务器回送一个请求接受应答。每条新的TCP连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个HTTP事务的话,这个值会快速地叠加上去。
3、一旦连接建立起来了,客户端就会通过新建立的TCP管道来发送HTTP请求。数据送达时,Web服务器会从TCP连接中读取请求报文,并对请求进行处理。因特网传输请求报文,以及服务器处理请求报文都需要时间。
4、然后,Web服务器会回送HTTP响应,这也需要花费时间。
这些TCP网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。TCP协议的技术复杂性也会对时延产生巨大的影响。
7.5.2、性能聚焦区域
常见的TCP相关时延包括:
- TCP连接建立握手
- TCP慢启动拥塞机制
- 数据聚集的Nagle算法
- 用于捎带确认的TCP延迟确认算法
- TIME_WAIT 时延和端口耗尽
如果要编写高性能的HTTP软件,就应该理解上面的每一个因素。
7.5.3、TCP连接的握手时延
建立一条新的TCP连接时,甚至是在发送任意数据之前,TCP软件之间会交换一系列的IP分组,对连接的有关参数进行沟通。如果连接只用来传送少量数据,这些交换过程就会严重降低HTTP的性能。
TCP连接握手需要经过以下几个步骤:
- 1、请求新的TCP连接时,客户端要向服务器发送一个小的TCP分组(通常是40-60个字节)。这个分组中设置了一个特殊的SYN标记,说明这是一个连接请求。
- 2、如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个TCP分组。这个分组中的SYN和ACK标记都被置位,说明连接请求已被接受。
- 3、最后,客户端向服务器回送一条确认信息,通知它连接已成功建立,现代的TCP栈都允许客户端在这个确认分组中发送数据。
HTTP程序员永远不会看到这些分组——这些分组都由TCP/IP软件管理,对其是不可见的。HTTP程序员看到的只是创建TCP连接时存在的时延。
HTTP事务可能会在TCP建立上花费50%,或更多的时间。可以通过重用现存连接来减少这种TCP建立时延所造成的影响。
7.5.4、延迟确认
由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以TCP实现了自己的确认机制来确保数据的成功传输。
每个TCP段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确认信息,发送者就认为分组已经被破坏或损毁,并重发数据。
由于确认报文很小,所以TCP允许在发往相同方向的输出数据分组中对其进行“捎带”。TCP将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多TCP栈都实现了“延迟确认”算法。延迟确认算法会在一个特定的窗口时间(通常是100-2--毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。
但是,HTTP具有双峰特性的请求-应答行为降低了捎带信息的可能。当希望有相反方向的回传分组的时候,偏偏没有那么多。通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。
在对TCP栈的任何参数进行修改之前,一定要对自己在做什么有清醒的认识。TCP中引入这些算法的目的是防止设计欠佳的应用程序对因特网造成的破坏。对TCP配置进行的任意修改,都要绝对确保应用程序不会引发这些算法所要避免的问题。
7.5.5、TCP慢启动
TCP数据传输的性能还取决于TCP连接的使用期(age)。TCP连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐被称为TCP慢启动(slow start),用于防止因特网的突然过载和拥塞。
TCP慢启动限制了一个TCP端点在任意时刻可以传输的分组数。简单来说,每成功接收一个分组,发送端就有了发送另外两个分组的权限。如果某个HTTP事务有大量数据要发送,是不能一次将所有分组都发送出去的。必须发送一个分组,等待确认;然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,以此类推。这种方式被称为“打开拥塞窗口”。
由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定数据的、已调谐连接慢一些。由于已调谐连接要更快一些,所以HTTP中有一些可以重用现存连接的工具。
7.5.6、Nagle算法与TCP——NODELAY
TCP有一个数据流接口,应用程序可以通过它将任意尺寸的数据放入TCP栈中——即使一次只放一个字节也可以!但是,每个TCP段中都至少装载了40个字节的标记和首部,所以如果TCP发送了大量包含少量数据分组,网络的性能就会严重下降。
备注:发送大量单字节分组的行为称为“发送端傻窗口综合症”。这种行为效率很低、违反社会道德,而且可能会影响其他的因特网流量。
Nagle算法(根据其发明者John Nagle命名)试图在发送一个分组之前,将大量TCP数据绑定在一起,以提高网络效率。RFC 896 “IP/TCP互联网络中的拥塞控制”对此算法进行了描述。
Nagle算法会引发几种HTTP性能问题。首先,小的HTTP报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生时延,其次,Nagle算法与延迟确认之间的交互存在物问题——Nagle算法会阻止数据的发送,直到有确认分组抵达为止,但确认分组自身会被延迟确认算法延迟100~200毫秒。
HTTP应用程序常常会在自己的栈中设置参数TCP_NODELAY,禁用Nagle算法,提高性能。如果要这么做的话,一定要确保会向TCP写入大块的数据,这样就不会产生一堆小分组了。