概览
本次探索之旅从用户在浏览器中输入网址(URL)开始。
生成HTTP请求消息
浏览器的工作会从对用户输入的网址进行解析开始。浏览器如何解析网址是我们的第一个看点。然后浏览器会根据网址的含义来生成请求消息,而请求消息实际的样子就是我们的第二个看点。
向DNS服务器查询Web服务器的IP地址
浏览器如何进行这一操作也是看点之一
全世界DNS服务器的大接力
全世界共有上万台的DNS服务器,它们相互借力才能完成IP地址的查询,而它们进行接力的方法也是本章看点之一。
委托协议栈发送消息
查询到IP地址之后,浏览器就可以将消息委托给操作系统发送给Web服务器了,但这个委托到底是如何完成的呢?这也是本章的看点之一。
生成HTTP请求消息
探索之旅从输入网址开始
URL的各种格式
尽管URL有各种不同的写法,但它们有一个共同点,那就是URL开头的文字,即
http:
, ftp:
, file:
, mailto:
,这部分文字表示浏览器应当使用的访问方法。我们可以把这部分理解为访问时使用的协议类型。
浏览器先要解析URL
Web浏览器解析URL的过程
省略文件名的情况
http://www.lab.glasscom.com/dir/
对于上面这个URL我们可以这样理解,以/
结尾代表/dir/
后面本来应该有的文件名被省略了。这种情况下会访问事先设定好的默认文件,大多数情况下是index.html
或者default.htm
之类的文件名。因此,对于这个例子,服务器就会访问/dir/index.html
或者/dir/default.htm
。
还有一些URL是像下面这样只有Web服务器的域名的,这也是一种省略了文件名的形式。
http://www.lab.glasscom.com/
这个URL也是以/
结尾的,也就是说它表示访问一个名叫/
的目录,由于省略了文件名,所以结果就是访问/index.html
或者/default.htm
这样的文件。
那么,下面这个URL又是什么意思呢?
http://www.lab.glasscom.com
这次连结尾的/
都省略了。像这样连目录名都省略时,就会访问根目录下事先设置的默认文件。
但下面这个例子就更诡异了
http://www.lab.glasscom.com/whatisthis
这种情况一般会按照下面的惯例进行处理:如果Web服务器上存在名为whatisthis
的文件,则将whatisthis
作为文件名来处理;如果存在whatisthis
的目录,则将whatisthis
作为目录名来处理。因为我们无法创建两个名字相同的文件和目录。
浏览器的第一步工作就是对URL进行解析。
HTTP的基本思路
HTTP请求消息中包含的内容是对什么和进行怎样的操作两个部分,其中“对什么”的部分称为
URI
。一般来说,URI的内容是一个存放网页数据的文件名或者是一个CGI程序。“进行怎样的操作”的部分称为方法,方法表示需要让Web服务器完成怎样的工作,常用方法包括GET
和POST
等。除此之外,HTTP消息中还有一些用来表示附加信息的头字段。客户端向Web服务器发送数据时,会先发送头字段,然后再发送数据。
在收到请求消息后,Web服务器会根据这些要求来完成自己的工作,然后将结果存放在响应消息中。在响应消息的开头有一个状态码,状态码后面就是头字段和网页数据。
响应消息会被发送回客户端,客户端收到之后,浏览器会从消息中读出所需的数据并显示在屏幕上。到这里,HTTP的整个工作就完成了。
生成HTTP请求消息
对URL进行解析之后,浏览器确定了Web服务器和文件名,接下来就是根据这些信息来生成HTTP请求消息了。
HTTP消息的格式
一条请求消息中只能写一个URI。如果需要获取多个文件,必须对每个文件单独发送1条请求。
向DNS服务器查询Web服务器的IP地址
Socket库提供查询IP地址的功能
我们的计算机上有相应的DNS客户端,而相当于DNS客户端的部分称为DNS解析器。通过DNS查询IP地址的操作称为域名解析。
解析器实际上是一段程序,它包含在操作系统的Socket库中。Socket库包含的程序组件可以让其他的应用程序调用操作系统的网络功能,而解析器就是这个库中的其中一种程序组件。
Socket库是用于调用网络功能的程序组件集合。
通过解析器向DNS服务器发出查询
解析器的用法非常简单,只要像下图一样写上解析器的程序名称gethostbyname
以及Web服务器的域名www.lab.glasscom.com
就可以了,这样就完成了对解析器的调用。
调用解析器后,解析器会向DNS服务器发送查询消息,然后DNS服务器会返回响应消息。响应消息中包含查询到的IP地址,解析器会取出IP地址,并将其写入浏览器指定的内存地址中。
根据域名查询IP地址时,浏览器会使用Socket库中的解析器。
解析器的内部原理
当浏览器调用解析器时,程序的控制流程就会转移到解析器的内部。解析器会根据DNS的规格,生成一条表示“请告诉我www.lab.glasscom.com的IP地址”的数据,并将它发送给DNS服务器。
HTTP消息是用文本编写的,但DNS消息是使用二进制数据编写的。
发送消息这个操作不是由解析器本身来执行,而是要委托给操作系统内部的协议栈来执行。这是因为和浏览器一样,解析器本身也不具备使用网络收发数据的功能。解析器调用协议栈后,控制流程会再次转移,协议栈会执行发送消息的操作,然后通过网卡将消息发送给DNS服务器。
向DNS服务器发送消息时,我们当然也需要知道DNS服务器的IP地址。只不过这个IP地址是作为TCP/IP的一个设置项目事先设置好的,不需要再去查询了。Windows的设置如下图所示。
全世界DNS服务器的大接力
DNS服务器的基本工作
DNS服务器的基本工作就是接收来自客户端的查询消息,然后根据消息的内容返回响应。来自客户端的查询消息包含以下三种信息:域名、Class以及记录类型。
DNS服务器上事先保存有前面这三种信息对应的记录数据,DNS服务器就是根据这些记录查找符合查询请求的内容并对客户端作出相应的。
查询IP地址时我们使用
A
这个记录类型,查询邮件服务器时我们要使用MX
类型。当记录类型为MX
时,DNS服务器会在记录中保存两种信息,分别是邮件服务器的域名和优先级。
DNS服务器会从域名与IP地址的对照表中查找相应的记录,并返回IP地址。
域名的层次结构
互联网中存在着不计其数的服务器,将这些服务器的信息全部保存在一台DNS服务器中是不可能的,因此一定会出现DNS服务器中找不到要查询的信息的情况。
直接说答案的话很简单,就是将信息分布保存在多台DNS服务器中,这些DNS服务器相互接力配合,从而查找出要查询的信息。但这个机制其实有点复杂。
首先,DNS服务器中的所有信息都是按照域名以分层次的结构来保存的。
一个域的信息是作为一个整体存放在DNS服务器中的,不能将一个域拆开来存放在多台DNS服务器中。但一台DNS服务器中可以存放多个域的信息。
寻找相应的DNS服务器并获取IP地址
这里的关键在于如何找到我们要访问的Web服务器的信息归哪一台DNS服务器管。我们可以采用下面发的方法。
将负责管理下级域的DNS服务器的IP地址注册到它们的上级DNS服务器中,然后上级DNS服务器的IP地址再注册到更上一级的DNS服务器中。也就是说,负责管理lab.glasscom.com
这个域的DNS服务器的IP地址需要注册到glasscom.com
的DNS服务器中,而glasscom.com
域的DNS服务器的IP地址又需要注册到com
域的DNS服务器中。
这样,我们就可以通过上级DNS服务器查询出下级DNS服务器的IP地址,也就可以向下级DNS服务器发送查询请求了。
在互联网中,com
和jp
这类域名上面还有一级域
,称为根域。根域在一般书写域名时经常被忽略,一般在域名的最后加上一个句号www.lab.glasscom.com.
,这个.
就代表根域。根域的服务器中保管者com
、jp
的DNS服务器的信息。由于上级DNS服务器保管者所有下级DNS服务器的信息,所以我们可以从根域开始一路往下找到任意一个域的DNS服务器。除此之外,我们还需要将根域的DNS服务器信息保存在互联网中所有的DNS服务器中。分配给根域DNS服务器的IP地址在全世界仅有13个,而且这些地址几乎不发生变化,因此将这些地址保存在所有的DNS服务器中也不是一件难事。
我们用一张图来看一下这个过程是如何进行的。
通过缓存加快DNS服务器的响应
有时候并不需要从最上级的根域开始查找,因为DNS服务器有一个缓存功能,可以记住之前查询过的域名。如果要查询的域名和相关信息已经在缓存中,那么就可以直接返回响应,缓存可以减少查询所需的时间。
由于信息被缓存后,原本的注册信息可能会发生改变,这时缓存中的信息就有可能是不正确的。因此,DNS服务器中保存的信息都设置一个有效期,当缓存中的信息超过有效期后,数据就会从缓存中被删除。
委托协议栈发送消息
数据收发操作概览
知道了IP地址之后,就可以委托操作系统内部的协议栈向这个目标IP地址,也就是我们要访问的Web服务器发送消息了。
和向DNS服务器查询IP地址的操作一样,这里也需要使用Socket库中的程序组件。不过,查询IP地址只需要调用一个程序组件就可以了,而这里需要按照指定的顺序调用多个程序组件。
向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用Socket库中的程序组件。
使用Socket库来收发数据的操作过程如下图所示。简单来说,收发数据的两台计算机之间连接了一条数据通道,数据沿着这条通道流动,最终到达目的地。
这条管道并不是一开始就有的,在进行收发数据操作之前,双方需要先建立起这条管道。建立管道的关键在于管道两端的数据出入口,这些出入口称为套接字。我们需要先创建套接字,然后再将套接字连接起来形成管道。服务器程序一般会在启动后就创建好套接字并等待客户端连接管道。管道在连接时是由客户端发起的,但在断开时可以由客户端或服务器任意一方发起。其中一方断开后,另一方也会随之断开,当管道断开后,套接字也会被删除。到此为止,通信操作就结束了。
综上所述,收发数据的操作分为若干个阶段,可以大致总结为以下4个。
- 创建套接字(创建套接字阶段)
- 将管道连接到服务器端的套接字上(连接阶段)
- 收发数据(通信阶段)
- 断开管道并删除套接字(断开阶段)
在每个阶段,Socket库中的程序组件都会被调用来执行相关的数据收发操作。这四个操作都是由操作系统中的协议栈来执行的,是由浏览器委托。这些委托操作都是通过调用Socket库中的程序组件来执行的,但这些数据通信用的程序组件其实仅仅充当了一个桥梁的作用,并不执行任何实质性的操作,应用程序的委托内容最终会被原原本本地传递给协议栈。因此后文会将Socket库和协议栈看成一个整体来讲解。
当调用Socket库中的程序组件时,应用程序所指定的参数会通过Socket库的程序组件传递给协议栈,并由协议栈来实际执行相应的操作。
下面我们来探索一下应用程序(浏览器)委托收发数据的过程。这个过程的关键点是按照一定的顺序调用Socket库中的若干个程序组件。
创建套接字阶段
客户端创建套接字的操作非常简单,只要调用Socket库中的socket
程序组件就可以了。和调用解析器一样,调用socket
之后,控制流程会转移到socket内部并执行创建套接字的操作。
套接字创建完成后,协议栈会返回一个描述符,应用程序会将收到的描述符放在内存中。描述符是用来识别不同的套接字的,因为实际上计算机中会同时进行多个数据的通信操作,有多个数据收发操作在进行,也就需要创建多个不同的套接字,描述符就是帮助我们来识别出特定的套接字的方法。
应用程序是通过“描述符”这一类似号码牌的东西来识别套接字的。
连接阶段:把管道接上去
接下来我们需要委托协议栈将客户端创建的套接字与服务器那边的套接字连接起来。应用程序通过调用Socket库中的名为connect
的程序组件来完成这一操作。当调用connect时,需要指定描述符、服务器IP地址和端口号这三个参数。
这里解释一下端口号,通过IP地址我们能识别出网络上的某台计算机。但连接操作的对象是某个具体的套接字,而通过端口号我们可以明确识别出某台具体的计算机上的某个具体的套接字。
如果说描述符是用来在一台计算机内部识别套接字的机制,那么端口号就是用来让通信的另一方能够识别出套接字的机制。
但网址上好像并没有端口号?也不能像IP地址一样去问DNS服务器,那怎么办?
其实服务器上所使用的端口号是根据应用的种类实现规定好的,比如Web是80号端口,电子邮件是25号端口。只要制定了实现规定好的端口号,就可以链接到相应的服务器程序的套接字。
而客户端自己在创建套接字时,协议栈会为这个套接字随便分配一个端口号。接下来,当协议栈执行连接操作时,会将这个随便分配的端口号通知给服务器。
总结:
当调用connect后,协议栈就会执行连接操作。当连接成功后,协议栈会将对方的IP地址和端口号等信息保存在套接字中,这样就可以开始收发数据了。
描述符:应用程序用来识别套接字的机制
IP地址和端口号:客户端和服务器之间用来识别对方套接字的机制
通信阶段:传递消息
只要将数据送入套接字,数据就会被发送到对方的套接字中。这个操作需要Socket库中的wirte
程序组件来完成。应用程序需要在内存中准备好要发送的数据,在这个具体例子里,根据用户输入的网址生成的HTTP请求消息就是我们要发送的数据。
当消息返回后,需要执行的是接收消息的操作。接收消息的操作是通过Socket库中的read
程序组件委托协议栈来完成的。调用read时需要制定用于存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区。
断开阶段:收发数据结束
当浏览器收到数据之后,收发数据的过程就结束了。我们需要调用Socket库的close
程序组件进入断开阶段。最终,连接在套接字之间的管道会被断开,套接字本身也会被删除。
断开的过程如下。Web使用的HTTP协议规定,当Web服务器发送完响应消息之后,应该主动执行断开操作。
这就是HTTP的工作过程,HTTP协议将HTML文档和图片都作为单独的对象来处理。因此,如果一个网页中包含很多张图片,就必须重复进行很多次连接、收发数据、断开的操作。重复连接和断开显然是效率很低的。因此后来人们又设计出了能够在一次连接中收发多个请求和相应的方法,在HTTP版本1.1中就可以使用这种方法。在这种情况下,当所有数据都请求完成后,浏览器会主动触发断开连接的操作。
专业名词
URL
- Uniform Resource Locator,统一资源定位符
FTP
- File Transfer Protocol,文件传送协议
- 这是一种在上传、下载文件时使用的协议。使用FTP协议来传送文件的程序也被叫做FTP。
HTTP
- Hypertext Transfer Protocol,超文本传送协议
URI
- Uniform Resource Identifier,统一资源标识符
DNS
- Domain Name System,域名服务系统
- 将服务器名称和IP地址进行关联是DNS最常见的用法。但DNS的功能并不仅限于此,它还可以将邮件地址和邮件服务器进行关联,以及为各种信息关联相应的名称
socket
表示程序组件的名称
Socket库
- Socket库是用于调用网络功能的程序组件集合
套接字
- 表示管道两端的接口
协议栈
- 操作系统内部的网络控制软件,也叫“协议驱动”“TCP/IP驱动”等。
邮件服务器优先级
- 当一个邮件地址对应多个邮件服务器时,需要根据优先级来判断哪个邮件服务器是优先的。优先级数值较小的邮件服务器代表更优先
UDP
- User Datagram Protocol,用户数据报协议