携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
1.生成HTTP请求消息
1.1.网址URL
URL(Uniform Resource Locator),统一资源定位符,浏览器可以根据不同的URL使用不同的功能,同时不同的URL也会包含不同的内容,几中常见的URL如下:
HTTP:Hypertext Transfer Protocol,超文本传输协议FTP:File Transfer Protocol,文件传送协议。在上传、下载文件时使用的协议- 使用“
file:”这样的URL时是不使用网络的
尽管URL有不同的写法,但它们的共同点就是开头的文字:“http:”、“ftp:”、“file:”、“mailto:”这部分文字表示浏览器使用的协议类型或访问方法。
1.2.解析URL
浏览器要对URL进行解析,然后生成发送给Web服务器的请求消息。
Web浏览器解析URL的过程:
省略文件名:
http://www.lab.glasscom.com/dir/
访问dir目录下的默认文件如:/dir/index.html或者/dir/default.html
http://www.lab.glasscom.com/
/目录表示目录层级中最顶层的根目录,省略了文件名结果访问/index.html或者/default.html
http://www.lab.glasscom.com
省略了目录名,代表访问根目录下事先设定的默认文件(home page),也就是/index.html或者/default.html
http://www.lab.glasscom.com/whatisthis
如果Web服务器上存在名为whatisthis的文件,则将whatisthis作为文件名处理;如果存在whatisthis的目录,则将whatisthis作为目录名来处理(因为无法创建两个名字相同的文件和目录,所以目录和文件不可能重名)
1.3.HTTP
1.3.1.基本思路
浏览器使用HTTP协议访问Web服务器,HTTP协议定义了客户端和服务端之间交互的消息内容和步骤,基本思路如下:
客户端会向服务器发送请求消息,包含对什么和进行怎样的操作两个部分。
URI:表示对什么进行操作,填写各种访问的目标。内容是一个存放网页数据的文件名或者是一个CGI程序(遵循Web服务器程序调用其他程序的规则来工作的程序)的文件名;或者也可以直接使用“http:”开头的URL来作为URI。- 方法:表示让Web服务器完成怎样的工作,典型的例子有读取URI表示的数据、将客户端的数据发送给URI表示的程序等。HTTP的主要方法:
除了URI和方法,HTTP消息中还包括头字段和消息体。
Web服务器收到请求消息后,会解析后根据要求完成自己的工作,然后将结果存放在响应消息中。响应消息开头有一个状态码,用来表示操作的执行结果是成功还是发生了错误。当访问Web服务器时如果找不到文件就会显示404 Not Found的错误信息,就是状态码。状态码后面就是头字段和网页数据。客户端收到响应消息后浏览器就会读出所需数据显示到屏幕上。
1.3.2.生成HTTP请求消息
浏览器解析完URL之后会根据Web服务器和文件名来生成HTTP请求消息,HTTP请求消息的格式如下:
- 请求消息的第一行称为请求行,最开头的方法是重点,可以告诉Web服务器进行怎样的操作。方法有很多种,要根据浏览器的工作状态来判断选用哪一种。在地址栏输入网址并显示网页、点击超级链接时使用GET,如果是表单,在HTML源代码中会在表单的属性中指定使用哪一种方法来发送请求,可能是GET也可能是POST,两种方法的区分:
写好方法之后加一个空格,然后写URI,URI一般是文件和程序的路径名,部分格式如下:
/<目录名>/.../<文件名>
路径名一般包含在URL中了,从中提取出来原封不动地写上去就可以了。第一行的末尾要写上HTTP的版本号,为了表示该消息是基于哪个版本的HTTP规格编写的。
- 第二行开始为消息头,用来存放一些额外的详细信息,如:日期、客户端支持的数据类型、语言、压缩格式等。
- 写完消息头后添加一个完全没有内容的空行,再写上要发送的数据。这一部分称为消息体。
1.3.3.收到响应
发送请求消息后,Web服务器会返回响应消息。响应消息的格式和请求消息基本上是相同的,差别在第一行上。
- 响应消息第一行内容为状态码和响应短语,用来表示请求的结果是成功还是出错。
状态码和响应短语表示的内容一致,但是用途不同。
- 状态码是一个数字,用来告诉程序执行的结果
- 响应短语是一段文字,用来告诉人们执行的结果
状态码中第一位数字表示状态类型,第二、三位数字表示具体的情况。第一位数字的含义如下:
1条请求消息中只能写一个URI,如果要获取多个文件,必须对每个单独的文件都发送1条请求
收到响应消息后,浏览器会将数据提取出来显示到屏幕上。如果网页中有图片,浏览器会再次访问Web服务器,和获取网页文件时一样,只要把URI部分写上图片的文件名并生成请求消息即可。
2.查询服务器的IP地址
浏览器能解析URL并生成HTTP请求消息,但是它不具备将消息发送到网络中的功能,需要委托操作系统发送给Web服务器。
发送消息的功能对于所有的应用程序来说都是通用的,让操作系统来实现这一功能,其他程序委托操作系统来发送,是一个比较合理的做法。
发送前首先要查询网址中服务器域名中对应的IP地址。
2.1.TCP/IP简介
互联网和公司内部的局域网都是基于TCP/IP的思路,其基本结构如下:
子网可以理解为用集线器连接起来的几台计算机,将它看作一个单位。几个小的子网通过路由器连接起来组成一个大的网络。在网络中,每个设备都会被分配一个IP地址,由网络号和主机号组成,网络号是分配给整个子网的,主机号是分配给子网中的计算机的。消息发送者通过IP地址可以判断对象服务器的位置:消息首先经过子网的集线器,转发到距离最近的路由器上,路由器根据目的地址将消息通过子网内的集线器转发到下一个路由器,此过程不断重复最终消息到达目的地。
2.2.IP地址简介
IP地址是一串32比特的数字,按照8比特为一组分为4组,分别用十进制表示再用圆点隔开,其中网络号和主机号的具体结构是不固定的,所以需要一些附加信息来表示其内部结构:
这个附加信息就是子网掩码,格式如下:
两种特殊的主机号:
- 全0:表示整个子网
- 全1:表示广播
2.3.域名和IP并用
- IP地址代替域名
在网址中不写服务器的名字,直接写IP地址也能正常工作(除非服务器使用了虚拟主机)。
缺点:记住一串由数字组成的IP地址相对困难,网址中使用服务器名称较好
- 域名代替IP地址
IP地址长度为32比特4字节,域名长度从几十字节到255字节不等
域名更长:增加路由器负担,传送数据会花费更多的时间
域名长度不固定:处理长度不固定的数据相对于固定的数据更复杂,效率低下
- 两者并用
让人来使用域名,路由器使用IP地址,使用DNS(Domain Name System / 域名系统)。实现两者之间的互相查询。
2.4.DNS解析器
查询某网址的IP地址只需向DNS服务器询问就可以了,所以我们的计算机上有相应的DNS客户端,称为DNS解析器,通过DNS查询IP地址的操作称为域名解析。
1.3.1.Socket库
DNS解析器是一段程序,包含在操作系统的Socket库中。
库就是一堆通用程序组件的集合,其他的应用程序都需要使用其中的组件。使用库的优点:
- 使用现成的组件搭建应用程序可以节省编程工作量
- 多个程序使用相同的组件可以实现
程序的标准化
Socket库中包含的程序组件可以让其他应用程序调用操作系统的网络功能。
1.3.2.发出查询
DNS解析器是Socket库中的程序,向DNS服务器发出查询时,只需从应用程序中调用就行了。调用方式:在浏览器编写中写上解析器的程序名称gethostbyname以及Web服务器的域名,再编写用于分配保存IP地址的内存空间。
调用之后解析器会向DNS服务器发出查询并收到响应,响应消息中包含查询到的IP地址,解析器将其写入浏览器指定的内存地址中。接下来在向Web服务器发送消息时,取出IP地址和HTTP请求消息一起交给操作系统就可以了。
1.3.3.内部原理
调用DNS解析器后计算机内部工作流程:
调用DNS解析器后,解析器会生成要发送给DNS服务器的查询消息,和浏览器一样,解析器也不具备使用网络收发数据的功能,要委托操作系统的协议栈(操作系统内部的网络控制软件,也叫协议驱动、TCP/IP驱动)来执行,协议栈会通过网卡将消息发送给DNS服务器。
如果查询的Web服务器已经再DNS服务器上注册,其IP地址就能被找到,会被写入响应消息并返回给客户端,经过协议栈传递给DNS解析器,然后解析器读出消息取出IP地址写入应用程序指定的内存地址中。
其中向DNS服务器发送查询时,也需要知道DNS服务器的IP地址,这是作为TCP/IP的一个设置项目事先设置好的:
3.DNS服务器
3.1.基本工作
DNS服务器的基本工作就是接收来自客户端的查询消息,然后根据内容返回响应。
查询消息中包含以下3个内容:
域名
服务器、邮件服务器(邮件地址中@后面的部分)的名称
Class
在最早设计 DNS 方案时,DNS 在互联网以外的其他网络中的应用也被考虑到了,而 Class 就是用来识别网络的信息。不过,如今除了 互联网并没有其他的网络了,因此 Class 的值永远是代表互联网的 IN
-
记录类型- A(address):表示域名对应的是IP地址
- MX(mail eXchange/邮件交换):表示域名对应的是邮件服务器
- PTR:根据IP地址反查域名
- CNAME:查询域名相关别名
- NS:查询DNS服务器IP地址
- SOA:查询域名属性信息
不同的记录类型对应的返回信息也会不同。DNS服务器事先保存了这3中信息对应的响应数据,当查询消息中的数据全部匹配时,会将对应的响应数据返回给客户端。
3.2.域名的层次结构
互联网中有不计其数的服务器,这些信息是按照域名的层次结构分布保存在多台DNS服务器中的。
DNS中的域名是用句点来分隔的,每个句点划分出了不同的层级,越靠右的位置表示其层级越高,相当于一个层级的部分称为域。比如:www.lab.glasscom.com,com域的下一级是glasscom域,再下一级是lab域,再下一级是www这个名字。
在DNS服务器中,每个域都是作为一个整体存放的,不能将一个域拆开存放在多台DNS服务器中,但是一台DNS服务器可以存放多个域的信息(上级域和下级域可能共享同一台DNS服务器),所以DNS服务器也具有了像域名一样的层次结构,每个域的信息都存放在相应的DNS服务器中。为了查询时不用一个一个查找对应的域名的服务器,我们把负责管理下级域的服务器的IP地址注册到它们的上级服务器中。
在域名中,com域上面还有一级域,叫做根域,它没有自己的名字,如果要明确表示根域,将域名写作:www.lab.glasscom.com.,com后面的句点就表示根域。根域的DNS服务器中保存着com等高级域的DNS服务器信息。由此查询时就可以从根域开始一路顺藤摸瓜找到任意一个域的DNS服务器。
3.3.查询流程
所有的DNS服务器还需要将根域的DNS服务器信息保存下来,由此任何DNS服务器就可以访问根域DNS服务器,并访问下层的某台目的DNS服务器。
分配给根域DNS服务器的IP地址在全世界仅有13个(根域DNS服务器在运营上使用多台服务器来对应一个IP地址,因此尽管IP地址只有13个,但其实服务器的数量是很多的),且这些地址几乎不发生变化。根域DNS服务器的相关信息已经包含在DNS服务器程序的配置文件中了,所以会自动配置根域DNS服务器的信息。
这样DNS服务器就能在上万台DNS服务器中找到目标服务器:
客户端首先会访问最近的一台DNS服务器,如果它没有存放目标域名对应的信息,所以要从顶端根域根据结构向下查找。
3.4.缓存优化
有时候并不需要从最上级的根域开始查找,因为DNS服务器有缓存功能,可以记住之前查询过的域名。 如果要查询的域名和相关信息已经在缓存中,那么就可以直接返回响应,接下来的查询可以从缓存的位置开始向下进行。相比每次都从根域找起来说,缓存可以减少查询所需的时间。
并且,当要查询的域名不存在时,“不存在”这一响应结果也会被缓存。这样,当下次查询这个不存在的域名时,也可以快速响应。
- 注意:信息被缓存后,原本的信息可能会发生改变,这时缓存的信息就是不正确的。所以缓存信息会有一个
有效期,超过有效期会从缓存中删除。并且在对查询进行响应时,DNS服务器也会告诉客户端响应的数据是来自对应的DNS服务器还是来自缓存。
4.委托协议栈发送消息
知道IP地址后就可以委托操作系统内部的协议栈向这个目标IP地址发送消息了,要发送的HTTP消息是一种数字信息,收发数字信息这一操作对于任何使用网络的应用程序都是共通的,适用于任何网络程序。
4.1.收发数据概览
向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用Socket库中的程序组件。
使用Socket库收发数据的“管道”结构如下:
收发数据的两台计算机之间连接了一条数据通道,数据沿着这条通道流动到达目的地。可以将这个数据通道想象为一条虚拟的管道,管道中数据的流动是双向的。建立管道的关键在于管道两边的数据出入口,这些出入口被称为套接字。
收发数据的操作大致分为以下阶段:
- 创建套接字阶段
服务器一方先创建套接字,然后等待客户端向该套接字连接管道;客户端也会先创建一个套接字
- 将管道连接到服务器端的套接字上(连接阶段)
客户端创建套接字后从该套接字延伸出管道到服务器的套接字上
- 收发数据(通信阶段)
双方的套接字连接起来后,只要将数据送入套接字就可以收发数据了
- 断开阶段
管道在断开时可以由客户端或服务器任意一方发起(在HTTP1.0中服务器会先断开管道),一方断开后,另一方也会随之断开,管道断开后套接字也会被删除。
客户端与服务器之间收发数据:
4.2.创建套接字
创建套接字需要调用Socket库中的socket程序组件(上图①),创建完成后会返回一个描述符,因为同时可能存在多个数据收发操作,这时同时存在多个套接字。应用程序就是通过描述符这一类似号码牌的东西来识别套接字的。
4.3.连接阶段
连接阶段需要调用Socket库中的connect程序组件,调用时需要指定描述符、服务器IP地址和端口号这3个参数(上图②)
描述符即让协议栈知道使用哪个套接字去和服务器端的套接字进行连接。
端口号的作用就是让通信的另一端识别到机器上具体的套接字:
- 使用IP地址不能识别吗
IP地址是为了区分网络中的各个计算机而分配的(IP地址是分配给设备中安装的网络硬件的,如果一台设备中安装了多个网络硬件,一个设备就会有多个IP地址),但是连接的对象必须是某台计算机上某个具体的套接字。
- 不能使用描述符识别吗
描述符是委托创建套接字的应用程序和协议栈进行交互时使用的,网络连接的另一方的客户端无法知道服务器上的描述符。
- 服务器怎么得知客户端套接字的端口号呢
客户端在创建套接字时,协议栈会为这个套接字随便分配一个端口号(也可以自行指定),进行连接时会将这个端口号通知给服务器。
服务器上所使用的端口号是根据应用的种类事先规定好的,其中Web是80号端口,电子邮件是25号端口。连接成功后,协议栈会将对方的IP地址和端口号等信息保存在套接字中。
4.4.通信阶段
通信阶段需要使用Socket库中的write程序组件。首先应用程序要在内存中准备好要发送的数据(HTTP请求消息),调用write时需要指定描述符和发送数据(上图③)。由于在连接过程中套接字中已经保存了通信对象的相关信息(IP地址和端口),所以只需要通过描述符告诉协议栈指定的套接字就可以了。
当客户端收到服务器的响应消息,需要通过Socket库中的read程序组件来完成(上图③')。调用read需要指定接收缓冲区(用于存放接收到的响应消息的内存地址),接收缓冲区位于应用程序内部的内存空间,当消息被存放到这里后就相当于已经转交给了应用程序。
4.5.断开阶段
收发数据过程结束后,需要调用Socket库中的close程序组件进入断开阶段(上图④)。过程如下:
- HTTP协议规定,Web服务器发送完响应消息后首先调用close来断开连接,主动执行断开操作(根据应用种类不同,客户端和服务器哪一方先执行close都有可能)
- 断开操作传达到客户端后,客户端的套接字也进入断开阶段
- 浏览器再调用read时,read告知浏览器收发数据操作已结束
- 浏览器调用close进入断开阶段
HTTP协议将HTML文档和图片都作为单独的对象来处理,每获取一次数据,就要执行一次收发数据的流程。如果一个网页包含很多张图片,就必须重复进行很多次连接、收发数据、断开的操作。对于同一台服务器来说,重复连接和断开显然是效率很低的,因此后来人们又设计出了能够在 一次连接中收发多个请求和响应的方法。在HTTP版本1.1中就可以使用这种方法,在这种情况下,当所有数据都请求完成后,浏览器会主动触发断开连接的操作。