function getParamsByName(name){
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regex = new RegExp("[\\?&]"+name+"=([^&#]*)"),
result = regex.exec(location.search);
return results ==null?"":decodeURIComponent(results[1])
}
複製代碼
/** * 功能:1.對象數組根據某一個key進行去重 key爲id, * 1:[{id:1,name:'2'},{id:1,name:'n1'},{id:2,name:'n2'}] =>[{id:1,name:'2'},{id:2,name:'n2'}] * * 2.對象數組按key進行去重,併合併爲一個針對key的數據格式 key爲id,isExtract =true * [{id:1,name:'2'},{id:1,name:'n1'},{id:2,name:'n2'}] =>[1,2] * 參數:soucreArray:數組 key :根據哪一個字段進行去重 isExtract * */
function sweepDuplicatte(sourceArray,key,isExtract){
let storeDesignationkey ={};
const targetArray = sourceArray.reduce((initalTargetArray,currentItem)=>{
if(!storeDesignationKey[currentItem[key]]){
storeDesignationKey[currentItem[key]]=true;
if(isExtract){
initalTargetArray.push(currentItem[key])
}else{
initalTargetArray.push(currentItem);
}
}
return initalTargetArray;
},[]);
return targetArray;
}
複製代碼
? 問號表示某個模式出現0次或1次,等同於{0, 1}。
* 星號表示某個模式出現0次或屢次,等同於{0,}。
+ 加號表示某個模式出現1次或屢次,等同於{1,}。javascript
replace方法的第二個參數能夠使用美圓符號$,用來指代所替換的內容。css
$&:匹配的子字符串。
$`:匹配結果前面的文本。
$’:匹配結果後面的文本。
$n:匹配成功的第n組內容,n是從1開始的天然數。
$$:指代美圓符號$。html
//先後的字符替換位置
'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
'abc'.replace('b', '[$`-$&-$\']') // "a[a-b-c]c" 複製代碼
function loadScript(url,callback){
var script = doucument.createElement('script');
script.type ='text/javascript';
if(script.readyState){
//IE
script.onreadystatechange =function(){
if(script.readyState =='loaded'||script.readyState=='complete'){
script.onreadysatatechange=null;
callback()
}
}
}else{
scripte.onload = function(){
callback();
}
}
script.src =url;
document.getElementByTagName('head')[0].appendChild(script);
}
複製代碼
每個js函數都是一個對象(是Function
對象的一個實例)。前端
內部屬性[[Scope]]包含一個函數被建立的做用域中對象的集合(做用域鏈)。函數做用域中的每一個對象被稱爲一個可變對象,而且以"鍵值對"的形式存在。java
執行函數時會建立一個執行環境(execution context)的內部對象。一個執行環境定義了一個函數執行時的環境。每次執行都不同。函數執行完畢,執行環節會被銷燬。node
每一個執行環境都有本身的做用域鏈,用於解析標識符。當執行環境被建立時,它的做用域鏈初始化爲當前運行函數的[[Scope]]屬性中的對象。這個複製過程完成後,一個唄稱爲"活動對象(activation object)"的新對象就爲執行環境建立好了。活動對象做爲函數運行時的變量對象,包含了全部局部變量,命名參數,參數集合以及this.而後此對象被推入做用域鏈的最前端。nginx
容許函數訪問局部做用域以外的數據。c++
文檔對象模型(DOM)是一個獨立於語言的,用於操做XML和HTML文檔的程序接口(API)。web
瀏覽器中一般會把DOM和JavaScript獨立實現。正則表達式
瀏覽器 | js引擎 | 渲染引擎 |
---|---|---|
IE | JScript(舊) Chakra(新) |
Trident |
Safari | JavaScriptCore(SquirreFish) | WebCore |
Chorme | V8 | WebCore |
FireFox | TraceMonkey | Gecko |
HTTP over TCP/IP
HTTP 是一個用在計算機世界裏的協議,它確立了一種計算機之間交流通訊的規範,以及相關的各類控制和錯誤處理方式。
HTTP 專門用來在兩點之間傳輸數據,不能用於廣播、尋址或路由。
HTTP 傳輸的是文字、圖片、音頻、視頻等超文本數據。
IP 協議是「Internet Protocol」的縮寫,主要目的是解決尋址和路由問題,以及如何在兩點間傳送數據包。IP 協議使用「IP 地址」的概念來定位互聯網上的每一臺計算機。
TCP 協議是「Transmission Control Protocol」的縮寫,意思是「傳輸控制協議」,它位於 IP 協議之上,基於 IP 協議提供可靠的、字節流形式的通訊,是 HTTP 協議得以實現的基礎。
「可靠」是指保證數據不丟失,「字節流」是指保證數據完整,因此在 TCP 協議的兩端能夠如同操做文件同樣訪問傳輸的數據,就像是讀寫在一個密閉的管道里「流動」的字節。
URI(Uniform Resource Identifier)
中文名稱是 統一資源標識符,使用它就可以惟一地標記互聯網上資源。
URL(Uniform Resource Locator)
URI 另外一個更經常使用的表現形式是 , 統一資源定位符,也就是咱們俗稱的「網址」,它其實是 URI 的一個子集
http://nginx.org/en/download.html
複製代碼
HTTP over SSL/TLS也就是運行在 SSL/TLS 協議上的 HTTP.
爲HTTP 套了一個安全的外殼
HTTP+SSL/TLS+TCP/IP
SSL 使用了許多密碼學最早進的研究成果,綜合了對稱加密、非對稱加密、摘要算法、數字簽名、數字證書等技術,可以在不安全的環境中爲通訊的雙方建立出一個祕密的、安全的傳輸通道,爲 HTTP 套上一副堅固的盔甲。
負責在以太網、WiFi 這樣的底層網絡上發送原始數據包,工做在網卡這個層次,使用 MAC 地址來標記網絡上的設備,因此有時候也叫 MAC 層。
IP 協議就處在這一層。由於 IP 協議定義了IP 地址的概念,因此就能夠在「連接層」的基礎上,用 IP 地址取代 MAC 地址,把許許多多的局域網、廣域網鏈接成一個虛擬的巨大網絡,在這個網絡裏找設備時只要把 IP 地址再「翻譯」成 MAC 地址就能夠了
保證數據在 IP 地址標記的兩點之間「可靠」地傳輸,是 TCP 協議工做的層次,另外還有它的一個「小夥伴」UDP。
兩個協議的另外一個重要區別在於數據的形式。
Telnet、SSH、FTP、SMTP,HTTP。
開放式系統互聯通訊參考模型(Open System Interconnection Reference Model)
使用字符串來代替 IP 地址,方便用戶記憶,本質上一個名字空間系統
DNS 是一個樹狀的分佈式查詢系統,但爲了提升查詢效率,外圍有多級的緩存
偏向於不容忍隱式類型轉換
偏向於容忍隱式類型轉換。譬如說C語言的int能夠變成double
編譯的時候就知道每個變量的類型,由於類型錯誤而不能作的事情是語法錯誤。
編譯的時候不知道每個變量的類型,由於類型錯誤而不能作的事情是運行時錯誤
漸進複雜度,包括時間複雜度和空間複雜度,用來分析算法執行效率與數據規模之間的增加關係,能夠粗略地表示,越高階複雜度的算法,執行效率越低
從低階到高階有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )
一種線性表數據結構。它用一組連續的內存空間,來存儲一組具備相同類型的數據。
線性表
As JavaScript arrays are implemented as hash-maps(哈希表) or dictionaries(字典) and not contiguous.(非連續)
HTTP 協議是運行在 TCP/IP 基礎上的,依靠 TCP/IP 協議來實現數據的可靠傳輸。因此瀏覽器要用 HTTP 協議收發數據,首先要作的就是創建 TCP 鏈接
具體交互過程以下
在使用域名訪問網頁的時候,多一步操做就是DNS的解析。在拿到對應的IP地址以後,操做流程同IP訪問是同樣的。
拿TCP 報文來舉例,它在實際要傳輸的數據以前附加了一個 20 字節的頭部數據,存儲 TCP 協議必須的額外信息,例如發送方的端口號、接收方的端口號、包序號、標誌位等等
HTTP 協議也是與 TCP/UDP 相似,一樣也須要在實際傳輸的數據前附加一些頭數據,不過與 TCP/UDP 不一樣的是,它是一個純文本的協議,因此頭數據都是 ASCII 碼的文本。
HTTP 協議的請求報文和響應報文的結構基本相同,由三大部分組成:
其中前兩部分起始行和頭部字段常常又合稱爲「請求頭」或「響應頭」,消息正文又稱爲「實體」,但與「header」對應,不少時候就直接稱爲「body」。
HTTP 協議規定報文必須有 header,但能夠沒有 body,並且在 header 以後必需要有一個空行,也就是「CRLF」,十六進制的「0D0A」。
HTTP 報文的格式以下:
如上的報文中:第一行「GET / HTTP/1.1」就是請求行,然後面的「Host」「Connection」等等都屬於 header,報文的最後是一個空白行結束,沒有 body
描述了客戶端想要如何操做服務器端的資源
請求行由三部分構成:
描述了服務器響應的狀態
狀態行也是由三部分組成
請求行或狀態行再加上頭部字段集合就構成了 HTTP 報文裏完整的請求頭或響應頭
它並不須要一塊連續的內存空間,它經過「指針」將一組零散的內存塊串聯起來使用
內存分配
鏈表經過指針將一組零散的內存塊串聯在一塊兒。其中,咱們把內存塊稱爲鏈表的「結點」
其中有兩個結點是比較特殊的,它們分別是第一個結點和最後一個結點。咱們習慣性地把第一個結點叫做頭結點,把最後一個結點叫做尾結點。其中,頭結點用來記錄鏈表的基地址。有了它,咱們就能夠遍歷獲得整條鏈表。而尾結點特殊的地方是:指針不是指向下一個結點,而是指向一個空地址 NULL,表示這是鏈表上最後一個結點。
循環鏈表的尾結點指針是指向鏈表的頭結點
雙向鏈表須要額外的兩個空間來存儲後繼結點和前驅結點的地址。因此,若是存儲一樣多的數據,雙向鏈表要比單鏈表佔用更多的內存空間。
數組中隨機訪問,指的是按下標的隨機訪問
大體的結構以下:
size和head爲LinkedList構造函數私有屬性,size記錄鏈表中有多少個節點,head指向鏈表的頭結點
/**
* 自定義鏈表:對外公開的方法有
* append(element) 在鏈表最後追加節點
* insert(index, element) 根據索引index, 在索引位置插入節點
* remove(element) 刪除節點
* removeAt(index) 刪除指定索引節點
* removeAll(element) 刪除全部匹配的節點
* set(index, element) 根據索引,修改對應索引的節點值
* get(index) 根據索引獲取節點信息
* indexOf(element) 獲取某個節點的索引位置
* clear() 清空全部節點
* length() 返回節點長度
* print() 打印全部節點信息
* toString() 打印全部節點信息,同print
* */
const LinkedList = function(){
let head = null;
let size = 0; //記錄鏈表元素個數
//Node模型
function LinkNode(element, next){
this.element = element;
this.next = next;
}
//元素越界檢查, 越界拋出異常
function outOfBounds(index){
if (index < 0 || index >= size){
throw("抱歉,目標位置不存在!");
}
}
//根據索引,獲取目標對象
function node(index){
outOfBounds(index);
let obj = head;
for (let i = 0; i < index; i++){
obj = obj.next;
}
return obj;
}
//新增一個元素
function append(element){
if (size == 0){
head = new LinkNode(element, null);
}
else{
let obj = node(size-1);
obj.next = new LinkNode(element, null);
}
size++;
}
//插入一個元素
function insert(index, element){
if (index == 0){
head = new LinkNode(element, head);
}
else{
let obj = node(index-1);
obj.next = new LinkNode(element, obj.next);
}
size++;
}
//修改元素
function set(index, element){
let obj = node(index);
obj.element = element;
}
//根據值移除節點元素
function remove(element){
if (size < 1) return null;
if (head.element == element){
head = head.next;
size--;
return element;
}
else{
let temp = head;
while(temp.next){
if (temp.next.element == element){
temp.next = temp.next.next;
size--;
return element;
}
else{
temp = temp.next;
}
}
}
return null;
}
//根據索引移除節點
function removeAt(index){
outOfBounds(index);
let element = null;
if (index == 0){
element = head.element;
head = head.next;
}
else{
let prev = node(index-1);
element = prev.next.element;
prev.next = prev.next.next;
}
size--;
return element;
}
//移除鏈表裏面的全部匹配值element的元素
function removeAll(element){
let virHead = new LinkNode(null, head); //建立一個虛擬頭結點,head爲次節點
let tempNode = virHead, ele = null;
while(tempNode.next){
if (tempNode.next.element == element){
tempNode.next = tempNode.next.next;
size--;
ele = element;
}
else{
tempNode = tempNode.next;
}
}
//從新賦值
head = virHead.next;
return ele;
}
//獲取某個元素
function get(index){
return node(index).element;
}
//獲取元素索引
function indexOf(element){
let obj = head, index = -1;
for (let i = 0; i < size; i++){
if (obj.element == element){
index = i;
break;
}
obj = obj.next;
}
return index;
}
//清除全部元素
function clear(){
head = null;
size = 0;
}
//屬性轉字符串
function getObjString(obj){
let str = "";
if (obj instanceof Array){
str += "[";
for (let i = 0; i < obj.length; i++){
str += getObjString(obj[i]);
}
str = str.substring(0, str.length - 2);
str += "], "
}
else if (obj instanceof Object){
str += "{";
for (var key in obj){
let item = obj[key];
str += "\"" + key + "\": " + getObjString(item);
}
str = str.substring(0, str.length-2);
str += "}, "
}
else if (typeof obj == "string"){
str += "\"" + obj + "\"" + ", ";
}
else{
str += obj + ", ";
}
return str;
}
function toString(){
let str = "", obj = head;
for (let i = 0; i < size; i++){
str += getObjString(obj.element);
obj = obj.next;
}
if (str.length > 0) str = str.substring(0, str.length -2);
return str;
}
//打印全部元素
function print(){
console.log(this.toString())
}
//對外公開方法
this.append = append;
this.insert = insert;
this.remove = remove;
this.removeAt = removeAt;
this.removeAll = removeAll;
this.set = set;
this.get = get;
this.indexOf = indexOf;
this.length = function(){
return size;
}
this.clear = clear;
this.print = print;
this.toString = toString;
}
////測試
// let obj = new LinkedList();
// let obj1 = { title: "全明星比賽", stores: [{name: "張飛vs岳飛", store: "2:3"}, { name: "關羽vs秦瓊", store: "5:5"}]};
//
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();
////測試2
console.log("\n\n......test2.....")
var obj2 = new LinkedList();
obj2.append(8); obj2.insert(1,99); obj2.append('abc'); obj2.append(8); obj2.append(false);
obj2.append(12); obj2.append(8); obj2.append('123'); obj2.append(8);
obj2.print();
obj2.removeAll(8); //刪除全部8
obj2.print();
複製代碼
/**
*
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let prev = null;
let curr = head;//定義"哨兵"
while (curr != null) {
let nextTemp = curr.next;//暫存剩餘鏈表
curr.next = prev;//與剩餘鏈表斷開鏈接,並將指向變動
prev = curr;//兩個節點交互位置
curr = nextTemp;//指針下移
}
return prev;
};
複製代碼
統一資源標識符(Uniform Resource Identifier)。由於它常常出如今瀏覽器的地址欄裏,因此俗稱爲「網絡地址」,簡稱「網址」。
URI 本質上是一個字符串,這個字符串的做用是惟一地標記資源的位置或者名字。
具體格式以下:
note: user:passwd@ 已經不推薦使用了
URI 裏只能使用 ASCII 碼
URI 引入了編碼機制,對於 ASCII 碼之外的字符集和特殊字符作一個特殊的操做,把它們轉換成與 URI 語義不衝突的形式。這在 RFC 規範裏稱爲「escape」和「unescape」,俗稱「轉義」。
響應狀態行格式以下:
RFC 標準把狀態碼分紅了五類
在 TCP/IP 協議棧裏,傳輸數據基本上都是「header+body」的格式。
MIME:多用途互聯網郵件擴展」(Multipurpose Internet Mail Extensions)用於發送 ASCII 碼之外的任意數據
MIME 把數據分紅了八大類,每一個大類下再細分出多個子類,形式是type/subtype的字符串。
經常使用的以下:
HTTP 在傳輸時爲了節約帶寬,有時候還會壓縮數據,有一個「Encoding type」,告訴數據是用的什麼編碼格式,這樣對方纔能正確解壓縮,還原出原始的數據。
有了 MIME type 和 Encoding type,不管是瀏覽器仍是服務器就均可以輕鬆識別出 body 的類型,也就可以正確處理數據了。
HTTP 協議爲此定義了兩個 Accept 請求頭字段和兩個** Content** 實體頭字段,用於客戶端和服務器進行「內容協商」。也就是說,客戶端用 Accept 頭告訴服務器但願接收什麼樣的數據,而服務器用 Content 頭告訴客戶端實際發送了什麼樣的數據。
Accept字段標記的是客戶端可理解的 MIME type,能夠用「,」作分隔符列出多個類型,讓服務器有更多的選擇餘地。
相應的,服務器會在響應報文裏用頭字段Content-Type告訴實體數據的真實類型。
MIME type 和 Encoding type 解決了計算機理解 body 數據的問題,但互聯網遍及全球,不一樣國家不一樣地區的人使用了不少不一樣的語言,雖然都是 text/html,但如何讓瀏覽器顯示出每一個人均可理解可閱讀的語言文字呢?
這實際上就是「國際化」的問題。HTTP 採用了與數據類型類似的解決方案,又引入了兩個概念:語言類型與字符集。
語言類型:人類使用的天然語言,例如英語、漢語、日語等,而這些天然語言可能還有下屬的地區性方言,因此在須要明確區分的時候也要使用「type-subtype」的形式,不過這裏的格式與數據類型不一樣,分隔符不是/,而是-。
HTTP 協議也使用 Accept 請求頭字段和 Content 實體頭字段,用於客戶端和服務器就語言與編碼進行「內容協商」。
Accept-Language字段標記了客戶端可理解的天然語言,也容許用「,」作分隔符列出多個類型,例如:
Accept-Language: zh-CN, zh, en
複製代碼
相應的,服務器應該在響應報文裏用頭字段Content-Language告訴客戶端實體數據使用的實際語言類型:
Content-Language: zh-CN
複製代碼
字符集在 HTTP 裏使用的請求頭字段是Accept-Charset,但響應頭裏卻沒有對應的 Content-Charset,而是在Content-Type字段的數據類型後面用charset=xxx來表示,這點須要特別注意。
一般瀏覽器在發送請求時都會帶着Accept-Encoding頭字段,裏面是瀏覽器支持的壓縮格式列表,例如 gzip、deflate、br 等,這樣服務器就能夠從中選擇一種壓縮算法,放進Content-Encoding響應頭裏,再把原數據壓縮後發給瀏覽器。
壓縮是把大文件總體變小,若是大文件總體不能變小,那就把它拆開,分解成多個小塊,把這些小塊分批發給瀏覽器,瀏覽器收到後再組裝復原。
這種化整爲零的思路在 HTTP 協議裏就是chunked分塊傳輸編碼,在響應報文裏用頭字段Transfer-Encoding: chunked來表示,意思是報文裏的 body 部分不是一次性發過來的,而是分紅了許多的塊(chunk)逐個發送。
分塊傳輸也能夠用於流式數據,例如由數據庫動態生成的表單頁面,這種狀況下 body 數據的長度是未知的,沒法在頭字段Content-Length裏給出確切的長度,因此也只能用 chunked 方式分塊發送。
Transfer-Encoding: chunked和Content-Length這兩個字段是互斥的,也就是說響應報文裏這兩個字段不能同時出現,一個響應報文的傳輸要麼是長度已知,要麼是長度未知(chunked)。
有了分塊傳輸編碼,服務器就能夠輕鬆地收發大文件了,可是若是想獲取一個大文件其中的片斷數據,而分塊傳輸並無這個能力。
HTTP 協議爲了知足這樣的需求,提出了範圍請求(range requests)的概念,容許客戶端在請求頭裏使用專用字段來表示只獲取文件的一部分,至關因而客戶端的「化整爲零」。
範圍請求不是 Web 服務器必備的功能,能夠實現也能夠不實現,因此服務器必須在響應頭裏使用字段Accept-Ranges: bytes明確告知客戶端,支持範圍請求。
請求頭Range是 HTTP 範圍請求的專用字段,格式是bytes=x-y,其中的 x 和 y 是以字節爲單位的數據範圍。
x、y 表示的是「偏移量」,範圍必須從 0 計數。
服務器收到 Range 字段後,須要作四件事。
第一,它必須檢查範圍是否合法,好比文件只有 100 個字節,但請求「200-300」,這就是範圍越界了。服務器就會返回狀態碼416,意思是「你的範圍請求有誤,我沒法處理,請再檢查一下」。
第二,若是範圍正確,服務器就能夠根據 Range 頭計算偏移量,讀取文件的片斷了,返回狀態碼206 Partial Content,和 200 的意思差很少,但表示 body 只是原數據的一部分。
第三,服務器要添加一個響應頭字段Content-Range,告訴片斷的實際偏移量和資源的總大小,格式是bytes x-y/length,與 Range 頭區別在沒有「=」,範圍後多了總長度。例如,對於「0-10」的範圍請求,值就是「bytes 0-10/100」。
最後剩下的就是發送數據了,直接把片斷用** TCP** 發給客戶端,一個範圍請求就算是處理完了。
剛纔說的範圍請求一次只獲取一個片斷,其實它還支持在 Range 頭裏使用多個x-y,一次性獲取多個片斷數據。
這種狀況須要使用一種特殊的 MIME 類型:multipart/byteranges,表示報文的 body 是由多段字節序列組成的,而且還要用一個參數boundary=xxx給出段之間的分隔標記。
關於棧,有一個很是貼切的例子,就是一摞疊在一塊兒的盤子。咱們平時放盤子的時候,都是從下往上一個一個放;取的時候,咱們也是從上往下一個一個地依次取,不能從中間任意抽出。後進者先出,先進者後出,這就是典型的「棧」結構。
從棧的操做特性上來看
棧是一種「操做受限」的線性表,只容許在一端插入和刪除數據。
實際上,棧既能夠用數組來實現,也能夠用鏈表來實現。用數組實現的棧,咱們叫做順序棧,用鏈表實現的棧,咱們叫做鏈式棧。(這也僅僅針對的是C,C++,JAVA等靜態類型語言)
操做系統給每一個線程分配了一塊獨立的內存空間,這塊內存被組織成「棧」這種結構, 用來存儲函數調用時的臨時變量。每進入一個函數,就會將臨時變量做爲一個棧幀入棧,當被調用函數執行完成,返回以後,將這個函數對應的棧幀出棧。
function main() {
let a = 1;
let ret = 0;
let res = 0;
ret = add(3, 5);
res = a + ret;
console.log(`處理結果爲 ${res}`);
reuturn 0;
}
function add( x, y) {
let sum = 0;
sum = x + y;
return sum;
}
複製代碼
圖中顯示的是,在執行到 add() 函數時,函數調用棧的狀況。
實際上,編譯器就是經過兩個棧來實現的。其中一個保存操做數的棧,另外一個是保存運算符的棧。咱們從左向右遍歷表達式,當遇到數字,咱們就直接壓入操做數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較。
將 3+5*8-6 這個表達式的計算過程畫成了一張圖,以下:
###實現瀏覽器的前進、後退功能
使用兩個棧,X 和 Y,咱們把首次瀏覽的頁面依次壓入棧 X,當點擊後退按鈕時,再依次從棧 X 中出棧,並將出棧的數據依次放入棧 Y。當咱們點擊前進按鈕時,咱們依次從棧 Y 中取出數據,放入棧 X 中。當棧 X 中沒有數據時,那就說明沒有頁面能夠繼續後退瀏覽了。當棧 Y 中沒有數據,那就說明沒有頁面能夠點擊前進按鈕瀏覽了。
如此反覆如上操做,就能夠簡單解釋瀏覽器回退和前進的操做步驟。
內存中的堆棧和數據結構堆棧不是一個概念,能夠說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。
內存空間在邏輯上分爲三部分:代碼區、靜態數據區和動態數據區,動態數據區又分爲棧區和堆區。
HTTP 協議最初(0.9/1.0)是個很是簡單的協議,通訊過程也採用了簡單的請求 - 應答方式。
它底層的數據傳輸基於 TCP/IP,每次發送請求前須要先與服務器創建鏈接,收到響應報文後會當即關閉鏈接。
由於客戶端與服務器的整個鏈接過程很短暫,不會與服務器保持長時間的鏈接狀態,因此就被稱爲短鏈接(short-lived connections)。早期的 HTTP 協議也被稱爲是無鏈接的協議。
由於在 TCP 協議裏,創建鏈接和關閉鏈接都是很是「昂貴」的操做。TCP 創建鏈接要有「三次握手」,發送 3 個數據包,須要 1 個 RTT;關閉鏈接是「四次揮手」,4 個數據包須要 2 個 RTT。
而 HTTP 的一次簡單「請求 - 響應」一般只須要 4 個包,若是不算服務器內部的處理時間,最可能是 2 個 RTT。這麼算下來,浪費的時間就是「3÷5=60%」,有三分之二的時間被浪費掉了,傳輸效率低得驚人。
RTT(Round-Trip Time):往返時延。是指數據從網絡一端傳到另外一端所需的時間。一般,時延由發送時延、傳播時延、排隊時延、處理時延四個部分組成。
針對短鏈接暴露出的缺點,HTTP 協議就提出了長鏈接的通訊方式,也叫持久鏈接(persistent connections)、鏈接保活(keep alive)、鏈接複用(connection reuse)。
其實解決辦法也很簡單,用的就是成本均攤的思路,既然 TCP 的鏈接和關閉很是耗時間,那麼就把這個時間成本由原來的一個「請求 - 應答」均攤到多個「請求 - 應答」上。
一個短鏈接與長鏈接的對比示意圖。
因爲長鏈接對性能的改善效果很是顯著,因此在 HTTP/1.1 中的鏈接都會默認啓用長鏈接。不須要用什麼特殊的頭字段指定,只要向服務器發送了第一次請求,後續的請求都會重複利用第一次打開的 TCP 鏈接,也就是長鏈接,在這個鏈接上收發數據。
固然,咱們也能夠在請求頭裏明確地要求使用長鏈接機制,使用的字段是Connection,值是keep-alive。
服務器支持長鏈接,它總會在響應報文裏放一個Connection: keep-alive字段。
由於 TCP 鏈接長時間不關閉,服務器必須在內存裏保存它的狀態,這就佔用了服務器的資源。若是有大量的空閒長鏈接只連不發,就會很快耗盡服務器的資源,致使服務器沒法爲真正有須要的用戶提供服務。
長鏈接也須要在恰當的時間關閉,不能永遠保持與服務器的鏈接,這在客戶端或者服務器均可以作到。
在客戶端,能夠在請求頭裏加上Connection: close字段,告訴服務器:「此次通訊後就關閉鏈接」。
服務器端一般不會主動關閉鏈接,但也能夠使用一些策略。拿 Nginx 來舉例,它有兩種方式:
隊頭阻塞與短鏈接和長鏈接無關,而是由 HTTP 基本的請求 - 應答模型所致使的。
由於 HTTP 規定報文必須是一發一收,這就造成了一個先進先出的「串行」隊列。隊列裏的請求沒有輕重緩急的優先級,只有入隊的前後順序,排在最前面的請求被最優先處理。
主動跳轉:跳轉動做是由瀏覽器的使用者主動發起(經過
<a/>
標籤進行頁面跳轉)
被動跳轉:服務器來發起的,瀏覽器使用者沒法控制,這在 HTTP 協議裏有個專門的名詞,叫作重定向(Redirection)。
一次重定向實際上發送了兩次 HTTP 請求,第一個請求返回了 302,而後第二個請求就被重定向到了目標地址(URI)。
Location字段屬於響應字段,必須出如今響應報文裏。但只有配合 301/302 狀態碼纔有意義,它標記了服務器要求重定向的** URI**。
在Location裏的 URI 既能夠使用絕對 URI,也能夠使用相對 URI。
絕對 URI:就是完整形式的 URI,包括 scheme、host:port、path 等。
相對 URI:就是省略了 scheme 和 host:port,只有 path 和 query 部分,是不完整的,但能夠從請求上下文裏計算獲得。
最多見的重定向狀態碼就是 301 和 302
HTTP 是無狀態的,這既是優勢也是缺點。優勢是服務器沒有狀態差別,能夠很容易地組成集羣,而缺點就是沒法支持須要記錄狀態的事務操做。
Cookie 是服務器委託瀏覽器存儲的一些數據,讓服務器有了「記憶能力」;
響應頭字段Set-Cookie和請求頭字段Cookie。
當用戶經過瀏覽器第一次訪問服務器的時候,服務器確定是不知道他的身份的。因此,就要建立一個獨特的身份標識數據,格式是key=value,而後放進 Set-Cookie 字段裏,隨着響應報文一同發給瀏覽器。
瀏覽器收到響應報文,看到裏面有 Set-Cookie,知道這是服務器給的身份標識,因而就保存起來,下次再請求的時候就自動把這個值放進 Cookie 字段裏發給服務器。
由於第二次請求裏面有了 Cookie 字段,服務器就知道這個用戶不是新人,以前來過,就能夠拿出 Cookie 裏的值,識別出用戶的身份,而後提供個性化的服務。
不過由於服務器的「記憶能力」實在是太差,一張小紙條常常不夠用。因此,服務器有時會在響應頭裏添加多個 Set-Cookie,存儲多個「key=value」。但瀏覽器這邊發送時不須要用多個 Cookie 字段,只要在一行裏用「;」隔開就行。
Cookie 就是服務器委託瀏覽器存儲在客戶端裏的一些數據,而這些數據一般都會記錄用戶的關鍵識別信息。因此,就須要在「key=value」外再用一些手段來保護,防止外泄或竊取,這些手段就是** Cookie 的屬性**。
讓它只能在一段時間內可用,就像是食品的「保鮮期」,一旦超過這個期限瀏覽器就認爲是 Cookie 失效,在存儲裏刪除,也不會發送給服務器。
Cookie 的有效期能夠使用 Expires 和** Max-Age **兩個屬性來設置。
Expires俗稱過時時間,用的是絕對時間點,能夠理解爲「截止日期」(deadline)。
Max-Age用的是相對時間,單位是秒,瀏覽器用收到報文的時間點再加上 Max-Age,就能夠獲得失效的絕對時間。
Expires 和 Max-Age 能夠同時出現,二者的失效時間能夠一致,也能夠不一致,但瀏覽器會優先採用 Max-Age 計算失效期。
讓瀏覽器僅發送給特定的服務器和 URI,避免被其餘網站盜用。
做用域的設置比較簡單,Domain和Path指定了 Cookie 所屬的域名和路徑,瀏覽器在發送 Cookie 前會從 URI 中提取出 host 和 path 部分,對比 Cookie 的屬性。若是不知足條件,就不會在請求頭裏發送 Cookie。
在 JS 腳本里能夠用 document.cookie來讀寫 Cookie 數據,這就帶來了安全隱患,有可能會致使跨站腳本(XSS)攻擊竊取數據。
屬性HttpOnly會告訴瀏覽器,此 Cookie 只能經過瀏覽器 HTTP 協議傳輸,禁止其餘方式訪問,瀏覽器的 JS 引擎就會禁用 document.cookie 等一切相關的 API,腳本攻擊也就無從談起了。
另外一個屬性SameSite能夠防範跨站請求僞造(XSRF)攻擊,設置成SameSite=Strict能夠嚴格限定 Cookie 不能隨着跳轉連接跨站發送.
緩存(Cache)是計算機領域裏的一個重要概念,是優化系統性能的利器。
基於請求 - 應答模式的特色,能夠大體分爲客戶端緩存和服務器端緩存。
HTTP緩存流程 就是:
服務器標記資源有效期使用的頭字段是Cache-Control,裏面的值max-age=x就是資源的有效時間,至關於告訴瀏覽器,「這個頁面只能緩存 x 秒,以後就算是過時,不能用。」
Cache-Control字段裏的max-age和 Cookie 有點像,都是標記資源的有效期。
這裏的max-age是「生存時間」(又叫「新鮮度」「緩存壽命」,相似 TTL,Time-To-Live),時間的計算起點是響應報文的建立時刻(即 Date 字段,也就是離開服務器的時刻),而不是客戶端收到報文的時刻,也就是說包含了在鏈路傳輸過程當中全部節點所停留的時間。
max-age是 HTTP 緩存控制最經常使用的屬性,此外在響應報文裏還能夠用其餘的屬性來更精確地指示瀏覽器應該如何使用緩存:
把服務器的緩存控制策略畫了一個流程圖,
不止服務器能夠發Cache-Control頭,瀏覽器也能夠發Cache-Control,也就是說請求 - 應答的雙方均可以用這個字段進行緩存控制,互相協商緩存的使用策略。
當你點刷新按鈕的時候,瀏覽器會在請求頭里加一個Cache-Control: max-age=0。由於 max-age 是「生存時間」,max-age=0 的意思就是我要最新的數據,而本地緩存裏的數據至少保存了幾秒鐘,因此瀏覽器就不會使用緩存,而是向服務器發請求。服務器看到 max-age=0,也就會用一個最新生成的報文迴應瀏覽器。
Ctrl+F5 的強制刷新,它實際上是發了一個Cache-Control: no-cache,含義和**max-age=**0基本同樣,就看後臺的服務器怎麼理解,一般二者的效果是相同的。
瀏覽器用Cache-Control作緩存控制只能是刷新數據,不能很好地利用緩存數據,又由於緩存會失效,使用前還必需要去服務器驗證是不是最新版。
HTTP 協議就定義了一系列If開頭的條件請求字段,專門用來檢查驗證資源是否過時。
條件請求一共有 5 個頭字段,咱們最經常使用的是if-Modified-Since和If-None-Match這兩個。須要第一次的響應報文預先提供Last-modified和ETag,而後第二次請求時就能夠帶上緩存裏的原值,驗證資源是不是最新的。
若是資源沒有變,服務器就回應一個304 Not Modified,表示緩存依然有效,瀏覽器就能夠更新一下有效期,而後放心大膽地使用緩存了。
ETag 是實體標籤(Entity Tag)的縮寫,是資源的一個惟一標識,主要是用來解決修改時間沒法準確區分文件變化的問題。
ETag 還有「強」「弱」之分。 強 ETag 要求資源在字節級別必須徹底相符,弱 ETag 在值前有個「W/」標記,只要求資源在語義上沒有變化,但內部可能會有部分發生了改變(例如 HTML 裏的標籤順序調整,或者多了幾個空格)。
都會保存到瀏覽器中,並能夠設置過時時間。
引入 HTTP 代理後,原來簡單的雙方通訊就變複雜了一些,加入了一個或者多箇中間人,但總體上來看,仍是一個有順序關係的鏈條,並且鏈條裏相鄰的兩個角色仍然是簡單的一對一通訊,不會出現越級的狀況。
鏈條的起點仍是客戶端(也就是瀏覽器),中間的角色被稱爲代理服務器(proxy server),鏈條的終點被稱爲源服務器(origin server),意思是數據的「源頭」「起源」。
代理服務就是指服務自己不生產內容,而是處於中間位置轉發上下游的請求和響應,具備雙重身份:面向下游的用戶時,表現爲服務器,表明源服務器響應客戶端的請求;而面向上游的源服務器時,又表現爲客戶端,表明客戶端發送請求。
代理最基本的一個功能是負載均衡。由於在面向客戶端時屏蔽了源服務器,客戶端看到的只是代理服務器,源服務器究竟有多少臺、是哪些 IP 地址都不知道。因而代理服務器就能夠掌握請求分發的「大權」,決定由後面的哪臺服務器來響應請求。
在負載均衡的同時,代理服務還能夠執行更多的功能,好比:
代理服務器須要用字段Via標明代理的身份。
Via 是一個通用字段,請求頭或響應頭裏均可以出現。每當報文通過一個代理節點,代理服務器就會把自身的信息追加到字段的末尾,就像是經手人蓋了一個章。
例以下圖中有兩個代理:proxy1 和 proxy2,客戶端發送請求會通過這兩個代理,依次添加就是「Via: proxy1, proxy2」,等到服務器返回響應報文的時候就要反過來走,頭字段就是「Via: proxy2, proxy1」。
服務器的 IP 地址應該是保密的,關係到企業的內網安全,因此通常不會讓客戶端知道。不過反過來,一般服務器須要知道客戶端的真實 IP 地址,方便作訪問控制、用戶畫像、統計分析。
HTTP 標準裏並無爲此定義頭字段,但已經出現了不少事實上的標準,最經常使用的兩個頭字段是X-Forwarded-For和X-Real-IP。
X-Forwarded-For的字面意思是爲誰而轉發,形式上和Via差很少,也是每通過一個代理節點就會在字段裏追加一個信息。但Via追加的是代理主機名(或者域名),而X-Forwarded-For追加的是請求方的 IP 地址。因此,在字段裏最左邊的 IP 地址就客戶端的地址。
X-Real-IP是另外一種獲取客戶端真實 IP 的手段,它的做用很簡單,就是記錄客戶端 IP 地址,沒有中間的代理信息,至關因而「X-Forwarded-For」的簡化版。若是客戶端和源服務器之間只有一個代理,那麼這兩個字段的值就是相同的。
在沒有緩存的時候,代理服務器每次都是直接轉發客戶端和服務器的報文,中間不會存儲任何數據,只有最簡單的中轉功能。
###加入了緩存後以後
代理服務收到源服務器發來的響應數據後須要作兩件事。第一個固然是把報文轉發給客戶端,而第二個就是把報文存入本身的 Cache 裏。
下一次再有相同的請求,代理服務器就能夠直接發送 304 或者緩存數據,沒必要再從源服務器那裏獲取。這樣就下降了客戶端的等待時間,同時節約了源服務器的網絡帶寬。
在 HTTP 的緩存體系中,緩存代理的身份十分特殊,它既是客戶端,又是服務器,同時也既不是客戶端,又不是服務器。
緩存代理便是客戶端又是服務器
是由於它面向源服務器時是客戶端,在面向客戶端時又是服務器,因此它便可以用客戶端的緩存控制策略也能夠用服務器端的緩存控制策略,也就是說它能夠同時使用各類Cache-Control屬性。
緩存代理即不是客戶端又不是服務器
由於它只是一個數據的中轉站,並非真正的數據消費者和生產者,因此還須要有一些新的Cache-Control屬性來對它作特別的約束
服務器端的Cache-Control屬性**:max-age、no_store、no_cache 和 must-revalidate這 4 種緩存屬性能夠約束客戶端,也能夠約束代理**。
但客戶端和代理是不同的,客戶端的緩存只是用戶本身使用,而代理的緩存可能會爲很是多的客戶端提供服務。因此,須要對它的緩存再多一些限制條件。
首先,要區分客戶端上的緩存和代理上的緩存,能夠使用兩個新屬性private和public。
private表示緩存只能在客戶端保存,是用戶私有的,不能放在代理上與別人共享。而public的意思就是緩存徹底開放,誰均可以存,誰均可以用。
而後,緩存失效後的從新驗證也要區分開(即便用條件請求Last-modified和ETag),must-revalidate是隻要過時就必須回源服務器驗證,而新的proxy-revalidate只要求代理的緩存過時後必須驗證,客戶端沒必要回源,只驗證到代理這個環節就好了。
再次,緩存的生存時間能夠使用新的s-maxage(s 是 share 的意思,注意 maxage 中間沒有「-」),只限定在代理上可以存多久,而客戶端仍然使用「max_age」。
還有一個代理專用的屬性no-transform。代理有時候會對緩存下來的數據作一些優化,好比把圖片生成 png、webp 等幾種格式,方便從此的請求處理,而no-transform就會禁止這樣作,不準「偷偷摸摸搞小動做」。
下面的流程圖是完整的服務器端緩存控制策略,能夠同時控制客戶端和代理。
客戶端在 HTTP 緩存體系裏要面對的是代理和源服務器
max-stale的意思是若是代理上的緩存過時了也能夠接受,但不能過時太多,超過 x 秒也會不要。min-fresh的意思是緩存必須有效,並且必須在 x 秒後依然有效。
隊列跟棧同樣,也是一種操做受限的線性表數據結構。
隊列跟棧同樣,也是一種抽象的數據結構。它具有先進先出的特性,支持在隊尾插入元素,在隊頭刪除元素。
隊列能夠用數組來實現,也能夠用鏈表來實現。用數組實現的棧叫做順序棧,用鏈表實現的棧叫做鏈式棧。一樣,用數組實現的隊列叫做順序隊列,用鏈表實現的隊列叫做鏈式隊列。
// 用數組實現的隊列
const ArrayQueue=function(){
// 數組:items,數組大小:n
let items =[];
let n = 0;
// head 表示隊頭下標,tail 表示隊尾下標
let head = 0;
let tail = 0;
// 申請一個大小爲 capacity 的數組
function createArrayQueue(capacity) {
items = new Array(capacity);
n = capacity;
}
// 入隊操做,將 item 放入隊尾
function enqueue(item) {
// tail == n 表示隊列末尾沒有空間了
if (tail == n) {
// tail ==n && head==0,表示整個隊列都佔滿了
if (head == 0) return false;
// 數據搬移
for (int i = head; i < tail; ++i) {
items[i-head] = items[i];
}
// 搬移完以後從新更新 head 和 tail
tail -= head;
head = 0;
}
items[tail] = item;
++tail;
return true;
}
// 出隊
function dequeue() {
// 若是 head == tail 表示隊列爲空
if (head == tail) return null;
// 爲了讓其餘語言的同窗看的更加明確,把 -- 操做放到單獨一行來寫了
let ret = items[head];
++head;
return ret;
}
this.createArrayQueue = createArrayQueue;
this.enqueue = enqueue;
this.dequeue = dequeue;
}
複製代碼
用數組實現的隊列,在 tail==n 時,會有數據搬移操做,這樣入隊操做性能就會受到影響。
想要避免數據搬移,能夠用循環隊列來處理。
咱們能夠看到,圖中這個隊列的大小爲 8,當前 head=4,tail=7。當有一個新的元素 a 入隊時,咱們放入下標爲 7 的位置。但這個時候,咱們並不把 tail 更新爲 8,而是將其在環中後移一位,到下標爲 0 的位置。當再有一個元素 b 入隊時,咱們將 b 放入下標爲 0 的位置,而後 tail 加 1 更新爲 1。因此,在 a,b 依次入隊以後,循環隊列中的元素就變成了下面的樣子:
經過這樣的方法,咱們成功避免了數據搬移操做。
const CircularQueue = function {
// 數組:items,數組大小:n
let items;
let n = 0;
// head 表示隊頭下標,tail 表示隊尾下標
let head = 0;
let tail = 0;
// 申請一個大小爲 capacity 的數組
function createCircularQueuee(capacity) {
items = new Array(capacity);
n = capacity;
}
// 入隊
function enqueue(item) {
// 隊列滿了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出隊
function dequeue() {
// 若是 head == tail 表示隊列爲空
if (head == tail) return null;
String ret = items[head];
head = (head + 1) % n;
return ret;
}
this.createCircularQueuee = createCircularQueuee;
this.enqueue = enqueue;
this.dequeue = dequeue;
}
複製代碼
HTTP 的一些缺點,其中的無狀態在加入 Cookie 後獲得瞭解決,而另兩個缺點——明文和不安全僅憑 HTTP 自身是無力解決的,須要引入新的 HTTPS 協議。
爲何要有 HTTPS?簡單的回答是由於 HTTP 不安全。因爲 HTTP 天生明文的特色,整個傳輸過程徹底透明,任何人都可以在鏈路中截獲、修改或者僞造請求 / 響應報文,數據不具備可信性。
若是通訊過程具有了四個特性,就能夠認爲是安全的,這四個特性是:
HTTPS 實際上是一個很是簡單的協議,RFC 文檔很小,只有短短的 7 頁,裏面規定了新的協議名https,默認端口號 443,至於其餘的什麼請求 - 應答模式、報文結構、請求方法、URI、頭字段、鏈接管理等等都徹底沿用 HTTP,沒有任何新的東西。
它把 HTTP 下層的傳輸協議由 TCP/IP 換成了** SSL/TLS**,由HTTP over TCP/IP變成了HTTP over SSL/TLS,讓 HTTP 運行在了安全的 SSL/TLS 協議上,收發報文再也不使用 Socket API,而是調用專門的安全接口。
SSL 即安全套接層(Secure Sockets Layer),在 OSI 模型中處於第 5 層(會話層)。
互聯網工程組 IETF 在 1999 年把它更名爲 TLS(傳輸層安全,Transport Layer Security),正式標準化。
TLS 由記錄協議、握手協議、警告協議、變動密碼規範協議、擴展協議等幾個子協議組成,綜合使用了對稱加密、非對稱加密、身份認證等許多密碼學前沿技術。
瀏覽器和服務器在使用 TLS 創建鏈接時須要選擇一組恰當的加密算法來實現安全通訊,這些算法的組合被稱爲密碼套件(cipher suite,也叫加密套件)。
TLS 的密碼套件命名很是規範,格式很固定。基本的形式是密鑰交換算法 + 簽名算法 + 對稱加密算法 + 摘要算法。
HTTPS 的安全性是由 TLS來保證的。它爲 HTTP 增長了機密性、完整性,身份認證和不能否認等特性。
機密性是信息安全的基礎,缺少機密性 TLS 就會成爲無水之源、無根之木。
實現機密性最經常使用的手段是加密(encrypt),就是把消息用某種方式轉換成誰也看不懂的亂碼,只有掌握特殊鑰匙的人才能再轉換出原始文本。
這裏的鑰匙就叫作密鑰(key),加密前的消息叫明文(plain text/clear text),加密後的亂碼叫密文(cipher text),使用密鑰還原明文的過程叫解密(decrypt),是加密的反操做,加密解密的操做過程就是加密算法。
因爲 HTTPS、TLS 都運行在計算機上,因此密鑰就是一長串的數字,但約定俗成的度量單位是位(bit),而不是字節(byte)。
1字節 = 8位
按照密鑰的使用方式,加密能夠分爲兩大類:對稱加密和非對稱加密。
對稱加密很好理解,就是指加密和解密時使用的密鑰都是同一個,是對稱的。只要保證了密鑰的安全,那整個通訊過程就能夠說具備了機密性。
TLS 裏有很是多的對稱加密算法可供選擇,好比 RC四、DES、3DES、AES、ChaCha20 等。目前經常使用的只有 AES 和 ChaCha20。
對稱算法還有一個分組模式的概念,它可讓算法用固定長度的密鑰加密任意長度的明文,把小祕密(即密鑰)轉化爲大祕密(即密文)。
對稱加密看上去好像完美地實現了機密性,但其中有一個很大的問題:如何把密鑰安全地傳遞給對方,術語叫密鑰交換。
出現了非對稱加密(也叫公鑰加密算法)
它有兩個密鑰,一個叫公鑰(public key),一個叫私鑰(private key)。兩個密鑰是不一樣的,不對稱,公鑰能夠公開給任何人使用,而私鑰必須嚴格保密。
公鑰和私鑰有個特別的單向性,雖然均可以用來加密解密,但公鑰加密後只能用私鑰解密,反過來,私鑰加密後也只能用公鑰解密。
非對稱加密能夠解決「密鑰交換」的問題。網站祕密保管私鑰,在網上任意分發公鑰,你想要登陸網站只要用公鑰加密就好了,密文只能由私鑰持有者才能解密.
非對稱加密沒有密鑰交換的問題,但由於它們都是基於複雜的數學難題,運算速度很慢,即便是 ECC 也要比 AES 差上好幾個數量級。若是僅用非對稱加密,雖然保證了安全,但通訊速度很慢,實用性就變成了零。
混合加密
在通訊剛開始的時候使用非對稱算法,好比 RSA、ECDHE,首先解決密鑰交換的問題。
而後用隨機數產生對稱算法使用的會話密鑰(session key),再用公鑰加密。由於會話密鑰很短,一般只有 16 字節或 32 字節,因此慢一點也無所謂。
對方拿到密文後用私鑰解密,取出會話密鑰。這樣,雙方就實現了對稱密鑰的安全交換,後續就再也不使用非對稱加密,全都使用對稱加密。
對稱加密和非對稱加密,以及二者結合起來的混合加密,實現了機密性。僅有機密性,離安全還差的很遠。
因此,在機密性的基礎上還必須加上完整性、身份認證等特性,才能實現真正的安全。
實現完整性的手段主要是摘要算法(Digest Algorithm),也就是常說的散列函數、哈希函數(Hash Function)。
把摘要算法近似地理解成一種特殊的壓縮算法,它可以把任意長度的數據壓縮成固定長度、並且獨一無二的摘要字符串,就好像是給這段數據生成了一個數字指紋。
換一個角度,也能夠把摘要算法理解成特殊的單向加密算法,它只有算法,沒有密鑰,加密後的數據沒法解密,不能從摘要逆推出原文。
摘要算法其實是把數據從一個大空間映射到了小空間,因此就存在衝突(collision,也叫碰撞)的可能性,可能會有兩份不一樣的原文對應相同的摘要。好的摘要算法必須可以「抵抗衝突」,讓這種可能性儘可能地小。
由於摘要算法對輸入具備單向性和雪崩效應,輸入的微小不一樣會致使輸出的劇烈變化,因此也被 TLS 用來生成僞隨機數(PRF,pseudo random function)。
目前 TLS 推薦使用的是 SHA-1 的後繼者:SHA-2。
摘要算法保證了數字摘要和原文是徹底等價的。因此,咱們只要在原文後附上它的摘要,就可以保證數據的完整性。
不過摘要算法不具備機密性,若是明文傳輸,那麼黑客能夠修改消息後把摘要也一塊兒改了,網站仍是鑑別不出完整性。
因此,真正的完整性必需要創建在機密性之上,在混合加密系統裏用會話密鑰加密消息和摘要,這樣黑客沒法得知明文,也就沒有辦法動手腳了。
這有個術語,叫哈希消息認證碼(HMAC)。
加密算法結合摘要算法,咱們的通訊過程能夠說是比較安全了。但這裏還有漏洞,就是通訊的兩個端點(endpoint)。
使用私鑰再加上摘要算法,就可以實現數字簽名,同時實現「身份認證」和「不能否認」。
數字簽名的原理其實很簡單,就是把公鑰私鑰的用法反過來,以前是公鑰加密、私鑰解密,如今是私鑰加密、公鑰解密。