访问过程

我们在浏览器地址栏中输入 google.com 这个地址,然后浏览器经过短暂加载后给我们呈现了 Google 的搜索页面。

这中间的步骤有哪些呢?

  1. 首先是域名解析 google.com 转换为这个网站服务器的 IP,这其中或许还包含有 CDN 加速、负载均衡,分布式服务等技术。

  2. 获取到服务器 IP 之后,我们的浏览器会通过 TCP 三次握手与服务器建立 TCP 连接。

    • 如果网站是 HTTPS,还会经过 TLS/SSL 的握手步骤与服务器建立 TLS/SSL 连接。
  3. 建立 TCP 连接之后,浏览器作为客户端发送 HTTP 请求 (request) 报文到目的 IP 地址即 Web 服务器 IP。

  4. 有的时候获取的 IP 是反向代理的服务器 IP,这些服务器接受客户端请求,并把请求转发给真实服务器 IP。

  5. 接下来是服务器的工作过程:

    • 服务器上 Google.com 服务进程监听服务端口,收到客户端的 HTTP 请求报文,这个报文被服务器上安装的 Web 服务器 (Web Server) 如 apache、nginx、LigHTTPd、IIS 所监听到。
    • Web Server 解析 HTTP 请求报文,根据路由配置去访问特定位置的资源或委托给服务器上的处理请求的程序进行处理,如 CGI 脚本、JSP、ASP、servlets、node.js 等。
    • Web App 把响应数据交给 Web Server,Web Server 按照 HTTP 响应报文格式封装数据为 HTTP 响应 (response) 报文,并把报文通过 TCP 连接链路回送给客户端。
  6. 客户端浏览器接受到 HTTP 响应报文,从数据部分提取出 html 代码,通过加载、解析、渲染的过程,来在屏幕上绘制出页面。

URL 格式探秘

URL (Uniform Resource Locator, 统一资源定位符),互联网设计为一个大的文件系统,就像是我们电脑中每个文件都有对应的文件地址,互联网上的资源地址就是 URL。其格式为

protocol://://

  • 协议 (protocol) 常见的有 HTTP,HTTPS,ftp,telnet,file 等几种。这些协议都属于应用协议,位于协议层的应用层,它们基于传输层的协议而工作,例如 HTTP 基于 TCP,HTTPS 基于 TLS/SSL,TLS/SSL 也是基于 TCP 传输协议。

  • 主机名 (hostname) 代表一个提供服务的服务器 IP,它可以是 IP 或者可以解析为 IP 的网址。

  • 端口号 (port) 代表服务器的一个服务进程,这个进程监听这个端口上的 TCP 或 UDP 数据报。一台主机往往提供多个服务,例如既提供 Web 服务也提供 FTP 服务,那么端口就可以告诉 web 服务器所在主机把请求交给哪一个服务。各个协议都有默认(缺省)的端口。

  • 路径 (route) 代表一个服务下的资源的路径,例如 https://myblog.com/blog/2017/index.html 中的 /blog/2017 代表 2017 年的所有博客,返回的资源取决于后端服务器的实现。

  • 文件名 (filename) 代表某个特定的资源,例如 https://myblog.com/blog/2017/index.htmlindex.html 代表这个特定的页面。

IP 地址

就像每栋房子都有地址,IP 代表一个计算机或者局域网在互联网上的地址。IP 协议如今已有 IPv4 和 IPv6,目前依旧是 IPv4 占据主流,但 IPv6 是未来的趋势。IPv4 协议中,存在公网地址不足的问题,所以人们发明了 NAT 技术把一个局域网地址映射为一个公网地址 (局域网网关地址)。局域网内的主机要提供互联网服务必须利用端口映射把该主机上的服务端口映射到局域网网关的端口。

但 IP 地址不易辨识,所以人们用网站域名来作为 IP 地址的别名,从 IP 地址到域名或从域名到 IP 地址的映射过程称为 DNS 解析。

DNS 解析

DNS 解析过程

在浏览器地址栏输入 url 之后,电脑会发出一个 DNS 解析请求到本地 DNS 服务器。然后按照以下步骤依次查找:

  1. 查找浏览器缓存

    浏览器检查缓存是否有未过期的该域名解析过的 IP 地址,如果有,那么解析过程成功结束。

▲ chrome 的浏览器缓存示例
  1. 查找操作系统缓存

    如果浏览器 dns 缓存不存在或已过期,那么浏览器会从 hosts 文件中查找,查找是否有对应的域名 dns 配置项。

  2. 查找路由器缓存

    如果系统缓存也没有,那么去路由器的 dns 缓存中查找。

  3. 网络接入服务商 (ISP) DNS 缓存

    ISP 都会提供 DNS 缓存服务器。我们也可以在网络配置中配置特定的 DNS 服务器。

如果是局域网内的主机,这个本地 DNS 服务器一般是局域网的网关。网关会把 DNS 解析请求转发给 ISP DNS 服务器。

  1. 递归查询

    经历了如上步骤都找不到 DNS 服务器的话,那么就会直接访问互联网的 DNS 服务器进行递归查询。

假设解析 www.google.com 这个域名,本地 dns 服务器把 dns 解析请求转发给互联网的根域名服务器,如果根域名服务器不存在该域名记录,则根据顶级域 com 向 com 顶级域 dns 服务器发送 dns 请求,如果还没有则根据二级域 google.com 服务器查询,知道最终得到该域名的 IP 地址,并把它缓存到本地。

所以网址的 dns 解析过程是一个由右向左的解析过程:

. --> .com --> google.com --> www.google.com

DNS 负载均衡

我们也可以在命令行通过 nslookup 命令来对某个域名进行 dns 解析获得它的 IP 地址:

1
2
3
4
5
6
7
$ nslookup google.com

Non-authoritative answer:
Name: baidu.com
Address: 220.181.57.216
Name: baidu.com
Address: 123.125.115.110

可以发现 baidu.com 这个域名有两个 IP 地址。这是采用了 DNS 负载均衡技术。

由于单台服务器的性能有限,现代的大型网络服务往往都是集群服务器提供服务,而这些服务器集群往往有多个 IP 地址,而 DNS 可以根据根据每台服务器的负载量,该机器离用户地理位置的距离等来返回一个合适的服务器的 IP 给用户,这就是 DNS 的负载均衡也称为 DNS 重定向。CDN 正是采用了这种技术。

TCP 连接

Web 服务都是基于 HTTP/HTTPS 协议提供的,而 HTTP/HTTPS 协议又是基于传输层的 TCP 协议的。所以进行 HTTP 通信前,必须先建立 TCP 连接,TCP 连接是通过三次握手来建立的,通过四次挥手来断开。这里不详细赘述。

HTTP 协议

HTTP 的无连接

HTTP 协议是无连接的协议 ,无连接的意思就是指每次连接只处理一个请求。服务器处理完客户请求,收到应答之后,即断开 TCP 连接。这种设计是因为客户端与服务端的交换数据时间间隔较大,而且两次传送的数据关联性较低,为了节省 TCP 连接信道所占据的资源,HTTP 协议被设计为 请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端 。但是对于某些情景,例如网页中含有大量引用资源,每次访问引用资源都需要建立一次 TCP 连接就显得十分低效(TCP 的三次握手和四次挥手性能消耗十分严重)。

为了实现持续 TCP 连接,HTTP 协议引入了 Keep-Alive 字段。Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 可以保持客户端与服务端的持续 TCP 连接 。对于静态网站,这个功能十分有用,但对于动态网站来说,过多的连接会耗尽服务器的连接池,持续占用资源影响性能。所以要谨慎使用 Keep-Alive 功能。

HTTP 的无状态

HTTP 协议是无状态的协议 ,这是指协议对于事务处理没有记忆能力,服务器不知道客户端中的状态,即我们与服务器的每一次请求响应过程,服务器都不会记录任何信息。每个请求都是独立的。

例如:电商网站中,我们向购物车放一个商品。如果我们没有登陆,那么服务器无法记录购物车里的商品清单,因为它无法分辨究竟是哪个用户的购物车。

为了解决这个问题,HTTP 使用了 Cookie 和 Session 两种保持 HTTP 连接状态的技术,我们常见的登陆操作常见的做法就是在本地生成一个状态 cookie,然后 cookie 中保存 sessionid,我们每次发送 http 请求时,服务器读取 cookie 中的 sessionid,在服务端查找持久化的 session 信息,判断用户的登陆状态,并读取对应的业务信息。

HTTPS 协议

HTTP 协议完全是明文传输,直接把明文的 HTTP 报文通过 TCP 连接传送到对方,这其中有相当的信息泄漏的风险。为了解决这个问题,网景公司提出了一种新协议 HTTPS,即基于 SSL 协议的 HTTP,这是把 HTTP 报文放在加密的 SSL 信道中进行通信。SSL 后来被标准化为 TLS 协议,一般称之为 TLS/SSL。

HTTPS 与 HTTP 协议不同之处在于,建立 TCP 连接之后,HTTPS 协议还需要再经过 TLS/SSL 握手过程,来建立 TLS/SSL 通道,然后才进行 HTTP 通信。这也带来了一定的性能损耗。

客户端构造 HTTP 请求报文

获取了目的 IP,建立了 TCP 连接之后,客户端构建 HTTP 请求报文,通过 TCP 协议发送到服务器指定端口 (HTTP 协议默认端口 80/8080, HTTPS 协议默认端口 443)。

服务端接受 HTTP 请求

从这里开始就是服务端的工作了。Web Server 监听 TCP 端口,当端口接收到 HTTPRequest 报文,就对报文进行解析,并封装称为 HTTPRequest 对象,供上层使用。

根据访问的资源,大致可分为静态资源和动态资源,静态资源就是指目录中某个位置中预先生成好的文件,例如 myblog/index.html,动态资源则是指会根据请求数据动态生成的页面,例如根据用户权限不同展示的数据统计表。

Web Server 从 HTTP 请求中获取参数后,如果是静态资源,则读取该资源文件,根据 HTTP 响应报文格式产生 HTTP 响应,回送给客户端。如果是动态资源,则进行一系列的处理,产生 HTTP 响应报文。

例如目前主流的后端 Web 框架是 MVC 模式。

MVC 框架首先根据 HTTPRequest 对象的参数、内容,先根据路由配置去选择对应的 Controller,Controller 根据参数进行业务处理,中间可能会进行读写数据库等操作,然后渲染一个数据模型 Model,数据模型用来渲染模版页,这里的工作由模版引擎来完成,最终生成 HTTP 响应。

假设 myblog.com/blogs/1/edit 是修改 myblog.com 的第一篇博客的接口,那么 Router 首先对 blogs/1/edit 进行路由匹配,匹配到的 controlleredit,那么调用 edit 这个方法,该方法从 HTTP Request 对象的正文获得了修改后的博文,于是调用数据库引擎,对数据库内的对应博文进行修改。然后返回一个新的博文的 json 数据,该数据即是数据模型 Model,然后调用模版引擎,把 Model 渲染到模版页,生成 html 页面,然后根据 html 页面产生 HTTPResponse 对象。

服务端发出 HTTP 响应

Web 服务器根据 HTTPResponse 对象来产生 HTTP 响应报文,通过 TCP 连接回送该报文。然后根据 HTTP 中的 Keep-Alive 字段的值确定是否要在响应发送完成后关闭 TCP 连接。

客户端接受 HTTP 响应报文

浏览器从 HTTP 响应报文中的数据段提取出 html、css、js 文件,然后浏览器对这些资源进行渲染,把渲染出来的网页绘制在屏幕上。

浏览器从上而下解析 HTML 文件,如果解析过程中遇到外部资源,如图像、JS、CSS 文件等,则会重复以上 HTTP 通信过程。这个对外部资源的请求是异步的,所以不会影响资源解析。当解析到 JS 文件时,HTML 文档则会挂起渲染过程,等待 JS 文件加载完毕和执行完毕。因此 JS 会阻塞后续资源的下载。JS 代码执行前必须保证所有的 CSS 文件加载完毕。

浏览器解析 HTML 文件构造 DOM 树,解析 CSS 文件构造渲染树,渲染树构建完成后,则开始根据渲染树绘制页面到屏幕上。