HTTP(7):键入回车后发生的事

340 阅读7分钟

使用 IP 地址访问 Web 服务器

首先运行 www 目录下的“start”批处理程序,启动本机的 OpenResty 服务器,启动后可以用“list”批处理确认服务是否正常运行。

打开 Wireshark,选择“HTTP TCP port(80)”过滤器,双击“Adapter for loopback traffuc capture”,开始抓取本机 127.0.0.1 地址上的网络数据。

在浏览器地址栏输入127.0.0.1,回车,等欢迎页面显示出来后 Wireshark 里就会有捕获的数据包,如下图所示。

image.png

抓包分析

HTTP 协议运行在 TCP/IP 基础上的,依靠 TCP/IP 协议来实现数据的可靠传输。所以浏览器要用 HTTP 协议收发数据,首先要做的就是建立 TCP 连接。

我们在地址栏里输入了 IP 地址 127.0.0.1,而 Web 服务器的默认端口是 80,所以浏览器就要依照 TCP 协议的规范,使用“三次握手”建立与 Web 服务器的连接。

对应到 Wireshark 里,就是最开始的三个抓包,浏览器使用的端口是 53671,服务器使用的端口是 80,经过 SYN、SYN/ACK、ACK 的三个包之后,浏览器与服务器的 TCP 连接就建立起来了。

有了可靠的 TCP 连接通道后,HTTP 协议就开始工作。浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个“GET / HTTP/1.1”请求报文,即Wireshark 里的第四个包。

Web 服务器回复了第五个包,在 TCP 协议层面确认:“刚才的报文我已经收到了”,不过这个 TCP 包 HTTP 协议是看不见的。

Web 服务器收到报文后在内部就要处理这个请求。同样也是依据 HTTP 协议的规定,解析报文,看浏览器发送这个请求想要干什么。

要求获取根目录下的默认文件,那我从磁盘上把那个文件全读出来,再拼成符合 HTTP 格式的报文发回去。这就是 Wireshark 里的第六个包“HTTP/1.1 200 OK”,底层走的还是 TCP 协议。

同样的,浏览器也要给服务器回复一个 TCP 的 ACK 确认,“你的响应报文收到了,多谢。”,即第七个包。

这时浏览器就收到了响应数据,但里面是什么呢?所以也要解析报文。一看,服务器给我的是个 HTML 文件,那我就调用排版引擎、JavaScript 引擎等等处理一下,然后在浏览器窗口里展现出了欢迎页面。

这之后还有两个来回,共四个包,重复了相同的步骤。这是浏览器自动请求了作为网站图标的“favicon.ico”文件,与我们输入的网址无关。但因为我们的实验环境没有这个文件,所以服务器在硬盘上找不到,返回了一个“404 Not Found”。

至此,“键入网址再按下回车”的全过程就结束了。

image.png

“四次挥手”在抓包里没有出现,这是因为 HTTP/1.1 长连接特性,默认不会立即关闭连接。

使用域名访问 Web 服务器

改用域名后这个过程会有什么不同吗?

把地址栏的输入改成

www.chrono.com

重复 Wireshark 抓包过程,你会发现,好像没有什么不同,浏览器上同样显示出了欢迎界面,抓到的包也同样是三次握手和两次 HTTP 传输。

浏览器看到了网址里的www.Chrono.com,发现不是数字形式的 IP 地址,那就肯定是域名了,于是发起域名解析动作,通过访问一系列的域名解析服务器,试图把这个域名翻译成 TCP/IP 协议里的 IP 地址。

域名解析的全过程实在是太复杂了,如果每一个域名都要大费周折地去网上查一下,那上网肯定会慢得受不了。

所以,在域名解析的过程中会有多级的缓存,浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts,也就是上一讲中我们修改的C:\WINDOWS\system32\drivers\etc\hosts

刚好,里面有一行映射关系127.0.0.1 www.Chrono.com,于是浏览器知道了域名对应的 IP 地址,就可以建立 TCP 连接发送 HTTP 请求了。

真实的网络世界

image.png

如果你用的是电脑,那么可能会使用网线连上网口,由交换机接入固定网络。如果是手机、平板,那么可能会通过蜂窝网络、WiFi,由电信基站、无线热点接入移动网络。

接入网络的同时,网络运行商会给你的设备分配一个 IP 地址,这个地址可能是静态分配的,也可能是动态分配的。静态 IP 就始终不变,而动态 IP 可能你下次上网就变了。

假设你要访问的是 Apple 网站,显然你是不知道它的真实 IP 地址的,在浏览器里只能使用域名www.apple.com访问,那么接下来要做的必然是域名解析。这就要用 DNS 协议开始从操作系统、本地 DNS、根 DNS、顶级 DNS、权威 DNS 的层层解析,当然这中间有缓存,可能不会费太多时间就能拿到结果。

还有另外一个重要的角色 CDN。DNS 解析可能会给出 CDN 服务器的 IP 地址,这样拿到的就是 CDN 服务器而不是目标网站的实际地址。

CDN 会缓存网站的大部分资源,比如图片、CSS 样式表,所以有的 HTTP 请求就不需要再发到 Apple,CDN 可以直接响应请求。

由 PHP、Java 等后台服务动态生成的页面属于“动态资源”,CDN 无法缓存,只能从目标网站获取。于是发出的 HTTP 请求就要开始在互联网上的“漫长跋涉”,经过无数的路由器、网关、代理,最后到达目的地。

目标网站的服务器对外表现的是一个 IP 地址,但为了能够扛住高并发,在内部也是一套复杂的架构。通常在入口是负载均衡设备,例如四层的 LVS 或者七层的 Nginx,在后面是许多的服务器,构成一个更强更稳定的集群。

负载均衡设备会先访问系统里的缓存服务器,通常有 memory 级缓存 Redis 和 disk 级缓存 Varnish,它们的作用与 CDN 类似,不过是工作在内部网络里,把最频繁访问的数据缓存几秒钟或几分钟,减轻后端应用服务器的压力。

如果缓存服务器里也没有,那么负载均衡设备就要把请求转发给应用服务器了。例如 Java 的 Tomcat/Netty/Jetty,Python 的 Django等等。它们又会再访问后面的 MySQL、PostgreSQL、MongoDB 等数据库服务,实现用户登录、商品查询等业务操作,把执行的结果返回给负载均衡设备,同时也可能给缓存服务器里也放一份。

应用服务器的输出到了负载均衡设备,请求的处理就算是完成了,按照原路再走回去,还是要经过许多的路由器、网关、代理。如果这个资源允许缓存,那么经过 CDN 的时候它也会做缓存,下次同样的请求就不会到达源站了。

最后网站的响应数据回到了你的设备,它可能是 HTML、JSON、图片或者其他格式的数据,需要由浏览器解析处理才能显示出来,如果数据里面还有超链接,指向别的资源,那么就又要重走一遍整个流程,直到所有的资源都下载完。

小结

  1. HTTP 协议基于底层的 TCP/IP 协议,所以必须要用 IP 地址建立连接;
  2. 如果不知道 IP 地址,就要用 DNS 协议去解析得到 IP 地址,否则就会连接失败;
  3. 建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规范构建和解析报文;
  4. 为了减少响应时间,整个过程中的每一个环节都会有缓存,能够实现“短路”操作;
  5. 虽然现实中的 HTTP 传输过程非常复杂,但理论上仍然可以简化成实验里的“两点”模型。