1. 引言
此前曾经写过一篇文章,从 OSI 七层协议的角度讲解了网络传输过程:
网络传输是怎么工作的 -- 详解 OSI 模型
在同事的桌上看到了一本小书,日本一个程序员户根勤的《网络是怎样连接的》,翻看了一下,发现这本书的内容由浅入深,语言非常详实,无论是入门者还是有经验的工程师,都能够有所收获,这也是它能够在豆瓣上评分 9.1 分的原因,于是本周我也买了一本。
这本书分为六个章节,按照 TCP/IP 协议族的五层协议逐层深入讲解,展现一次浏览器的网络请求是如何实现传输通讯的,所以我打算本周开始,每个周末至少阅读一个章节,分别对每一个章节进行一篇总结性的笔记文章,从应用层到物理层,展现书中的精髓内容。
2. 浏览器的组成
我们大多数人日常在电脑上浏览网页,用的都是浏览器,浏览器作为一个直接面对用户的应用程序,他无疑处在 TCP/IP 协议族的应用层。
无论是 google 家的 chrome 浏览器、微软的 IE 浏览器,还是 firefox 的浏览器,亦或是微软新近推出的 edge 浏览器,他们的内核体量都是十分庞大的。在他们庞大的内核中,最为复杂、最核心的模块便是网页的渲染模块,除此之外是网络通信模块,这便是我们本文要介绍的重点。
当收到用户在地址栏输入的 URL 或者检测到用户点击等触发事件后,浏览器内核会通过网络通讯获取网页内容,然后,经过 HTML 解释器、CSS 解释器、Javascript 引擎等组件处理,生成浏览器制定的内部表示协议文本,布局和绘图模块通过以这个处理后的文本为参数,实现图形、音频、视频等的渲染,就能够顺利将网页展示在用户面前了。
那么,这其中十分重要的一环,网络通信是怎样实现的呢?
3. 浏览器消息的生成
浏览器接到请求后,做了以下工作:
- 解析 URL,获取 URL 对应的协议及协议内部的详细信息;
- 生成 http 协议规定的请求消息体;
- 与操作系统域名解析器通信查询 web 服务器的 ip 地址;
- 委托操作系统协议栈发送消息。
下面我们逐条详细介绍一下。
4. 解析 URL
通常我们的 URL 是这样的:
http://techlog.cn/debin/3
在这样的 URL 中,:// 这个特殊标记的左侧就是协议名称,他标志着这个 url 指向的资源将如何和浏览器通信,常见的有 http、ftp、email 等。
:// 的右边就是指向具体通信目标的链接部分,通过前面指定的协议,浏览器内核中的 URL 解析器就可以对后面的链接进行解析,找到通信目标的地址以及指定的通信方式,从而生成对应协议的消息体,本文我们以 http 协议为例。
5. 生成 http 请求消息体
此前,我们详细介绍了 http 协议的内容:
<a id="10182870"></a>
http 协议的消息体中主要包含了以下内容:
请求行
– <HTTP 方法><空格><URI><空格><HTTP 版本>header
– <字段名>:<字段值>空行
消息体
6. 与操作系统域名解析器通信查询 web 服务器的 ip 地址
6.1 linux 实现
C 语言标准库中,定义了用于通过域名解析获取 ip 地址的函数 -- gethostbyname,大部分系统的 Socket 库都是通过提供这一函数来实现这一功能的。
在 linux 系统中,gethostbyname 是通过下列流程实现的:
- 通过与 nscd 进程通信,nscd 进程缓存了 /etc/hosts 和 /etc/resolv.conf 文件内容;
- 如果在 /etc/hosts 文件内容中没有匹配到对应的 ip 地址,则通过 /etc/resolv.conf 中配置的 DNS 地址,向 DNS 服务发出域名解析请求;
- 如果 nscd 进程不存在,则通过 /etc/nsswitch.conf 中配置的获取顺序到指定目标中获取。
参考:https://garlicspace.com/2019/05/11/gethostbyname函数实现分析/#gethostbyname_glibc229
这在我们此前的文章中有提到:
你知道 java 获取本地 ip 地址有两种方法吗?讲讲隐藏在他们背后的哪些坑
6.2 IP 地址
ip 地址是网络上每台设备独特的地址,占用 32bit,包含网络号和主机号两部分信息,网络号在前,主机号在后,但具体网络号和主机号各占用多少 bit,是由子网掩码决定的。
IP 地址相关的信息可用参看:
网际协议 -- IP
如果被访问的服务端没有使用虚拟主机功能,那么使用 ip 地址和使用域名是一样的,但虚拟主机功能让一个 ip 地址可以对应到多个不同的域名上,此时域名就是必要的了。
但总的来说,域名是为了方便人去记忆的,ip 则是为了便于网络传输的,因为 ip 只有 4 字节。
ip 与域名映射关系的维护就是 DNS 服务。
6.3 DNS 服务器
DNS 服务器是用来保存 ip 与域名映射关系的。
来自客户端的查询消息包含以下三种信息:
- 域名;
- class – 早期设计中,考虑到了 DNS 不仅用于互联网的情况,实际使用中,class 固定为 IN,代表互联网;
- 记录类型 – 表示域名对应何种类型的记录,例如,A 表示域名对应的是 ip 地址,MX 则对应邮件服务器,不同的记录类型,DNS 服务器会返回不同的信息。
DNS 服务器收到这三个信息后,在自己维护的表格中查找对应的记录。
互联网上有数万台 DNS 服务器,不可能再一台服务器上存放有全世界所有域名的信息,所以 DNS 服务器按照域名以分层次的结构进行保存,类似于 java 包名的组织方式。如果要查找 search.baidu.com.cn,具体的查询过程是,首先要到保存有 cn 的顶级 DNS 服务器中查找 com.cn 这一项,在这台顶级 DNS 服务器中,保存了下一级 DNS 服务器也就是 com.cn 所在服务器的 ip 地址,接下来到这台 DNS 服务器中查找 baidu.com.cn 项对应的下级 DNS 服务器的 IP 地址,再到最后这台服务器上查询 search.baidu.com.cn 对应的 ip 即可。
实际上,这是一个理想化的模型,在真实的互联网环境中,一台 DNS 服务器是可以保存多级域名的,不过整体原理上是一致的,而且上级 DNS 在完成下级 DNS 服务查询后,会将结果缓存起来,以加速后续同样查询的返回。
7. 委托操作系统协议栈发送消息
完成了上述步骤,浏览器已经拥有了用于通信的全部信息,包括作为通讯目标的服务端 ip 端口,以及完整的消息体,接下来就需要调用操作系统 Socket 库中的对应函数,实现消息的发送了。
在 linux 环境下,主要步骤和调用的系统调用函数如下:
- 客户端创建套接字 socket
- 连接 connect
- 发送数据 write
- 接收数据 read
- 断开连接 close
可以参看:
传输控制协议 -- TCP