网络请求与远程资源
网络分层
一、OSI七层模型、TCP/IP概念层模型
区别:OSI模型注重通信协议必要的功能是什么,TCP/IP模型更强调在计算机上实现协议应该开发哪种程序。
二、应用层的网络协议
- FTP:文本传输协议
- SMTP:简单邮件传输协议
- TELNET:Internet远程登录服务的标准协议和主要方式
- HTTP:超文本传输协议
- TFTP:简单文件传输协议
- DNS:域名系统
- SNMP:简单网络管理协议
- NFS:网络文件系统
三、数据的单位
- 包:全能性术语
- 帧:数据链路层中包的单位
- 数据包:网络层以上的分层中包的单位
- 段:TCP数据流中的信息
- 消息:应用协议中数据的单位
四、地址
- MAC地址:数据链路中的地址,用来识别同一链路中不同的计算机
- IP地址:IP中的地址,用来识别连接到网络中的主机和路由器
- 程序地址(端口号):识别同一台计算机中进行通信的不同应用程序
OSI 模型的数据传输:
发送包的时候,是一层层加上各种头和数据的,接收到包是一层层解开包,一步步拆开拿下包的各种头最终到了相应的应用就变成了数据的。
1)物理层(Physical Layer)
2)数据链路层(Data Link Layer)
- 基本数据单位为帧;
- 主要的协议:以太网协议;
- 两个重要设备名称:网桥和交换机
- 数据链路层在物理层提供的服务的基础上为网络层提供可靠的数据传输;
3)网络层(Network Layer)路径选择、路由及逻辑寻址
网络层中涉及众多的协议,其中包括最重要的协议,也是TCP/IP的核心协议——IP协议。IP协议非常简单,仅仅提供不可靠、无连接的传送服务。IP协议的主要功能有:无连接数据报传输、数据报路由选择和差错控制
- 网络层负责对子网间的数据包进行路由选择。此外,网络层还可以实现拥塞控制、网际互连等功能;
- 基本数据单位为IP数据报;
- 包含的主要协议:
IP协议(Internet Protocol,因特网互联协议);
ICMP协议(Internet Control Message Protocol,因特网控制报文协议);
ARP协议(Address Resolution Protocol,地址解析协议);
RARP协议(Reverse Address Resolution Protocol,逆地址解析协议)。
- 重要的设备:路由器。
4)传输层
在这一层,信息传送的协议数据单元称为段或报文。 网络层只是根据网络地址将源结点发出的数据包传送到目的结点,而传输层则负责将数据可靠地传送到相应的端口
- 传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输以及端到端的差错控制和流量控制问题;
- 运输层主要使用以下两种协议
- 传输控制协议 TCP–提供面向连接的,可靠的数据传输服务。
- 用户数据协议 UDP–提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
- 重要设备:网关。
5)会话层
会话层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话
6)表示层
表示层对上层数据或信息进行变换以保证一个主机应用层信息可以被另一个主机的应用程序理解。表示层的数据转换包括数据的加密、压缩、格式转换等。
7)应用层
为操作系统或网络应用程序提供访问网络服务的接口
会话层、表示层和应用层重点:
- 数据传输基本单位为报文;
- 包含的主要协议:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议),POP3协议(邮局协议),HTTP协议(Hyper Text Transfer Protocol),简单网络管理协议SNMP。
- 用到了UDP协议的SNMP和DNS
网络层 | 作用 | 协议 | 单位 |
应用层 | 支持各种网络应用 | FTP、SMTP、HTTP | 报文 |
传输层 | 进程到进程的数据传输 | TCP、UDP | 报文段 |
网络层 | 源主机到目的主机的数据分组路由与转发 | IP、ICMP、OSPF等 | 数据报 |
数据链路层 | 把网络层传下来的数据报组装成帧 | Ethernet、PPP | 帧 |
物理层 | 比特传输 | 比特 |
TCP/IP协议将应用层、表示层、会话层合并为应用层,物理层和数据链路层合并为网络接口层。
TCP/IP协议不仅仅指的是TCP和IP两个协议,⽽是指的⼀个由FTP,SMTP,TCP,UDP,IP,ARP
等等协议构成的协议集合。
浏览器解析URL
URL和URI的区别?
URI(Uniform Resource Identifier) 统一资源标识符
URL(Uniform Resource Locator) 统一资源定位符
URI用字符串标识某一互联网资源,而URL表示资源的位置,URL是URI的子集。
URI的目的就是唯一标识互联网中的一份资源,具体可以用资源名称、资源地址等,但是资源地址是目前使用最广泛的,因此URL就容易和URI混淆。URI相当于抽象类,URL就是这个抽象类的具体实现类。
URL 主要由 协议
、主机
、端口
、路径
、查询参数
、锚点
6部分组成。
值得一提的是浏览器会自动补全URL,比如我输入的是 rainbowinpaper.com,实际上访问的是 BOB·(中国)综合官方网站-网页版。
浏览器通过 URL 能够知道下面的信息:
Protocol
"http" 使用HTTP协议
Resource
"/" 请求的资源是主页(index)
输入的是 URL 还是搜索的关键字?
当协议或主机名不合法时,浏览器会将地址栏中输入的文字传给默认的搜索引擎。大部分情况下,在把文字传递给搜索引擎的时候,URL会带有特定的一串字符,用来告诉搜索引擎这次搜索来自这个特定浏览器。
转换非 ASCII 的 Unicode 字符
- 浏览器检查输入是否含有不是
a-z
,A-Z
,0-9
,-
或者.
的字符 - 这里主机名是 rainbowinpaper.com ,所以没有非ASCII的字符;如果有的话,浏览器会对主机名部分使用 Punycode 编码
检查 HSTS 列表
- 浏览器检查自带的“预加载 HSTS(HTTP严格传输安全)”列表,这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站
- 如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用HTTP协议发送
- 注意,一个网站哪怕不在 HSTS 列表里,也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后,网站会返回浏览器一个响应,请求浏览器只使用 HTTPS 发送请求。然而,就是这第一个 HTTP 请求,却可能会使用户受到 downgrade attack 的威胁,这也是为什么现代浏览器都预置了 HSTS 列表。
说回输入URL后,浏览器会解析出协议、主机、端口、路径等信息,并构造一个HTTP请求。
// 请求方法是GET,路径为根路径,HTTP协议版本为1.1
GET / HTTP/1.1
但是这个HTTP请求不一定会发送出去,因为浏览器会先检查本地缓存中与该请求头一致的缓存信息。
输入完网址按下回车,到看到网页这个过程中发生了什么
- 浏览器向DNS服务器请求解析该 URL 中的域名所对应的 IP 地址;
输入url后,首先需要找到这个url域名的服务器ip,为了寻找这个ip,浏览器首先会寻找缓存,查看缓存中是否有记录,
缓存的查找记录为:浏览器缓存-》系统缓存-》路由器缓存,缓存中没有则查找系统的hosts文件中是否有记录,如果没有则查询DNS服务器,
得到服务器的ip地址后,浏览器根据这个ip以及相应的端口号,构造一个http请求,
- 跟服务器建立TCP连接(三次握手);
为什么要第三次挥手?避免服务器等待造成资源浪费
- 建立TCP连接后浏览器发出HTTP 请求
得到服务器的ip地址后,浏览器根据这个ip以及相应的端口号,构造一个http请求,
这个请求报文会包括这次请求的信息,主要是请求方法,请求说明和请求附带的数据,并将这个http请求封装在一个tcp包中
这个tcp包会依次经过传输层,网络层,数据链路层,物理层到达服务器,服务器解析这个请求来作出响应,返回相应的html给浏览器
- 服务器对浏览器请求作出响应,并把对应的 html 代码发送给浏览器;
5.浏览器解析HTML代码,并请求HTML代码中资源(如js,css,图片)
因为html是一个树形结构,浏览器根据这个html来构建DOM树,在dom树的构建过程中如果遇到JS脚本和外部JS连接,则会停止构建DOM树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面
之后根据外部样式,内部样式,内联样式构建一个CSS对象模型树CSSOM树,构建完成后和DOM树合并为渲染树,这里主要做的是排除非视觉节点,比如script,meta标签和排除display为none的节点
之后进行布局,布局主要是确定各个元素的位置和尺寸
6.浏览器对页面进行渲染并呈现给用户;
之后是渲染页面,因为html文件中会含有图片,视频,音频等资源,在解析DOM的过程中,遇到这些都会进行并行下载,浏览器对每个域的并行下载数量有一定的限制,一般是4-6个
7.服务器关闭TCP连接(四次挥手)
当然在这些所有的请求中我们还需要关注的就是缓存,缓存一般通过Cache-Control、Last-Modify、Expires等首部字段控制。 Cache-Control和Expires的区别在于Cache-Control使用相对时间,Expires使用的是基于服务器 端的绝对时间,因为存在时差问题,一般采用Cache-Control,在请求这些有设置了缓存的数据时,会先 查看是否过期,如果没有过期则直接使用本地缓存,过期则请求并在服务器校验文件是否修改,如果上一次 响应设置了ETag值会在这次请求的时候作为If-None-Match的值交给服务器校验,如果一致,继续校验 Last-Modified,没有设置ETag则直接验证Last-Modified,再决定是否返回304
浏览器请求页面时,各个进程间是怎么配合的?
- 用户输入url并回车。
- 用户输入URL,浏览器进程会根据用户输入的信息判断是搜索还是网址,如果是搜索内容,就将搜索内容+默认搜索引擎合成新的URL;如果用户输入的内容符合URL规则,浏览器进程就会根据URL协议,在这段内容上加上协议合成合法的URL。
- 浏览器导航栏显示loading状态,但是页面还是呈现之前的页面不变,因为新页面的响应数据还没有获得。
- 浏览器进程构建请求行信息,通过进程间通信(IPC)把url请求发送给网络进程
- 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
- 如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
- 进行DNS解析,获取服务器ip地址
- 利用ip地址和服务器建立tcp连接
- 完成构建请求信息并发送请求
- 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
- 网络进程解析响应流程:
- 检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步,如果是200,则继续处理请求。
- 检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染,如果是html等资源则将其转发给浏览器进程。
- 浏览器进程接收到网络进程的响应头数据之后,检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程。
- 渲染进程准备好后,浏览器进程发送CommitNavigation消息到渲染进程,发送CommitNavigation时会携带响应头、等基本信息。渲染进程接收到消息和网络进程建立传输数据的“管道”。
- 渲染进程接收完数据后,向浏览器进程发送“确认提交”。
- 浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、前进后退的历史状态、更新web页面。
http缓存机制
HTTP报文
一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求数据4个部分组成
响应报文和请求报文结构类似,不再赘述
常见Request Headers
- Accept 浏览器可接收的数据格式
- Accept-Encoding 浏览器可接收的压缩算法,如gzip
- Accept-Languange 浏览器可接收的语言,如zh-CN
- Connection: keep-alive 一次TCP连接重复使用
- cookie
- Host 请求域名
- User-Agent 简称UA 浏览器信息
- Content-type 发送数据的格式,如application/json
常见 Response Headers
- Content-type 发送数据的格式,如application/json
- Content-length 返回数据的大小,多少字节
- Content-Encoding 返回数据的压缩算法,如gzip
- Set-Cookie 设置Cookie
- Cache-Control 缓存控制
- Expires 控制缓存过期
- Last-Modified 资源最后修改时间
- Etag 资源唯一标识
HTTP----HTTP缓存机制
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
禁止进行缓存 Cache-Control: no-store
no-store 指令规定不能对请求或响应的任何一部分进行缓存。
强制确认缓存 Cache-Control: no-cache
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。
缓存过程分析
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:
由上图我们可以知道
1、浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
2、浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了。
为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存 。
哪些资源可以被缓存——静态资源(js、css、img)
强缓存
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应的报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高。
Expires
用来指定资源到期绝对时间,服务器响应时,添加在响应头中。
expires: Wed, 22Nov202108:41:00GMT
注意:如果服务器和浏览器端时间不一致的话可能导致失败。比如现在时间是8月1,expires过期时间是8月2,客户端把电脑时间改成了8月3,那就用不了这个缓存
Cache-Control
指定资源过期时间秒,如下,表示在这个请求正确返回后的300秒内,资源可以使用,否则过期
cache-control:max-age=300
为什么指定缓存过期时间需要两个字段呢?
因为有的浏览器只认识 Cache-Control,有的浏览器不认识,不认识的情况下再找 Expires
Expires 和 Cache-Control 的区别
Expires | Cache-Control | |
协议版本 | HTTP/1.0 | HTTP/1.1 |
字段值格式 | Mon, 16 Apr 2021 00:00:00 GMT | public、private、no-cache、no-store、max-age=600…… |
来源 | 存在于服务端返回的响应头中 | 响应头和请求头 |
缺点 | 服务器的时间和浏览器的时间可能并不一致导致失效 | 时间最终还是会失效 |
由于Cache-Control的优先级比expires高,那么直接根据Cache-Control的值进行缓存,max-age=600
意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。
注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。
- 内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
- 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
强缓存的缺点
就是缓存过期之后,不管资源有没有变化,都会重新发起请求,重新获取资源
而我们希望的是在资源文件没有更新的情况下,即使过期了也不重新获取资源,继续使用旧资源
所以协商缓存它来了,在强缓存过期的情况下,再走协商缓存的流程,判断文件有没有更新
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
- 协商缓存生效,返回304
- 协商缓存失败,返回200和请求结果
同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match
,其中Etag / If-None-Match
的优先级比Last-Modified / If-Modified-Since
高。
第一次请求资源时,服务器除了会返回给浏览器上面说的过期时间,还会在响应头添加 Last-Modified 字段,告诉浏览器该资源的最后修改时间
last-modified: Fri, 27Oct202108:35:57GMT
然后浏览器再次请求的时候就把这个时间再通过另一个字段If-Modified-Since,发送给服务器
if-modified-since: Fri, 27Oct202108:35:57GMT
服务器再把这两个字段的时间对比,如果是一样的,就说明文件没有被更新过,就返回状态码304和空响应体给浏览器,浏览器直接拿过期了的资源继续使用即可;如果对比不一样说明资源有更新,就返回状态码200和新的资源,
所以说Last-Modified/If-Modified-Since它俩是成对的,是为了对比文件修改时间
缺点
- 如果本地打开了缓存文件,即使没有对文件进行修改,但还是会造成Last-Modified被修改,服务器端不能命中缓存导致发送相同资源
- 因为Last-Modified只能以秒计时,如果在不可感知的时间内修改了文件,服务器端会认为还是命中了,无法返回正确的资源
- 如果资源有周期性变化,如资源修改后,在一个周期内又改回了原来的样子,我们认为这个周期前的缓存是可以使用的,但是Last-Modified不这样认为
因为这些缺点,所以便有了另外一对 ETag/If-None-Match,用来对比文件内容
ETag/If-None-Match
第一次请求资源时,服务器除了会在响应头上返回Expires、Cache-Control、Last-Modified,还在返回Etag字段,表示当前资源文件的一个唯一标识。这个标识符由服务器基于文件内容编码生成,能精准感知文件的变化,只要文件内容不同,ETag就会重新生成
etag: W/"132489-1627839023000"
然后浏览器再次请求的时候就把这个文件标识 再通过另一个字段 If-None-Match,发送给服务器
if-none-match: W/"132489-1627839023000"
服务器再把这两个字段的时间对比,如果发现是一样的,就说明文件没有被更新过,就返回状态码304和空响应体给浏览器,浏览器直接拿过期了的资源继续使用;如果对比不一样说明资源有更新,就返回状态码200和新的资源
Last-Modified 和 ETag 的区别
- Etag 感知文件精准度要高于 Last-Modified
- 同时使用时,服务器校验优先级 Etag/If-None-Match
- Last-Modified 性能上要优于 Etag,因为 Etag 生成过程中需要服务器付出额外开销,会影响服务器端的性能,所以它并不能完全替代 Last-Modified,只能作为补充和强化
到这里浏览器缓存的处理已经完成,接下来就要进行网络请求了
DNS解析
由于 IP 地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过域名解析协议(DNS,Domain Name System)来将域名和 IP 地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的 IP 地址数串。将域名映射成 IP 地址称为正向解析,将 IP 地址映射成域名称为反向解析。
DNS 分为查询请求和查询响应,请求和响应的报文结构基本相同。DNS 协议可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。但大多数情况下 DNS 都使用 UDP 进行传输。DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议。
域名解析
-
- DNS域名系统,是应用层协议,运行UDP协议之上,使用端口43。
- 浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有该请求资源,会直接在屏幕中显示页面内容,没有则表示不存在 DNS 缓存,这个时候就需要发起 DNS 查询,本地查询是递归查询,依次通过浏览器缓存 —>> 本地hosts文件 —>> 本地DNS解析器 —>>本地DNS服务器 —>> 根域名(.com)服务器查询请求。接下来的过程就是迭代查询
-
- 当根域名接收到本地DNS的解析后,发现后缀是.com,于是就把负责.com顶级域名的服务器IP地址返回给本地DNS;
- 本地DNS拿着返回的ip地址再去找对应的顶级域名服务器,该服务器将负责该域名的权威服务器ip返回回去;
- 本地DNS又拿着ip去找对应的权威服务器,权威服务器最终将对应的主机ip返回给本地DNS,至此完成了域名的解析。
-
- 递归查询一般而言,发送一次请求就够,迭代过程需要用户发送多次请求。
- DNS 使用 UDP 协议作为传输层协议的主要原因是为了避免使用 TCP 协议时造成的连接时延。
- 为了得到一个域名的 IP 地址,往往会向多个域名服务器查询,如果使用 TCP 协议,那么每次请求都会存在连接时延,这样使 DNS 服务变得很慢。
- 大多数的地址查询请求,都是浏览器请求页面时发出的,这样会造成网页的等待时间过长。
(1)递归查询:本机向本地域名服务器发出一次查询请求,就静待最终的结果。如果本地域名服务器无法解析,自己会以DNS客户机的身份向其它域名服务器查询,直到得到最终的IP地址告诉本机。
(2)迭代查询:本地域名服务器向根域名服务器查询,根域名服务器告诉它下一步到哪里去查询,然后它再去查,每次它都是以客户机的身份去各个服务器查询。
1和8这中间还会查询浏览器缓存、操作系统缓存、路由器缓存,再到本地域名服务器。DNS解析完成后我们获得了目标域名的IP地址,接下来我们就可以建立TCP连接。
TCP连接
常用的熟知端口号
应用程序 | FTP | TFTP | TELNET | SMTP | DNS | HTTP | SSH | MYSQL |
熟知端口 | 21,20 | 69 | 23 | 25 | 53 | 80 | 22 | 3306 |
传输层协议 | TCP | UDP | TCP | TCP | UDP | TCP | TCP | TCP |
TCP的概述
TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。
TCP报文首部
- 源端口和目的端口,各占2个字节,分别写入源端口和目的端口;
- 序号,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;
- 确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701;
- 数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;
- 保留,占6位,保留今后使用,但目前应都位0;
- 紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;
- 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
- 推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;
- 复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
- 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
- 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
- 窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;
- 检验和,占2字节,校验首部和数据这两部分;
- 紧急指针,占2字节,指出本报文段中的紧急数据的字节数;
- 选项,长度可变,定义一些其他的可选的参数。
socket与流套接字
在网络中采用发送方和接收方的套接字组合来识别端点,套接字唯一标识了网络中的一个主机和它上的一个进程。
套接字(Socket)=(主机IP地址,端口号)
当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket
,请求一个 TCP流套接字 。
这个请求首先被交给传输层,在传输层请求被封装成 TCP 报文段。目标端口会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range
)。
什么是Socket?
举一个例子:A跟B两人聊QQ,QQ是一个独立的应用程序,那么它对应了两个Socket,一个在A的电脑上,一个在B的电脑上。当A对B说:”周末我们去玩吧!“,这句话就是一段数据,这段数据会先储存在A电脑Socket上,当A的QQ和B的QQ连接成功后,A的Socket将这段话的数据发送到B的电脑中,但是B暂时还没看到,因为数据会先存放在B电脑的Socket当中,然后Socket会把数据呈现给B看。
数据传送过程中为什么要多出Socket这样东西?
答:因为不同的应用程序对应不同的Socket,而Socket保证了QQ的数据不会到处乱跑,不会一冲动跑到MSN上去了。因为QQ和MSN两个应用程序的Socket内容是完全不同的。
那么Socket里面到底是什么?
答:Socket套接字地址!套接字地址是一个数据结构,我们仅基于TCP传输协议作为例子。套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。
这里要提醒一点,Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待。如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。
TCP协议
传输层有2个协议:
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
UDP(User Datagram Protocol),用户数据报协议。
特点
- TCP是面向连接(虚连接)的传输层协议。
- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的。
- TCP提供可靠交付的服务,无差错、不丢失、不重复、按序到达。可靠有序,不丢不重。
- TCP提供全双工通信。发送和接收可以同时进行
发送缓存:准备发送的数据&已发送但尚未收到确认的数据。
接收缓存:按序到达但尚未被接受应用程序读取的数据&不按序到达的数据。
- TCP面向字节流。
流:流入到进程或从进程流出的字节序列。
TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。
TCP报文段首部格式:
TCP和UDP
TCP | UDP |
面向连接的可靠性传输 | 无连接不可靠 |
保证数据的无差错、不丢失、不重传且按序到达 | 尽最大努力交付,但不保证可靠交付 |
面向字节流 | 面向报文 |
只能是一对一 | 一对一或一对多 |
首部较大有20字节 | 首部只有8字节 |
用于需要可靠传输的情况 | 用于高速传输和对实时性有较高要求的通信(视频、音频等多媒体通信)或广播通信 |
TCP特点:流量控制、拥塞控制、面向连接、可靠传输
区别:TCP是面向连接的,可靠的字节流服务;UDP是面向无连接的,不可靠的数据报服务。
- TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接;
- TCP提供可靠数据传输,通过使用流量控制、序号、确认和定时器,TCP确保正确的、按序的将数据从发送进程交付给接收进程;UDP尽最大努力交付,即不保证可靠交付;
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性比较高的通信或广播通信;
- 每一条TCP连接只能是一对一的,UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP是面向字节流的,即把应用层传来的报文看成字节流,将字节流拆分成大小不等的数据块,并添加TCP首部;UDP是面向报文的,对应用层传下来的报文不拆分也不合并,仅添加UDP首部
- TCP数据传输慢,UDP数据传输快
- TCP对系统资源要求较多,UDP对系统资源要求较少。
- TCP首部开销较大,20字节;UDP只有8字节
- TCP的逻辑通信信道是全双工的可靠信道;而UDP则是不可靠信道
TCP对应的协议:
(1) FTP:定义了文件传输协议,使用21端口。
(2) Telnet:一种用于远程登陆的端口,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。
(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是25号端口。
(4) POP3:它是和SMTP对应,POP3用于接收邮件。POP3协议所用的是110端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议。
UDP对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。
(2) SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
(3) TFTP(Trival File Tran敏感词er Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。
面向连接和非面向连接的服务的特点是什么?
面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。
非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
三次握手
TCP连接的建立需要经历三次握手的过程:
- SYN:
synchronous
建立联机
为1时表示是建立连接的请求,不携带应用层数据。
- ACK:
acknowledgement
确认
为1时表示是回应SYN为1的请求建立连接的请求。
- seq:
sequence
序号
是一个随机产生的序号。
- ack:确认序号
表示此序号之前的序号都已确认,期望获取此序号开始往后的数据。
- 从最开始双方都处于
CLOSED
状态。然后服务端开始监听某个端口,进入了LISTEN
状态,准备好接收来自外部的 TCP 连接,等待客户端连接请求。 - 客户端向服务器发出连接请求报文段,请求中首部同步位
SYN = 1
,同时选择一个初始序号sequence
,简写seq = x
。SYN 报文段不允许携带数据,只消耗一个序号。此时,客户端进入SYN-SEND
状态。 - 服务器收到客户端连接后,需要确认客户端的报文段。在确认报文段中,把 SYN 和 ACK 位都置为 1 。确认号是 ack = x + 1,同时也为自己选择一个初始序号 seq = y。服务器端为该TCP连接分配缓存和变量,并向客户端返回确认报文段,允许连接。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。此时,TCP 服务器进入
SYN-RECEIVED(同步收到)
状态。 - 客户端在收到服务器发出的响应后,还需要给出确认连接。确认连接中的 ACK 置为 1 ,序号为
seq = x + 1
,确认号为ack = y + 1
。TCP 规定,这个报文段可以携带数据也可以不携带数据,如果不携带数据,那么下一个数据报文段的序号仍是seq = x + 1
。客户端为该TCP连接分配缓存和变量,并向服务器端返回确认的确认,可以携带数据。这时,客户端进入ESTABLISHED (已连接)
状态。 - 服务器收到客户的确认后,也进入
ESTABLISHED
状态。
第三次握手的时候,可以携带数据。前两次握手不能携带数据。
-
- 如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。
- 第三次握手的时候,客户端已经处于
ESTABLISHED
状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。
如果第3次握手失败了,会怎么处理?
- 此时server的状态为
SYN-RCVD
,若等不到client的 ACK,server会重新发送 SYN+ACK 包 - 如果server多次重发 SYN+ACK 都等不到client的 ACK,就会发送 RST包,强制关闭连接
为什么握手要3次?
因为TCP是双工传输模式,不区分客户端和服务端,连接的建立是双向的过程。如果只有两次,无法做到双向连接的建立,从建立连接server回复的SYN和ACK合并成一次可以看出来,他也不需要4次。
为什么要三次握手?
防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误
在只有两次"握手"的情形下,假设Client想跟Server建立连接,但是却因为中途连接请求的数据报丢失了,故Client端不得不重新发送一遍;这个时候Server端仅收到一个连接请求,因此可以正常的建立连接。但是,有时候Client端重新发送请求不是因为数据报丢失了,而是有可能数据传输过程因为网络并发量很大在某结点被阻塞了,这种情形下Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据…问题就在这里,Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!所以,"三次握手"很有必要!
四次挥手
当然除了建立TCP连接的三次握手,还有对应的断开TCP连接的四次挥手。当本次连接的数据传输完成,就可以关闭本次连接,关闭的发起可以是服务端也可以是客户端。连接结束后,主机中的“资源”(缓存和变量)将被释放。
这里我们也是直接上图:
- 终止位FIN:FIN=1时,表明此报文段发送方数据已发完,要求释放连接。
- 刚开始双方处于
ESTABLISHED
状态。客户端发送连接释放报文段,停止发送数据,主动关闭TCP连接。发送后客户端变成了FIN-WAIT-1
状态。注意, 这时候客户端同时也变成了half-close(半关闭)
状态,即无法向服务端发送报文,只能接收。
FIN=1,seq=u
- 服务端接收后向客户端确认,回送一个确认报文段,变成了
CLOSED-WAIT
状态。客户端接收到了服务端的确认,变成了FIN-WAIT2
状态。
ACK=1,seq=v,ack=u+1
- 随后,服务器端发完数据,服务端向客户端发送连接释放报文段,自己进入
LAST-ACK
状态,主动关闭TCP连接。
FIN=1,ACK=1,seq=w,ack=u+1
- 客户端收到服务端发来的连接释放报文段后,自己变成了
TIME-WAIT
状态,然后回送一个确认报文段给服务端,再等到时间等待计时器设置的2MSL(最长报文段寿命)后,连接彻底关闭。
ACK=1,seq=u+1,ack=w+1
注意了,这个时候,客户端需要等待足够长的时间,具体来说,是2个 MSL
(Maximum Segment Lifetime,报文最大生存时间
),在这段时间内如果客户端没有收到服务端的重发请求,那么表示 ACK 成功到达,挥手结束,否则客户端重发 ACK。
等待2MSL的意义
如果不等待会怎样?
如果不等待,客户端直接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的应用占用,那么就接收到了无用数据包,造成数据包混乱。所以,最保险的做法是等服务器发来的数据包都死翘翘再启动新的应用。
那,照这样说一个 MSL 不就不够了吗,为什么要等待 2 MSL?
1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达
这就是等待 2MSL 的意义。
挥手为什么要四次?
因为挥手的ACK和FIN不能同时发送,因为数据发送的截止时间不同
为什么要四次挥手?
试想一下,假如现在你是客户端你想断开跟Server的所有连接该怎么做?第一步,你自己先停止向Server端发送数据,并等待Server的回复。但事情还没有完,虽然你自身不往Server发送数据了,但是因为你们之前已经建立好平等的连接了,所以此时他也有主动权向你发送数据;故Server端还得终止主动向你发送数据,并等待你的确认。其实,说白了就是保证双方的一个合约的完整执行!
到这里我们已经说完了TCP的连接管理,这些是面试考察比较多的,我们可以再往下看看TCP数据包的序号是怎么用的,可靠传输是怎么实现的。
TCP数据包
TCP数据包大小
以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中,1500字节是负载(payload),22字节是头信息(head)。
IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。
TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节,称为MSS(Maximum Segment Size)。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。
因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求报文可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。
TCP数据包的编号(SEQ)
一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。
发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。
第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。
TCP 数据包的组装
收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。
对于应用程序来说,不用关心数据通信的细节。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。
TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length
,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 数据包,将它们按照顺序组装好,一个数据包都不少。
操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。
TCP可靠传输
网络层:提供尽最大努力交付,不可靠传输。
传输层:使用TCP实现可靠传输。
可靠:保证接收方进程从缓存区读出的字节流与发送方发出的字节流是完全一样的。
TCP怎么保证传输过程的可靠性?
- 校验和:发送方在发送数据之前计算校验和,接收方收到数据后同样计算,如果不一致,那么传输有误。
- 确认应答,序列号:TCP进行传输时数据都进行了编号,每次接收方返回ACK都有确认序列号。
- 超时重传:如果发送方发送数据一段时间后没有收到ACK,那么就重发数据。连接管理:三次握手和四次挥手的过程。
- 流量控制:TCP协议报头包含16位的窗口大小,接收方会在返回ACK时同时把自己的即时窗口填入,发送方就根据报文中窗口的大小控制发送速度。
- 拥塞控制:刚开始发送数据的时候,拥塞窗口是1,以后每次收到ACK,则拥塞窗口+1,然后将拥塞窗口和收到的窗口取较小值作为实际发送的窗口,如果发生超时重传,拥塞窗口重置为1。这样做的目的就是为了保证传输过程的高效性和可靠性。
- 确认应答机制&序列号
TCP将每个字节的数据都进行了编号,即为序列号。
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;;下一次你从哪里开始发。
- 超时重传
主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B; 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;
主机A未收到B发来的确认应答,也可能是因为ACK丢失了,因此主机B会收到很多重复数据.。那么TCP协议需要能够识别出那些包是重复的包,,并且把重复的丢弃掉.,这时候我们可以利用序列号, 就可以很容易做到去重的效果。
- 拥塞控制
拥塞控制机制,在网络拥塞的时候会控制发送数据的速率,有助于减少数据包的丢失和减轻网络中的拥塞程度。
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口。
提高传输效率:滑动窗口、流量控制、延迟应答、捎带应答
- 滑动窗口机制
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值.
- 发送窗口内字段的时候, 不需要等待任何ACK, 直接发送;
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送下一个窗口字段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
- 窗口越大, 则网络的吞吐率就越高
- 流量控制
流量控制机制,保证了通信双方的发送和接收速率相同。如果接收方可接收的缓存很小时,发送方会降低发送速率,避免因为缓存填满而造成的数据包的丢失。
TCP滑动窗口和拥塞窗口比较
- 滑动窗口:发送方+接收方
解决发送方和接收方收发数据速率不一致的问题。滑动窗口相当于接收方的缓存,接收方向发送方通知自己可接受数据的大小,而发送方会根据这个数值发送数据
- 拥塞窗口:发送方
控制全局网络的拥塞情况。通过控制发送方每次发送的流量多少,来逐渐试探整体网络的拥塞程度。
如果没有拥塞控制,发送方每次发送的数据大小为滑动窗口,在只有两台主机的时候没有问题,但在现实的网络大环境中,如果每台主机都发送滑动窗口大小的数据,那整个网络系统必然会瘫痪,所以通过在发送方设置拥塞窗口,可以有效缓解网络压力。
流量控制
目的是让发送方慢点,要让接收方来得及接收。
TCP利用滑动窗口机制实现流量控制。
在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,即接收窗口rwnd(receive window),发送方的发送窗口取接收窗口rwnd和**拥塞窗口cwnd(congestion window)**的最小值。
发送窗口=Min{接收窗口rwnd,拥塞窗口cwnd}
A向B发送数据,建立连接时,B告诉A:“我的rwnd=400(字节)”,设每个报文段100B,报文段序号初始值为1。
到这里A不再发送数据,如果B主机再次发送了设置新的接收窗口的响应,如果这个响应丢失了是不是,主机A和主机B就进入了无限等待了呢?
答案是否定的。
TCP为每一个连接设置有一个持续计时器,只要TCP连接的一方受到对方的零窗口通知,就启动持续计时器。
若持续计时器设置的时间到期,就发送一个零窗口探测报文段。接收方收到探测报文段时给出现在的窗口值。
若窗口仍然是0,那么发送方就重新设置持续计时器。这就避免了上述问题中的情况发生。
这里值得注意的是TCP协议通过使用数据链路层中的 连续ARQ协议和滑动窗口协议,来保证数据传输的正确性,从而提供可靠的传输和流量控制。
ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括 停止等待ARQ协议和 连续ARQ协议。
简单来说,停止等待ARQ协议就是发送一个报文,等待响应,有响应就发送下一个,没有就超时重传。这种方式的缺点很明显,就是信道利用率很低。
连续ARQ协议则是连续发送多组报文,然后通过累计确认的方式实现可靠传输。
连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口,如下图所示:
滑动窗口协议在在发送方和接收方之间各自维持一个滑动窗口,发送发是发送窗口,接收方是接收窗口,而且这个窗口是随着时间变化可以向前滑动的。它允许发送方发送多个分组而不需等待确认。TCP的滑动窗口是以字节为单位的。
如下图所示,发送窗口中有四个概念::已发送并收到确认的数据(不在发送窗口和发送缓冲区之内)、已发送但未收到确认的数据(位于发送窗口之内)、允许发送但尚未发送的数据(位于发送窗口之内)、发送窗口之外的缓冲区内暂时不允许发送的数据。
接收窗口中也有四个概念:已发送确认并交付主机的数据(不在接收窗口和接收缓冲区之内)、未按序收到的数据(位于接收窗口之内)、允许的数据(位于接收窗口之内)、不允许接收的数据(位于发送窗口之内)。
规则:
(1)凡是已经发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。
(2)只有当发送方A收到了接收方的确认报文段时,发送方窗口才可以向前滑动几个序号。
(3)当发送方A发送的数据经过一段时间没有收到确认(由超时计时器控制),就要使用 后退N帧协议(GBN),回到最后接收到确认号的地方,重新发送这部分数据。
拥塞控制
有了流量控制为什么还要拥塞控制?
客户端和服务端建立连接后,双方通过流量控制得到发送窗口与拥塞窗口,但是这个窗口只是针对主机与服务器的性能而决定的,整个网络的拥塞程度才决定了发送出去的数据到底能不能到达,所以可以把拥塞控制看成一个共同维护网络通畅的君子协定。
出现拥塞的条件:对资源需求的总和>可用资源
拥塞控制是一个全局性的过程
- 涉及到所有的主机、路由器
- 以及与降低网络传输性能有关的所有因素
- 是大家共同努力的结果
拥塞控制四种算法:
慢启动+拥塞避免;快速重传+快速恢复
假设:
- 数据单方向传送,而另一个方向只传送确认。
- 接收方总是有足够大的缓存空间,因而发送窗口大小取决于拥塞程度。
拥塞控制:发送方根据自己估算的网络拥塞程度而设置的窗口值,反映网络当前容量。
- 慢启动:防止一开始速率过快,导致耗尽中间路由器存储空间,从而严重降低TCP连接的吞吐量
- 拥塞避免:当拥塞发生时,降低网络传输速率
- 快速重传:在接收到相同ACK后,推断出丢失报文段起始序号,然后立即重传此报文
- 快速恢复:在快速重传基础上,如果发生了快速重传,则执行拥塞避免算法而非慢启动
慢开始和拥塞避免
开始时发送一个数据包,收到响应后再发送两个(指数增长),当到达阈值ssthresh
后,变为加法增长。当出现丢包的时候判断为网络拥塞,再回归到初始的一个数据包,并设置新的阈值,重复之前的操作。
快重传和快恢复
快重传与快恢复是针对上面方法的升级版,区别就是当出现网络拥塞时恢复到新的阈值水平加法增长,而不是初始值。
HTTP请求
HTTP发展历史
HTTP/0.9
- 只有一个命令GET
- 响应类型:仅超文本
- 没有
header
等描述数据的信息 - 服务器发送完毕,就关闭TCP连接
HTTP的1991原型版本称为HTTP/0.9
。这个协议有很多严重的设计缺陷,只应该用于老客户端的交互。HTTP/0.9
只支持GET方法,不支持多媒体内容的MIME类型、各种HTTP首部,或者版本号。HTTP/0.9
定义的初衷是为了获取简单的HTML对象,它很快就被HTTP/1.0
取代了。
HTTP/1.0
- 支持POST、HEAD等请求方法
- 增加
status code
和header
- 多字符集支持、多部分发送、权限、缓存等
- 响应:不再只限于超文本(支持MIME类型)
1.0是第一个得到广泛使用的HTTP版本。HTTP/1.0
添加了版本号、各种HTTP首部、一些额外的方法,以及对多媒体对象的处理。HTTP/1.0
使得包含生动图片的Web页面和交互式表格成为可能,而这些页面和表格促使万维网为人们广泛地接受。
HTTP/1.0+&HTTP/1.1
- 持久连接。TCP三次握手会在任何连接被建立之前发生一次。最终,当发送了所有数据之后,服务器发送一个消息,表示不会再有更多数据向客户端发送了;则客户端才会关闭连接(断开 TCP)
- 支持的方法:
GET
,HEAD
,POST
,PUT
,DELETE
,TRACE
,OPTIONS
- 进行了重大的性能优化和特性增强,分块传输、压缩/解压、内容缓存磋商、虚拟主机(有单个IP地址的主机具有多个域名)、更快的响应,以及通过增加缓存节省了更多的带宽
在20世纪90年代中叶,很多流行的Web客户端和服务器都在飞快地向HTTP中添加各种特性,以满足快速扩张且在商业上十分成功的万维网的需要。其中很多特性,包括持久的keep-alive
连接、虚拟主机支持,以及代理连接支持都被加入到HTTP之中,并成为非官方的事实标准。这种非正式的HTTP扩展版本通常称为HTTP/1.0+
。
HTTP/1.1
重点关注的是校正HTTP设计中的结构性缺陷,明确语义,引入重要的性能优化措施,并删除一些不好的特性。HTTP/1.1
还包含了对20世纪90年代末正在发展中的更复杂的Web应用程序和部署方式的支持。
HTTP/1.1协议的不足
- 同一时间,一个连接只能对应一个请求
- 针对同一个域名,大多数浏览器允许同时最多6个并发连接
- 只允许客户端主动发起请求
- 一个请求只能对应一个响应
- 同一个会话的多次请求中,头信息会被重复传输
- 通常会给每个传输增加 500~800 字节的开销
- 如果使用 Cookie,增加的开销有时会达到上千字节
http1.0和http1.1的区别
1️⃣
- 缓存处理。 在http1.0中主要使用header的if-modified-since/expires来做缓存的判断依据。
http1.1引入了更多的缓存策略控制策略。比如Entity
tag,If-Unmodified-Since,If-match,If-None-Match等更多可供选择的缓存头来控制缓存
2️⃣
- 带宽优化及网络连接的使用。
HTTP1.0中存在一些浪费带宽的现象。例如客户端只需要某个对象中的一部分,服务器端却把整个对象送过来了,并且不能支持断点续传的功能。
HTTP1.1在请求头中引入了range头域,它允许只请求资源中的某一部分,即返回码是206(partial
content)。方便开发者的自由选择、节省带宽。
3️⃣
- 错误通知的管理。
在HTTP1.1中新增了24个错误状态响应码。如409(conflict)表示请求的资源与资源当前的状态发生冲突,401(gone)表示服务器上的资源被永久的删除。
4️⃣
- host头处理。 在HTTP1.0中认为每一个服务器都绑定一个ip地址,因此请求消息头中并没有传递主机名(hostname)。
但是随着虚拟主机技术的发展,在一台物理服务器上可以存在多台虚拟主机,并且他们共享一个ip地址。
HTTP1.1中请求和响应都支持host头域,且请求消息中如果没有host头域会报错(400bad request)
5️⃣
- 长链接
HTTP1.1支持长链接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立多个连接的消耗和延迟。在HTTP1.1中默认打开onnection:keep-alive,在一定程度上弥补了http1.0每次请求都要重新连接的缺陷。
SPDY
如果浏览器是 Google 出品的,它不会使用 HTTP 协议来获取页面信息,而是会与服务器端发送请求,商讨使用 SPDY 协议。
SPDY(读作“SPeeDY”)是Google开发的基于TCP的会话层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。
SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。
SPDY是HTTP/2的前身。2015年9月,Google宣布移除对SPDY的支持,拥抱HTTP/2。
http1.x版本问题
在传输数据过程中,所有内容都是明文,客户端和服务器端都无法验证对方的身份,无法保证数据的安全性。
HTTP/1.1 版本默认允许复用TCP连接,但是在同一个TCP连接里,所有数据通信是按次序进行的,服务器通常在处理完一个回应后,才会继续去处理下一个,这样子就会造成队头阻塞。
http/1.x 版本支持Keep-alive,用此方案来弥补创建多次连接产生的延迟,但是同样会给服务器带来压力,并且的话,对于单文件被不断请求的服务,Keep-alive会极大影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。
HTTP2.0和HTTP1.X相比的新特性
- 二进制分帧 这是一次彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧":头信息帧和数据帧。
- 头部压缩 HTTP 1.1版本会出现 User-Agent、Cookie、Accept、Server、Range
等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用 HPACK 算法进行压缩。 - 多路复用 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。
- 服务器推送 允许服务器未经请求,主动向客户端发送资源,即服务器推送。
- 请求优先级 可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。
HTTP 2
HTTP/2.0
的目标是异步连接多路复用、头部压缩、请求/响应管线化,保持与HTTP 1.1
语义的向后兼容性也是该版本的一个关键目标。HTTP实现的瓶颈之一是其并发要依赖于多重连接。HTTP管线化技术可以缓解这个问题,但也只能做到部分多路复用。此外,已经证实,由于存在中间干扰,浏览器无法采用管线化技术。
在开放互联网上HTTP 2.0将只用于https://
网址,而 http://
网址将继续使用HTTP/1.1
,目的是在开放互联网上增加使用加密技术,以提供强有力的保护去遏制主动攻击。
HTTP/2的特性
二进制格式
HTTP/2 采用二进制格式传输数据,而非HTTP/1.1的文本格式;
二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
HTTP/2基本概念 - 数据流、消息、帧
数据流:已建立的连接内的双向字节流,可以承载一条或多条消息
- 所有通信都在一个TCP连接上完成,此连接可以承载任意数量的双向数据流
消息:与逻辑HTTP请求或响应消息对应,由一系列帧组成
帧:HTTP/2通信的最小单位,每个帧都包含帧头(会标识出当前帧所属的数据流)
- 来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装
HTTP/2的特性 - 多路复用(Multiplexing)
多路复用基本原理以及解决的问题
HTTP/2解决的问题,就是HTTP/1.1存在的问题:
- TCP慢启动: TCP连接建立后,会经历一个先慢后快的发送过程,就像汽车启动一般,如果我们的网页文件(HTML/JS/CSS/icon)都经过一次慢启动,对性能是不小的损耗。另外慢启动是TCP为了减少网络拥塞的一种策略,我们是没有办法改变的。
- 多条TCP连接竞争带宽: 如果同时建立多条TCP连接,当带宽不足时就会竞争带宽,影响关键资源的下载。
- HTTP/1.1队头阻塞: 尽管HTTP/1.1长链接可以通过一个TCP连接传输多个请求,但同一时刻只能处理一个请求,当前请求未结束前,其他请求只能处于阻塞状态。
为了解决以上几个问题,HTTP/2一个域名只使用一个TCP⻓连接来传输数据,而且请求直接是并行的、非阻塞的,这就是多路复用
实现原理: HTTP/2引入了一个二进制分帧层,客户端和服务端进行传输时,数据会先经过二进制分帧层处理,转化为一个个带有请求ID的帧,这些帧在传输完成后根据ID组合成对应的数据。
客户端和服务器可以将 HTTP消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来
- 并行交错地发送多个请求,请求之间互不影响
- 并行交错地发送多个响应,响应之间互不干扰
- 使用一个连接并行发送多个请求和响应
不必再为绕过HTTP/1.1限制而做很多工作
- 比如精灵图 (image sprites)、合并CSS\JS、内嵌CSS\JS\Base64图片、域名分片等
精灵图 (image sprites)
image sprites(也叫做CSS Sprites),将多张小图合并成一张大图
最后通过CSS结合小图的位置、尺寸进行精准定位
HTTP/2的特性 - 优先级
HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系
- 可以向每个数据流分配一个介于1至256之间的整数
- 每个数据流与其他数据流之间可以存在显式依赖关系
客户端可以构建和传递 “优先级树”,表明它倾向于如何接收响应
服务器可以使用此信息通过控制CPU、内存和其他资源的分配设定数据流处理的优先级
- 在资源数据可用之后,确保将高优先级响应以最优方式传输至客户端
HTTP/2的特性 - 头部压缩
早期版本的 HTTP/2 和 SPDY 使用 zlib 压缩请求头和响应头
可以将所传输头数据的大小减小85%~88%
但在2012年夏天,被攻击导致会话劫持
后被更 安全的 HPACK 取代
目前,HTTP/2使用 HPACK 压缩请求头和响应头
- 可以极大减少头部开销,进而提高性能
HTTP/2的特性 - 服务器推送(Server Push)
服务器可以对一个客户端请求发送多个响应
- 除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端额外明确地请求
HTTP/2的问题 - 队头阻塞(head of line blocking)
后面 HTTP/3 提出的解决方案是 QUIC…
HTTP/2的问题 - 握手延迟
RTT (Round Trip Time):往返时延,可以简单理解为通信一来一回的时间
HTTP 3
- QUIC“快速UDP互联网连接”(Quick UDP Internet Connections)
- 通过高链接利用效率减少RTT,提高数据交互速度
- 在高效的基础上,保证安全需求
- 解决当前实际网络环境中的适配问题
HTTP 3
的主要改进在传输层上。传输层不会再有繁重的 TCP 连接了。现在,一切都会走 UDP。
互联网通信发展史其实是人类与RTT斗争的历史, RTT是 Round Trip Time的缩写,通俗地说,就是通信一来一回的时间。
TCP建立连接需要 1.5RTT
HTTP交互一次需要 1RTT
那么基于TCP传输的HTTP通信,一共花费的时间总和: 2.5 RTT
基于TLS的HTTPS请求在TCP握手阶段多了四次,所以连接时间是 4.5RTT
在没有持久连接之前,请求一个有图片的页面需要建立两次TCP连接就是 9RTT
可以重用TCP连接后多次请求时间就减少了 4.5RTT
Google开发的QUIC协议集成了TCP可靠传输机制、TLS安全加密、HTTP /2 流量复用技术,其页面的加载时间为 2.5RTT,重连要等待的时间是 1RTT
HTTP 3
基于QUIC,整体页面加载时间为 2RTT
HTTP协议
超文本传输协议(HTTP):
HTTP是应用层协议,是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。设计HTTP最初的目的是提供一种发布和接收HTML页面的方法,由URI来标识具体的资源,后面用HTTP来传递的数据格式不仅仅是HTML,应用非常广泛。
支持的方法
HTTP1.0定义了三种请求方法,GET,POST和HEAD方法
HTTP1.1新增六种请求方法:OPTIONS,PUT,PATCH,DELETE,TRACH和CONNECT
请求方法:
- GET:获取资源
- POST:传输资源
- PUT:更新资源
- DELETE:删除资源
- HEAD:获得报文首部
- OPTIONS:返回支持的请求方法
- TRACE:追踪路径
方法 | 作用 |
get | 请求指定的页面信息并返回响应主体,一般用于数据的读取 |
post | 向指定资源提交数据,请求服务器去处理 |
head | 获取服务器的响应头信息,常用于客户端查看服务器的性能 |
options | 请求服务器返回该资源所支持的所有HTTP请求方法,常用于客户端查看服务器的性能 |
put | 向指定资源位置上传其最新内容 |
delete | 请求服务器删除所请求URI所标识的资源 |
connect | 将连接改为管道方式的代理服务器,常用于SSL加密服务器与非加密的HTTP代理服务器的通信 |
trace | 请求服务器回显其收到的请求信息,常用于HTTP请求的测试或诊断 |
特点
- 简单快速 客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活可扩展:一个是语法上只规定了基本格式,空格分隔单词,换行分隔字段等。另外一个就是传输形式上不仅可以传输文本,还可以传输图片,视频等任意数据。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。连接一次就会断开
- 请求-应答模式,通常而言,就是一方发送消息,另外一方要接受消息,或者是做出响应等。
- 可靠传输,HTTP是基于TCP/IP,因此把这一特性继承了下来。
怎么解决HTTP协议无状态协议?Cookie
为了解决HTTP协议不能保存通信状态的问题,引入了Cookie状态管理。
Cookie技术通过在请求和响应报文中写入Cookie信息来控制客户端的状态。
Cookie会根据从服务端发送的响应报文的一个叫Set-Cookie的首部字段,通知客户端保存Cookie。
当下次客户端再往该服务端发送请求时,客户端会自动在请求报文中加入Cookie值发送出去,服务端发现客户端发来的Cookie后,会检查是哪一个客户端发来的连接请求,对比服务器上的记录,最后得到之前的状态信息。
HTTP报文格式
http报文包括:请求报文和响应报文。
请求报文= 请求行+请求头+空行+请求体
(1)请求行:包含请求方法,请求url,http协议及版本。描述请求或响应的基本信息
(2)请求头:key,value值,告诉服务端我要哪些内容。使用key-value形式更详细地说明报文
(3)空行:分隔请求头、请求体。
(4)请求体:实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据
响应报文=状态行+响应头+空行+响应体
状态行:报文协议及版本、状态码及状态描述。
HTTP头字段
头部字段是key-value
的形式,key和value之间用“:”分隔,最后用CRLF换行表示字段结束。
之前说到的HTTP缓存机制就说到了几个头字段,如:Cache-Control: max-age=600
这里的key就是Cache-Control
,value就是max-age=600
。
HTTP头字段非常灵活,不仅可以使用标准里的Host、Connection
等已有头,也可以任意添加自定义头,这就给HTTP协议带来了无限的拓展可能。
头部字段注意事项
- 字段名不区分大小写,字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值可以有多个空格。
- 字段的顺序是没有意义的,可以任意排列不影响语义。
- 字段原则上不能重复,除非这个字段本身的语义允许,例如:
Set-Cookie
。
常用头字段
HTTP协议中有非常多的头字段,但基本上可以分为四大类:
- 请求字段:请求头中的头字段,如:
Host
、Referer
。 - 响应字段:响应头中的头字段,如:
Server
、Date
。 - 通用字段:在请求头和响应头里都可以出现,如:
Content-type
、Connection
。
总结来说,只要符合上述条件的一段字符串,都可以作为请求报文或者响应报文被TCP连接发送出去。浏览器和HTTP服务器就是对HTTP协议的实现与应用的技术,比如之前说到的浏览器缓存机制就是HTTP缓存机制,因为浏览器对HTTP协议中缓存相关字段完成了技术上的实现,所以两者是一个概念。
介绍一下Connection:keep-alive
什么是keep-alive
我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);
当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
为什么要使用keep-alive
keep-alive技术的创建目的,能在多次HTTP之前重用同一个TCP连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等)
get post
GET 用于向服务器查询信息,POST 用于向服务器提交应该保存的数据。
从原理性看:
根据HTTP规范,GET用于信息获取,而且应该是安全和幂等的
根据HTTP规范,POST请求表示可能修改服务器上资源的请求
从表面上看:
GET请求的数据会附在URL后面,POST的数据放在HTTP包体
POST安全性比GET安全性高
1、GET参数通过URL传递,POST放在Request body中。
2、GET请求会被浏览器主动cache,而POST不会,除非手动设置。
3、GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
4、Get 请求中有非 ASCII 字符,会在请求之前进行转码,POST不用,因为POST在Request body中,通过 MIME,也就可以传输非 ASCII 字符。
5、 一般我们在浏览器输入一个网址访问网站都是GET请求
6、HTTP的底层是TCP/IP。HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。但是请求的数据量太大对浏览器和服务器都是很大负担。所以业界有了不成文规定,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。
7、GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
8、在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。但并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
传递数据的最大长度
GET 是通过URL提交数据,因此GET可提交的数据量就跟URL所能达到的最大长度有直接关系。
POST理论上讲是没有大小限制的,HTTP协议规范也没有进行大小限制,但实际上POST所能传递的数据量大小取决于服务器的设置和内存大小。
PUT和POST区别
PUT是幂等的,POST不是。
幂等是数学的一个用语,对于单个输入或者无输入的运算方法,如果每次都是同样的结果,则称其是幂等的。也就是说,如果一个网络重复执行多次,产生的效果是一样的,那就是幂等(idempotent)。
PUT请求:如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)
POST请求:后一个请求不会把第一个请求覆盖掉。(所以POST用来增资源)
状态码
按第一个数字分类:1表示信息,2表示成功,3表示重定向,4表示客户端错误,5表示服务器错误
状态码 | 含义 |
200 OK | 请求成功。一般用于get和post请求 |
301 Moved Permanently | 永久移动。请求的信息已经被移动到新的URI,会返回新的URI |
302 Found | 临时移动。资源只是临时被移动,客户端继续使用原URI |
304 Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码,不会返回任何资源 |
400 Bad Request | 客户端请求的语法错误,服务器无法理解(产生原因:前端提交的数据在后台找不到与之相对应的实体) |
401 Unauthorized | 当前请求需要用户验证 |
403 Forbidden | 服务器已经收到请求,但拒绝执行 |
404 Not Found | 服务器无法根据用户的请求找到资源 |
500 Internal Server Error | 服务器内部错误,无法完成请求 |
1xx 信息类
接受的请求正在处理,信息类状态码。
2xx 成功
- 200 OK 表示从客户端发来的请求在服务器端被正确请求。
- 204 No content,表示请求成功,但没有资源可返回。
- 206 Partial Content,该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求响应报文中包含由 Content-Range 指定范围的实体内容。
3xx 重定向
- 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL,这时应该按 Location 首部字段提示的 URI 重新保存。
- 302 found,临时性重定向,表示资源临时被分配了新的 URL。
- 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源。
- 304 not modified,当协商缓存命中时会返回这个状态码。
- 307 temporary redirect,临时重定向,和302含义相同,不会改变method
当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET,并删除请求报文内的主体,之后请求会自动再次发送301、302 标准是禁止将 POST 方法改变成 GET 方法的,但实际使用时大家都会这么做
4XX 客户端错误
- 400 bad request,请求报文存在语法错误。
- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息。
- 403 forbidden,表示对请求资源的访问被服务器拒绝。
- 404 not found,表示在服务器上没有找到请求的资源。
- 405 Method Not Allowed,服务器禁止使用该方法,客户端可以通过options方法来查看服务器允许的访问方法,如下 👇
Access-Control-Allow-Methods →GET,HEAD,PUT,PATCH,POST,DELETE
5XX 服务器错误
- 500 internal sever error,表示服务器端在执行请求时发生了错误。
- 502 Bad Gateway,服务器自身是正常的,访问的时候出了问题,具体啥错误我们不知道。
- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求。
接下来,我们数据拿到了,你认为就会断开TCP连接吗?
这个的看响应头中的Connection字段。上面的字段值为close,那么就会断开,一般情况下,HTTP1.1版本的话,通常请求头会包含「Connection: Keep-Alive」表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。
上面的情况就会断开TCP连接,请求-响应流程结束。
到这里的话,网络请求就告一段落了,接下来的内容就是渲染流程了👇
浏览器解析渲染页面
构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制
浏览器渲染原理:
当我们在浏览器地址输入URL时,浏览器会发送请求到服务器,服务器将请求的HTML文档发送回浏览器,浏览器将文档下载下来后,便开始从上到下解析,解析完成之后,会生成DOM。如果页面中有css,会根据css的内容形成CSSOM,然后DOM和CSSOM会生成一个渲染树,最后浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其绘制在浏览器上。
css文件的加载是与DOM的加载并行的
也就是说,css在加载时Dom还在继续加载构建,而过程中遇到的css样式或者img,则会向服务器发送一个请求,待资源返回后,将其添加到dom中的相对应位置中;
由于js文件不会与DOM并行加载,因此需要等待js整个文件加载完之后才能继续DOM的加载
解决方法:
前提,js是外部脚本;
- 在scirpt标签中添加 async=“ture”,这个属性告诉浏览器该js文件是异步加载执行的,也就是不依赖于其他js和css,也就是说无法保证js文件的加载顺序,但是同样有与DOM并行加载的效果;
- 在script标签中添加 defer=“ture”,则会让js与DOM并行加载,待页面加载完成后再执行js文件,这样则不存在阻塞;
- 解析HTML成DOM树
- 解析HTML文档并将HTML标签解析为DOM节点生成内容树。DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素。渲染树不包括 head 和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性
- 构建CSSOM
- 诸如图片,CSS,JS 等额外的资源,这些资源需要从网络上或者 cache 中获取。主进程可以在构建 DOM 的过程中会逐一请求它们
- 节点中样式可以通过继承得到,也可以自己设置,因此在构建的过程中浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。为了CSSOM的完整性,也只有等构建完毕才能进入到下一个阶段,哪怕DOM已经构建完,它也得等CSSOM,然后才能进入下一个阶段。
CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去 所以,CSS的加载速度与构建CSSOM的速度将直接影响首屏渲染速度,因此在默认情况下CSS被视为阻塞渲染的资源
- 构建渲染树(Render Tree)
当我们生成DOM树和CSSOM树后,我们需要将这两颗树合并成渲染树,在构建渲染树的过程中浏览器需要做如下工作:
- 从 DOM 树的根节点开始遍历每个可见节点。
- 有些节点不可见(例如脚本Token、元Token等),因为它们不会体现在渲染输出中,所以会被忽略。
- 某些节点被CSS隐藏,因此在渲染树中也会被忽略。例如某些节点设置了display: none属性。
- 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们
- 渲染阻塞
- JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,遇到 <script> 标签时,渲染进程会停止解析 HTML,而去加载,解析和执行 JS 代码,停止解析 html 的原因在于 JS 可能会改变 DOM 的结构(使用诸如 documwnt.write()等API),等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
- 也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。如果在<script> 标签上添加了 async 或 defer 等属性,浏览器会异步的加载和执行JS代码,而不会阻塞渲染
- 因此script的位置很重要,在实际使用过程中遵循以下两个原则:
- CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
- JS置后:我们通常把JS代码放到页面底部,且JavaScript 应尽量少影响 DOM 的构建
优化方案:
移除js和css的文件下载:移除内联 JavaScript、内联 CSS;
尽量减少文件大小:如通过 webpack 等工具移除不必要的注释,并压缩 js 文件;
将不进行DOM操作或CSS样式修改的 JavaScript 标记上 async 或者 defer异步引入;
- 样式计算 主进程还会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值。即使不提供任何 CSS,浏览器对每个元素也会有一个默认的样式。
- 布局与绘制
- 浏览器拿到渲染树后,就会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,通常这一行为也被称为“自动重排”。布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对测量值都将转换为屏幕上的绝对像素。这一过程也可称为回流
- 布局完成后,浏览器会立即发出Paint Setup和Paint事件,将渲染树转换成屏幕上的像素。
- 合成帧
HTTPS
HTTPS (HyperText Transfer Protocol Secure),译为:超文本传输安全协议
- HTTPS的默认端口号是 443 (HTTP是80)
需要注意的是HTTPS协议并没有对HTTP做多大改变,重点在于TCP三次握手时加上了TLS握手。
HTTP的缺点有哪些?
- 使用明文进行通信,内容可能会被窃听;
- 不验证通信方的身份,通信方的身份有可能遭遇伪装;
- 无法证明报文的完整性,报文有可能遭篡改。
SSL / TLS
HTTPS 是在 HTTP 的基础上使用 SSL/TLS
来加密报文,对窃听和中间人攻击提供合理的防护。
TLS (Transport Layer Security),译为:传输层安全性协议
- 前身是 SSL (Secure Sockets Layer),译为:安全套接层
SSL/TLS工作在哪一层?
OpenSSL
OpenSSL 是SSL/TLS协议的开源实现
常用命令
- 生成私钥:
openssl genrsa -out mj.key
- 生成公钥:
openssl rsa -in mj.key -pubout -out mj.pem
可以使用 OpenSSL 构建一套属于自己的CA,自己给自己颁发证书,称为“自签名证书”
HTTPS的成本
- 证书的费用
- 加解密计算
- 降低了访问速度
有些企业的做法是:包含敏感数据的请求才使用HTTPS,其他保持使用HTTP。
HTTPS是如何进行加密的
我们通过分析几种加密方式,层层递进,理解HTTPS的加密方式以及为什么使用这种加密方式:
对称加密
客户端和服务器公用一个密匙用来对消息加解密,这种方式称为对称加密。客户端和服务器约定好一个加密的密匙。客户端在发消息前用该密匙对消息加密,发送给服务器后,服务器再用该密匙进行解密拿到消息。
这种方式一定程度上保证了数据的安全性,但密钥一旦泄露(密钥在传输过程中被截获),传输内容就会暴露,因此我们要寻找一种安全传递密钥的方法。
非对称加密
采用非对称加密时,客户端和服务端均拥有一个公钥和私钥,公钥加密的内容只有对应的私钥能解密。私钥自己留着,公钥发给对方。这样在发送消息前,先用对方的公钥对消息进行加密,收到后再用自己的私钥进行解密。这样攻击者只拿到传输过程中的公钥也无法破解传输的内容
尽管非对称加密解决了由于密钥被获取而导致传输内容泄露的问题,但中间人仍然可以用篡改公钥的方式来获取或篡改传输内容,而且非对称加密的性能比对称加密的性能差了不少
第三方认证
上面这种方法的弱点在于,客户端不知道公钥是由服务端返回,还是中间人返回的,因此我们再引入一个第三方认证的环节:即第三方使用私钥加密我们自己的公钥,浏览器已经内置一些权威第三方认证机构的公钥,浏览器会使用第三方的公钥来解开第三方私钥加密过的我们自己的公钥,从而获取公钥,如果能成功解密,就说明获取到的自己的公钥是正确的
但第三方认证也未能完全解决问题,第三方认证是面向所有人的,中间人也能申请证书,如果中间人使用自己的证书掉包原证书,客户端还是无法确认公钥的真伪
数字签名
为了让客户端能够验证公钥的来源,我们给公钥加上一个数字签名,这个数字签名是由企业、网站等各种信息和公钥经过单向hash而来,一旦构成数字签名的信息发生变化,hash值就会改变,这就构成了公钥来源的唯一标识。
具体来说,服务端本地生成一对密钥,然后拿着公钥以及企业、网站等各种信息到CA(第三方认证中心)去申请数字证书,CA会通过一种单向hash算法(比如MD5),生成一串摘要,这串摘要就是这堆信息的唯一标识,然后CA还会使用自己的私钥对摘要进行加密,连同我们自己服务器的公钥一同发送给我我们。
浏览器拿到数字签名后,会使用浏览器本地内置的CA公钥解开数字证书并验证,从而拿到正确的公钥。由于非对称加密性能低下,拿到公钥以后,客户端会随机生成一个对称密钥,使用这个公钥加密并发送给服务端,服务端用自己的私钥解开对称密钥,此后的加密连接就通过这个对称密钥进行对称加密。
综上所述,HTTPS在验证阶段使用非对称加密+第三方认证+数字签名获取正确的公钥,获取到正确的公钥后以对称加密的方式通信
HTTPS 默认工作在 TCP 协议443端口,它的工作流程一般如以下方式:
1、TCP 三次同步握手
2、客户端验证服务器数字证书
3、DH 算法协商对称加密算法的密钥、hash 算法的密钥
4、SSL 安全加密隧道协商完成
5、网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。
SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
但是,这里有两个问题。
(1)如何保证公钥不被篡改?
解决方法:将公钥放在 数字证书中。只要证书是可信的,公钥就是可信的。
(2)公钥加密计算量太大,如何减少耗用的时间?
解决方法:每一次对话(session),客户端和服务器端都生成一个"对话密钥"(session key),用它来加密信息。由于"对话密钥"是对称加密,所以运算速度非常快,而服务器公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。
因此,SSL/TLS协议的基本过程总的可以分为3大阶段
- TCP的3次握手
- TLS的连接
- 客户端向服务器端索要并验证公钥
- 双方协商生成"对话密钥"
- 双方采用"对话密钥"进行加密的HTTP通信
HTTPS 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。
确保传输安全过程(其实就是rsa原理):
- Client给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
- Server确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
- Client确认数字证书有效,然后生成呀一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给Server。
- Server使用自己的私钥,获取Client发来的随机数(Premaster secret)。
- Client和Server根据约定的加密方法,使用前面的三个随机数,生成”对话密钥”(session key),用来加密接下来的整个对话过程。
持久连接与非持久连接
其实TCP连接经过三次握手后就可以正常发送HTTP请求了,但是我们再想一个问题:
每发送一个HTTP请求就要建立一个TCP连接吗?
当然,答案是否定的。
HTTP存在持久连接与非持久连接两种状态。
- 非持久连接:
HTTP/1.0
中 的首部字段Connection
默认值为close
,即每次请求都会重新建立和断开 TCP 连接。 - 持久连接:
HTTP/1.1
中 的首部字段Connection
默认值为keep-alive
,连接可以复用,只要发送端、接收端都没有提出断开连接,则保持TCP连接状态。
HTTP1.1
中,所有的连接默认为持久连接,但在HTTP1.0
中并未标准化,即使有部分的服务器通过非标准化的手段实现了持久连接,但是服务器端不一定支持持久连接。
持久连接的优点: 减少了TCP连接的重复建立和断开所造成的额外开销,减轻了服务器端的负担,其中减少开销的这部分时间实际上也使HTTP请求和响应更早的结束,提高了web页面的显示速度。
注意是否持久连接不是TCP协议决定的而是上层的HTTP协议去控制的TCP断开的时机实现的。
并发请求
既然实现了TCP持久连接那我们再思考一个问题:
TCP 连接中多个 HTTP 请求可以并行发送吗?
在HTTP/1.1
中,单个 TCP 连接在同一时刻只能处理一个请求,即两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。上一个请求得到响应之后,才能发送下一个请求。
但是我们依然有方法实现并发请求:
管线化技术的出现,实现了同时发送多个HTTP请求,不必等待上一请求返回响应,但是浏览器默认关闭管线化,原因如下:管线化(pipelining)- 一些代理服务器不能正确的处理
HTTP Pipelining
Head-of-line Blocking
连接头阻塞:在建立起一个 TCP 连接之后,假设客户端在这个连接连续向服务器发送了多个请求。如果按照标准的话,服务器应该按照收到请求的顺序返回结果,假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应,造成了阻塞。
- 一些代理服务器不能正确的处理
- 多路复用(Multiplexing)
因为HTTP/1.1
中的管线化实际上无法使用,因此在HTTP/2.0
中出现了Multiplexing
多路传输特性
- 在
HTTP/2.0
中,有两个非常重要的概念,分别是帧(frame)和流(stream),帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 - 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。
- 并行交错地发送多个请求,请求之间互不影响
- 并行交错地发送多个响应,响应之间互不干扰
- 使用一个连接并行发送多个请求和响应
HTTP与TCP的关系
HTTP 与 HTTPS 区别
HTTPS=HTTP+加密+认证+完整性保护
HTTPS通过SSL证书验证通信方的身份,并为浏览器和服务器之间的通信进行加密。
- HTTP直接和TCP通信,当使用SSL时,则HTTP先和SSL通信,再由SSL和TCP通信。
- HTTP 明文传输,数据都是未加密的,连接无状态,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 是建构在 SSL/TLS 之上的 HTTP 协议,所以 HTTPS 更耗费服务器资源
- Https 通信会由于加减密处理消耗更多的 CPU 和内存资源;
- http:无状态,无连接,基于请求和响应,通信使用明文,请求和响应不会对通信方进行确认,无法保证数据的完整性,
https:基于http协议,通过SSL提供加密数据(采用混合加密,中间者无法查看明文内容),验证身份及数据完整性保护(防止传输的内容被篡改)
其实到这里我们可以看出来了,HTTP协议是最上层的应用层协议,它所规定的东西都是针对软件需要实现的,它不需要关注数据在网络中是怎么传输以及怎么确保可靠性与网络拥塞,因为这些问题都由下层TCP去解决,之前我们也提到,TCP有发送队列和接收队列,那么HTTP只需要去操作这两个队列就可以发送或者获取自己想要的数据。TCP三次握手建立的连接并不是真实的物理连接,而是虚连接,连接的本质就是在客户端与服务端开辟本次连接所需要的资源(内存、进程等)。调用Socket利用TCP通过三次握手连接建立后,之前准备好的HTTP请求报文被送入发送队列,接下来就交给了TCP完成后续过程。
所以纵观整个网络分层结构,HTTP与TCP之间的协作如此,TCP与IP之间也是如此,下层为上层提供服务,上层向下层传递包装好的数据,各层次只与目标对应层次相互通讯,对于当前层来说,上层传递下来的数据它是不关心的,也不会解析。
总结
现在再回头看一下文章开头的那张总结图,是不是能理解了网络通讯的过程,还有浏览器到底在这期间做了哪些事情。其实TCP下层的种种协议都已经在操作系统层面实现了,我们能操作的就是TCP以及上层协议,那么关于前端的种种网络优化就从socket开始,不管是缓存的使用还是持久连接,优化的目的也只有一个,就是减小网络延迟。弄清楚这些,可以让我们对底层知识有更全面的认识,从而加深对HTTP的理解与其发展方向的认知。
跨域
两个页面地址中的协议,域名,端口号一致,则表示同源。
同源策略是一种安全协议。指一段脚本只能读取来自同一来源的窗口和文档的属性。
同源策略的限制:不能通过ajax请求不同域的数据,不能通过脚本操作不同域下的DOM,
为什么要用同源策略
设置同源限制主要是为了安全,如果没有同源限制存在浏览器中的cookie等其他数据可以任意读取,不同域下DOM任意操作,ajax任意请求的话如果浏览了恶意网站那么就会泄漏这些隐私数据
违反浏览器同源策略的就是跨域。跨域本身就是就是为了保护浏览器的安全, 主要是用来防止 CSRF 攻击的
如何解决跨域问题
JSONP、CORS、通过修改document.domain来跨子域、使用window.name来进行跨域、HTML5中新引进的window.postMessage方法、在服务器上设置代理页面
跨域的方法很多,不同的应用场景我们都可以找到一个最合适的解决方案。比如单向的数据请求,我们应该优先选择JSONP或者window.name,双向通信优先采取location.hash,在未与数据提供方达成通信协议的情况下我们也可以用server proxy的方式来抓取数据。
单向跨域
一般用于获取数据
JSONP
原理是:
通过<script>标签的异步加载来实现的。
优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。
为什么script标签引入的文件不受同源策略的限制?因为script标签引入的文件内容是不能够被客户端的js获取到的,不会影响到被引用文件的安全,所以没必要使script标签引入的文件遵循浏览器的同源策略。而通过ajax加载的文件内容是能够被客户端js获取到的,所以ajax必须遵循同源策略,否则被引入文件的内容会泄漏或者存在其他风险。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求(虽然采用post+动态生成iframe是可以达到post跨域的目的,但这样做是一个比较极端的方式,不建议采用)。一般get请求能完成所有功能。比如如果需要给其他域服务器传送参数可以在请求后挂参数(注意不要挂隐私数据),即
<script src="http://www.google.com/getUsers.php?flag=do&time=1"></script>。 |
JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。可以看出来JSONP跨域一般用于获取其他域的数据。
一般能够用JSONP实现跨域就用JSONP实现,这也是前端用的最多的跨域方法。
CORS
此跨域方法目前只在很少的浏览器中得以支持,这些浏览器可以发送一个跨域的HTTP请求(Firefox, Google Chrome等通过XMLHTTPRequest实现,IE8下通过XDomainRequest实现),请求的响应必须包含一个Access- Control-Allow-Origin的HTTP响应头,该响应头声明了请求域的可访问权限。例如baidu.com对google.com下的getUsers.php发送了一个跨域的HTTP请求(通过ajax),那么getUsers.php必须加入如下的响应头:
header("Access-Control-Allow-Origin: 百度一下,你就知道");//表示允许baidu.com跨域请求本文件 |
CORS 背后的基本思路就是
使用自定义的 HTTP 头部允许浏览器和服务器相互了解,以确定请求或响应应该成功还是失败
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
CORS的简单请求和非简单请求:
简单请求就是使用设定的请求方式请求数据
而非简单请求则是在使用设定的请求方式请求数据之前,先发送一个OPTIONS请求,看服务端是否允许客户端发送非简单请求.
只有"预检"通过后才会再发送一次请求用于数据传输
只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD,GET,POST
(2)Content-Type 的值仅限于下列三者之一👇
text/plain
multipart/form-data
application/x-www-form-urlencoded
「与JSONP对比」
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
- JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)
使用window.name来进行跨域
window 对象的name属性是一个很特别的属性,当该window的location变化,然后重新加载,它的name属性可以依然保持不变。那么我们可以在页面 A中用iframe加载其他域的页面B,而页面B中用JavaScript把需要传递的数据赋值给window.name,iframe加载完成之后(iframe.onload),页面A修改iframe的地址,将其变成同域的一个地址,然后就可以读出iframe的window.name的值了(因为A中的window.name和iframe中的window.name互相独立的,所以不能直接在A中获取window.name,而要通过iframe获取其window.name)。这个方式非常适合单向的数据请求,而且协议简单、安全。不会像JSONP那样不做限制地执行外部脚本。
服务器代理
在数据提供方没有提供对JSONP协议或者 window.name协议的支持,也没有对其它域开放访问权限时,我们可以通过server proxy的方式来抓取数据。例如当baidu.com域下的页面需要请求google.com下的资源文件getUsers.php时,直接发送一个指向 google.com/getUsers.php的Ajax请求肯定是会被浏览器阻止。这时,我们在baidu.com下配一个代理,然后把Ajax请求绑定到这个代理路径下,例如baidu.com/proxy/, 然后这个代理发送HTTP请求访问google.com下的getUsers.php,跨域的HTTP请求是在服务器端进行的(服务器端没有同源策略限制),客户端并没有产生跨域的Ajax请求。这个跨域方式不需要和目标资源签订协议,带有侵略性。
双向跨域
两个iframe之间或者两个页面之间,一般用于获取对方数据,document.domain方式还可以直接操作对方DOM
通过修改document.domain来跨子域
将子域和主域的document.domain设为同一个主域.前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域
主域相同的使用document.domain
问题:
- 安全性,当一个站点被攻击后,另一个站点会引起安全漏洞。
- 如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。
location.hash(两个iframe之间),又称FIM,Fragment Identitier Messaging的简写
因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他 window的location来发送消息(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe),并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,这样做也存在缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等。下面举例说明:
假如父页面是baidu.com/a.html,iframe嵌入的页面为google.com/b.html(此处省略了域名等url属性),要实现此两个页面间的通信可以通过以下方法
(1) a.html传送数据到b.html
- a.html下修改iframe的src为google.com/b.html#paco
- b.html监听到url发生变化,触发相应操作
(2) b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe
- b.html下创建一个隐藏的iframe,此iframe的src是baidu.com域下的,并挂上要传送的hash数据,如src="http://www.baidu.com/proxy.html#data"
- proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
- a.html监听到url发生变化,触发相应操作
b.html页面的关键代码如下
try { parent.location.hash = 'data'; } catch (e) { // ie、chrome的安全机制无法修改parent.location.hash, var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = "http://www.baidu.com/proxy.html#data"; document.body.appendChild(ifrproxy); } |
proxy.html页面的关键代码如下
//因为parent.parent(即baidu.com/a.html)和baidu.com/proxy.html属于同一个域,所以可以改变其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1); |
使用HTML5的postMessage方法(两个iframe之间或者两个页面之间)
高级浏览器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都将支持这个功能。这个功能主要包括接受信息的"message"事件和发送消息的"postMessage"方法。比如baidu.com域的A页面通过iframe嵌入了一个google.com域的B页面,可以通过以下方法实现A和B的通信。
A页面通过postMessage方法发送消息:
window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = "http://www.google.com"; ifr.contentWindow.postMessage('hello world!', targetOrigin); }; |
postMessage的使用方法:
otherWindow.postMessage(message, targetOrigin); |
otherWindow: 指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
targetOrigin: 是限定消息接收范围,不限制请使用 '*'
B页面通过message事件监听并接受消息:
var onmessage = function (event) { var data = event.data;//消息 var origin = event.origin;//消息来源地址 var source = event.source;//源Window对象 if(origin=="百度一下,你就知道"){ console.log(data);//hello world! } }; if (typeof window.addEventListener != 'undefined') { window.addEventListener('message', onmessage, false); } else if (typeof window.attachEvent != 'undefined') { //for ie window.attachEvent('onmessage', onmessage); } |
同理,也可以B页面发送消息,然后A页面监听并接受消息。
跨域的方法很多,不同的应用场景我们都可以找到一个最合适的解决方案。比如单向的数据请求,我们应该优先选择JSONP或者window.name,双向通信优先采取location.hash,在未与数据提供方达成通信协议的情况下我们也可以用server proxy的方式来抓取数据。
面试题
进程的三种状态
- 就绪(Ready)状态
进程已分配到除CPU以外的所有必要资源,只要获得处理机便可立即执行。
- 执行(Running)状态
进程已获得处理机,其程序正在处理机上执行。
- 阻塞(Blocked)状态
正在执行的程序,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的原因可能是等待I/O完成、申请缓冲区不能满足、等待信号等。
Q1:为什么在转换图中没有就绪到阻塞和阻塞到执行的转换方向?
就绪状态进程没有占有处理机,即不经过执行,其状态就不会改变;阻塞状态进程唤醒后要先进入到就绪队列,才会被调度程序选中,进行执行状态
进程间通信方式
一、进程间通信的方式
- 无名管道:半双工通信方式,数据只能单向流动且只能在有亲缘关系的进程间使用
- 有名管道:半双工通信方式,允许在非亲缘关系的进程间使用
- 信号:通知接收进程某个事件已发生
- 消息队列:传递消息的链表,存放在内核中。克服了信号传输信息少,管道只能传输无格式字节流以及缓冲区大小受限的缺点
- 信号量:一个计数器,用来控制多个进程对共享资源的访问。常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源
- 共享内存:映射一份能被其他进程所访问的内存,这份内存由一个进程创建但其他进程可以访问
- 套接字:不同机器之间的进程通信
二、共享内存有什么缺点?如何解决?
共享内存不提供同步机制,在使用共享内存进行通信时需要借助其他手段来进行进程间的同步工作,常与信号量一起使用实现同步对共享内存的访问。
进程与线程
一、进程与线程的区别
- 进程是具有一定功能的程序,是系统进行资源分配调度的一个独立单位。
- 线程是进程的一个实体,是CPU调度分配的基本单位,线程之间基本上不拥有系统资源。
- 一个程序至少有一个进程,一个进程至少有一个线程,资源分配给进程,同一个进程下所有线程共享该进程的资源。
二、线程哪些资源共享?哪些资源不共享?
共享:堆、全局变量、静态变量、文件等共用资源
独享:栈、寄存器
并发与并行
并发:逻辑上的同时发生,强调有处理多个任务的能力但不一定要同时
并行:物理上的同时发生,强调有同时处理多个任务的能力
打个比方,并发相当于一个人吃三个馒头,而并行相当于三个人吃三个馒头。而Erlang之父Joe Armstrong则用咖啡机和等待取咖啡的人来类比:并发是两条队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机。
与可以一起出发的并发(concurrent)相对的是不可以一起出发的顺序(sequential)
- 顺序:上一个开始执行的任务完成后,当前任务才能开始执行
- 并发:无论上一个开始执行的任务是否完成,当前任务都可以开始执行
与可以一起执行的并行(parallel)相对的是不可以一起执行的串行(serial)
- 串行:有一个任务执行单元,从物理上就只能一个任务一个任务执行
- 并行:有多个任务执行单元,从物理上就可以多个任务一起执行
网络模型和网络协议
一、OSI七层模型、TCP/IP概念层模型
区别:OSI模型注重通信协议必要的功能是什么,TCP/IP模型更强调在计算机上实现协议应该开发哪种程序。
二、应用层的网络协议
- FTP:文本传输协议
- SMTP:简单邮件传输协议
- TELNET:Internet远程登录服务的标准协议和主要方式
- HTTP:超文本传输协议
- TFTP:简单文件传输协议
- DNS:域名系统
- SNMP:简单网络管理协议
- NFS:网络文件系统
三、数据的单位
- 包:全能性术语
- 帧:数据链路层中包的单位
- 数据包:网络层以上的分层中包的单位
- 段:TCP数据流中的信息
- 消息:应用协议中数据的单位
四、地址
- MAC地址:数据链路中的地址,用来识别同一链路中不同的计算机
- IP地址:IP中的地址,用来识别连接到网络中的主机和路由器
- 程序地址(端口号):识别同一台计算机中进行通信的不同应用程序
TCP握手挥手
TCP连接的建立(三次握手)
最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
- TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
- TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
- TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
- TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
- 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
为什么TCP客户端最后还要发送一次确认呢?
一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
TCP连接的释放(四次挥手)
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。
- 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
- 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
- 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗ *∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
- 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
为什么客户端最后还要等待2MSL?
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
为什么建立连接是三次握手,关闭连接确是四次挥手呢?
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP和UDP
一、TCP和UDP的区别
TCP | UDP |
面向连接的可靠性传输 | 无连接不可靠 |
保证数据的无差错、不丢失、不重传且按序到达 | 尽最大努力交付,但不保证可靠交付 |
面向字节流 | 面向报文 |
只能是一对一 | 一对一或一对多 |
首部较大有20字节 | 首部只有8字节 |
用于需要可靠传输的情况 | 用于高速传输和对实时性有较高要求的通信(视频、音频等多媒体通信)或广播通信 |
TCP特点:流量控制、拥塞控制、面向连接、可靠传输
二、TCP滑动窗口和拥塞窗口比较
- 滑动窗口:发送方+接收方
解决发送方和接收方收发数据速率不一致的问题。滑动窗口相当于接收方的缓存,接收方向发送方通知自己可接受数据的大小,而发送方会根据这个数值发送数据
- 拥塞窗口:发送方
控制全局网络的拥塞情况。通过控制发送方每次发送的流量多少,来逐渐试探整体网络的拥塞程度。
如果没有拥塞控制,发送方每次发送的数据大小为滑动窗口,在只有两台主机的时候没有问题,但在现实的网络大环境中,如果每台主机都发送滑动窗口大小的数据,那整个网络系统必然会瘫痪,所以通过在发送方设置拥塞窗口,可以有效缓解网络压力。
三、TCP拥塞控制
慢启动+拥塞避免;快速重传+快速恢复
- 慢启动:防止一开始速率过快,导致耗尽中间路由器存储空间,从而严重降低TCP连接的吞吐量
- 拥塞避免:当拥塞发生时,降低网络传输速率
- 快速重传:在接收到相同ACK后,推断出丢失报文段起始序号,然后立即重传此报文
- 快速恢复:在快速重传基础上,如果发生了快速重传,则执行拥塞避免算法而非慢启动
HTTPS
一、HTTPS工作原理
- 客户端通过URL发起HTTPS请求,要求服务器建立SSL链接
- 服务器收到客户端的请求后,返回公钥证书
- 客户端验证公钥证书是否有效,验证不通过则显示警告信息;验证通过则利用伪随机数生成器生成会话密钥,然后用证书的公钥加密会话密钥并发送给服务器
- 服务器通过自己的私钥解密会话密钥。至此,客户端和服务器双方都持有了相同的会话密钥
- 服务器和客户端之间通过会话密钥加密双方间的通信
二、HTTPS加密方式
HTTPS使用非对称加密传输一个对称密钥,服务器和客户端使用这个对称密钥来加密解密收发数据;而具体传输数据则是用对称加密的方式。
- 对称加密DES:加密和解密使用同一个密钥(速度快)
- 非对称加密RSA:发送端使用公开的公钥加密,接收端使用私密的私钥解密(安全)
三、HTTPS优点和缺点
优点
- 能够进行信息加密、完整性校验和身份验证,很大程度上避免了HTTP协议容易发生信息窃听、信息篡改、信息劫持的风险。
缺点
- 握手阶段比较费时,会使页面加载时间延长,增加耗电
- HTTPS缓存不如HTTP高效,会增加数据开销
- SSL证书需要费用,功能越强大的证书费用越高
- SSL证书需要绑定IP,不能在同一个IP上绑定多个域名,ipv4资源支持不了这种消耗
四、HTTPS和HTTP的区别
HTTP:超文本传输协议,TCP协议的一种,用于从WWW服务器传输超文本到本地浏览器的一种网络协议
HTTPS:HTTP+SSL,是HTTP的安全版,加入SSL层实现加密传输和身份认证
区别
- HTTP传输的数据是未加密的,即明文传输;HTTPS是具有安全性的SSL加密传输协议
- HTTPS需要使用SSL证书;HTTP不用
- 端口号不同,HTTP端口号80;HTTPS端口号443
- HTTPS基于传输层;HTTP基于应用层
五、HTTPS中间人攻击及其防范
MITM中间人攻击:攻击者相当于一个介入通信的传话员,攻击者知道通信双方的所有通信内容且可以任意增加、删除、修改双方的通信内容,而双方对此并不知情。
通信过程安全性的保证(自下而上)
- 公钥的正确性:双方通信采用非对称加密的方式,非对称加密中私钥不会传递,而公钥是公开的 。
- 数字证书的正确性:公钥由对方在通信初始提供,但很容易被中间人替换,因此发送公钥的时候也要提供对应的数字证书,用于验证公钥来自于对方而不是中间人。
- 上级CA证书的正确性:数字证书由上级CA签发给个人或组织,上级CA用自己的私钥给个人证书签名,保证证书的公钥不被篡改。
- 根证书的私钥不被泄露或其公钥不被篡改:上级CA证书也是由其上级CA签发的,这条信任链一直延续到根证书,而根证书是自签名的。
- 设备分发到消费者手中之前不被恶意修改:根证书一般通过操作系统而非网络分发;最初的操作系统应采用原始的当面交流的方式分发。因此,硬件厂商和证书签发机构合作,在设备出厂前在其操作系统中内置签发机构的根证书。
浏览器
什么是URL
URL指的是统一资源定位符,即一个给定的独特资源在Web上的地址
组成部分:
- protocol:协议,表明浏览器必须使用何种协议
- domain:域名,表示正在请求哪个服务器
- port:端口
- path:网络服务器上资源的路径
- parameters:提供给网络服务器的额外参数
- anchor:资源本身的另一部分的锚点,锚点表示资源中的一种“书签”,给浏览器显示位于该“加书签”位置的内容的方向
什么是BOM?有哪些常用BOM对象及其属性?
BOM是浏览器对象
- location对象:用于获取或设置窗体的URL,并且可以用于解析URL
属性 | 描述 |
hash | 设置或返回从井号(#)开始的URL(锚) |
host | 设置或返回主机名和当前URL的端口号 |
hostname | 设置或返回当前URL的主机名 |
href | 设置或返回完整的URL |
pathname | 设置或返回当前URL的路径部分 |
port | 设置或返回当前URL的端口号 |
protocol | 设置或返回当前URL的协议 |
search | 设置或返回从问号(?)开始的URL(查询部分) |
- history对象:记录用户曾经浏览过的页面(URL),并可以实现浏览器的前进与后退相似导航功能
属性 | 描述 |
length | 返回浏览器历史列表中的URL数量 |
方法 | 描述 |
back() | 加载history列表中的前一个URL |
forward() | 加载history列表中的下一个URL |
go() | 加载history列表中的某个具体的页面 |
- navigator对象:包含有关浏览器的信息,通常用于检测浏览器与操作系统的版本
属性 | 描述 |
appCodeName | 浏览器代码名的字符串表示 |
appName | 返回浏览器的名称 |
appVersion | 返回浏览器的平台和版本信息 |
platform | 返回运行浏览器的操作系统平台 |
userAgent | 返回由客户端发送服务器的user-agent头部的值 |
Cookie作用
- 保存用户登录状态:一段时间内免登录
- 跟踪用户行为:天气预报网站中保存用户上次访问时的地区;有换肤功能的网站保存用户上次访问的界面风格
Cookie有哪些字段可以设置?
- name:cookie的名称
- value:cookie的值(文本数据)
- domain:可以访问此cookie的域名
- path:可以访问此cookie的页面路径
- expires/max-age:cookie过期时间
- size:cookie的大小
- httponly:true则只有在http请求头中会带有此cookie信息,而无法通过js脚本来访问cookie
- secure:是否只能通过HTTPS来传递此条cookie
Cookie和Session的共同点和区别?
共同点:都是用来跟踪浏览器用户身份的会话方式
区别:
- cookie数据保存在浏览器,session数据保存在服务器
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑安全性使用session
- session会在一定时间内保存在服务器上,当访问增多是会占用服务器性能,考虑性能使用cookie
cookie,localStorage,sessionStorage的共同点和区别
共同点:
都是前端本地存储的方式,均保存在浏览器端且同源
区别:
- 数据和服务器之间的交互方式
cookie可以在客户端和服务器之间来回传递
localStorage和sessionStorage不会主动把数据发送给服务器,仅在本地保存。 - 生命周期
cookie在设置的有效期到期前都有效,默认是关闭浏览器后失效sessionStorage仅在当前网页会话下有效,关闭页面或浏览器后失效localStorage除非被手动清除,否则始终有效,永久保存 - 存放数据大小
cookie存储容量较小,一般只有4KB
localStorage和sessionStorage可以保存5MB的信息 - 作用域
cookie和localStorage在所有同源窗口都是共享的
sessionStorage不在不同的浏览器窗口中共享
CSRF和XSS攻击及防御手段
名称 | 方式 | 防御手段 |
CSRF(跨站请求伪造) | 攻击者在页面中嵌入恶意JS脚本,当用户浏览该页面时进行攻击 | cookie设置httponly和secure;进行特殊字符过滤;对用户的输入进行检查 |
XSS(跨站脚本攻击) | 攻击者盗用用户身份,以用户名义发送恶意请求 | 使用验证码;检查HTTPS头部referer字段 |
Cookie如何防范XSS攻击?
在http头部配上set-cookie,其中
httponly:该属性会禁止JS脚本使用document.cookie来访问cookie
secure:该属性告诉浏览器仅在请求为HTTPS的时候才发送cookie
在地址栏里输入一个URL,到这个页面呈现出来,中间会发生什么?
- 简答
DNS解析→TCP连接→发送HTTP请求→服务器处理请求并返回HTTP报文→浏览器解析渲染页面→连接结束
- 详细叙述
- 根据URL域名寻找服务器ip,浏览器首先在缓存中查找,查找的顺序是浏览器缓存→系统缓存→路由器缓存,缓存中查找不到则去系统的hosts文件中查找,没有则查询DNS服务器
- 得到ip地址后,浏览器根据ip和相应端口号构建一个http请求并将该http请求封装在一个tcp包中,这个tcp包依次经过传输层、网络层、数据链路层、物理层到达服务器,服务器解析这个请求并作出响应,返回相应的html给浏览器
- 浏览器根据返回的html来构建DOM树,构建DOM树的过程中如果遇到图片、音视频等资源会并行下载,如果遇到js脚本或外部js连接,则会停止DOM树的构建去执行和下载相应js脚本,这会造成阻塞;之后根据外部样式、内部样式、内联样式构建CSSOM树,构建完成后和DOM树合并成渲染树,主要目的是排除非视觉节点,比如script、meta标签和排除display为none的节点
- 进行布局,确定各个元素的位置和尺寸,然后渲染页面,显示给用户
- 上述所有请求中都会涉及http缓存机制
浏览器在生成页面时会生成哪两棵树?
DOM树 CSSOM规则树
当浏览器接收到服务器返回的HTML文档后,会遍历文档节点,生成DOM树;CSSOM规则树由浏览器解析CSS文件生成。
前端优化/Web性能优化
- 降低请求量:合并资源、减少HTTP请求数、gzip压缩
- 加快请求速度:减少域名数、并行加载、预解析DNS
- 缓存:HTTP缓存机制、离线数据缓存localStorage
- 渲染:使用外部JS和CSS、加载顺序(将CSS样式表放在顶部,JS脚本放在底部)、图片懒加载
什么是重排/回流(reflow)和重绘(repaint)?哪些情况会引起重排重绘?怎样减少重排重绘?
- 重排:部分或整个渲染树需要重新分析并且节点尺寸需要重新计算 重绘:由于节点的几何属性发生改变或者样式发生改变,屏幕上的部分内容需要更新
- 引起重排重绘的原因(重排一定重绘,重绘不一定重排):任何改变用来构建渲染树的信息都会导致一次重排或重绘
- 浏览器初始化窗口
- 添加或删除可见的DOM元素
- 移动或者给页面中的DOM节点添加动画
- 添加一个样式表,调整样式属性
- 用户行为,如调整窗口大小、改变字号、滚动等
- 减少重排重绘的方法:
- 批量改变和表现DOM:复制即将更新的节点,在副本上操作,然后将旧的节点和新的节点交换;通过display:none属性隐藏元素,添加足够多的变更后,通过display属性显示(只触发两次重排重绘)
- 使用cssText(动态改变)、className(静态改变)一次性改变属性
- 对于多次重排的元素,比如动画,使用绝对定位使其脱离文档流,不影响其他元素
图片的懒加载和预加载
预加载:提前加载图片,当用户需要时可直接从本地缓存中渲染
懒加载/延迟加载:访问页面时先把图片替换成一张占位图,当图片出现在浏览器可视区域时,才显示真正的图片内容
什么是按需加载?
用户触发了动作时才加载对应的功能
触发的动作:鼠标点击、键盘输入、鼠标移动、窗口大小更改、拉动滚动条等
加载的文件:HTML、CSS、JS、图片等
如何解决网页高并发?
网页高并发是指在同一时间内,大量用户对网站发起请求,这可能导致服务器性能下降或者响应变慢。为了解决网页高并发问题,可以考虑以下方案:
- 前端优化:前端性能优化能够减轻服务器负担,从而缓解高并发的情况。可以采用如下优化策略:
- 减少HTTP请求次数:将多个小文件合并成一个文件,使用雪碧图、图片懒加载等技术。
- 前端缓存:使用缓存技术,例如浏览器缓存、CDN缓存等,可以减轻服务器负担,提高网站响应速度。
- 代码压缩:将代码压缩成最小体积,可以减少页面加载时间,提高页面响应速度。
- 服务器优化:服务器的性能直接影响到网页的响应速度,因此可以考虑如下优化策略:
- 负载均衡:使用负载均衡技术,将请求分发到不同的服务器上,从而分散服务器压力,提高服务器性能。
- 数据库优化:对数据库进行优化,例如添加索引、查询语句优化等,可以提高数据库的响应速度。
- 缓存技术:使用缓存技术,例如Redis、Memcached等,可以将热点数据缓存到内存中,减轻服务器负担,提高服务器性能。
- CDN加速:使用CDN加速技术,可以将网站静态资源分发到不同地区的CDN节点上,从而减少服务器的负载,提高网站访问速度。
- 分布式架构:采用分布式架构,将请求分散到不同的服务器上,从而提高整个系统的性能和可靠性。
综上所述,前端优化、服务器优化、CDN加速和分布式架构是解决网页高并发问题的常用方法。
浏览器请求页面时,各个进程间是怎么配合的?
- 用户输入url并回车。
- 用户输入URL,浏览器进程会根据用户输入的信息判断是搜索还是网址,如果是搜索内容,就将搜索内容+默认搜索引擎合成新的URL;如果用户输入的内容符合URL规则,浏览器进程就会根据URL协议,在这段内容上加上协议合成合法的URL。
- 浏览器导航栏显示loading状态,但是页面还是呈现之前的页面不变,因为新页面的响应数据还没有获得。
- 浏览器进程构建请求行信息,通过进程间通信(IPC)把url请求发送给网络进程
- 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
- 如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
- 进行DNS解析,获取服务器ip地址
- 利用ip地址和服务器建立tcp连接
- 完成构建请求信息并发送请求
- 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
- 网络进程解析响应流程:
- 检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步,如果是200,则继续处理请求。
- 检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染,如果是html等资源则将其转发给浏览器进程。
- 浏览器进程接收到网络进程的响应头数据之后,检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程。
- 渲染进程准备好后,浏览器进程发送
CommitNavigation
消息到渲染进程,发送CommitNavigation
时会携带响应头、等基本信息。渲染进程接收到消息和网络进程建立传输数据的“管道”。 - 渲染进程接收完数据后,向浏览器进程发送“确认提交”。
- 浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、前进后退的历史状态、更新web页面。
如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?
当从服务器接收HTML页面的第一批数据时,DOM解析器就开始工作了,在解析过程中,如果遇到了JS脚本,如下所示:
<html>
<body>
极客时间
<script>
document.write("--foo")
</script>
</body></html>复制代码
那么DOM解析器会先执行JavaScript脚本,执行完成之后,再继续往下解析。 那么第二种情况复杂点了,我们内联的脚本替换成js外部文件,如下所示:
<html>
<body>
极客时间
<scripttype="text/javascript"src="foo.js"></script>
</body></html>复制代码
这种情况下,当解析到JavaScript的时候,会先暂停DOM解析,并下载foo.js文件,下载完成之后执行该段JS文件,然后再继续往下解析DOM。这就是JavaScript文件为什么会阻塞DOM渲染。 我们再看第三种情况,还是看下面代码:
<html>
<head>
<styletype="text/css"src = "theme.css" ></style>
</head>
<body>
<p>极客时间</p>
<script>
let e = document.getElementsByTagName('p')[0]
e.style.color = 'blue'
</script>
</body></html>复制代码
当我在JavaScript中访问了某个元素的样式,那么这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的解析。
浏览器的渲染流程是什么?
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
为什么减少重绘、重排能优化 Web 性能吗?那又有那些具体的实践方法能减少重绘、重排呢?
重排需要更新完整的渲染流水线,所以开销也是最大的。
相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
如果更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。
使用 CSS 的 transform 来实现动画效果,可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
减少重排重绘, 方法很多:
- 使用 class 操作样式,而不是频繁操作 style
- 避免使用 table 布局
- 批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React
- Debounce window resize 事件
- 对 dom 属性的读写要分离
- will-change: transform 做优化
MVVM
- MVVM(Model-View-ViewModel)是对MVC(Model-View-Control)和MVP(Model-View-Presenter)的进一步改进。
- View:视图层(UI用户界面)
- ViewModel:业务逻辑层(一切JS可视为业务逻辑)
- Model:数据层(存储数据及对数据的处理,如增删改查)
- MVVM将数据双向绑定(data-binding)作为核心思想,View和Model之间没有联系,他们通过ViewModel这个桥梁进行交互。
- Model和ViewModel之间的交互是双向的,因此View的变化会自动同步到Model,而Model的变化也会立即反映到View上显示。
解决跨域问题
- JSONP
- CORS
- 通过修改
document.domain
来跨子域 - 使用
window.name
来进行跨域 - 使用
window.postMessage
方法 flash
- 在服务器上设置代理页面
XML和JSON的区别
- 数据体积方面
JSON相对XML来讲数据体积小,传递速度快
- 数据交互方面
JSON与JS的交互更加方便,更容易解析处理,更好的数据交互
- 数据描述方面
JSON的数据描述性比XML差
- 传输速度方面
JSON的速度远快于XML
浏览器缓存位置和优先级
- Service Worker
- Memory Cache(内存缓存)
- Disk Cache(硬盘缓存)
- Push Cache(推送缓存)
- 以上缓存都没命中就会进行网络请求
不同缓存间的差别
Service Worker
和Web Worker类似,是独立的线程,我们可以在这个线程中缓存文件,在主线程需要的时候读取这里的文件,Service Worker使我们可以自由选择缓存哪些文件以及文件的匹配、读取规则,并且缓存是持续性的
Memory Cache
即内存缓存,内存缓存不是持续性的,缓存会随着进程释放而释放
Disk Cache
即硬盘缓存,相较于内存缓存,硬盘缓存的持续性和容量更优,它会根据HTTP header的字段判断哪些资源需要缓存
Push Cache
即推送缓存,是HTTP/2的内容,目前应用较少
如何根据浏览器渲染机制加快首屏速度
- 优化文件大小:HTML和CSS的加载和解析都会阻塞渲染树的生成,从而影响首屏展示速度,因此我们可以通过优化文件大小、减少CSS文件层级的方法来加快首屏速度
- 避免资源下载阻塞文档解析:浏览器解析到<script>标签时,会阻塞文档解析,直到脚本执行完成,因此我们通常把<script>标签放在底部,或者加上
defer、async
来进行异步下载
什么是GPU加速,如何使用GPU加速,GPU加速的缺点
- 优点:使用transform、opacity、filters等属性时,会直接在GPU中完成处理,这些属性的变化不会引起回流重绘
- 缺点:GPU渲染字体会导致字体模糊,过多的GPU处理会导致内存问题
如何减少回流
- 使用
class
替代style
,减少style的使用 - 使用
resize、scroll
时进行防抖和节流处理,这两者会直接导致回流 - 使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流 - 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流
- 避免触发同步布局事件,我们在获取
offsetWidth
这类属性的值时,可以使用变量将查询结果存起来,避免多次查询,每次对offset/scroll/client
等属性进行查询时都会触发回流 - 对于复杂动画效果,使用绝对定位让其脱离文档流,复杂的动画效果会频繁地触发回流重绘,我们可以将动画元素设置绝对定位从而脱离文档流避免反复回流重绘。
负载均衡有哪些实现方式?
- DNS:这是最简单的负载均衡的方式,一般用于实现地理级别的负载均衡,不同地域的用户通过DNS的解析可以返回不同的IP地址,这种方式的负载均衡简单,但是扩展性太差,控制权在域名服务商。
- Http重定向:通过修改Http响应头的Location达到负载均衡的目的,Http的302重定向。这种方式对性能有影响,而且增加请求耗时。
- 反向代理:作用于应用层的模式,也被称作为七层负载均衡,比如常见的Nginx,性能一般可以达到万级。这种方式部署简单,成本低,而且容易扩展。
- IP:作用于网络层的和传输层的模式,也被称作四层负载均衡,通过对数据包的IP地址和端口进行修改来达到负载均衡的效果。常见的有LVS(Linux Virtual Server),通常性能可以支持10万级并发。
按照类型来划分的话,还可以分成DNS负载均衡、硬件负载均衡、软件负载均衡。其中硬件负载均衡价格昂贵,性能最好,能达到百万级,软件负载均衡包括Nginx、LVS这种
WebSocket协议
HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。
说到优点,这里的对比参照物是HTTP协议,概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。
webscoket的连接原理是什么?
WebSocket是一种基于TCP协议的全双工通信协议,可以在浏览器和服务器之间建立双向的通信连接。其连接原理可以概括为以下几个步骤:
- 握手:客户端向服务器发起一个HTTP请求,请求头包含特殊的Upgrade头和Connection头,请求体为空。服务器收到请求后进行验证,并返回101状态码和升级头(Upgrade header)和连接头(Connection header)。
- 建立连接:客户端和服务器在完成握手之后,会建立一个TCP连接。该连接会一直保持打开状态,以便后续通信的进行。
- 数据传输:客户端和服务器在建立连接之后,可以通过该连接发送消息。消息可以是文本或二进制格式,它们以帧(frame)的形式进行传输。
- 关闭连接:当客户端或服务器需要关闭连接时,可以发送一个特殊的关闭帧,该帧会告知对方关闭连接。客户端和服务器会在接收到关闭帧后,关闭连接并释放资源。
在WebSocket连接中,客户端和服务器可以随时发送消息,无需等待对方的响应。这种双向的通信方式可以用于实时通信、游戏、股票行情等需要实时更新的应用场景。
什么是 JWT
JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。
是一种认证授权机制。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{"姓名":"张三","角色":"管理员","到期时间":"2018年7月1日0点0分"}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
JWT 的几个特点
(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。
(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
Cookie 和 Session 的区别
- 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
- 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
- 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
- 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
Token 和 Session 的区别
- Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
- Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
- 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。所以简单来说:如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
什么是Restful API
传统methods
- GET 获取服务器数据
- POST 向服务器提交数据
现代methods
- GET 获取数据
- POST 新建数据
- PATCH/PUT 更新数据
- DELETE删除数据
传统API:把每个URL当作一个功能
Restful API: 把每个URL当作一个唯一的资源
用 URL 定位资源,用 HTTP 动词(GET,POST,DELETE,PUT)描述操作
尽量不用URL参数,用method表示操作类型
① url参数
传统API :/api/list?pageIndex=2
Restful API :/api/list/2
② method表示操作类型
- 传统API
POST请求 /api/create-blog
POST请求 /api/update-blog?id=100
GET请求 /api/get-blog?id=100 - Restful API
POST请求 /api/blog
PATCH请求 /api/blog/100
GET请求 /api/blog/100