网络是怎样连接的---第一章笔记

189 阅读13分钟

第一章 Web 浏览器

第一章探索了浏览器的工作方式。大家可以认为我们的探索之旅是从在浏览器中输入网址(URL)开始的。例如,当我们输入下面这样的网址时,浏览器就会按照一定的规则去分析这个网址的含义,然后根据其含义生成请求消息。

www.lab.glasscom.com/sample1.htm…

在上面这个例子中,浏览器生成的请求消息表示“请给我 sample1.html 这一文件中储存的网页数据”,接着浏览器会将请求消息发送给 Web 服务器。当然,浏览器并不会亲自负责数据的传送。传送消息是搬运数字信息的机制(协议栈)负责的工作,因此浏览器会委托它将数据发送出去。具体来说,就是委托操作系统中的网络控制软件将消息发送给服务器。第 1 章中,我们会探索到浏览器将数据委托出去为止。”

探索之旅的看点

(1)生成 HTTP 请求消息

(2)向 DNS 服务器查询 Web 服务器的 IP 地址

(3)全世界 DNS 服务器的大接力

(4)委托协议栈发送消息

总的来说,就是从用户在 Web 浏览器上输入 URL 开始,浏览器解析用户输入的 URL 从而生成 HTTP 请求消息。Web 浏览器通过调用 dns 解析器向 dns 服务器发送查询消息, dns 服务器返回要访问服务器的 IP 地址。获得 IP 地址后,Web 浏览器就可以委托操作系统内部的协议栈向这个目的 IP 地址发送请求消息了。

(1) 生成 HTTP 请求

用户在浏览器中输入要访问的网址,浏览器对用户输入的网址进行URL解析(确定了 web 服务器名和文件名),然后根据解析得到的信息构造 http 请求,分为三个部分(请求行声明访问的方法、URI 和协议版本、消息头给出了更加具体的访问控制字段、消息体中存放了需要发送的数据(如post 方法参数))。

image.png

(2)向 DNS 服务器查询 Web 服务器的 IP 地址

生成 HTTP 请求消息后, 接下来我们需要委托操作系统将消息发给 Web 服务器。在进行这个操作之前,我们需要调用 Socket 库中的解析器发送 dns 查询消息来获得要访问的 Web 服务器的 IP 地址。

注释:为什么需要委托操作系统发送?

浏览器虽然可以解析网址并生成 HTTP 请求,但它本身并不具备网络数据的收发功能,因此这个功能需要委托操作系统来实现(dns 解析器同理)。在进行委托操作时,我们还需要提供要访问的 Web 服务器的 IP 地址,因为委托操作系统发送消息时,必须要告诉协议栈目的 IP 地址。因此,这也是为什么在生成 HTTP 消息之后的下一步是根据域名查询 IP 地址。

具体流程

web 浏览器通过调用 Socket 库中的解析器向 dns 服务器发送查询消息,当控制流程转移到解析器后,解析器会生成要发送给 dns 服务器的查询消息,发送查询消息的这个操作也要委托给操作系统内部的协议栈来执行。解析器调用协议栈后,控制流程会再次转移,协议栈会执行发送消息的操作,然后通过网卡将消息发送 dns 服务器。当 dns 服务器收到查询消息后,它会根据消息中的查询内容进行查询(具体查询流程见第(3)部分),然后 DNS 服务器会返回响应消息。响应消息中包含查询到的 IP 地址,解析器会取出 IP 地址,并将其写入浏览器指定的内存地址中。接下来, 浏览器在向 Web 服务器发送消息时,只要从该内存地址取出 IP 地址,并将它与 HTTP 请求消息一起交给操作系统就可以了。

解析器

解析器实际上是一段程序,它包含在操作系统的 Socket 库中。在程序中写上解析器的程序名称“gethostbyname”以及要查询的 Web 服务器的域名“www.xxx.xxxx”就可以了,这样就完成了对解析器的调用。

image.png

image.png

(3)全世界 DNS 服务器的大接力

dns 查询过程示例

客户端首先会访问距离最近的一台 dns 服务器,如下图所示,假设我们要查询具有四级域名(www.lab.glasscom.com) 的一个 web 服务器的 IP 地址。由于距离最近的 dns 服务器中没有存放这个四级域名的对应信息,所以我们需要访问根域 dns 服务器(其 IP 地址在配置文件中存储),根域服务器没有这个四级域名的存储记录,但它知道 com 域中 dns 服务器对应的 IP 地址,因此根域服务器返回它所管理的 com 域中的 dns 服务器的 IP 地址,接下来最近的 dns 服务器向 com 域的 dns 服务器发送查询消息,com 域服务器返回它所管理的 glasscom.com 域的 dns 服务器的 IP 地址,然后最近的 dns 服务器向 glasscom.com域发送查询消息,glasscom.com 域服务器返回它所管理的 lab.glasscom.com 域中 dns 服务器的 IP 地址。最近的 dns 服务器再向 lab.glasscom.com 发送查询消息,lab.glasscom.com 服务器返回 www.lab.glasscom.com Web服务器的 IP 地址,其 IP 地址会被写入响应消息并返回给最近的 dns 服务器,最后返回给客户端计算机,然后再经过协议栈被传递给解析器(dns 客户端进程),然后解析器读取消息取出 IP 地址并写入到上层应用程序指定的内存地址中,就相当于将 IP 地址交给了应用程序。

image.png

image.png

dns 缓存功能

并不是每次查询都需要走完从上到下的流程,因为 dns 服务器有缓存功能,可以记住之前查询过的域名,如果要查询的域名和相关信息已经在缓存中,那么直接从缓存中返回响应,接下来的查询就可以从缓存的位置向下进行,因为 dns 服务器的 IP 地址有时候会发生变化,因此为每个 dns 服务器设置一个有效期,当缓存中的信息超过有效期后,数据就会从缓存中删除。同时 dns 服务器也会告知客户端这一响应结果是来自缓存还是来自负责管理该域名的 dns 服务器。

(4)委托协议栈发送消息

上层应用程序成功获取要访问服务器的 IP 地址后,就可以委托操作系统内部的协议栈向这个目的 IP 地址发送数字消息了(http 请求)。但在进行收发数据操作之前,双方需要先建立起通信管道才行。建立管道的关键在于管道两端的数据出入口,这些出入口称为套接字。我们需要先创建套接字,然后再将套接字连接起来形成管道。

首先是服务器一方先创建套接字,然后等待客户端向该套接字连接管道。当服务器进入等待状态时,客户端就可以连接管道了,客户端也会创建一个一个套接字,然后从套接字延伸出管道,最后管道连接到服务器端的套接字上,当双方的套接字连接起来之后,通信准备就完成了。

image.png

image.png

上面只是套接字连接建立和收发数据的简要介绍,下面我们详细看一下具体的流程(大致分为 4 个阶段):

  1. 创建套接字阶段
  2. 连接阶段
  3. 通信阶段
  4. 断开阶段

浏览器自身并没有数据收发的能力,它委托操作系统的协议栈来执行这些操作,这些委托都是通过调用 Socket 库中的程序组件来执行的,但这些数据通信用的程序组件其实仅仅充当了一个"桥梁"的角色,并不执行任何实质性的操作,应用程序的委托内容最终会被原原本本的传递给协议栈,最终由协议栈执行操作。

创建套接字阶段

首先是套接字创建阶段,客户端创建套接字只需要调用 Socket 库中的 socket 程序组件就可以了。套接字创建完成后,协议栈会返回一个套接字的描述符,应用程序会将收到的描述符放在内存中。当创建好套接字后,我们就可以使用这个套接字来执行收发数据的操作了,这时只要我们出示描述符,协议栈就能够判断出我们希望使用哪一个套接字来连接或者收发数据了。

连接阶段

接下来,我们需要委托协议栈将客户端创建的套接字与服务器端那边的套接字连接起来。应用程序通过调用 Socket 库中的名为 connect 的程序组件来完成这一操作。当我们调用 connect 时,需要指定描述符之前查询到的服务器 IP 地址端口号这三 个参数;

  • 第一个参数:描述符,就是在创建套接字的时候由协议栈返回的那个描述符。connect 会将应用程序指定的描述符告知协议栈,然后协议栈根据这个描述符来判断到底使用哪一个套接字去和服务器端的套接字进行连接,并执行连接的操作。

  • 第二个参数:服务器 IP 地址,我们要访问的服务器的 IP 地址(通过 dns 服务器查询得到)。在进行数据收发操作时,双方必须要知道对方的 IP 地址并告知协议栈,这个参数就是那个 IP 地址;

  • 第三个参数:端口号,用来确定交给服务器的哪个应用进程。当同时指定 IP 地址和端口号时,就可以明确识别出某台具体的计算机上的某个具体的套接字。

描述符是在一台计算机内部识别套接字的机制,端口号是用来让通信的另一方能够识别出套接字的机制

服务器端使用默认端口号,客户端在创建套接字时,协议栈会为这个套接字随机分配一个端口号,当协议栈执行连接操作时,会将这个端口号通知给服务器。总之,当调用 conect时,协议栈会执行连接操作,将客户端和服务器端的套接字进行连接,当连接成功后,协议栈会将对方的 IP 地址和端口号等信息保存在套接字中,这样我们就可以收发数据了。

通信阶段

  • 客户端向服务器发送 http 请求

当套接字连接起来之后,只要将数据送入套接字,数据就会被发送到对方的套接字中。应用程序无法直接控制套接字,因此还是要通过 Socket 库委托协议栈来完成这个操作,这个操作需要使用 write 这个程序组件;

首先,应用程序需要在内存中准备好要发送的数据(根据用户输入生成的 http 请求),接下来,当调用 write 程序组件时,需要指定描述符要发送的数据,然后协议栈就会将数据发送到服务器,由于套接字中已经保存了已连接的通信对象的相关信息,所以只要通过描述符指定套接字,就可以识别出服务器端的通信对象,并向其发送数据,接着,发送的数据会通过网络到达我们要访问的服务器。

  • 服务器从缓冲区读取 http 请求

服务器从缓冲区中执行接收操作,对其中的内容进行解析,通过 URI 和方法来判断“对什么”、“进行怎样的操作”,并根据这些要求来完成自己的工作,然后将结果存放在响应消息中。在响应消息的开头有一个状态码,它用来表示操作的执行结果是成功还是发生了错误;状态码后面就是头字段和网页数据,响应消息会被发送回客户端。

  • 客户端接收 http 响应

当消息返回后,客户端需要执行的是接收消息的操作。接收消息的操作是通过 Socket 库的 read 程序组件委托协议栈来完成的。调用 read 时需要指定用于存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区,于是,当服务器返回响应消息时,read 就会负责将接收到的响应消息存放到接收缓冲区中。由于接收缓冲区是一块位于应用程序内部的内存空间,因此当消息被存放到接收缓冲区中时,相当于已经转交给了应用程序。

客户端收到响应消息之后之后,浏览器会从消息中读出所需的数据并显示在屏幕上,这时我们就可以看到网页的样子了,如果网页内容只有文字,那么到这里就全部处理完毕了,但如果网页中还包括图片等资源,则还需要进一步处理。

当网页中包含图片时,会在网页中相应位置嵌入表示图片文件标签的控制信息。浏览器会在显示文字时搜索相应的标签,当遇到图片相关的标签时,会在屏幕上留出用来显示图片的空间,然后再次访问 web 服务器,按照标签中指定的文件名向 web 服务器请求获取相应的图片并显示在预留空间中。这个步骤和获取网页文件时一样,只要在 URI 部分写上图片的文件名并生成和发送请求消息就可以了。由于每条请求消息中只能写入一个URI, 所以每次只能获取1 个文件,如果需要获取多个文件,必须对每个文件单独发送一条请求,比如 1 个网页中包含 1 张图片,那么获取网页加上获取图片一共需要向 Web 服务器发送 2 条请求。

image.png

image.png

image.png

image.png

断开阶段

当浏览器收到数据之后,收发数据的过程结束了,接下来,我们需要调用 Socket 库的close 程序组件进入断开阶段。最终,连接在套接字之间的管道会被断开,套接字本身也会被删除。

web 使用的 http 协议规定,当 web 服务器发送完响应消息之后,应该主动执行断开操作,因此 web 服务器会首先调用 close 来断开连接。断开操作传达到客户端之后,客户端的套接字也会进入断开阶段。接下来,当浏览器调用 read 执行接收数据操作时,read 会告知浏览器收发数据操作已经结束,连接已经断开。浏览器得知后,也会调用 close 进入断开阶段。

总结

image.png

image.png