探索之旅总览
第一章 浏览器生成消息——探索浏览器内部
1.1 生成HTTP请求消息
1.1.1 从输入网址开始
- 基础概念
1.URL:Uniform Resource Locater 统一资源定位符,就是我们通常说的网址。
常见的http://开头,而网址还可以ftp:、file:等开头。
2.浏览器:具备多种客户端功能的综合性软件。使用各种不同的URL来判断应该使用哪种功能去访问哪里的数据。例如:访问Web服务器使用"http://",访问FTP使用"ftp:"。后面的内容告诉要访问数据的位置。可以理解成URL=访问方法+访问位置。
3.协议:通信操作的规则定义(protocol)。
4.HTTP:Hypertext Transfer Protocol,超文本传送协议
URL根据访问目标不同写法不同:
1.1.2 浏览器解析URL
浏览器第一步工作是先对输入的网址(URL)进行解析,以访问Web浏览器(使用http://)为例,解析过程如下:
示例中就是在找www.lab.glasscom.com这个Web服务器上路径为/dir1/file.html的文件。
1.1.3 URL省略文件名的情况
有时候我们会见到一些不符合上面介绍的格式的URL。
以
/
结尾,在/dir/
后面省略了访问的文件名,服务器已经默认设置在省略文件名情况下要访问的默认文件名,一般都会访问/dir/index.html
或者/dir/default.html
。
一般处理方案:如果web服务器上存在dir文件,则将dir当作文件名来处理;如果web服务器上存在名为dir的目录,则将dir当作目录来处理。
这个URL表示:它访问一个名叫
/
的目录。由于省略了文件名,所以根据上一种情况,它访问的文件也就是/index.html
或者/default.html
。
注释:/
目录表示的是目录层级中最顶级的『根目录』。
没有路径时,表示访问
根目录
下事先设置的默认文件夹,也就是/index.html
或者/default.html
。
1.1.4 HTTP的基本思路
- 基本概念
1.HTTP协议:定义了客户端和服务器之间交互的消息内容和步骤。解析完URL之后,浏览器使用HTTP协议来访问Web浏览器。
2.URI:Uniform Resource Identifier,统一资源标志符。
3.CGI程序:对Web服务器程序调用其他程序的规则所做的定义就是CGI。按照CGI规范来工作的程序是CGI程序。
4.表单:网页中的文本框、复选框等能够输入数据的部分。
基本思路是
- 客户端向服务器发送请求消息,消息包括内容为“对什么”和“进行怎样的操作”两部分。
1.第一部分,访问目标——“对什么”-URI,URI的一般内容是存放一个网页数据的文件名或者一个CGI程序的文件名。“/dir1/file1.html”“/dir1/program1.cgi”等。
2.第二部分,方法——“进行怎样的操作”:表示需要让web服务器完成怎样的工作,其中典型的例子包括读取URI表示的数据、将客户端输入的数据传递给CGI程序等。
Web服务器对收到请求消息,并对其中内容进行解析,通过URI和方法来判断“对什么”“进行怎样的操作”,完成工作之后把结果放在响应消息中。响应消息开头有一个状态吗,表示操作的执行结果是成功还是发生了错误。我们经常见我交的教务处网站404 NOT FOUND就是表示错误信息的状态码。响应消息被发送回客户端之后,浏览器会从消息中读出所需数据并显示在屏幕上。整个HTTP工作就完成了。
1.1.5生成HTTP请求消息
对URL解析之后,浏览器确定了服务器和文件名,接着就要根据这些信息按照规定的格式来生成HTTP请求消息了。
“方法”有很多种,我们探索是从地址栏输入网址开始,会向Web服务器发送GET请求消息。而触发发送动作的还有点击超链接,或在表单填写信息之后点击“提交”按钮等等,选用哪种方法(GET POST等等)根据场景来决定。有表单的一般是POST(表单的属性中也可以指定GET方法,但是GET能够发送的表单数据只有几百个字节)
1.1.6 发送请求后收到响应
- 基本概念
标签:编写网页所使用的HTML 语言中规定的控制信息。例如,当需要在网 页中插入图片时,需要在相应位置嵌入形如 <img src="image1.jpg"> 的标签。
状态行=状态码(一个数字,向程序告知执行结果)+响应短语(一段文字,向人类告知运行结果)
如果网页的内容只有文字,那么到这里就全部 处理完毕了,但如果网页中还包括图片等资源,则还有下文。
当网页中包含图片时,会在网页中的相应位置嵌入表示图片文件的标签的控制信息。浏览器会在显示文字时搜索相应的标签,当遇到图片相关的标签时,会在屏幕上留出用来显示图片的空间,然后再次访问Web 服务 器,按照标签中指定的文件名向Web 服务器请求获取相应的图片并显示在 预留的空间中。这个步骤和获取网页文件时一样,只要在URI部分写上图 片的文件名并生成和发送请求消息就可以了。 由于每条请求消息中只能写1 个 URI,所以每次只能获取1 个文件, 如果需要获取多个文件,必须对每个文件单独发送1 条请求。比如1 个 网页中包含3张图片,那么获取网页加上获取图片,一共需要向Web 服务 器发送4条请求。
下面是一个HTTP消息的示例:
1.2向DNS服务器查询Web服务器的IP地址
浏览器能够解析网址并生成HTTP消息,但它本身并不具备将消息发送到网络中的功能,发送消息功能由操作系统实现,浏览器会委托操作系统发送自己生成的HTTP消息。而委托时候,必须告诉操作系统发送目标的地址,要提供IP地址,而不是域名。而浏览器得到的是域名,所以必须要通过域名来查询IP地址。
1.2.1 IP地址的基础知识
- 基本概念
- 域名和IP地址:类似于www.xjtu.edu.cn和192.168.xx.xx这样的。IP地址和现实中的地址含义是相同的,不能有两台设备使用相同的IP。IP地址=网络号(在哪个子网)+主机号(是哪台设备)
- 路由器和集线器:对包进行转发的设备,第三章会详细介绍。
下面是TCP/IP的结构
实际的IP地址是一串 32比特的数字,按照8比特(1字节)为一组分成4组,分别用十进制表示然后再用圆点隔开。通过子网掩码来区分主机号和网络号。是一 串与IP地址长度相同的32比特数字,其左边一半都是1,右边一半都是0。其中,子网掩码为1的部分表示网络号,子网掩码为0的部分表示主机号。子网掩码的表示方法也可以比较简略的只写出1的个数。
1.2.2 域名和IP地址并用的理由
Q:既然最终都是要得到IP地址,才能委托操作系统发送消息,那为什么不一开始就在网址里面填写IP,而是去写域名?
A:确实如此,直接写IP也是能够正常工作的(但是如果Web服务器使用了虚拟主机功能就不行)对大部分人类来讲,记住一串数字可能比记住www.baidu.com更容易一些。
Q:那干脆不用IP了,直接用域名岂不是更好,操作系统为什么只接受域名作为发送目标?
A:第一,IP地址4个字节,而域名最短也要几十个字节,最长可以有255字节。处理更长的字节数显然效率更加低下;第二,域名不光长,长度还不固定,处理长度不固定的数据更是复杂。
因此,我们让人类来使用域名,让路由器使用IP地址。我们需要一个能在他们之间互相翻译的东西。连接他们的桥梁就是DNS服务器。
1.2.3 Socket库提供的查询IP地址的功能
- 基本概念
- DNS:Domain Name System,域名服务系统。将服务器名称和IP 地址进行 关联是DNS 最常见的用法,但DNS 的功能并不仅限于此,它还可以将邮 件地址和邮件服务器进行关联,以及为各种信息关联相应的名称。
- DNS解析器:通过DNS查询IP地址的操作称为域名解析,我们的计算机上负责执行解析(resolution)这一操作的就叫解析器(resolver)。
- Socket库:是用于调用网络功能的程序组件集合。包含很多用于发送和接收数据的程序组件。
查询的过程大致是:询问最近的DNS 服务器“www. lab.glasscom.com的IP地址是什么”就可以了,DNS服务器会回答说“该服务器的IP地址为xxx.xxx.xxx.xxx”。
1.2.4 通过解析器向DNS服务器发出查询
解析器实际上是一段程序,包含在操作系统的Socket库中。要用解析器,只需要在编写浏览器等应用程序时候,调用Socket库相关组件就可以了(就是c语言里面调用一个函数)。调用解析器是这样,后面各种其他组件的调用也都是类似的
- 解析器向DNS服务器发送查询消息
- DNS返回响应消息,包含IP地址
- 解析器取出IP地址,放入浏览器指定的内存地址
- 最后浏览器向Web服务器发送消息时候,只需要从内存中取出IP地址,将其和HTTP请求消息一起交给操作系统
1.2.5 解析器内部原理
- 基本概念
- 协议栈:操作系统内部的网络控制软件,也叫“协议驱动”“TCP/IP 驱动”等。
解析器只是一段程序,和浏览器一样,本身并不具备使用网络收发数据的功能。发送查询消息是交给操作系统协议栈来完成的。协议栈发送消息,通过网卡将消息发送给DNS服务器。
如果要访问的Web 服务器已经在DNS服务器上注册,那么这 条记录就能够被找到,然后其IP地址会被写入响应消息并返回给客户端 。
- Q:向DNS服务器发送消息,不需要知道DNS服务器的IP地址吗?
-
A:好问题,当然需要知道,只不过这个IP地址是作为TCP/IP的一个设置项目设置好了。
1.3全世界DNS服务器大接力
1.3.1 DNS服务器的基本工作
DNS服务器的基本工作就是接收来自客户端的查询消 息,然后根据消息的内容返回响应。
虽然图1.14展示的是表格形式,但实际上这些信息是保存在配 置文件中的,表格中的一行信息被称为一条资源记录。
1.3.2 域名的层次结构
互联网中存在着不计其数的服务器,将这些服务器的信息全部保存在一台DNS服务器中是不可能的,因此一定 会出现在DNS服务器中找不到要查询的信息的情况。为了解决这个问题,需要将信息分布保存在多台DNS服务器中, 这些DNS服务器相互接力配合,从而查找出要查询的信息。
域
DNS中的域名都是用句点
来分隔的,比如 www.lab.glasscom.com,这里的句点代表了不同层次之间的界限,就相当于公司里面的组织结构不用部、科之类的名称来划分,只是用句点来分隔而已 。
在域名中,越靠右的位置表示其层级越高,比如 www. lab. glasscom. com这个域名如果按照公司里的组织结构来说,大概就是“ com事业集团 glasscom部 lab科的 www”这样。其中,相当于一个层级的部分称为域。因此, com域的下一层是 glasscom域,再下一层是 lab域,再下面才是 www这个名字。域对应DNS服务器
每个域作为一个整体来处理,一个域的信息作为整体存放在DNS服务器中。在域的下面建立子域,分给其它的DNS服务器。于是DNS服务器也具有了像域名一样的层次结构。
例如:比如www.nikkeibp.co.jp这个域 名,最上层的jp代表分配给日本这个国家的域;下一层的co是日本国内 进行分类的域,代表公司;再下层的nikkeibp就是分配给某个公司的域; 最下层的www就是服务器的名称。
1.3.3 寻找相应的DNS服务器并获取IP地址
- 基本概念
- 根域:com、jp、cn这些域(称为顶级域)的上面还有一级域,称为根域。根域不像 com、 jp那样有自己的名字,因此在一般书写域名时经常被省略,如果要明确表示根域,应该像 www. lab. glasscom. com.这样在域名的最后再加上一个句点,而这个最后的句点就代表根域。不过,一般都不写最后那个句点,因此根域的存在往往被忽略,但根域毕竟是真实存在的,根域的 DNS服务器中保管着 com、 jp等的 DNS服务器的信息。由于上级 DNS服务器保管着所有下级 DNS服务器的信息,所以我们可以从根域开始一路往下顺藤摸瓜找到任意一个域的 DNS服务器。
- 通过根域找到目标DNS服务器:还需要完成另一项工作,那就是将根域的 DNS服务器信息保存在互联网中所有的 DNS服务器中。这样一来,任何 DNS服务器就都可以找到并访问根域 DNS服务器了。因此,客户端只要能够找到任意一台 DNS服务器,就可以通过它找到根域 DNS服务器,然后再一路顺藤摸瓜找到位于下层的某台目标 DNS服务器。分配给根域 DNS服务器的 IP地址在全世界仅有 13个,而且这些地址几乎不发生变化,因此将这些地址保存在所有的 DNS服务器中也并不是一件难事。实际上,根域 DNS服务器的相关信息已经包含在 DNS服务器程序的配置文件中了,因此只要安装了 DNS服务器程序,这些信息也就被自动配置好了。
关键在于如何找到我们要访问的Web 服务器的信息归哪一台DNS服务器管。
Q:互联网中数以万计的DNS服务器,怎么找到相应的那个DNS服务器?
A:肯定不能一台一台挨个去找。但是别忘了我们上一节讲过的层次结构。我们可 以采用下面的办法。首先,将负责管理下级域的DNS服务器的IP地址注 册到它们的上级DNS服务器中,然后上级DNS服务器的IP地址再注册到 更上一级的DNS服务器中,以此类推。这样就可以通过上级DNS服务器查询出下级DNS服务器的IP地址,也就可以像下级DNS服务器发送查询请求了。
1.3.4 通过缓存加快DNS服务器的响应
1.真实互联网中,一个DNS服务器往往管辖了多个域,例如上图中,每一个 域旁边都写着一台DNS服务器,但现实中上级域和下级域有可能共享同一 台DNS服务器。在这种情况下,访问上级DNS服务器时就可以向下跳过 一级DNS服务器,直接返回再下一级DNS服务器的相关信息。
2.有时候并不需要从最上级的根域开始查找,因为DNS服务器有一 个缓存
功能,可以记住之前查询过的域名。如果要查询的域名和相关信息已 经在缓存中,那么就可以直接返回响应,接下来的查询可以从缓存的位置开 始向下进行。相比每次都从根域找起来说,缓存可以减少查询所需的时间。 并且,当要查询的域名不存在时,“ 不存在”这一响应结果也会被缓 存。这样,当下次查询这个不存在的域名时,也可以快速响应。
所有的缓存功能都要注意一个点,那就是信息被缓存后,原本的注册信 息可能会发生改变,这时缓存中的信息就有可能是不正确的。
解决这个问题一般是两个方向:
1.缓存有效期,过了有效期,缓存就会删除,在对查询进行响应时,DNS服务器也会告知客 户端这一响应的结果是来自缓存中还是来自负责管理该域名的DNS服务器。
2.在每次查询时候,确认一下有没有更新。
这在后面我们遇到其他类型的缓存之后,还会讨论这个问题。
1.4委托协议栈发送消息
1.4.1 数据收发操作概览
知道IP地址之后,就要委托操作系统内部协议栈来向目标地址发送请求消息了。向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用 Socket 库中的程序组件。(和向DNS查询IP地址调用gethostbyname是一样的)
TCP协议收发数据的过程,简单来说,就是在两台计算机之间连接了一条数据通道,数据沿着这条通道流动,流动是双向的。
这条管道并不是一开始就有的,收发数据之前,先需要建立这条管道,建立的关键在于管道两端的数据出入口——套接字。
1.服务器一方 先创建套接字,然后等待客户端向该套接字连接管道(服务器程序一般会在启动后就创建好套接字并等待客户端连接管道。)
2.客户端也会先创建一个套 接字,然后从该套接字延伸出管道,最后管道连接到服务器端的套接字上。
3.当双方的套接字连接起来之后,通信准备就完成了。
4.然后就可以将数据送入套接字收发数据了。
5.当数据全部发送完毕之 后,连接的管道将会被断开。管道在连接时是由客户端发起的,但在断开 时可以由客户端或服务器任意一方发起 (根据应用程序的规则来决定的)
简略版是这样的:
(1)创建套接字(创建套接字阶段)
(2)将管道连接到服务器端的套接字上(连接阶段)
(3)收发数据(通信阶段)
(4)断开管道并删除套接字(断开阶段)
各个阶段的细节到底做了什么,我们第二章会探索,下面只是简述一下。
1.4.2 创建套接字阶段——socket(<使用IPv4>,<流模式>,...);
调用socket之后套接字就创建好了,协议栈会返回一个描述符,应用程序会将收到的描述符存放在内存中。
- 基本概念
- 描述符:是用来识别不同的套接字的。计算机中会同时进行多个数据的通信操作,比如可以打开两个浏览器窗口,同时访问两台Web 服务器。这时,有两个数据收发操作在同时进行,也就需要创建两个不同的套接字。这个例子说明,同一台计算机上可能同时存在多个套接字,在这样的情况下,我们就需要一种方法来识别出某个特定的套 接字,这种方法就是描述符。
1.4.3 连接阶段,把管道连接上去——connect(<描述符>,<服务器的IP地址和端口号>,...)
接下来,我们需要委托协议栈将客户端创建的套接字与服务器那边的套接字连接起来。应用程序通过调用Socket库中的名为connect的程序组件来完成这一操作。这里的要点是当调用connect 时,需要指定描述符、 服务器IP地址和端口号这3个参数。
前两个参数不用解释了,要去连接当然要知道自己是谁,和别人的位置了。但端口号是什么?通过几组QA来解释这个问题。
Q:不是已经知道IP地址了吗,发过去就可以连接了啊,需要什么端口号?
A:IP地址是为了区分网络中的各个计算机而分配的数值 ,。因此,只要知道了IP地址,我们就可以识别出网络上的某台计算机。但是,连接操作的对象是某个具体的套接字,因此必须要识别到具体的套接字才行,而仅凭IP地址是无法做到这一点的。我们在说描述符的时候就提到了,一台计算机上可能有多个套接字,所以需要使用端口号来区分对方计算机上的套接字。
Q:能不能用前面创建套接字时提到的那个描述符来识别套接字呢?
A:不行,描述符是和委托创建套接字的应用程序进行交互时使用的,并不是用来告诉网络连接的另一方的,因此另一方并不知道这个描述符,也就是客户端无法知道服务器上的描述符,也就不可能用来识别套接字,所以我们需要一个新的机制,就是端口号。可以说,描述符对内,而端口号对外,都是用来识别套接字的。
Q:既然可以用端口号来识别套接字,那为什么还需要描述符呢?
A:这个问题,需要对端口号进行更深入的了解,第六章会进行学习和介绍。
Q:那到底怎么确定使用哪一个端口呢?
A:一般情况下,网址中也没有端口,并且不能去问DNS服务器。但实际上很简单,服务器上所使用的端口号是根据应用的种类事先规定好的,如Web 是80号端口,电子邮件是25号端口(端口号的规则是全球统一的,为了避免重复和冲突,端口号和 IP 地址一样 都是由IANA(Internet Assigned Number Authority,互联网编号管理局)这 一组织来统一管理的。)
Q:既然是双向的,那么服务器也得知道客户端套接字的端口号吧,这总没办法事先规定吧,这是怎么分配的?
A:客户端在创建套接字时,协议栈会为这个套接字随便分配一个端口号。接下来,当协议栈执行连接操作时,会将这个随便分配的端口号通知给服务器。具体的细节第二章将进行学习。
1.4.4 通信阶段:传递消息——write(<描述符>,<发送数据>,<发送数据长度>); read(<描述符>,<接收缓冲区>,...)
创建好套接字之后,只要将数据送入套接字,数据就会被发送到对方的套接字中。调用write,传入描述符、发送数据、数据长度三个参数。发送之后,接收数据调用read,
1.应用程序需要在内存中准备好要发送的数据。根据用户输入的 网址生成的HTTP请求消息就是我们要发送的数据;
2.调用write,需要指定描述符和发送数据;
3.协议栈就会将数据发送到服务器。由于套接字中已经保存了已连接的通信对象的相关信息,所以 只要通过描述符指定套接字,就可以识别出通信对象,并向其发送数据;
4.发送数据会通过网络到达我们要访问的服务器。;
5.服务器执行接收操作,解析收到的数据内容并执行相应的操作,向客户端返回响应消息 ;
6.调用read接收数据,需要指定用于存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区,位于应用程序内部,就相当于把数据交给了应用程序。
1.4.5 断开阶段:收发数据结束——close(<描述符>)
调用close,连接在套接字之间的管道会被断开,套接字本身也会被删除。
断开的过程如下(以HTTP协议,Web服务器先断开为例,实际上哪一方先断开都有可能,根据具体规则):
1.当Web 服务器发送完 响应消息之后,主动执行断开操作,调用close断开连接;
2.断开操作传达到客户端;
3.浏览器调用read执行接收数据操作时,read会告知浏览器收发数据操作已结束,连接已经断开;
4.浏览器得知后,也会调用 close进入断开阶段。
总结
这就是HTTP的工作过程。HTTP协议将HTML文档和图片都作为单 独的对象来处理,每获取一次数据,就要执行一次连接、发送请求消息、 接收响应消息、断开的过程。因此,如果一个网页中包含很多张图片,就 必须重复进行很多次连接、收发数据、断开的操作。对于同一台服务器来 说,重复连接和断开显然是效率很低的,因此后来人们又设计出了能够在 一次连接中收发多个请求和响应的方法。在HTTP版本1.1中就可以使用 这种方法,在这种情况下,当所有数据都请求完成后,浏览器会主动触发 断开连接的操作。
实际负责收发消息的是协议栈、网卡驱动和网卡,下一章将进行探索。