# 浏览器工作原理

# CPU、进程、线程 的关系

  • CPU(工厂):计算机的核心是CPU,承担所有计算任务。
  • 进程(车间):进程是CPU资源分配和独立运行的最小单位。进程之间相互独立,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
    • CPU使用时间片轮转进度算法来实现同时运行多进程。
    • 不同进程之间也可以通信,不过代价较大。
  • 线程(工人):线程是CPU调度的最小单位。一个进程中可以有多个线程,多个线程共享进程资源。
    • 单线程与多线程,都是指在一个进程内的单和多。

# 线程和进程的区别

  • 进程是资源分配的最小单位,线程是CPU调度(即程序执行)的最小单位
  • 一个进程可以包含多个线程,每个进程有自己的独立内存空间,不同进程间数据很难共享
  • 同一进程下不同线程间共享全局变量、静态变量,数据很易共享;
  • 进程要比线程消耗更多的计算机资源;
  • 进程间不会相互影响,而一个线程挂掉将导致其他线程阻塞。

# 进程间通信(6种)

  1. 管道pipe:速度慢,容量有限,半双工(数据只能单向流动),且只有父子进程能通讯
  2. 命名管道namedpipe:同样速度慢,半双工,但是相比pipe,具有任何进程间都能通讯的特点
  3. 消息队列MessageQueue:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
  4. 信号量Semaphore:是一个计数器,不能传递复杂消息,只可用来控制多个进程对共享资源的访问,起同步作用
  5. 共享内存SharedMemory:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
  6. 套接字Socket:与其他通信机制不同的是,它可用于不同及其间的进程通信。

# 线程间通信(3种)

  1. 锁机制:包括互斥锁条件变量读写锁
    • a. 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
    • b. 条件变量对条件的测试是在互斥锁的保护下进行的,始终与互斥锁一起使用。可以以原子的方式阻塞进程,直到某个特定条件为真为止。
    • c. 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
  2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  3. 信号机制(Signal):类似进程间的信号处理线程间的通信目的.主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
多线程中产生死锁的原因和解决死锁的办法
  • 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
  • 产生死锁的原因,概括来说是由于线程间竞争系统资源,或者进程的推进顺序不当引起的
    • 具体来说,如果在一个系统中以下四个条件同时成立,那么就能引起死锁(4个必要条件):
      • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
      • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
      • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
      • 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
  • 解决死锁的办法:
    • 加锁顺序(线程按照一定的顺序加锁)
    • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
    • 死锁检测

# 并行与并发

  • 并行:两个任务同时运行(多个CPU)。
  • 并发:指两个任务同时请求运行,处理器一次只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间很短,就会感觉两个任务同时执行

# 浏览器是多进程的

  • 计算机中的每个应用程序都是一个进程,应用程序可分为多个子模块,这些子模块就对应多个子进程。
  • 浏览器应用程序就是一个大进程,而每个tab页都是一个子进程,所以浏览器是多进程的。

# 浏览器多进程分类

  • 主进程:浏览器的主进程(负责协调、主控),只有一个;
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建;
  • GPU进程:用于3D绘制,最多一个;
  • 渲染进程(浏览器内核)
    • 每个tab页面是一个渲染进程,互不影响。
    • 这个渲染进程也是多线程的,包含5大类线程。

# 渲染进程是多线程的

每一个tab页面可以看作是「渲染进程」进程,然后这个进程是多线程的,它有几大类线程:

  • GUI渲染线程
    • 负责渲染页面,布局和绘制
    • 页面需要重绘和回流时,该线程就会执行
    • 与js引擎线程互斥,防止渲染结果不可预期
  • JS引擎线程
    • 负责处理解析和执行javascript脚本程序
    • 只有一个JS引擎线程(单线程)
    • 与GUI渲染线程互斥,防止渲染结果不可预期
  • 事件触发线程
    • 用来控制事件循环(鼠标点击、setTimeout、ajax等)
    • 当事件满足触发条件时,将事件放入到JS引擎所在的执行队列中
  • 定时器线程
    • setInterval与setTimeout所在的线程
    • 定时任务并不是由JS引擎计时的,是由定时器线程来计时的
    • 计时完毕后,通知事件触发线程
  • 异步http请求线程
    • 浏览器有一个单独的线程用于处理AJAX请求
    • 当请求完成时,若有回调函数,通知事件触发线程

# 两个问题

# 为什么 javascript 是单线程的

  • 创建 js 语言时,多线程架构的硬件支持并不好。
  • 因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。
  • 作为浏览器脚本语言,js的主要用途是与用户互动,以及操作DOM,如果同时操作 DOM ,在多线程不加锁的情况下,最终会导致 DOM 渲染的结果不可预期。
  • 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

# 为什么 GUI渲染线程 与 JS引擎线程 互斥

由于 js 可以操作 DOM,如果同时修改元素属性并同时渲染界面(即 JS线程和GUI线程同时运行),会导致渲染线程前后获得的元素可能不一致。因此,为了防止渲染出现不可预期的结果,浏览器设定 GUI渲染线程和JS引擎线程为互斥关系,当JS引擎线程执行时GUI渲染线程会被挂起,GUI更新则会被保存在一个队列中等待JS引擎线程空闲时立即被执行。

这部分建议与任务队列 与 Event Loop一同食用。

# 浏览器存储

  • Cookie 和 Session都为了用来保存状态信息,都是保存客户端状态的机制,它们都是为了解决HTTP无状态的问题的。
  • Cookie是保存在客户端浏览器的一小段的文本信息,每个domain最多只能有20条Cookie,每个Cookie长度不能超过4KB,否则会被截掉。考虑到安全应当使用Session而不是cookie。
  • Session保存在服务器上。Session的运行依赖Session Id,而Session Id是存在Cookie中的,也就是说,如果浏览器禁用了Cookie,同时Session也会失效(但是可以通过其它方式实现,比如在url中传递session_id)。如果Cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道Cookie的意义,他只要原样转发Cookie就可以达到目的了。考虑到减轻服务器性能方面,应当使用cookie而不是session。
  • 注:
    • cookie默认是不能跨域访问的,但对于主域相同子域不同的两个页面,document.domain设置为相同的父域名,即可实现不同子域名之间的跨域通信。
    • cookie的键/值对中的值不允许出现分号、逗号和空白符,因此在设置cookie前要用encodeURIComponent()编码,读取时再用decodeURIComponent()解码;
    • cookie默认的有效期是浏览器会话期间,作用域是整个浏览器而不仅仅局限于窗口或标签页;

# Cookie属性设置

cookie选项包括:expires、domain、path、secure、HttpOnly。在设置这些属性时,属性之间由一个分号和一个空格隔开:

  • expires:cookie失效日期。
  • domain、path:域名、路径,两者加起来就构成了 URL,domain和path一起来限制 cookie 能被哪些 URL 访问。
  • secure:用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含 secure 选项的 cookie才能被发送至服务器。
  • HttpOnly:用来设置cookie是否能通过 js 去访问。默认情况下不带httpOnly选项,即可以访问。
  • SameSite:用来防止 CSRF 攻击和用户追踪。

服务端设置 cookie

  • 服务端都会返回response。header中有一项叫set-cookie,是服务端专门用来设置cookie的。
  • 一个set-cookie字段只能设置一个cookie,当你要想设置多个 cookie,需要添加同样多的set-Cookie字段。
  • 服务端可以设置cookie 的所有选项:expires、domain、path、secure、HttpOnly

客户端设置 cookie

  • document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";

H5的Web Storage提供了两个拥有一致API的对象:

  • window.localStorage:用来在本地存储永久数据的;
  • window.sessionStorage:用来存储会话数据的(也就是tab不关闭时的数据);
  • 这两个对象存储的都是 name/value 。而且与Cookie不同,Web存储的大小比Cookie的大多了(最少5MB),而且通过这个策略存储的数据是永远不会传输至server的。
  • 纵使Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外Cookie不可以跨域调用。但是Cookie也是不可以或缺的:Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生。
  • 除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像Cookie需要前端开发者自己封装setCookie,getCookie。

# 登录验证方案

# 基于cookie/session的身份验证

基于cookie/session的验证是有状态的,即 验证信息必须同时在客户端和服务端保存。这个信息服务端一般在数据库中记录,而前端会保存在cookie中。

验证的一般流程如下:

  • 用户输入登陆凭据;
  • 服务器验证凭据是否正确,并创建会话,然后把会话数据存储在数据库session中;
  • 具有会话sessionId的cookie被放置在用户浏览器中;
  • 在后续请求中,服务器会根据数据库验证sessionId,如果验证通过,则继续处理;
  • 一旦用户登出,服务端和客户端同时销毁该会话。

# 基于token的身份验证

基于token的验证是无状态的。服务器不记录哪些用户已登陆或者已经发布了哪些JWT(JSON Web Tokens)。对服务器的每个请求都需要带上验证请求的token。该标记既可以加在header中,可以在POST请求的主体中发送,也可以作为查询参数发送。

验证流程如下:

  • 用户输入登陆凭据;
  • 服务器验证凭据是否正确,然后返回一个经过签名的token;
  • 客户端负责存储token,可以存在local storage,或者cookie中;
  • 对服务器的请求带上这个token;
  • 服务器对JWT进行解码,如果token有效,则处理该请求;
  • 一旦用户登出,客户端销毁token。

# token相对cookie的优势

  • 无状态:基于token的验证是无状态的,这也许是它相对cookie来说最大的优点。后端服务不需要记录token。每个令牌都是独立的,包括检查其有效性所需的所有数据,并通过声明传达用户信息。服务器唯一的工作就是在成功的登陆请求上签署token,并验证传入的token是否有效。
  • 防跨站请求伪造(CSRF):使用了token作为验证手段时,攻击者无法获取正确的token,所以可避免CSRF。
  • 性能:一次网络往返时间(通过数据库查询session信息)总比做一次 HMAC-SHA256 计算的Token验证和解析要费时得多。
  • 多站点使用:cookie绑定到单个域。而使用token就没有这样的问题。这对于需要向多个服务获取授权的单页面应用程序尤其有用。
  • 支持移动平台:在移动平台上,cookie是不被支持的,而token可以同时支持浏览器,iOS和Android等移动平台。
Last Updated: 10/19/2020, 4:50:16 PM