一、浏览器生成消息
1.1、生成http请求消息
- 根据访问的目标不同,而有不同的URL(Uniform Resource Locator)访问格式。
- 尽管URL有不同的格式,但他们都有一个共同点,就是开头的文字,http、ftp、file,这部分文字表示浏览器应该使用的访问方法,比如访问Web服务器时,使用HTTP协议,访问FTP服务器时,使用FTP协议,因此可以把这部分理解为访问时使用的协议类型。
1.1.1、解析URL
1.1.2 HTTP的基本思路
HTTP定义了服务器和客户端交互的消息内容和步骤。首先客户端会向服务器发送请求消息,请求消息包含“对什么”和“进行怎样的操作”,“对什么”就是URL,而“进行怎样的操作”表示需要Web服务器完成什么样的操作。如下图。 收到请求消息之后,Web服务器会对其中的内容进行解析,通过URI和方法来判断“对什么”“进行怎么样的操作”,并根据这些要求完成自己的工作,再将结果放在响应消息中。
1.2、向DNS服务器查询Web服务器的IP地址
1.2.1、 IP的基础知识
- 生成HTTP消息之后,接下来我们需要委托操作系统将消息发给Web服务器。
- 尽管浏览器可以解析网址并生成HTTP消息,但它不具备将消息发送到网络的功能,所以需要借助操作系统将信息发布到网络中
- 在这一过程中,我们还需要查询服务器域名对应的IP。在委托操作系统发送消息的时候,需要提供的是通信对象的IP,而不是它的域名。
IP地址的内部结构: 子网掩码全部为1的部分是网络号,而全部为0的是主机号。
IP 地址的主机号
- 全 0:表示整个子网
- 全 1:表示向子网上所有设备发送包,即“广播”
1.2.2 域名和IP并用的原因
TCP/IP网络是通过IP地址来确定通信对象的,因此不知道IP对象就无法将消息发送给对方。
- 那么就会有一个疑问,那么直接在网址中使用IP不就行了吗?这种方式肯定是可以实现网站的访问的的,但是记住IP地址的一串数字的难度远大于记住网址的难度。
- 还有人肯定又要说了,既然如此,那么不使用IP,而直接使用名称来确定通信对象。这样的方法当然也可以实现网站的访问,但是IP地址是32比特,才4字节,而如果是域名的话,最低就几十个字节,最多可能255字节,所以这就加大了路由器的负担,传输数据也会花费更多的时间。
- 因此我们使用的方案是用户使用名称,而路由器来使用IP。
1.2.3 、Socket库提供查询IP地址的功能,DNS(Domain Name System)
对于DNS服务器,我们的计算机上一定有对应的DNS客户端,而相当于DNS客户端的部分称为DNS解析器。通过DNS查询IP地址的过程叫做域名解析,因此负责解析的这一操作也就叫做解析器(resolver)。 Socket 库是用于调用网络功能的程序组件集合。
1.2.4 、通过解析器向 DNS 服务器发出查询
调用DNS解析器后,解析器会给DNS服务器发送查询消息,然后DNS服务器返回响应消息。响应消息中包含查询到的IP地址,该IP地址会存储到浏览器指定的内存地址中。
1.2.5 、解析器内部原理
当控制流程转移到解析器后,解析器会生成要发送给 DNS 服务器的查询消息。这个过程与浏览器生成要发送给 Web 服务器的 HTTP 请求消息的过程类似,解析器会根据 DNS 的规格,生成一条表示“请告诉我 www.lab.glasscom.com 的 IP 地址” 的数据,并将它发送给 DNS 服务器(图 1.12 ③)。发送消息这个操作并不是由解析器自身来执行,而是要委托给操作系统内部的协议栈 来执行。这是因为和浏览器一样,解析器本身也不具备使用网络收发数据的功能。解析器调用协议栈后,控制流程会再次转移,协议栈会执行发送消息的操作,然后通过网卡将消息发送给 DNS 服务器。 顺带一提,向 DNS 服务器发送消息时,我们当然也需要知道 DNS 服务器的 IP 地址。只不过这个 IP 地址是作为 TCP/IP 的一个设置项目事先设置好的,不需要再去查询了。不同的操作系统中 TCP/IP 的设置方法也有差异,Windows 中的设置如图所示,解析器会根据这里设置的 DNS 服务器 IP 地址来发送消息。
1.3 、全世界DNS服务器的大接力
1.3.1 、DNS服务器的基本工作
DNS服务器的基本工作就是接受客户端的消息,然后根据消息的内容返回响应。 其中,客户端查询的消息有:
- 域名
服务器、邮件服务器(邮件地址@后面的部分)的名称 - Class
在最早设计 DNS 方案时,DNS 在互联网以外的其他网络中的应用也被考虑到了,而 Class 就是用来识别网络的信息。不过,如今除了互联网并没有其他的网络了,因此 Class 的值永远是代表互联网的 IN - 记录类型
表示域名对应何种类型的记录。例如,当类型为 A 时,表示域名对应的是 IP 地址;当类型为 MX 时,表示域名对应的是邮件服务器。对于不同的记录类型,服务器向客户端返回的信息也会不同
DNS 服务器会从域名与 IP 地址的对照表中查找相应的记录,并返回 IP 地址。
1.3.2 、域名的层次结构
- DNS 中的域名都是用句点来分隔的,比如www.lab.glasscom.com,这里的句点代表了不同层次之间的界限,就相当于公司里面的组织结构不用部、科之类的名称来划分,只是用句点来分隔而已。在域名中,越靠右的位置表示其层级越高,比如 www.lab.glasscom.com这个域名如果按照公司里的组织结构来说,大概就是“com 事业集团 glasscom 部 lab 科的www”这样。其中,相当于一个层级的部分称为域。因此,com 域的下一层是glasscom 域,再下一层是 lab 域,再下面才是 www这个名字。
- 这种具有层次结构的域名信息会注册到 DNS 服务器中,而每个域都是作为一个整体来处理的。换句话说就是,一个域的信息是作为一个整体存放在DNS 服务器中的,不能将一个域拆开来存放在多台DNS 服务器中。不过,DNS 服务器和域之间的关系也并不总是一对一的,一台 DNS 服务器中也可以存放多个域的信息。为了避免把事情搞得太复杂,这里先假设一台 DNS服务器中只存放一个域的信息,后面的讲解也是基于这个前提来进行的。于是,DNS服务器也具有了像域名一样的层次结构,每个域的信息都存放在相应层级的 DNS 服务器中。例如,这里有一个公司的域,那么就相应地有一台 DNS 服务器,其中存放了公司中所有 Web 服务器和邮件服务器的信息 。
1.3.3、寻找相应的 DNS 服务器并获取 IP 地址
- 互联网中有数万台 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 这些域(称为顶级域)就是最顶层了, 它们各自负责保存下级 DNS服务器的信息,但实际上并非如此。在互联网 中,com 和 jp 的上面还有一级域,称为根域。根域不像 com、jp 那样有自己的名字,因此在一般书写域名时经常被省略,如果要明确表示根域,应 该像 www.lab.glasscom.com.这样在域名的最后再加上一个句点,而这个最后的句点就代表根域。不过,一般都不写最后那个句点,因此根域的存在往往被忽略,但根域毕竟是真实存在的,根域的 DNS 服务器中保管着 com、jp 等的 DNS 服务器的信息。由于上级 DNS服务器保管着所有下级 DNS 服务器的信息,所以我们可以从根域开始一路往下顺藤摸瓜找到任意一个域的 DNS 服务器。
- 除此之外还需要完成另一项工作,那就是将根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中。这样一来,任何 DNS服务器就都可以找到并访问根域 DNS 服务器了。因此,客户端只要能够找到任意一台 DNS 服务器,就可以通过它找到根域 DNS服务器,然后再一路顺藤摸瓜找到位于下层的某台目标 DNS 服务器。分配给根域 DNS 服务器 的 IP 地址在全世界仅有13 个 ,而且这些地址几乎不发生变化,因此将这 些地址保存在所有的 DNS 服务器中也并不是一件难事。实际上,根域 DNS 服务器的相关信息已经包含在 DNS 服务器程序的配置文件中了,因此只要安装DNS 服务器程序,这些信息也就被自动配置好了。
- 如图 1.16 所示,客户端首先会访问最近的一台 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址),假设我们要查询 www.lab.glasscom.com 这台 Web 服务器的相关信息(图 1.16 ①)。由于最近的 DNS 服务器中没有存放 www.lab.glasscom.com 这一域名对应的信息,所以我们需要从顶层开始向下查找。最近的 DNS 服务器中保存了根域 DNS 服务器的信息,因此它会将来自客户端的查询消息转发给根域 DNS 服务器(图 1.16 ②)。根域服务器中也www.lab.glasscom.com 这个域名,但根据域名结构可以判断这个域名属于 com 域,因此根域 DNS 服务器会返回它所管理的 com 域中的DNS 服务器的 IP 地址,意思是“虽然我不知道你要查的那个域名的地址,但你可以去 com 域问问看”。接下来,最近的 DNS 服务器又会向 com 域的DNS 服务器发送查询消息(图 1.16 ③)。com 域中也没有 www.lab.glasscom.com这个域名的信息,和刚才一样,com 域服务器会返回它下面的 glasscom.com域的 DNS 服务器的 IP 地址。以此类推,只要重复前面的步骤,就可以顺藤摸瓜找到目标 DNS 服务器(图 1.16 ⑤),只要向目标 DNS 服务器发送查询消息,就能够得到我们需要的答案,也就是 www.lab.glasscom.com 的 IP 地址了。收到客户端的查询消息之后,DNS 服务器会按照前面的方法来查询 IP地址,并返回给客户端(图 1.16 ⑥)。这样,客户端就知道了 Web 服务器的 IP 地址,也就能够对其进行访问了(图 1.16 ⑦)
1.4、委托协议栈发送消息
1.4.1、数据收发操作概览
- 知道了 IP 地址之后,就可以委托操作系统内部的协议栈向这个目标 IP地址,也就是我们要访问的 Web服务器发送消息了。要发送给 Web 服务器的 HTTP 消息是一种数字信息(digitaldata),因此也可以说是委托协议栈来发送数字信息。收发数字信息这一操作不仅限于浏览器,对于各种使用网络的应用程序来说都是共通的。因此,这一操作的过程也不仅适用于Web,而是适用于任何网络应用程序。
- 使用 Socket 库来收发数据的操作过程如图 1.17 所示。简单来说,收发数据的两台计算机之间连接了一条数据通道,数据沿着这条通道流动,最终到达目的地。我们可以把数据通道想象成一条管道,将数据从一端送入管道,数据就会到达管道的另一端然后被取出。数据可以从任何一端被送入管道,数据的流动是双向的。不过,这并不是说现实中真的有这么一条管道,只是为了帮助大家理解数据收发操作的全貌。
- 收发数据的整体思路就是这样,但还有一点也非常重要。光从图上来看,这条管道好像一开始就有,实际上并不是这样,在进行收发数据操作之前,双方需要先建立起这条管道才行。建立管道的关键在于管道两端的数据出入口,这些出入口称为套接字。我们需要先创建套接字,然后再将套接字连接起来形成管道。实际的过程是下面这样的。首先,服务器一方先创建套接字,然后等待客户端向该套接字连接管道。当服务器进入等待状态时,客户端就可以连接管道了。具体来说,客户端也会先创建一个套接字,然后从该套接字延伸出管道,最后管道连接到服务器端的套接字上。当双方的套接字连接起来之后,通信准备就完成了。接下来,就像我们刚刚讲过的一样,只要将数据送入套接字就可以收发数据了。
- 我们再来看一看收发数据操作结束时的情形。当数据全部发送完毕之后,连接的管道将会被断开。管道在连接时是由客户端发起的,但在断开时可以由客户端或服务器任意一方发起 A。其中一方断开后,另一方也会随之断开,当管道断开后,套接字也会被删除。到此为止,通信操作就结束了。综上所述,收发数据的操作分为若干个阶段,可以大致总结为以下 4 个。
- (1)创建套接字(创建套接字阶段)
- (2)将管道连接到服务器端的套接字上(连接阶段)
- (3)收发数据(通信阶段)
- (4)断开管道并删除套接字(断开阶段)
1.4.2、创建套接字阶段
- 套接字创建完成后,协议栈会返回一个描述符,应用程序会将收到的描述符存放在内存中。描述符是用来识别不同的套接字的,大家可以作如下理解。我们现在只关注了浏览器访问 Web 服务器的过程,但实际上计算机中会同时进行多个数据的通信操作,比如可以打开两个浏览器窗口,同时访问两台 Web 服务器。这时,有两个数据收发操作在同时进行,也就需 要创建两个不同的套接字。多个套接字,在这样的情况下,我们就需要一种方法来识别出某个特定的套接字,这种方法就是描述符。我们可以将描述符理解成给某个套接字分配的编号。也许光说编号还不够形象,大家可以想象一下在酒店寄存行李时的场景,酒店服务人员会给你一个号码牌,向服务人员出示号码牌,就可以取回自己寄存的行李,描述符的原理和这个差不多。当创建套接字后,我们就可以使用这个套接字来执行收发数据的操作了。这时,只要我们出示描述符,协议栈就能够判断出我们希望用哪一个套接字来连接或者收发数据了。
1.4.3、连接阶段:把管道接上去
这里的要点是当调用 connect 时,需要指定描述符、服务器 IP 地址和端口号这 3 个参数(图 1.18 ②)
- 第 1 个参数,即描述符,就是在创建套接字的时候由协议栈返回的那个描述符。connect会将应用程序指定的描述符告知协议栈,然后协议栈根据这个描述符来判断到底使用哪一个套接字去和服务器端的套接字进行连接,并执行连接的操作 。
- 第 2 个参数,即服务器 IP 地址,就是通过 DNS 服务器查询得到的我们要访问的服务器的 IP 地址。在 DNS服务器的部分已经讲过,在进行数据收发操作时,双方必须知道对方的 IP 地址并告知协议栈。这个参数就是那个 IP 地址了。
- 第 3个参数,即端口号,这个需要稍微解释一下。可能大家会觉得,IP 地址就像电话号码,只要知道了电话号码不就可以联系到对方了吗?其实,网络通信和电话还是有区别的,我们先来看一看 IP 地址到底能用来干 什么。IP 地址是为了区分网络中的各个计算机而分配的数值B。因此,只要 知道了 IP 地址,我们就可以识别出网络上的某台计算机。但是,连接操作的对象是某个具体的套接字,因此必须要识别到具体的套接字才行,而仅 凭 IP 地址是无法做到这一点的。我们打电话的时候,也需要通过“请帮我 找一下某某某”这样的方式来找到具体的某个联系人,而端口号就是这样一种方式。当同时指定 IP 地址和端口号时,就可以明确识别出某台具体的计算机上的某个具体的套接字。
- 如果说描述符是用来在一台计算机内部识别套接字的机制,那么端口号就是用来让通信的另一方能够识别出套接字的机制 。
- 既然需要通过端口号来确定连接对象的套接字,那么到底应该使用几号端口呢?网址中好像并没有端口号 ,也不能像 IP 地址一样去问 DNS 服务器 。找了半天也没有任何线索,这可怎么办?其实,这件事情也并没有那么神奇,服务器上所使用的端口号是根据应用的种类事先规定好的,仅此而已。比如 Web 是 80 号端口,电子邮件是 25 号端口 。关于端口号,我们将在第 6 章探索服务器内部工作的时候进行介绍,这里大家只要这样记住就行了:只要指定了事先规定好的端口号,就可以连接到相应的服务器程序的套接字。也就是说,浏览器访问 Web 服务器时使用 80 号端口,这是已经规定好的。可能大家还有一个疑问,既然确定连接对象的套接字需要使用端口号,那么服务器也得知道客户端的套接字号码才行吧,这个问题是怎么解决的呢?事情是这样的,首先,客户端在创建套接字时,协议栈会为这个套接字随便分配一个端口号 。接下来,当协议栈执行连接操作时,会将这个随便分配的端口号通知给服务器。说了这么多,总而言之,就是当调用 connect 时,协议栈就会执行连接操作。当连接成功后,协议栈会将对方的 IP 地址和端口号等信息保存在套接字中,这样我们就可以开始收发数据了。
- 描述符:应用程序用来识别套接字的机制
- IP 地址和端口号:客户端和服务器之间用来识别对方套接字的机制
1.4.4、通信阶段:传递消息
- 当套接字连接起来之后,剩下的事情就简单了。只要将数据送入套接字,数据就会被发送到对方的套接字中。当然,应用程序无法直接控制套接字,因此还是要通过 Socket 库委托协议栈来完成这个操作。这个操作需要使用 write 这个程序组件,具体过程如下。首先,应用程序需要在内存中准备好要发送的数据。根据用户输入的网址生成的 HTTP 请求消息就是我们要发送的数据。接下来,当调用 write时,需要指定描述符和发送数据(图 1.18 ③),然后协议栈就会将数据发送到服务器。由于套接字中已经保存了已连接的通信对象的相关信息,所以只要通过描述符指定套接字,就可以识别出通信对象,并向其发送数据。接着,发送数据会通过网络到达我们要访问的服务器。接下来,服务器执行接收操作,解析收到的数据内容并执行相应的操作,向客户端返回响应消息 。
1.4.5、断开阶段:收发数据结束
- 当浏览器收到数据之后,收发数据的过程就结束了。接下来,我们需要调用 Socket 库的 close 程序组件进入断开阶段(图 1.18 ④)。最终,连接在套接字之间的管道会被断开,套接字本身也会被删除。
- 断开的过程如下。Web 使用的 HTTP 协议规定,当 Web 服务器发送完响应消息之后,应该主动执行断开操作 ,因此 Web 服务器会首先调用close 来断开连接。断开操作传达到客户端之后,客户端的套接字也会进入断开阶段。接下来,当浏览器调用 read 执行接收数据操作时,read 会告知浏览器收发数据操作已结束,连接已经断开。浏览器得知后,也会调用close 进入断开阶段。
- 这就是 HTTP 的工作过程。HTTP 协议将 HTML 文档和图片都作为单独的对象来处理,每获取一次数据,就要执行一次连接、发送请求消息、接收响应消息、断开的过程。因此,如果一个网页中包含很多张图片,就必须重复进行很多次连接、收发数据、断开的操作。对于同一台服务器来说,重复连接和断开显然是效率很低的,因此后来人们又设计出了能够在一次连接中收发多个请求和响应的方法。在 HTTP 版本 1.1 中就可以使用这种方法,在这种情况下,当所有数据都请求完成后,浏览器会主动触发断开连接的操作。
|