有一天晚上,
突然想到,
经历了这么多,
要是不留下点了什么,
岂不是白瞎了吗~
wulang8353 / demacial Goto Github PK
View Code? Open in Web Editor NEW从情人节当天开始记录的学习之旅-Gitbook总结
Home Page: https://wulang8353.gitbooks.io/demacial/content/
从情人节当天开始记录的学习之旅-Gitbook总结
Home Page: https://wulang8353.gitbooks.io/demacial/content/
有一天晚上,
突然想到,
经历了这么多,
要是不留下点了什么,
岂不是白瞎了吗~
单独把这一点拿出来是因为这是一道经典的面试题,浏览器渲染过程包含在了题目之中。这道题没有一个标准的答案,从前端的角度,我认为起码需要回答以下几点:
1、浏览器的地址栏输入URL并按下回车
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期
3、DNS解析URL对应的IP
4、根据IP建立TCP连接(三次握手)
5、HTTP发起请求,浏览器接收HTTP响应
6、渲染页面,构建DOM树
7、关闭TCP连接(四次挥手)
Web浏览器通过URL从Web服务器请求页面, 一个网页地址实例:
http://www.runoob.com/html/html-tutorial.html
语法规则如下:
scheme://host.domain:port/path/filename
说明:
这个属于基本知识了,其中还涉及到了同源策略、跨域、XSS与CRXF等,这里先不介绍了。
根据是否需要重新向服务器发起请求来分类,可以将缓存类型分为强制缓存,对比缓存。
Expires
是一个绝对时间,即服务器时间。浏览器会检查当前时间,如果没有到服务器时间,说明还没有过期,直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端本地时间可能不一致,就无法判断文件的有效期,因此该字段已经很少使用。
cache-control
中的 max-age 保存一个相对时间。例如Cache-Control: max-age = 484200
,表示浏览器收到文件后,在484200s内缓存有效,在这段时间内,发出的请求都会直接使用缓存。 如果同时存在 cache-control 和 Expires,浏览器总是优先使用 cache-control。
Last-Modified/If-Modified-Since
上面说的当有效期过后,检查服务端文件是否更新的第一种方式,要配合Cache-Control使用。
按下 ctrl+r 刷新,ctrl+r会默认跳过 max-age 和 Expires 的检验直接去向服务器发送请求
last-modified 第一次请求资源时,服务器返回该字段,表示最后一次更新的时间。下一次浏览器请求资源时,请求中会发送if-modified-since 字段。服务器用自己本地 Last-modified 时间与if-modified-since 时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送 304
状态码,让浏览器继续使用缓存。
Etag/If-None-Match
ETag并不是文件的版本号,而是一串可以代表该文件唯一的字符串(文件的索引节,大小和最后修改时间进行Hash后得到的)
当客户端发现和服务器约定的直接读取缓存的时间过了,就在请求中发送If-None-Match选项,值即为上次请求后响应头的ETag值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则相应HTTP304,客户端直接读取缓存,如果不相同,HTTP200,下载正确的数据,更新ETag值。
如果两者同时存在,If-None-Match/ETag优先,忽略Last-Modified/If-Modified-Since
HTTP1.1中 ETag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:
Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但 Last-Modified 却改变了,导致文件没法使用缓存
有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
通过上表可以看到,在按 F5 进行刷新的时候,会忽略 Expires/Cache-Control 的设置,会再次发送请求去服务器请求,而 Last-Modified/Etag 还是有效的,服务器会根据情况判断返回 304 还是 200;而当使用 Ctrl+F5 进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源。
当在浏览器中输入一个地址时,例如 www.baidu.com
, 其实不是百度网站真正意义上的网络地址。
互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆。用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机,也就是上面提到的百度的网址。
可以这样理解, 整个网络相当于一个非常巨大的电话簿。我们在打电话的时候首先要找到名字,然后再去拨打对应的电话号码。
例如 www.baidu.com 这个域名就相当于名字,而如何找到对应的电话号码呢? DNS域名解析的过程实际是将域名还原为IP地址的过程,这样就能找到电话号码进行通信了。
首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。
如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。
最后迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。
通过DNS域名解析后拿到IP地址,在获取到IP地址后,便会开始建立一次连接,这是由TCP协议完成的。
主要通过三次握手进行连接。
》 第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
》 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
》 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
从图中就能看出为什么是三次握手而不是二次。因为要确保客户端与服务器双方的发送和接收的功能均正常。
第一次握手,客户端传到服务器,建立连接
第二次握手,服务器传到客户端
服务器接收到客户端的消息,并发送消息到客户端,落脚点在客户端
说明服务器能够正常接收消息,所以客户端的发送正常,服务器的接收也正常 -> 服务器 接收
√ | 客户端 发送
√
第三次握手,客户端传到服务器
客户端接收到服务器的消息,并发送消息到服务器,落脚点在服务器
说明客户端能够正常接收消息,所以服务器的发送正常,客户端的接收也正常 -> 客户端 发送
√ 接收
√ | 服务器 接收
√ 发送
√
HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。
这里就不用说太多,平常开发调试中应该是经常遇的到,就讲讲状态码吧:
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
301:永久移动。请求的资源已被永久的移动到新URI,会返回新的URI,并且自动定向到该地址
302:临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
304: 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。 缓存经常遇到。
4xx:客户端错误–请求有语法错误或请求无法实现
401:请求身份认证,需要验证
405:请求中的方法被禁止,接口中限定了请求方法
415:服务器无法处理请求附带的媒体格式,一般是MIME
5xx:服务器端错误–服务器未能实现合法的请求
503:服务器暂时的无法处理客户端的请求,服务器未开启
504:网管超时,一般是服务器方法有问题
浏览器拿到内容后,渲染大概可以划分成以下几个步骤:
如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM。
HTML页面加载和解析流程具体一点来说:
1、解析HTML结构。发现 ``<head>`` 标签中有一个 ``<link> `` 标签引用外部CSS文件
2、浏览器又发出CSS文件的请求,服务器返回这个CSS文件
3、浏览器继续载入html中 ``<body>`` 部分的代码,并且CSS文件就位,开始渲染页面了
4、浏览器在代码中发现一个 ``<img>`` 标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码
5、服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码
6、浏览器发现了一个包含一行Javascript代码的 ``<script>`` 标签,解析并运行
7、Javascript命令浏览器隐藏掉掉某个DOM元素,浏览器重新渲染这部分代码
8、解析至</html>的到来,整个流程结束
由上可知,script标签的存在会阻塞渲染,但浏览器总是并行加载资源,当浏览器遇到script时候:
1、<script src="script.js"></script>
没有defer和async,浏览器会立即加载并执行脚本,此时会将渲染挂起,页面会有延迟的感觉,所以一般会将其放在</body
>之前
2、<script src="script.js" async></script>
有async,加载和渲染后续文档元素的过程和script.js的加载与执行并行进行,不一定要按照JS的顺序执行
3、<script src="script.js" defer></script>
有defer。加载后续文档元素的过程和script.js的加载并行,但是和script.js的执行要在所有元素解析完成之后, DOMContentLoaded事件(DOM树构建完成)之前完成,并且是按照JS的顺序执行
这一过程可以由客户端或者服务器任一方来触发,流程如下:
第一次挥手:浏览器发完数据后,发送FIN请求断开连接。
第二次挥手:服务器发送ACK表示同意
第三次挥手:但考虑到服务器可能还有数据要发送,那么就等服务器发送数据结束后再发送FIN,表示断开连接
第四次挥手:浏览器需要返回ACK表示同意
为什么是四次挥手?
客户端发送 FIN 仅仅表示不再发送数据,但是还能接受数据。服务器发送ack表示知道你准备关闭,但是我这还没发送完。直到服务器也发送了 FIN 表示不再发送数据,同意现在关闭连接。浏览器接收后发送ack到服务器,整个连接关闭。
抽象理解成:
客户端:"老服同志,我不想讲了,要关闭连接了哈" -> FIN、ack
服务器:“你个老客,你要走了啊? 等回,我还没说完” -> ack
(服务器啰嗦了一会,看到没回应觉得没意思)
服务器: “算了,不说了,没意思,回见了您” -> FIN
客户端:“回见” -> ack
一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在可以通过JS直接操作二进制数据。 Blob对象可以看做是存放二进制数据的容器,此外还可以通过Blob设置二进制数据的MIME类型。
一个Blob对象就是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件
var blob = new Blob(dataArr, option)
1、创建一个有DOMString的Blob对象
2、创建一个有ArrayBuffer的Blob对象
此方法返回一个新的Blob对象,包含了原Blob对象中指定范围内的数据
Blob.slice(start:number, end:number, contentType:string)
思路: window.URL对象可以为Blob对象生成一个网络地址,结合a标签的download属性,可以实现点击url下载文件
download("download.txt", "download-file");
function download(fileName, url) {
var blob = new Blob([url]); \\ 创建Blob对象,可以指定MIME
if (window.navigator.msSaveOrOpenBlob) { \\ 针对IE浏览器打开Blob URL连接
navigator.msSaveBlob(blob, fileName); \\ msSaveOrOpenBlob 提高保存和打开选项,而msSaveBlob只提供保存选项
} else {
var link = document.createElement("a");
link.download = fileName; \\ 下载的文件名称
link.innerHTML = fileName; \\ 强行指定文件内容
link.href = window.URL.createObjectURL(blob); \\ 创建Blob连接
document.getElementsByTagName("body")[0].appendChild(link);
\\ link.click(); 浏览器会直接打开文件
\\ window.URL.revokeObjectURL(link.href); 只需要访问一次,一旦已经访问到了,这个对象URL就不再需要了,就被释放掉
}
}
通过Blob.slice方法,可以将大文件分片,轮循向后台提交各文件片段,即可实现文件的分片上传。
分片上传逻辑如下:
1、获取要上传文件的File对象,根据chunk(每片大小)对文件进行分片
2、通过post方**循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息
3、post body中存放本次要上传的二进制数据片段,接口每次返回offset,用于执行下次上传
initUpload();
//初始化上传
function initUpload() {
var chunk = 100 * 1024; //每片大小
var input = document.getElementById("file"); //input file
input.onchange = function (e) {
var file = this.files[0];
var query = {};
var chunks = [];
if (!!file) {
var start = 0;
//文件分片
for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
var end = start + chunk;
chunks[i] = file.slice(start , end);
start = end;
}
// 采用post方法上传文件
// url query上拼接以下参数,用于记录上传偏移
// post body中存放本次要上传的二进制数据
query = {
fileSize: file.size,
dataSize: chunk,
nextOffset: 0
}
upload(chunks, query, successPerUpload);
}
}
}
// 执行上传
function upload(chunks, query, cb) {
var queryStr = Object.getOwnPropertyNames(query).map(key => {
return key + "=" + query[key];
}).join("&");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://xxxx/opload?" + queryStr);
xhr.overrideMimeType("application/octet-stream");
//获取post body中二进制数据
var index = Math.floor(query.nextOffset / query.dataSize);
getFileBinary(chunks[index], function (binary) {
if (xhr.sendAsBinary) {
xhr.sendAsBinary(binary);
} else {
xhr.send(binary);
}
});
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var resp = JSON.parse(xhr.responseText);
// 接口返回nextoffset
// resp = {
// isFinish:false,
// offset:100*1024
// }
if (typeof cb === "function") {
cb.call(this, resp, chunks, query)
}
}
}
}
}
// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
if (resp.isFinish === true) {
alert("上传成功");
} else {
//未上传完毕
query.offset = resp.offset;
upload(chunks, query, successPerUpload);
}
}
// 获取文件二进制数据
function getFileBinary(file, cb) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
if (typeof cb === "function") {
cb.call(this, this.result);
}
}
}
思路: img的src属性及background的url属性,都可以通过接收图片的网络地址或base64来显示图片,所以可以把图片转化为Blob对象,生成URL。
单线程:一个进程中只有一个线程。程序顺序执行,前面的执行完,才会执行后面的程序。
问题1、JavaScript为什么是单线程?
举个栗子,在页面中直接alert('hello'),只要不关闭这个对话框,后续的JS代码就不会再执行。单线程就是这样一步一步的顺次执行,前面不执行完,后面不会执行。也就是说,在具体的某一时刻,只有一段代码在执行
问题2、为什么有时候感觉JavaScript是多线程?
先说答案:JS单线程通过执行浏览器的任务队列,从而在感觉上可以处理各种触发事件。
任务队列(事件队列)
单线程意味所有任务需要排队,前一个任务结束,才会执行后一个。假设任务耗时很长,后一个任务得一直等着。
但比如一些异步操作(Ajax请求)得到结果是需要一定时间的,所以CPU总不能在等待结果的这段时间内一直闲着。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务 指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务 指的是,不进入主线程、而进入任务队列的任务。通过任务队列去通知js引擎主线程,告诉主线程某个异步任务可以执行了,该任务才会进入js引擎主线程执行。
总结一下:
1、所有同步任务都在主线程上执行,形成一个执行栈
2、主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个该异步任务对应的事件。执行顺序是先进先出。
3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件以及对应的异步任务,将其放入执行栈,开始执行任务队列中的事件。
4、主线程不断重复上面的第三步。
只要主线程中所有的同步任务结束完了,就会去读取"任务队列"的异步任务(按照事件的触发顺序,比如鼠标点击了、定时器设定的时间到了、HTTP 请求的状态变更),这也就是JavaScript的运行机制。
以谷歌浏览器为例,每打开一个Tab页,就相当于创建了一个独立的浏览器进程,可以在Chrome的任务管理器中(更多工具 -> 任务管理器
,或者shift + esc
)看到有多个进程
注意:在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了。浏览器有时会将多个进程合并(譬如打开多个空白标签页后,会发现多个空白标签页被合并成了一个进程)
强化记忆:在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程),所以浏览器多进程的。
知道了浏览器是多进程后,再来看看它到底包含哪些其他的进程
上面介绍了浏览器是多进程的,那么,对于前端操作来说,最终要的是什么呢?答案是浏览器渲染进程,想要了解深入了解就先得要了解浏览器的基础结构,主要包括如下几个部分:
1、用户界面(User Interface):用户所看到及与之交互的功能组件,如地址栏,返回前进按钮,标签页等,可以理解成除去页面内容显示之外的其他各部分的组合。
2、浏览器引擎(Browser engine): 负责在用户界面与渲染引擎之间指令传输
3、渲染引擎(Rendering engine):负责显示内容,通过解析用户请求的内容(如HTML以及相关CSS)然后返回解析后的内容并显示
4、网络(Networking):负责处理网络相关的事务,如HTTP请求等
5、UI后端(UI backend):负责绘制提示框等浏览器组件,公开通用接口,其底层使用的是操作系统的用户接口
6、JavaScript解释器(JavaScript interpreter):负责解析和执行JavaScript代码
7、数据存储(Data storage):负责持久存储诸如cookie和缓存等应用数据,是一个完整但轻便的浏览器数据库
渲染引擎被称为浏览器内核,也就是所说的渲染进程。可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。
各大主要浏览器使用内核也是有差别的,大致可以分为以下几类:
Trident内核: IE
Webkit内核:Chrome,Safari
Gecko内核:FireFox
请牢记,浏览器的渲染进程是多线程的
那么接下来看看它都包含了哪些线程
GUI渲染线程
JS引擎线程
定时触发器线程
http请求线程
事件触发线程
总结一下:
由此可知,浏览器是多进程的,而其中的渲染进程是多线程的
看到这里,首先,应该对浏览器内的进程和线程都有一定理解了。那么进程与渲染进程间是如何通信的呢?
根据浏览器的任务管理所示,可以看到有一个浏览器
任务,这就是主进程;其他则是打开了Tab页面的渲染进程,比如百度
、github
等。
通信过程如下:
1、主进程(Browser)进程根据用户操作,首先需要通过网络请求下载资源获取页面内容,随后将该下载任务传递给渲染进程(Render进程)
2、渲染进程的Renderer接口收到消息,解析后交给其中的GUI渲染线程,然后开始渲染
3、渲染线程接收请求,加载网页并渲染网页,这其中可能需要主进程获取其他资源例如图片、音频等,也可能会存在JS引擎线程操作DOM
5、最后渲染进程将结果传递给主进程,主进程接收到结果并将结果绘制出来
渲染引擎被称为浏览器内核,也就是所说的渲染进程。可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。 既然渲染进程主要影响了浏览器页面内容的显示,那么接下来就看看进程中各线程的关系。
首先回顾一下,渲染进程中的有哪些线程:
GUI渲染线程与JS引擎线程互斥,这里借用一下阮一峰老师在阮一峰《JavaScript 运行机制详解:再谈Event Loop》的例子:假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
因此,为了避免由于JS线程和GUI线程同时运行而造成的渲染线程前后获得的元素数据不一致,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,当JS引擎线程空闲时,保存在一个队列中的GUI线程会被立刻执行。由此可见,JS如果执行时间过长就会阻塞页面,所以要尽量避免JS执行时间过长,导致页面渲染速度慢而产生卡顿的感觉。
大量的JS计算无疑会对页面渲染的效率产生影响,那么如何高效的处理这种密集型计算? HTML5支持了Web Worker
。
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面
一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件
这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window
因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误
简单理解:
创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,而且不能操作DOM)
JS引擎线程与worker线程间通过特定的方式通信(postMessage API)
Worker线程专门处理耗时的工作,而且并不会影响JS引擎线程,当计算结束后传给主线程即可
使用教程可参考阮一峰《Web Worker 使用教程》
WebWorker只属于某个页面,由于每一个Tab页就是一个render进程,每个render进程都会有独立的Worker线程来运行JS。
SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用。
SharedWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程。
参考资料:
工厂:假设CPU是一个有很多车间的工厂、但工厂的电力有限,一次只能供一个车间使用,也就是说,一个车间开工,其他车间必须停工。
车间:进程就好比工厂的车间,所代表需要处理的单个任务,某个时间段,CPU运行一个进程,其他进程就不能运行。
工人:车间需要工人, 所以车间里的工人就好比线程,协同完成一个任务。即一个进程中可以包含多个线程。
房间:车间会有比如装配室、加工室等独立的房间,都可以让工人自由出入。这些房间相当于内存空间,这象征一个进程(车间)的内存空间(房间)是共享的,每个线程(工人)都可以使用这些共享内存。
独卫:有人的地方就会有厕所,当厕所里面有人的时候,其他人就必须在外面憋着。说明一个线程使用共享内存时,其他线程必须等待他使用完。
上厕所记得关门:先到的人锁上门,后来的人看到锁知道里面有人后,就在门口排队,等里面的人用完开锁再进去,这就叫“互斥锁”,防止多个线程同时读写某一块内存。
下班换衣服咯:假设有n个更衣室,墙上挂了n把钥匙,进去的人就取一把钥匙,出来时再把钥匙挂回原处。没有钥匙就得排队等着。这种做法叫做"信号量",由于某些内存区域只能供给固定数目的线程使用,这样就可以保证多个线程不会互相冲突。
总结一下:
任务是由人完成的,所以CPU的基本调度单位是线程
进程表示程序(任务)的一次执行,也是CPU资源分配的最小单位(要人干活总得有地方吧)
进程间相互独立,同一进程的各线程共享内存
互斥锁和信号量区别就在于资源个数,互斥锁是信号量在n=1的特殊情况
场景想象:
周末晚上在家玩电脑,想听听歌打开了网易云音乐放了首《单身的人不要听情歌》
这时微信显示有消息,发现是一个妹子找你聊天说心情不太好.
你激动的写了一段文字发了过去,又觉得这样不太主动,换了首《慢慢喜欢你》,刻意压低声音发了一段语音过去。
妹子听了很感动然后觉得你人不错想和你视频,然后你马上穿上背心,向双手唾了唾抹在头发上就接收了视频邀请,
美妙的夜晚于是就拉开了帷幕~~~
从上面yy的场景中可以知道:
1、进程:网易云音乐、微信
2、线程:歌曲搜索、对话框弹出、文字传输、语音传输...
进程就是我们打开的一个软件,而在软件运行的过程中,多个工作(线程)协同才能支撑软件完成整个运行。
比如快播播放器,而快播里面的那些功能,如搜索、播放、暂停。
有没有发现,其实进程和线程是CPU工作时间段的描述
1、说到这需要讲讲电脑的组成,可以大概的将电脑的构成包括:CPU+RAM+配置(显卡、光驱、显示屏、键盘等等)。 但电脑的运行实际上是CPU和寄存器以及RAM之间的事情
2、CPU执行速度太快了,当多个任务要执行的时候,在CPU看来就是轮流着“宠幸”
3、现在有个程序要执行了,等着CPU"宠幸"。再此之前,是不是环境得准备好啊,鲜花啊、蜡烛啊。不对不对,是比如显卡啊、声卡啊都得就位就差与CPU金风玉露一相逢了。
4、除了CPU以外所有的就构成了这个程序的执行环境,即程序的上下文环境。当程序执行结束 or 分配给它的CPU执行时间用完,程序就被切换出去,只能咬着白手绢等待下一次CPU的临幸了。
5、在被切换出去之前,最后一步是要保存上下文环境,原因很简单,因为想让CPU知道,还是熟悉的配方、熟悉的味道。
整个过程串起来就是:
先加载程序A的上下文,然后执行A,结束后保存程序A的上下文。继续调入下一个程序B的上下文,执行B,结束后保存...
》 进程是包含上下文环境切换与程序执行的时间总和 = CPU加载上下文 + CPU执行 + CPU保存上下文
CPU执行肯定不会只有一个逻辑,肯定会有多个分支和程序段,类似于程序A的执行需要分成A1,A2,A3等多个部分。所以当CPU执行程序A的时候,整个过程等价于:
CPU加载程序A上下文环境 -> CPU开始执行A1小段,A2小段,A3小段...直到执行完毕 -> 执行结束CPU保存程序A的上下环境
》 A1,A2,A3 就是线程,它们共享了进程(程序A)的上下文环境,是一种更为细分的CPU时间段
所以, 进程和线程是CPU工作时间段的描述,区别是其执行时间的颗粒大小不同
总结一下:
进程:一段程序的执行,是CPU资源分配的最小单位
线程:支撑这段程序完整执行的多项工作,是CPU的基本调度单位
进程和线程是CPU工作时间段的描述,区别是其执行时间的颗粒大小不同
前端传输的数据一般有几种加密做法:
js加密一般分为两种手段:
一、是使用数据摘要进行数据杂凑 ( 哈希加密 )
二、使用非对称加密算法对明文进行加密 ( 密钥加密 )
严格来讲, 第一种方式并非加密而是一种信息摘要的过程.
可见哈希与密钥是两个不同的东西, 其不同点在于:
哈希算法: 用于数据摘要, 通过 hash 函数生成相同长度的文本, 其加密文本长度与明文文本有关. 哈希加密是不可逆的
密钥加密: 分为对称加密与非对称加密
非对称加密算法, 加密和解密的密钥是不同的, 分为私钥和公钥. 常用的 RSA 就是一种非对称加密算法.
对称加密算法, 加密和解密公用同一个密钥, 如 AES/DES.
从性能上来说, 非对称加密相对于对称加密要慢很多. 在HTTPS中, 认证过程使用了非对称加密, 非认证过程使用了对称加密.
在HTTP协议下, 数据是明文传输, 在传输过程中可以通过网络直接获取信息, 具有安全隐患; 另一方面在非加密的传输过程中,攻击者可更改数据或插入恶意的代码等。
HTTPS 的诞生就是为了解决中间人攻击的问题. 但如果仍然使用 HTTP 协议, 前端加密的作用即在数据发送前将数据进行哈希或使用公钥加密。如果数据被中间人获取,拿到的则不再是明文.
1、哈希加密
首先回顾下后端通过哈希算法如何验证用户: 如果前端传过来的是明文, 那么在用户在注册时, 将哈希算法也存入数据库. 待用户登录后,将密码进行哈希后和数据库对应的数据比对, 若一致则说明密码正确 ( 密码+哈希 )
为了避免攻击者窃取前后端加密生成的字典,我们需要加盐,并不定期更新盐值:
前端加密后使用定期刷新的 Salt 再一次哈希生成密文,将该密文发送到后端, 数据库中的密码哈希值再与 Salt 哈希,两者进行密文比对. ( 密码+Salt+哈希 )
由于攻击者仍可以使用加密后的密码进行直接登陆,所以固定的 Salt 并不安全. 动态 Salt 有很多方法,可以是动态的 Token,也可以直接使用现成的验证码, 或者图形验证码
前端先将密码哈希, 然后和用户输入的验证码进行哈希, 得到的结果作为密码字段发送给服务器. 服务器先确认验证码正确,然后再进行密码验证,否则直接返回验证码错误信息
先来理解两个概念:
撞库: 由于用户在不同网站使用的是相同的帐号密码, 收集已泄露的用户和密码信息,生成对应的字典表,尝试批量登陆其他网站后,得到一系列可以登录的账号
拖库: 指网站遭到入侵后,黑客窃取其数据库
js哈希加密保证了密码在传输过程中的资料安全, 因为验证码是动态的, 即使攻击者拿到了数据也无法重放. 图形化验证码更是增加了难度. 另一方面该实践大大增加了撞库的成本
使用一些前端加密手段,可以加大拖库后的数据破解的难度. 但是之前介绍的验证码方法不具有这样的功能,因为数据库存的仍是哈希后的明文密码后的结果,那么攻击者可以直接窃取数据库进行暴力破解
从两个方面分别去思考如何进一步提升性能
降低暴力破解速度
暴力破解即是使用相同的算法把常用的字符组跑一遍
数据被暴力破解出来的时间与哈希算法速度负相关. 也就是说, 哈希算法越快, 破解的时间越快.
例如 MD5 加密一次耗费1微秒,那么攻击者一秒钟就可以猜大约 100万 个词组. 所以为了减慢破解速度,我们需要增加破解的时间,一个直接的方法就是使用更慢的算法,我们可以把常用的MD5算法替换为 bcrypt,PBKDF2 等慢算法
前端加密结果影响数据库的数据存储
由于慢的算法会增加服务器计算压力,可以把一部分哈希工作分到前端,即减慢了加密速度,减轻了服务器压力,也顺带完成了前端加密的工作
哈希加密速度减慢一定程度会降低用户体验,这也是一部分站点未启用 https 的原因之一。但是因为我们的前端加密只会用在不常使用的登录和注册上,所以不会影响网站整体的体验
因为可以在前端窃取数据,一旦密钥失窃就相当于明文数据了, 所以使用对称加密是不安全的.
非对称加密原理很简单,前后端共用一套加密解密算法,前端使用公钥对数据加密,后端使用私钥将数据解密为明文。中间攻击人拿到密文,如果没有私钥的话是没办法破解的。
这里介绍 jsencrypt 插件实现前端参数加密,jsencypt具体的使用参考 jsencrypt
思路: 拿到公钥和私钥. 利用公钥RSA加密, 私钥解密
1 创建密钥对JKS格式keystore:
keytool -genkey -v -alias test -keyalg RSA -keystore test.jks
2 将JKS格式keystore转换成PKCS12证书文件:
keytool -importkeystore -srckeystore test.jks -destkeystore test.p12 -srcstoretype JKS -deststoretype PKCS12
3 使用OpenSSL工具从PKCS12证书文件导出密钥对:
openssl pkcs12 -in test.p12 -nocerts -nodes -out test.key
4 从密钥对中提取出公钥:
openssl rsa -in test.key -pubout -out test_public.pem
拿到公钥test_public.pem后,在cat test_public.pem查看这个公钥内容,内容是base64格式的
const jse = new JSEncrypt() // 创建该对象
jsencrypt.setPublicKey(this.publicKey) // 设置公钥
let enPassword = jse.encrypt(this.password) // 对密码加密
总结: 在没有 https 的情况下,使用 JavaScript 非对称加密或者插件加密通讯是比较好的替代方法
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.