Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
年來,前端技術(shù)日新月異,前端已經(jīng)不僅僅是網(wǎng)頁,更多的開始由狹義向廣義發(fā)展。
先后涌現(xiàn)出了具備后端能力的node,具備移動開發(fā)能力的react native,具備游戲渲染能力的cocos2d-js,以及iOS上的熱修復(fù)技術(shù)JSPatch等等新技術(shù)。
咋一看,幾乎各個端都被JavaScript攻陷,大有一統(tǒng)江湖之勢。
究竟,JavaScript如何做到上天入地?zé)o所不能?JavaScript真的能一統(tǒng)江湖嗎?
亂世出英雄:JavaScript的誕生故事要從JavaScript的由來說起。
高能瞎扯淡版,正經(jīng)臉的同學(xué)可以忽略
有人的地方就有江湖,有江湖的地方就有紛爭。
故事要從當(dāng)年的瀏覽器之戰(zhàn)說起。
時間回到1994年,
(→ 那時候我還是個寶寶~ #天真臉#)
景兄弟橫空出世,并自帶神器網(wǎng)景導(dǎo)航,戰(zhàn)斗力爆表,勢如劈竹,瞬時間威震天下。
一出世就武裝到牙齒,武力值這么高還自帶兵器,這個科學(xué)嗎?
港真,我也覺得不科學(xué),也許跟熊孩子哪吒、女漢子雅典娜是一個品種吧?
這一切北方的老前輩微軟大濕,都看在眼里,不甘天下盡歸景兄弟這個初出茅廬的毛孩子,大濕積淀多年,潛心修煉一年,終于帶著大殺器IE 1.0出關(guān)了,誓于景兄弟爭個高低。
自此景兄弟的網(wǎng)景導(dǎo)航 VS 微軟大濕的IE 的軍備戰(zhàn)爭開始。
景兄弟仔細掂量,微軟大濕財大氣粗,內(nèi)功深厚,臣妾實在是辦不到啊啊啊啊啊啊。
景兄弟緊急召集門人商議對策,有一門人曰:”以我們微薄之力硬磕,是萬萬使不得的。如今我們,一是宜施行合縱之策,抱大腿,組成聯(lián)盟!二是避其鋒芒,出奇招致勝。“
于是景兄弟依照此策略,一方面找到了當(dāng)時德高為重的另一位前輩SUN,組成了開發(fā)者聯(lián)盟。
(微軟大濕:握草,聯(lián)盟都粗來了,那我是不是得搞個部落?)
另一方面,景兄弟找到了鍛造大師布蘭登,請布大師幫忙升級兵器網(wǎng)景導(dǎo)航,大師就是大師,不費吹灰之力就完成了強化升級,然而布大師突發(fā)奇想,本來這是近距離攻擊兵器,要是有多一個遠距離攻擊的能力那豈不是更好?Just do it. 想罷大師就加了一個遠距離攻擊的feature。于是有了自帶遠距離攻擊能力的網(wǎng)景導(dǎo)航2.0。景兄弟一看這么流弊心里甚是歡喜,不過遠距離攻擊的技能叫做LiveScript,感覺不是特別Fashion。特然想到這不是跟SUN前輩聯(lián)盟嘛,SUN家的Java正是獨霸武林之時。不如把名字改成跟Java有關(guān),蹭一把東風(fēng),蹭點光環(huán)。一拍腦袋,JavaScript!!!眾門人一聽:”好好好,JavaScript 流弊炫酷吊炸天!“
果然第一節(jié)下半場,景兄弟攜強化過的網(wǎng)景導(dǎo)航2.0 戰(zhàn)個痛快,那是杠杠的!人家一問,你咋還能遠程攻擊,你這個遠程攻擊用的是啥?答曰:JavaScript。“JavaScript,一定是跟SUN家Java是一個系列產(chǎn)品,一定很流弊!”#光環(huán)加成,各種膜拜臉#
微軟大濕虧了一場,痛定思痛,也要搞遠程攻擊功能,果然不久,就祭出了同樣帶有遠程攻擊能力的IE 3.0,鑒于景兄弟的遠程攻擊叫做JavaScript,J開頭的感覺應(yīng)該比較流弊,所以微軟大濕的叫做JScript。
然后戰(zhàn)爭就從地面貼身肉搏戰(zhàn),開始逐步升級到了遠距離核戰(zhàn)爭。
正所謂,城門失火,殃及池魚。這么打下去苦逼的是搬磚的頁面仔,就是我這種,到處都是雷區(qū),無處下腳。
最后到了1997年,“聯(lián)合國安理會秘書長”艾瑪(ECMA)出來調(diào)停,多方簽署了“核不擴散條約”,約束各種遠程攻擊武器的使用,這才走上了正軌。
1995年SUN開發(fā)了Java技術(shù),這是第一個通用軟件平臺。Java擁有跨平臺、面向?qū)ο蟆⒎盒途幊痰奶匦裕瑥V泛應(yīng)用于企業(yè)級Web應(yīng)用開發(fā)和移動應(yīng)用開發(fā)。Java也伴隨著互聯(lián)網(wǎng)的迅猛發(fā)展而發(fā)展,逐漸成為重要的網(wǎng)絡(luò)編程語言。名噪一時。
1994年Netscape公司成立,并推出了自己的瀏覽器的免費版本 Netscape Navigator,很快就占有了瀏覽器市場。到了 1995 年,微軟公司開始加入,并很快發(fā)布了自己的 Internet Explorer 1.0。
1995年,當(dāng)時在Netscape就職的Brendan Eich(布蘭登·艾克),正為Netscape Navigator 2.0瀏覽器開發(fā)的一門名為LiveScript的腳本語言,后來Netscape與Sun Microsystems組成的開發(fā)聯(lián)盟,為了讓這門語言搭上Java這個編程語言“熱詞”,將其臨時改名為“JavaScript”,日后這成為大眾對這門語言有諸多誤解的原因之一。
JavaScript最初受Java啟發(fā)而開始設(shè)計的,目的之一就是“看上去像Java”,因此語法上有類似之處,一些名稱和命名規(guī)范也借自Java。但JavaScript的主要設(shè)計原則源自Self和Scheme。JavaScript與Java名稱上的近似,是當(dāng)時Netscape為了營銷考慮與SUN達成協(xié)議的結(jié)果。
==> 所以,JavaScript和Java其實沒有半毛錢關(guān)系。
JavaScript推出后在瀏覽器上大獲成功,微軟在不久后就為Internet Explorer 3.0瀏覽器推出了JScript,以與處于市場領(lǐng)導(dǎo)地位的Netscape產(chǎn)品同臺競爭。JScript也是一種JavaScript實現(xiàn),這兩個
JavaScript語言版本在瀏覽器端共存意味著語言標(biāo)準(zhǔn)化的缺失,對這門語言進行標(biāo)準(zhǔn)化被提上了日程,在1997年,由Netscape、SUN、微軟、寶藍等公司組織及個人組成的技術(shù)委員會在ECMA(歐洲計算機制造商協(xié)會)確定定義了一種名叫ECMAScript的新腳本語言標(biāo)準(zhǔn),規(guī)范名為ECMA-262。JavaScript成為了ECMAScript的實現(xiàn)之一。ECMA-262 第五版,即是ES5。
==> ECMA-262,包括ES5, ES6等是一個標(biāo)準(zhǔn),JavaScript是ECMAScript的一個實現(xiàn)。
完整的JavaScript實現(xiàn)應(yīng)該包含三個部分:
在網(wǎng)景導(dǎo)航2.0和IE 3.0出現(xiàn)之后的幾年間,網(wǎng)景和微軟公司不停的發(fā)布新版本的瀏覽器,支持更多的新功能。自此拉開了瀏覽器之戰(zhàn)的序幕。這場瀏覽器之戰(zhàn)到現(xiàn)在還在繼續(xù),以下一張圖看清楚過程。
從瀏覽器之戰(zhàn)可以看出,各家瀏覽器比拼的大致兩個方面視覺體驗(渲染排版)和速度(腳本運行)。
==> 所以一個完整的瀏覽器組成,至少包含兩個部分:
補充一個市面常見瀏覽器的內(nèi)核和JavaScript引擎搭配:
其他JavaScript引擎,Rhino,由Mozilla基金會管理,開放源代碼,完全以Java編寫,可以看做SpiderMonkey的Java版。
注意:webkit不單單只是一個排版引擎,webkit = 排版引擎 + JavaScript引擎。
==> 所以,JavaScript是動態(tài)語言,它的運行都是基于JavaScript引擎,引擎大都是由靜態(tài)語言實現(xiàn)C++、Java、and so on。JavaScript的能力也是由引擎賦予。不管是瀏覽器環(huán)境中是window,亦或是node環(huán)境中的process,均是由引擎提供。
(番外:Mozilla的人不知道為啥特別喜歡猴子,經(jīng)常以猴子命名技術(shù),所以看到帶Monkey的,十有八九估計是他們搞的。)
在瀏覽器環(huán)境中,DOM、BOM、window對象、setTimeout/setInterval,alert,console等方法均不是JavaScript自身具備的能力,而是瀏覽器native實現(xiàn),然后通過JavaScript引擎注入到JS運行的全局上下文中,供JS使用。
鑒別方式,在調(diào)試器console中打出來,帶有[native code]的即是:
講道理:
JavaScript運行 → 依賴于JavaScript引擎 ← 瀏覽器集成了JavaScript引擎,同時通過JavaScript引擎注入native代碼工JS腳本使用
發(fā)散一下思維,只要有JavaScript引擎,就能運行JS腳本,不管有沒有瀏覽器!只是缺少瀏覽器提供的alert,window等方法。
既然瀏覽器可以往JavaScript引擎中注入代碼,賦予JS腳本在網(wǎng)頁中特殊的能力,同理我們可以自己集成JavaScript引擎,自己定義自己的方法往JavaScript引擎中注入,賦予JS更多更強的自定義能力!
注入的關(guān)鍵是:值類型相互對應(yīng),Obj映射class的一個實例,function映射一個句柄或者引用
JavaScript內(nèi)部,所有數(shù)字都是以64位浮點數(shù)形式儲存,即使整數(shù)也是如此
這就是說,在JavaScript語言的底層,根本沒有整數(shù),所有數(shù)字都是小數(shù)(64位浮點數(shù))。容易造成混淆的是,某些運算只有整數(shù)才能完成,此時JavaScript會自動把64位浮點數(shù),轉(zhuǎn)成32位整數(shù),然后再進行運算。由于浮點數(shù)不是精確的值,所以涉及小數(shù)的比較和運算要特別小心。盡量避免使用JavaScript做精準(zhǔn)計算和密集計算。
根據(jù)國際標(biāo)準(zhǔn)IEEE 754,JavaScript浮點數(shù)的64個二進制位,從最左邊開始,是這樣組成的。
第1位:符號位,0表示正數(shù),1表示負數(shù)
第2位到第12位:儲存指數(shù)部分
第13位到第64位:儲存小數(shù)部分(即有效數(shù)字)
符號位決定了一個數(shù)的正負,指數(shù)部分決定了數(shù)值的大小,小數(shù)部分決定了數(shù)值的精度。
IEEE 754規(guī)定,有效數(shù)字第一位默認總是1,不保存在64位浮點數(shù)之中。也就是說,有效數(shù)字總是1.xx…xx的形式,其中xx..xx的部分保存在64位浮點數(shù)之中,最長可能為52位。因此,JavaScript提供的有效數(shù)字最長為53個二進制位(64位浮點的后52位+有效數(shù)字第一位的1)。
內(nèi)部表現(xiàn)公式:(-1)^符號位 1.xx…xx 2^指數(shù)位
精度最多只能到53個二進制位,這意味著,絕對值小于2的53次方的整數(shù),即-(253-1)到253-1,都可以精確表示。
而大部分的后端語言,C++、Java、Python等的long型都是可以支持到64位,因此long型數(shù)據(jù)從后端語言傳給JavaScript會發(fā)生低位截斷。遇到這種情況一般使用String處理,如需要在JavaScript中做long型計算,需要自行實現(xiàn)計算器。
有了自行往JavaScript引擎中注入的想法,接下來就是分析可行性。
大部分是JavaScript引擎是使用C++編寫,如果自己的程序使用的是C++可以很方便的進行注入,如果是OC,可以使用OC和C++混編的形式。
其他語言怎么破?
要在一門靜態(tài)語言上與動態(tài)語言JavaScript相互調(diào)用,最便捷的方式是找到一個這門語言實現(xiàn)的JavaScript引擎(開源),直接進行集成,注入。如果沒有,則需要使用多一層橋接,把這門語言的接口暴露給C++,再由C++實現(xiàn)的JavaScript引擎將接口注入供JavaScript使用。
服務(wù)端集成思路&實踐:
我們都知道nodeJS,但是nodeJS的運行依賴于Google的V8 引擎,V8是C++實現(xiàn),底層使用C++實現(xiàn)底層功能,比如網(wǎng)絡(luò),數(shù)據(jù)庫IO,對外暴露一個構(gòu)造器接口注入到上下文中,注意此處暴露的只是一個構(gòu)造器接口而不是一個創(chuàng)建完的實例。然后實現(xiàn)了一個require的hook函數(shù)。當(dāng)使用require加載一個JS模塊時,跟網(wǎng)頁中使用AMD 的require并無異樣,當(dāng)使用require加載系統(tǒng)庫,既是C++的模塊時,會調(diào)用暴露出來的構(gòu)造器接口,得到一個實例對象。不管是裝載JS模塊還是裝載C++模塊,得到的都可以看做是一個Module Object,node會將裝載完的模塊緩存到binding_cache中,下次在別處的代碼中使用require裝載模塊時,就會先去binding_cache中查找,如果找到了則返回該module object,如果沒找到再執(zhí)行上面的裝載流程。
這就是node的基本原理:C++封裝底層操作,通過V8注入,使得JS腳本有網(wǎng)絡(luò)和IO能力
以上說到的幾個都是C++層面的應(yīng)用,那么經(jīng)典的Java怎么玩?是不是Java就必須是靜態(tài)語言的玩法,沒有辦法像C++之類的,可以使用JS的動態(tài)特性?
當(dāng)然不是。這個時候,我們需要說起前面介紹過的一個JS引擎 Rhino,Rhino是完全由Java編寫,可想而知,Rhino幾乎就是為Java應(yīng)用而生的。
用法是這樣:
首先在我們的Java應(yīng)用中集成Rhino;
所有的IO操作,網(wǎng)絡(luò)操作等,都封裝成service,并提供增刪改查,setter && getter等多種方法
通過spring,把這些service bean注入到Rhino中;
把業(yè)務(wù)邏輯寫到JS代碼中,JS代碼調(diào)用多個已注入的Java service處理業(yè)務(wù)邏輯,拼裝數(shù)據(jù)返回!
好處:修改業(yè)務(wù)邏輯不需要修改Java代碼,也就是不需要重新編譯和部署,只需要刷新下跑在Rhino中的JS代碼即可。以往Java應(yīng)用的一個痛點是部署,需要重新編譯,打包,部署重啟服務(wù)器,現(xiàn)在以這種形式開發(fā),可以達到服務(wù)端的熱更新和熱部署。既可以享有Java服務(wù)的穩(wěn)定性和可靠性,又可以享有JS的靈活性。
這種技術(shù)和用法在差不多十年前就有過,前EMC的工程師基于EMC著名的商業(yè)產(chǎn)品Documentum,設(shè)計了一套Java開源的中小企業(yè)CMS系統(tǒng)Alfresco,在該系統(tǒng)中實現(xiàn)了這種技術(shù),這種技術(shù)基于spring,叫做spring-surf,做了一個膠水層。可以看做小十年前的node吧。
Demo,使用spring-surf框架的系統(tǒng)中一個webscript模塊
categorynode.get.xml定義URL攔截器和權(quán)限控制;
.get指明是處理GET請求,RESTful;
在categorynode.get.js中調(diào)用已注入的Java Bean處理業(yè)務(wù)邏輯;
若為網(wǎng)頁請求返回.html.ftl,若為Ajax,返回.json.ftl;
(此處配套使用的是FreeMarker模板引擎)
==> categorynode.get.desc.xml
==> categorynode.get.js
==> categorynode.get.html.ftl
==> categorynode.get.json.ftl
React Native目前也是異常火爆,RN程序的運行依賴于Facebook的RN框架。在iOS、Android的模擬器或是真機上,React Native使用的是JavaScriptCore引擎,也就是Safari所使用的JavaScript引擎。但是在iOS上JavaScriptCore并沒有使用即時編譯技術(shù)(JIT),因為在iOS中應(yīng)用無權(quán)擁有可寫可執(zhí)行的內(nèi)存頁(因而無法動態(tài)生成代碼),在安卓上,理論上是可以使用的。JavaScriptCore引擎也是使用C++編寫,在iOS和安卓中,JavaScriptCore都做了一層封裝,可以無須關(guān)心引擎和系統(tǒng)橋接的那一層。iOS/Android系統(tǒng)通過JavaScriptCore引擎將定制好的各種原生組件注入,如:listview,text等。
cocos2dx是游戲開發(fā)中非常常用的游戲渲染引擎,有一系列的產(chǎn)品,如:cocos2dx(C++),cocos2d-lua(lua), cocos2d-js(JavaScript)等多個產(chǎn)品。其中最新退出的是cocos2dx的JS版本的cocos2d-js,編寫游戲渲染特效代碼相比于C++和lua非常方便。對于做需要經(jīng)常更新的渲染場景,C++是靜態(tài)語言,每次修改都需要重新編譯才能運行,顯然是不合適的。自然也就想到了腳本語言,lua和js,兩者有些類似,都是動態(tài)語言,只需要集成一個運行引擎,提供一個運行的容器即可運行,同時通過引擎注入底層方法供腳本調(diào)用即可。lua好處是精簡,語法精簡,引擎頁很小很精簡,所以不可避免的代碼量會比js多,同時學(xué)習(xí)成本比較高。js的好處是有ECMAScrtpt的核心,語法比較豐富,同時有支持一些高級屬性。在cocos2d-js中,cocos2dx(C++)集成了SpiderMonkey(C++)作為JS運行引擎,中間做了一個膠水層既是JS Binding,通過引擎注入了一個cc的全局對象,映射的是底層C++的一個單例C++實例。表面上寫的是JS代碼,實際上操作的是底層的C++。cocos2d-js是代碼可以運行在多種環(huán)境中,當(dāng)運行的網(wǎng)頁環(huán)境中時,使用的是cocos2d-html5引擎,底層操作的是canvas;當(dāng)運行在客戶端上時,使用的是cocos2dx引擎,底層操作的是C++,再由C++去操控openGL做繪制和渲染。提供相同的API,對開發(fā)者幾乎是透明無差異的,開發(fā)者只需要關(guān)注實現(xiàn)效果即可。達到一套代碼,多端運行(網(wǎng)頁端,客戶端)。
JSPatch是目前比較流行的iOS上的熱修復(fù)技術(shù),JSPatch 能做到通過 JS 調(diào)用和改寫 OC 方法最根本的原因是 Objective-C 是動態(tài)語言,OC 上所有方法的調(diào)用/類的生成都通過 Objective-C Runtime 在運行時進行,我們可以通過類名/方法名反射得到相應(yīng)的類和方法。JSPatch 的基本原理就是:JS 傳遞字符串給 OC,OC 通過 Runtime 接口調(diào)用和替換 OC 方法。
關(guān)鍵技術(shù)之一是 JS 和 OC 之間的消息互傳。JSPatch里包含了,一個JS引擎JavaScriptCore(Safari,React Native用的同款)。用到了 JavaScriptCore 的接口,OC 端在啟動 JSPatch 引擎時會創(chuàng)建一個 JSContext 實例,JSContext 是 JS 代碼的執(zhí)行環(huán)境,可以給 JSContext 添加方法,JS 就可以直接調(diào)用這個方法。本質(zhì)上就是通過JavaScriptCore引擎注入,暴露OC的方法供JS調(diào)用來實現(xiàn)動態(tài)修改OC的反射。
Demo,iOS熱更新,熱修復(fù):
集成JavaScriptCore引擎;
通過引擎,橋接JS和OC;
通過JS修改OC反射。
詳細的JSPatch技術(shù)介紹請移步:https://github.com/bang590/JSPatch/wiki
關(guān)于JavaScript引擎:
在iOS 或 android 上能夠運行的JavaScript 引擎有4個:JavaScriptCore,SpiderMonkey,V8,Rhino。下面這個表格展示各個引擎在iOS 和 Android 的兼容性。
因為iOS平臺不支持JIT即時編譯,而V8只有JIT模式,所以V8無法在iOS平臺使用(越獄設(shè)備除外,想體驗iOS JIT的同學(xué)可以自行越獄)。
所以,目前可以做到橫跨iOS和Android雙平臺的JS引擎,只有兩款,即是SpiderMonkey和JavaScriptCore。
JavaScript引擎會受很多東西影響,比如交叉編譯器的版本、引擎的版本和操作系統(tǒng)的種類等。
至于如何選擇,可以參考:《Part I: How to Choose a JavaScript Engine for iOS and Android Development》
至此,JavaScript從立足于前端,到征戰(zhàn)全端的逆襲之路,可以總結(jié)為“攜引擎以令天下”。
不足之處,還請各位看官輕拍~
bang590/JSPatch中問參考文檔
Cocos2d-JS | Cocos2d-x官方參考文檔
Alfresco官方參考文檔
《Browser Wars: The End or Just the Beginning?》
《Part I: How to Choose a JavaScript Engine for iOS and Android Development》
《React Native 從入門到源碼》
.函數(shù)的返回值
4.1return 語句
有的時候,我們會希望函數(shù)將值返回給調(diào)用者,此時通過使用return語句就可以實現(xiàn)。
<script>
function getResult(){
return 666;
}
// getResult();
console.log(getResult());
</script>
利用函數(shù)求任意一個數(shù)組中的最大值
<script>
function getArrMax(arr){
var max = arr[0];
for(var i=1;i<=arr.length;i++){
if(arr[i]>max){
max=arr[i];
}
}
return max;
}
var re = getArrMax([5,2,99,101,167,77]);
console.log(re);
</script>
4.2return終止函數(shù)
注意事項:
<script>
//1.return終值函數(shù)
function getSum(num1,num2){
return num1 + num2; //return 后面的代碼不會被執(zhí)行
alert('我不會被執(zhí)行')
}
console.log(getSum(1,2));
//2.return 只能返回一個值
function fn(num1,num2){
return num1,num2; //返回的結(jié)果最后一個值
}
console.log(fn(1,2));
//3.我們求任意兩個數(shù)的 加減乘除結(jié)果
function getResule(num1,num2){
return [num1+num2,num1-num2,num*num2,num1/num2];
}
var re =getResule(1,2); //返回的是一個數(shù)組
console.log(re);
</script>
4.4函數(shù)沒有return返回undefined
4.5 break,continue,return的區(qū)別
段時間在知乎上了看一些網(wǎng)絡(luò)方面的知識,剛好小編自己也是從事這一塊的相關(guān)工作由對網(wǎng)絡(luò)方面有一定的了解。今天我們來講講,當(dāng)你在瀏覽器中輸入本站域名并回車后,這背后到底發(fā)生來什么事情?
(因平臺原因本文中www即為xxx ,com即為zzz,http/ccccc即為cccc/ccccc)
大致總結(jié)歸納了下,發(fā)生的事情如下圖:
看到這里,大部分人都是蒙的。哪怕你有一點網(wǎng)絡(luò)知識的基礎(chǔ),看起來也是蒙的。為了跟大家生動形象地說明原理,下面,我們用擬人的手法,講述從輸入一個網(wǎng)址,到最終顯示出頁面,這背后所發(fā)生的事情。
某天,小明心血來潮,想去艾西服務(wù)器論壇看一下有沒有更新的博文,于是他打開了chrome瀏覽器,稍微回憶了一下網(wǎng)址,輸入了:27server.zzz,并按下了回車。
一 ,瀏覽器格式化檢查
chrome檢查了一下,嗯?這好像是一個蠻標(biāo)準(zhǔn)的網(wǎng)址。但如果小明輸入的是27server@zzz或者27server.zzz1這樣的,chrome就會無情的拒絕小明的提議,并提示他網(wǎng)址出錯。
所以在第一步,瀏覽器對網(wǎng)址進行了格式化檢驗,確定這是一個有效的網(wǎng)址,才會進入下一步。但是,在訪問之前,瀏覽器必須知道,用了什么協(xié)議,是http呢,還是https呢?顯然chrome對這種事情已經(jīng)見怪不怪了,他有自己的一套方案,這里小明沒有給定是什么協(xié)議的,于是chrome默認用了http協(xié)議,于是將網(wǎng)址修改成了:cccc://27server.zzz
二 DNS分級查詢
在互聯(lián)網(wǎng)上,有一家名叫TCP/IP的快遞公司,專門負責(zé)運送用戶的包裹。
于是chrome聯(lián)系了TCP/IP快遞公司,但卻被告知:需要知道對方的IP地址,才能把包裹寄給對方!chrome并不是一個灰心的孩子,于是它找到了“黃頁公司”-DNS。
求DNS幫忙查詢下“27server.zzz”的IP地址是多少?DNS于是著手開始查找。首先看了下本地的hosts文件,沒有找到對應(yīng)!然后看了下本地內(nèi)存,也沒有!沒辦法,DNS只好聯(lián)系了村口的一家名叫路由器的公司,192.168.1.1。路由器查看了下,最近村里沒人訪問過這個域名呀,要不你去市里的DNS公司問問?這是市里的DNS公司地址:114.114.114.114
于是DNS又聯(lián)系了市里的DNS公司,卻被告知最近市里最近也沒人訪問過這個域名。但是市里的DNS公司畢竟是專業(yè)的DNS公司,不會隨便推卸責(zé)任,對DNS說,你等一等,我找我的上級查詢下。于是市里的DNS公司聯(lián)系了它的上級,根域名服務(wù)器。這個根域名服務(wù)器可了不得,全球只有13臺,掌控著全球的DNS根域名查詢。市里的DNS公司找到了離他最近的根域名服務(wù)器,說:“老哥,麻煩幫我查下.com頂級域名的服務(wù)器地址。”
沒過一會兒,根域名服務(wù)器回復(fù)道:“你好,你要查詢的地址是1.2.3.4。”
于是市里的DNS公司又聯(lián)系到了1.2.3.4,詢問 27server這個二級域名的域名服務(wù)器地址。
同樣,1.2.3.4回復(fù)給了市里的DNS公司,地址是2.3.4.5。
市里的DNS公司又找2.3.4.5問了下,地址是:27server.zzz.w.kunluncan.zzz。等等,這怎么是一個別名(CNAME)?
于是市里的DNS公司又不厭其煩的重復(fù)了上面的幾個步驟,一級一級地查找了 com > kunluncan > w > com > 27server的域名服務(wù)器地址,一直找到了馳網(wǎng)云的CDN域名服務(wù)器,DNS聯(lián)系了對方,對方回復(fù)道:“查詢到25個IP地址,分別是xxxxxxxx,幫你找到了離你最近的地址,3.4.5.6,已經(jīng)打包,收件人地址:114.114.114.114 發(fā)件人地址:3.3.3.3,已委托TCP/IP快遞公司的UDP小哥發(fā)送給你。”
三,網(wǎng)關(guān) ARP原理
UDP是這一行的老司機,隨手在包裹上寫著:
收件人門牌號:53
發(fā)件人門牌號:56002
因為UDP知道,同一個地址會有很多的門牌號,為了避免混淆,必須要寫。
UDP隨手聯(lián)系了TCP/IP公司的貨車司機,讓他捎帶一下這個包裹。
司機來了,把包裹放進了駕駛室,坐上車準(zhǔn)備開車。
司機打開了導(dǎo)航(路由表),發(fā)現(xiàn)要出關(guān)(網(wǎng)關(guān)),但是要知道關(guān)口的編號(mac地址)。但是導(dǎo)航信息里并沒有關(guān)口的編號。于是司機找到了當(dāng)?shù)氐南驅(qū)RP,問這個關(guān)口的編號是多少?ARP吼了一嗓子,關(guān)口回復(fù)說:“我的編號是xxxxx”。司機很快就到達了關(guān)口,關(guān)口放行,載著快遞,上了Internet高速公路,一路狂奔不止。
四,建立DNS緩存
市里的DNS公司收到結(jié)果后,又通過村口的路由器公司聯(lián)系到了DNS,并把結(jié)果告訴給了對方。同時村口的路由器公司和DNS都在自己的小本本上默默地記下了這個對應(yīng)關(guān)系。
最后,DNS又把結(jié)果告訴給了已經(jīng)等得不耐煩的chrome了。
五 ,TCP的三次握手
知道了27server.zzz的IP地址,chrome又立即聯(lián)系了TCP/IP快遞公司,快遞公司派出了TCP小哥來接洽此時。和UDP小哥不同的是,TCP小哥是一個做事一絲不茍的人,知道chrome想要去拜訪3.4.5.6,就要先和對方聯(lián)系一下,確定在不在,這是通過TCP三次握手實現(xiàn)的。
TCP小哥:在嗎?有人想要去拜訪您。
對方:在啊,隨時歡迎。
TCP小哥:馬上到。
這三次消息,要通過TCP/IP公司的司機來來回回運輸三次。DNS查詢也是同樣的道理,這里就不贅述了。
通過這三次握手,TCP小哥建立起了一條chrome到3.4.5.6的通道。
chrome將http請求消息(我是誰,要找誰等),打包給了TCP小哥,寄件人IP地址:2.2.2.2 門牌號:23755。收件人IP地址:3.4.5.6 門牌號:80
然后TCP小哥聯(lián)系了司機,將包裹送到了3.4.5.6的80門牌號上。
六 Nginx的工作原理
3.4.5.6的80門牌號的門衛(wèi)是nginx老大爺。老大爺一看,是送給家里的27server.zzz的。
路上老大爺看了下nginx.conf里找人的順序:首先找index.php,如果找不到就找index.htm,再找不到就找index.html。
于是敲開了27server.zzz的門,里面只有index.html在家。index.html頭也不回地告訴nginx老大爺,告訴chrome,我們已經(jīng)搬家了,去找ccccc://xxx.27server.zzz(強制跳轉(zhuǎn))。
七 https加密原理
chrome收到了這個消息,立馬又重復(fù)了上面的所有步驟,聯(lián)系了“黃頁公司”DNS,費了很大一番周折,找到了 xxx.27server.zzz的IP地址。
其實這兩次訪問,整體流程相似,不一樣的地方如下:
查找到 xxx.27server.zzz的IP地址后,chrome這回沒有直接把包裹給TCP小哥,而是聯(lián)系了TLS安保大叔,讓他全權(quán)負責(zé)包裹的安全問題,確保包裹在運輸過程中的安全,即包裹的內(nèi)容保密,包裹內(nèi)容不能被篡改、替換。
TLS大叔需要先和對方溝通安保措施,溝通的渠道,就是上文三次握手建立的渠道。
TLS大叔先發(fā)言:你好,我支持TLS版本1.2,以及我的認證算法、加密算法、數(shù)據(jù)校驗算法,此外還有我的隨機碼,收到請回復(fù)。
TLS服務(wù)器回復(fù):你好,我也支持1.2版本,那我們就使用xx認證算法、xx加密算法、xx數(shù)據(jù)校驗算法,我的隨機碼是xx,來實現(xiàn)安保措施,你看好嗎?
TLS大叔:沒問題啊,能出示一下你的證件(數(shù)字證書)嗎?
TLS服務(wù)器:okay,這是我的證件,請過目。
TLS大叔發(fā)現(xiàn)對方發(fā)過來的證書:
TLS大叔不放心,驗證了下證書,過程如下:
1. 用DigiCert Global Root CA的公鑰解密證書Encryption Everywhere DV的簽名
作為一個權(quán)威CA,已經(jīng)被瀏覽器預(yù)先安裝在可信任根證書列表,那么我們信任該CA的一切,當(dāng)然包括其公鑰,在該證書里包含了明文的公鑰。
解開了,證明是該CA私鑰加密的,由于CA私鑰只有CA知道,證書有效,并信任Encryption Everywhere DV的公鑰。
2. 同樣原理用Encryption Everywhere DV的公鑰解密DigiCert Global Root CA的簽名。
如果上述2個步驟都成功了,就有了27server.zzz的公鑰。
3. 再檢查下證書的有效期,如果沒有過期,那么進入下一個步驟。
TLS大叔用“27server.zzz“公鑰加密一段隨機的字符串,發(fā)送給TLS服務(wù)器。
TLS服務(wù)器用自己的私鑰解密,得到明文字符串。
至此,雙方分享了這個神秘的字符串,雙方還有早前分享的隨機碼(nonce),雙方使用同樣的算法,可以推導(dǎo)出相同的master key,進而推導(dǎo)出session key、HMAC key。
Session Key用于加密/解密數(shù)據(jù), HMAC Key主要用于保護數(shù)據(jù)的完整性,以防被第三方篡改。
整個TLS溝通過程就算完成了,TLS大叔把瀏覽器扔給自己的包裹,外面加了一層保險箱,密碼鎖(session key)只有TLS大叔、TLS服務(wù)器知道。
然后TLS大叔把包裹給了TCP小哥,TCP小哥看了下包裹,沒啥兩樣,就是收件人的門牌號從80變成了443。
包裹到達后,443門牌號的門衛(wèi)是TLS服務(wù)器,TLS服務(wù)器用自己的密碼打開了包裹,包裹里有個小紙條,上面寫著“Application Data =http”,知道這是http的東西,于是轉(zhuǎn)手又讓nginx老大爺把包裹送到 xxx.27server.zzz的房間。
八 CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))原理
這回nginx老大爺一看index.php不在家,但是留了個字條,說對方如果要圖片這些靜態(tài)資源,那么直接給他,如果要找我們,請到IP地址:5.6.7.8來找我。沒辦法,nginx老大爺又委托TLS大叔跟對方的服務(wù)器5.6.7.8的TLS對接了下,商量下包裹加密,又TCP/IP快遞公司,把包裹寄到了5.6.7.8的443門牌,送給了index.php。index.php發(fā)現(xiàn)需要主頁,這需要進行計算。于是找來了php家族,計算完了又把主頁打包成了html樣式,返回給了chrome。
chrome收到了html樣式的包裹,這是一種標(biāo)記語言,需要經(jīng)過解釋才能把原來的頁面還原出來。
于是chrome又埋頭計算,終于通過里面寫的規(guī)則,把圖片文字拼拼湊湊,拼成了一個完整的頁面,展示給了小明。
小明怎么也不會想到,按下了一次回車,在后臺居然觸發(fā)了這么大的計算量。。。
對于以上的內(nèi)容,這里我們用稍微專業(yè)點的語言解釋一下:
訪問網(wǎng)址,首先通過域名查詢IP地址。
瀏覽器會先查詢hosts文件,然后查詢內(nèi)存中是否有對應(yīng)的DNS緩存。如果電腦連接路由器上網(wǎng),而DNS又是自動獲取的話,DNS服務(wù)器就會被指定為路由器(局域網(wǎng)網(wǎng)關(guān)),如果局域網(wǎng)中沒人訪問過這個地址,那么在路由器的內(nèi)存中不會存在這條DNS解析記錄。于是又往上查找各層級的DNS服務(wù)器,從家里到區(qū),到城市的DNS服務(wù)器,能不能找到緩存,取決于這個區(qū)域有沒有人訪問過這個域名。一直查到最上層,如果都沒有找到的話,就會請求根域名服務(wù)器,從找到com域名服務(wù)器地址,再從com上找到27server二級域名服務(wù)器地址….以此類推,最終找到完整域名的服務(wù)器地址。
找到地址后,是一個CNAME形式,解析到了阿里云CDN的DNS服務(wù)器上,阿里云的DNS服務(wù)器又通過用戶的訪問IP判斷出用戶的地理位置,返回最靠近用戶的CDN服務(wù)器地址。CDN通俗點說,就是把你的網(wǎng)站資源鏡像到全國各地的服務(wù)器上,從而實現(xiàn)不同地區(qū)用戶的訪問加速。
用戶通過這兩層DNS查詢后,得到了離自己最近的CDN服務(wù)器地址,訪問這個地址,CDN服務(wù)器根據(jù)配置的緩存規(guī)則,返回了靜態(tài)資源,其他則通過訪問源服務(wù)器進行返回(CDN回源)。
因為對應(yīng)27server.zzz這個地址,我只放了一個index.html。訪問這個頁面,會自動跳轉(zhuǎn)到 ccccc://xxx.27server.zzz。瀏覽器訪問27server.zzz后,又繼續(xù)訪問ccccc://xxx.27server.zzz,此時通過兩次DNS查詢,CDN返回圖片,js,css等靜態(tài)資源,然后php的內(nèi)容則通過CDN回源,源服務(wù)器的php計算后,轉(zhuǎn)化為html樣式,返回給訪問者。
你對 27server.zzz的一次訪問,一共觸發(fā)了4次完整的DNS查詢,1次www強行跳轉(zhuǎn),1次https強行跳轉(zhuǎn),1次CDN回源,2次CDN地址解析,更有數(shù)不清的資源文件從各個地方被傳輸?shù)搅四愕碾娔X。這些都是通過TCP的三次握手協(xié)議,網(wǎng)關(guān),ARP,UDP,nginx,CDN,DNS服務(wù)器聯(lián)合工作。
我是艾西,今天的分享就到這里啦希望對有需要的小伙伴有幫助我們下期見
擁有一臺服務(wù)器可以做很多有趣的事情!
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。