以前知道的是浏览器发出请求,服务器返回响应。实际上浏览器经过了解析网址、生成 HTTP 消息、查询服务器 ip 地址、委托协议栈(操作系统里的网络控制软件)发送消息。下面就来看看这些步骤具体是怎么完成的。
一、浏览器解析网址
所谓的解析网址就是把一个网址拆分开来,获取其中包含的信息。
网址就是 URL(Uniform Resource Locator 统一资源定位符)。除了我们常见的 http:// 开头的网址,还有 ftp:// 、file://、mailto:。以一个 http 开头的网址为例:
- http 表示访问数据源的机制,也就是协议
- // 后面跟着的就是服务器的名称,即为 www.lab.glasscom
- /dir1/file1.html 表示要访问的资源的具体位置,这里就是 dir1 目录下的 file1.html 文件
二、浏览器生成消息
解析 URL 之后就可以根据需要生成 HTTP 消息了
根据需要使用不同的方法,如果是想发送数据的服务器处理就使用 POST 方法,如果是想从服务器获取数据就用 GET 方法。
消息格式:1
2
3
4
5
6
7<方法><空格><URI><空格><HTTP 版本>
<字段名>:<字段值>
...
...
...
<空行>
<消息体>
(具体需要了解 HTTP 协议)
URI 就是要访问的资源的具体位置
而解析出来的服务器的名称会在字段值中出现 host : www.lab.glasscom
三、查询服务器 ip 地址
通过对网址的解析我们已经得到了想要访问的服务器的名称(域名),但我们还不能找到这台服务器。
这就像是我们知道了一个人的名字,但是名字是可以重复的,我们需要确定这个人的身份。在互联网中就是通过 ip 来标志每一台计算机的身份。
想要得到目的服务器的 ip 地址就需要向 DNS 服务器查询。
简单来说,就是向 DNS 服务器提出询问:www.lab.glasscom 的 ip 地址是什么?
然后 DNS 服务器回答:它的 ip 地址是 xx.xx.x.xx
稍微具体一点点,就是计算机一定有一个 DNS 客户端(称为 DNS 解析器),它可以跟 DNS 服务器通信。
- 浏览器想到得到一个网址所对应的 ip 地址的时候,就调用解析器
- 解析器就生成查询消息,通过操作系统的协议栈发送出去。(凡是网络通信,都需要委托给协议栈进行操作)
- 协议栈得到响应之后,返回信息给解析器,解析器再将 ip 地址返回给浏览器。
(这里感觉类似于函数的调用,函数中调用函数,最后将结果层层返回)
四、全世界 DNS 服务器的大接力
上面说到向 DNS 服务器查询 ip 地址,在服务器个数有限的情况下一切都合情合理,但是全世界那么多台计算机,难道全部保存在一台 DNS 服务器上面吗?如果分开保存,这台找不到的情况下又应该去哪里找呢?
域名的层次结构
域名可以是: www.zhihu.com、www.baidu.com,他们都可以看成是 .com 的手下,而他们也可以有自己的手下,比如 tieba.baidu.com 就是 www.baidu.com 的手下,这就是域名的层次结构。假设一台 DNS 服务器只能存放一个域的信息,那么就有一台服务器存放着类似于 www.zhihu.com、www.baidu.com 这样的域名的信息,但是你要问这台服务器 tieba.baidu.com 的 ip 是多少它是不知道的,不过它会告诉你 xx 服务器上存放着你想要的信息,让你去找它。
简单来说,有一台服务器存放 .com 下的域名的信息,另一台存放 .cn 下的域名的信息。当然还有很多这样的服务器,他们各自是老大,掌握了下一级别的小弟的信息,你要谁的信息就直接给你。但是大哥并不知道所有人的信息,你想要小弟的小弟的信息,他最多知道这个人是谁的小弟,然后让你去找这个人。
实际上还有一台 DNS 服务器上存放着所有老大的信息,我们可以称之为超级大佬,想要任意任意一个人的信息,问他就好了,顺藤摸瓜肯定可以找到的。
DNS 服务器的工作方式
任意一台服务器都存放着超级大佬的信息,当我们向一台 DNS 服务器发出询问时,如果它也不知道那它就会让你去问超级大佬,超级大佬会让你去问某个老大,如果那个老大知道的话就告诉你,否则就让你去问他的小弟…一直到得到想要的答案为止。
五、委托协议栈发送消息
数据收发操作概览
简单来说,数据收发就是在客户端和服务器之间建立了一条数据通道,数据就在通道里面传输。
这条数据通道并非一开始就有,而是在收发数据之前就建立起来的。收发数据大致可以分为以下 4 个阶段:
- 创建套接字
- 将管道连接到服务器端的套接字上
- 收发数据
- 断开管道并删除套接字
这里先理解成是协议栈调用 Socket 库完成上面 4 个步骤。
创建套接字阶段
套接字的创建就看成是协议栈调用 Socket 库的组件来创建套接字,创建之后会返回一个描述符,这个描述符相当于这个套接字的身份证。当要收发数据的时候,向协议栈出示描述符,协议栈就知道要对哪个套接字进行操作了。
连接阶段
连接是通过调用 Socket 库的 connect 组件完成的。需要指定以下信息:
- 描述符
- 服务器 ip 地址
- 服务器端口号
描述符是告诉协议栈要对哪个套接字进行操作,ip 和端口号是用来识别对方套接字。
通信阶段
发送数据时调用 Socket 的 write 组件,需要提供描述符和要发送的数据即可。
接收数据时调用 Socket 的 read 组件,需要指定用于存放接收到的响应信息的内存地址。
断开阶段
Web 服务器发送响应消息之后主动断开连接,客户端收到断开指令后也断开连接,当浏览器调用 read 读取数据时,read 会告知浏览器收发数据操作结束,连接已经断开,然后浏览器也进入断开阶段。
(实际上双方都有可能先执行断开操作)
以前的 HTTP 协议每次获取数据都要进行所有步骤,后来在 1.1 版本中一次连接可以收发多个请求和响应,当所有数据请求完毕之后,浏览器会主动断开连接。
六、总结
一个上午的时间就这么过去了,后面的内容会越来越难,希望自己可以坚持看完。
第一章有部分知识我跳过了,学到的知识基本就是这么多了,刚才去百度了一下 C语言 Socket 编程的例子,还是觉得一脸懵逼,完全不知道说的是什么。
不管怎样,坚持看完这本书,加油!!!!