使用 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 里就会有捕获的数据包,如下图所示。
抓包分析
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”。
至此,“键入网址再按下回车”的全过程就结束了。
“四次挥手”在抓包里没有出现,这是因为 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 请求了。
真实的网络世界
如果你用的是电脑,那么可能会使用网线连上网口,由交换机接入固定网络。如果是手机、平板,那么可能会通过蜂窝网络、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、图片或者其他格式的数据,需要由浏览器解析处理才能显示出来,如果数据里面还有超链接,指向别的资源,那么就又要重走一遍整个流程,直到所有的资源都下载完。
小结
- HTTP 协议基于底层的 TCP/IP 协议,所以必须要用 IP 地址建立连接;
- 如果不知道 IP 地址,就要用 DNS 协议去解析得到 IP 地址,否则就会连接失败;
- 建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规范构建和解析报文;
- 为了减少响应时间,整个过程中的每一个环节都会有缓存,能够实现“短路”操作;
- 虽然现实中的 HTTP 传输过程非常复杂,但理论上仍然可以简化成实验里的“两点”模型。