前言
网络是计算机世界的基石,也是信息时代的核心驱动力。没有网络,第三次工业革命的光芒将无从谈起,现代计算机文明的辉煌也将不复存在。然而,网络的知识体系如同一片浩瀚的海洋,深邃而复杂,即便是其中的一小部分,也足以让人倾尽一生去探索与钻研。
对于程序员而言,网络知识不仅是技能树中的重要分支,更是理解计算机世界运行逻辑的关键。如果仅仅停留在表面,知其然而不知其所以然,那么无论技术如何娴熟,也难以称得上是一名真正优秀的程序员。
本系列文章将从最基础的浏览器输入网址开始,逐步揭开网络连接背后的神秘面纱。通过深入分析网络通信的机制与原理,我们希望为读者呈现一个清晰而完整的网络世界图景,帮助大家不仅会用网络,更能懂网络。
本系列参考《网络是怎样连接的》一书
一、从浏览器输入网址出发-探索浏览器的内部
1、生成http请求消息
1.1、关于网址
网址又称URL(Uniform Resource Locator),统一资源定位符。下图是一些常见的URL类型,浏览器作为一个具备具备多种客户端功能的综合性客户端软件,根据网址开头的协议类型来选择对应的哪种功能来访问相应的数据。例如在访问web服务器和ftp服务器时,URL会包含服务器的域名和文件的路径等信息,而发邮件的URL则包含收件人的邮件地址。此外,根据需要,URL中还会包含有用户名、密码、服务器端口号等信息。
1.2、解析URL
在用户输入网址之后,浏览器首先需要解析URL,从而生成发送给WEB服务器的请求消息(本系列如无特殊说明,皆以访问WEB服务器的情况作为示例)。浏览器解析URL时,首先会按照下图所示格式对URL进行拆分。
下方所示URL会被拆分成WEB服务器域名www.lab.glasscom.com 和文件的路径名/dir/file1.html,该路径名表示服务器上/目录下dir/目录下的file1.html文件
http://www.lab.glasscom.com/dir/file1.html
除了上述URL,通常也会见到以“/”结尾的URL,比如http://www.lab.glasscom.com/dir/, 以“/” 结尾代表/dir/目录后原有的文件名被省略了,按照URL的规则,这么做是被允许的。对于省略文件名的情况,我们通常会在服务器上设置好默认访问的文件,一般是index.html或者default.html等。此外http://www.lab.glasscom.com/和http://www.lab.glasscom.com 也是同样的道理,表示访问/(根目录)下的默认文件。
也有比较特殊的URL如http://www.lab.glasscom.com/whatisthis。 这种情况下如果WEB服务器上存在名为whatisthis的文件,则将whatisthis作为文件名来处理;如果存在名为whatisthis的目录,则将whatisthis作为目录名来处理。
1.3、HTTP的基本思路
在介绍浏览器根据URL使用HTTP协议访问WEB服务器之前,先来讲一讲HTTP协议是怎么回事。
HTTP协议规定了客户端和服务器交互的消息内容和步骤,基本思路非常简单。如下图所示
客户端向服务器发送消息时,请求消息包含方法和URI两部分,即表示客户端希望服务端对URI表示的资源进行方法表示的操作。
URI
URI(Uniform Resource Identifier),统一资源标识符。一般的,URI的内容是是一个存放网页数据的文件名或者CGI程序的文件名,如“/dir1/file1.html”“/dir1/program1.cgi”等。此外,也可以直接使用“http”开头的URL做为URI(这个后续会说明)。也就是说,这里可以写各种访问目标,而这些访问目标统称为URI。
方法
方法是对“进行怎样操作”的抽象。如典型的GET方法表示希望服务器读取URI表示的资源,POST方法表示将客户端输入的数据发送给URI表示的程序。下图列举了HTTP协议定义的主要方法。
在HTTP的请求消息中,除了方法和URI之外,还会有一些用来表示附加信息的头字段。客户端在向WEB服务器发送数据时,会先发送头字段,然后在发送数据。关于HTTP中的头字段,会在下文中提到。
此外,在使用POST方法时,除了URI和头字段等,客户端还会传递用户输入的信息给服务器,服务器收到消息后将消息传递给URI指定的应用程序。
WEB服务器处理完客户端的请求后,会返回响应消息给客户端,响应数据包含状态码和响应信息两部分。客户端收到后,浏览器从消息中读取所需的数据并显示。到此,HTTP的整个工作就完成了。
1.4、生成HTTP请求消息
在解析完URL之后,WEB服务器根据域名和URI来生成HTTP请求消息。HTTP请求消息有严格的格式,如下图所示
其中,如果是GET方法,仅凭方法和URI,WEB服务器就能够判断需要进行怎样的操作,因此不需要在消息体中填写任何数据。
HTTP的头字段允许我们存放一些额外的信息,比如日期、客户端支持的数据类型、压缩格式等等。下图中列举了主要的头字段。头字段有很多,不必全部了解。
1.5、收到响应
WEB服务器收到客户端的请求后,会返回响应消息(后续详细介绍),响应消息的具体格式与思路与请求消息类似,如下图所示
其中状态和短语用来向浏览器和用户表示本次执行的结果。其中状态码如下图所示
浏览器在拿到响应消息之后,会将数据提取并渲染。如果返回内容中有图片,则浏览器会再执行请求服务器的操作。
2、向DNS服务器查询WEB服务器的地址
浏览器自身不具备发送消息到网络的能力,需要委托给操作系统。而在协议栈的设计中,必须使用IP地址而不是域名来通信
2.1、IP地址的基本认识
要了解IP地址,首先需要了解网络是如何设计的。互联网和局域网都是基于TCP/IP的思路来设计的。TCP/IP的结构如下图所示,就是由一些小的子网,通过路由器连接起来组成的一个大的网络。子网可以理解为用集线器(中继式和交换式,后续介绍)连接起来的几台计算机。而路由器之间又可以通过集线器连接组成更大的网络。
IP地址就是每台计算机在这个大的网络中被分配的唯一标识,有代表所在子网的网络号和代表子网内具体某台机器的主机号构成。 如下图所示
在IP地址的规则中,网络号和主机号连起来总共是32比特,但这两部分的具体结构是不固定的。在组建网络时,用户可以自行决定它们之间的分配关系,因此,我们还需要子网掩码来表示IP地址的内部结构。子网掩码与IP地址的结构一样,都是32位无符号比特数,其中左半边全部为1,右半边全部0。其中1代表IP地址中的网络号,0代表IP地址中的主机号。如下图所示
特别的,机号部分的比特全部为0或者全部为1时代表两种特殊的含义。主机号部分全部为0代表整个子网而不是子网中的某台设备,主机号部分全部为1代表向子网上所有设备发送包,即广播
2.2、为什么要有DNS
用户可以通过域名来访问服务器以及在绝大部分的场景下也可以通过IP地址来访问服务器(如果WEB服务器使用了虚拟主机功能,有可能无法通过IP地址来访问),但是处于性能上的考虑,网络的设计者们决定让路由器通过IP地址来访问服务器。为了填补人与路由器之间的障碍,需要有一个机制能够通过名称来查询IP地址,或者通过IP地址来查询名称,这个机制就是DNS。
socket库与解析器
库是操作系统提供的通用程序组件的集合,socket库是一种标准化的提供调用操作系统网络功能的库。DNS解析器就是socket库中的一段通用程序。程序在获取域名对应的IP地址时,只需要调用解析器即可。解析器的工作流程与浏览器类似,如下图
解析器内部会生成查询消息并通过事先配置好的DNS的IP地址调用操作系统的协议栈发送查询消息,在接受到DNS服务器的响应后,将响应的IP地址填入到内存空间中。
DNS服务器的工作原理
如下图
当一个邮件地址对应多个邮件服务器时,需要根据优先级来判断,数值越小,级别越高
域名,服务器、邮件服务器(邮件地址中@后面的部分)的名称。
Class,网络的类型。可以用来识别网络信息,如互联网(IN)和其他网络。
记录类型,域名对应何种类型的记录。类型为A时,域名对应的是IP地址;当类型为MX时,域名对应的是邮件服务器。
除了A、MX,还有很多其他类型;如根据IP地址反查域名的PTR类型,查询域名相关别名的CNAME类型,查询DNS服务器IP地址的NS类型,以及查询域名属性信息的SOA类型等。
由于单台DNS保存的数据量是有限的,因此我们必须按照一定的规则将信息分别保存到不同的DNS服务器中,在查询时按照定义的规则就可以到指定的DNS服务器上查找而不必遍历所有的服务器。
那么保存的规则是什么呢,就是将域名按层级结构划分,保存到对应的DNS中;比如www.lab.glasscom.com这个域名,我们把com这个域名叫做顶层域名,glasscom.com叫做子级域名,lab.glasscom.com则是更下一层的域名。这种具有层次结构的域名信息会注册到DNS服务器中,而每个域都是作为一个整体来处理的。换句话说就是,一个域的信息是作为一个整体存放在DNS服务器中的,不能将一个域拆开来存放在多台DNS服务器中。不过,DNS服务器和域之间的关系也并不总是一对一的,一台DNS服务器中也可以存放多个域的信息。
说完了保存规则,那么根据域名查找IP的过程又是怎样的呢?要通过域名查找IP信息,那么首先要找到域名所在的DNS服务器。我们在管理DNS服务器时,将负责管理下级域的DNS服务器的IP地址注册到它们的上级DNS服务器中,然后上级DNS服务器的IP地址再注册到更上一级的DNS服务器中,以此类推。也就是说,负责管理lab.glasscom.com这个域的DNS服务器的IP地址需要注册到glasscom.com域的DNS服务器中,而glasscom.com域的DNS服务器的IP地址又需要注册到com域的DNS服务器中。这样,我们就可以通过上级DNS服务器查询出下级DNS服务器的IP地址,也就可以向下级DNS服务器发送查询请求了。而像com、cn、jp这类域名的所在的DNS服务器信息则会保存在根域所在DNS服务器上,由于根域所在服务器的ip地址一般情况下不发生变化,根域DNS服务器的相关信息已经包含在DNS服务器程序的配置文件中了,因此只要安装了DNS服务器程序,这些信息也就被自动配置好了。
根据定义好的规则,DNS的查询过程就很清楚了,如下图
客户端首先会向最近一台DNS服务器发起请求,如果DNS服务器上没有对应的域名信息,则向根域服务器发起请求;根域服务器根据域名结构判断所属下级域并返回下级域所在DNS的IP地址,当前DNS拿到响应后重复上述过程,直到找到域名对应的IP地址。
委托协议栈发送消息
在获取到域名的IP地址后,应用程序通过socket库调用通信组件,委托协议栈完成数据收发的操作。数据收发的操作可以分为创建套接字、通过套接字创建连接、收发数据、断开连接并删除套接字。
创建套接字
如下图所示
应用程序调用socket库提供的创建套接字的方法(socket库中事先写好的程序组件,每个操作系统具体实现有所差异),socket库创建成功后返回一个描述符给应用程序。操作系统同一时间内可能存在多个通信操作并对应存在多个套接字,描述符用来识别特定的套接字。
创建连接
如下图所示
在创建成功套接字后,应用程序使用socket库的connect方法来创建连接。这里说明下参数,
文件描述符,创建套接字时返回的描述符,用来告知socket库使用哪个套接字。
IP地址,通过DNS查询得到的服务器的IP地址。
端口号,通信连接是通过套接字建立的,通过IP+端口号的形式可以指定服务器上具体的套接字。
当服务端接收到客户端的连接请求后,协议栈会将客户端的IP地址和端口号保存在服务端的套接字中(服务端的套接字在服务启动后就创建完成,等待客户端连接),客户端协议栈也会将服务端的IP地址和端口号保存在自己的套接字中。 至此,连接就算建立成功了。
通信阶段
在连接成功后,应用程序调用socket库提供的write方法发送消息,调用read方法接受消息。
断开连接
在客户端或服务端任何一方调用socket库的close方法后,连接断开,协议栈删除套接字。在HTTP的工作流程中,每一次请求都要执行一遍上述流程,对于同一台服务器来说,效率显然低下;因此,在HTTP 1.1中,允许在一次连接中发送多个请求和响应,在这种情况下,当所有数据都请求完成后,浏览器会主动触发断开连接的操作。
总结
本章阐述了浏览器与WEB服务器之间收发消息的过程,但实际负责收发消息的是协议栈、网卡驱动和网卡,只有这3者相互配合,数据才能够在网络中流动起来。下一章对这一部分内容展开分析。