轉載於https://github.com/poetries/FE-Interview-Questions,by poetriesnode
1.1 面向報文git
UDP
是一個面向報文(報文能夠理解爲一段段的數據)的協議。意思就是UDP
只是報文的搬運工,不會對報文進行任何拆分和拼接操做github
具體來講面試
UDP
協議,UDP
只會給數據增長一個 UDP
頭標識下是 UDP
協議,而後就傳遞給網絡層了UDP
只去除 IP
報文頭就傳遞給應用層,不會任何拼接操做1.2 不可靠性算法
UDP
是無鏈接的,也就是說通訊不須要創建和斷開鏈接。UDP
也是不可靠的。協議收到什麼數據就傳遞什麼數據,而且也不會備份數據,對方能不能收到是不關心的UDP
沒有擁塞控制,一直會以恆定的速度發送數據。即便網絡條件很差,也不會對發送速率進行調整。這樣實現的弊端就是在網絡條件很差的狀況下可能會致使丟包,可是優勢也很明顯,在某些實時性要求高的場景(好比電話會議)就須要使用 UDP 而不是 TCP
1.3 高效數組
UDP
沒有 TCP
那麼複雜,須要保證數據不丟失且有序到達。因此 UDP
的頭部開銷小,只有八字節,相比 TCP
的至少二十字節要少得多,在傳輸數據報文時是很高效的頭部包含了如下幾個數據瀏覽器
IPv4
可選 字段),該字段用於發現頭部信息和數據中的錯誤1.4 傳輸方式緩存
UDP
不止支持一對一的傳輸方式,一樣支持一對多,多對多,多對一的方式,也就是說 UDP 提供了單播,多播,廣播的功能安全
2.1 頭部服務器
TCP
頭部比UDP
頭部複雜的多
對於
TCP
頭部來講,如下幾個字段是很重要的
Sequence number
,這個序號保證了 TCP
傳輸的報文都是有序的,對端能夠經過序號順序的拼接報文Acknowledgement Number
,這個序號表示數據接收端指望接收的下一個字節的編號是多少,同時也表示上一個序號的數據已經收到Window Size
,窗口大小,表示還能接收多少字節的數據,用於流量控制標識符
URG=1
:該字段爲一表示本數據報的數據部分包含緊急信息,是一個高優先級數據報文,此時緊急指針有效。緊急數據必定位於當前數據包數據部分的最前面,緊急指針標明瞭緊急數據的尾部。ACK=1
:該字段爲一表示確認號字段有效。此外,TCP
還規定在鏈接創建後傳送的全部報文段都必須把 ACK
置爲一 PSH=1
:該字段爲一表示接收端應該當即將數據 push 給應用層,而不是等到緩衝區滿後再提交。RST=1
:該字段爲一表示當前 TCP
鏈接出現嚴重問題,可能須要從新創建 TCP
鏈接,也能夠用於拒絕非法的報文段和拒絕鏈接請求。SYN=1
:當SYN=1
,ACK=0
時,表示當前報文段是一個鏈接請求報文。當SYN=1
,ACK=1
時,表示當前報文段是一個贊成創建鏈接的應答報文。FIN=1
:該字段爲一表示此報文段是一個釋放鏈接的請求報文2.2 狀態機
HTTP
是無鏈接的,因此做爲下層的TCP
協議也是無鏈接的,雖然看似TCP
將兩端鏈接了起來,可是其實只是兩端共同維護了一個狀態
TCP
的狀態機是很複雜的,而且與創建斷開鏈接時的握手息息相關,接下來就來詳細描述下兩種握手。創建鏈接三次握手
TCP
協議中,主動發起請求的一端爲客戶端,被動鏈接的一端稱爲服務端。無論是客戶端仍是服務端,TCP
鏈接創建完後都能發送和接收數據,因此 TCP
也是一個全雙工的協議。CLOSED
狀態。在通訊開始前,雙方都會建立 TCB
。 服務器建立完 TCB
後遍進入 LISTEN
狀態,此時開始等待客戶端發送數據第一次握手
客戶端向服務端發送鏈接請求報文段。該報文段中包含自身的數據通信初始序號。請求發送後,客戶端便進入 SYN-SENT 狀態,x 表示客戶端的數據通訊初始序號。
第二次握手
服務端收到鏈接請求報文段後,若是贊成鏈接,則會發送一個應答,該應答中也會包含自身的數據通信初始序號,發送完成後便進入
SYN-RECEIVED
狀態。
第三次握手
當客戶端收到鏈接贊成的應答後,還要向服務端發送一個確認報文。客戶端發完這個報文段後便進入
ESTABLISHED
狀態,服務端收到這個應答後也進入ESTABLISHED
狀態,此時鏈接創建成功。
TCP
快速打開(TFO
)技術。其實只要涉及到握手的協議,均可以使用相似 TFO
的方式,客戶端和服務端存儲相同 cookie
,下次握手時發出 cookie
達到減小 RTT
的目的你是否有疑惑明明兩次握手就能夠創建起鏈接,爲何還須要第三次應答?
能夠想象以下場景。客戶端發送了一個鏈接請求 A,可是由於網絡緣由形成了超時,這時 TCP 會啓動超時重傳的機制再次發送一個鏈接請求 B。此時請求順利到達服務端,服務端應答完就創建了請求。若是鏈接請求 A 在兩端關閉後終於抵達了服務端,那麼這時服務端會認爲客戶端又須要創建 TCP 鏈接,從而應答了該請求並進入
ESTABLISHED
狀態。此時客戶端實際上是 CLOSED 狀態,那麼就會致使服務端一直等待,形成資源的浪費
PS:在創建鏈接中,任意一端掉線,TCP 都會重發 SYN 包,通常會重試五次,在創建鏈接中可能會遇到 SYN FLOOD 攻擊。遇到這種狀況你能夠選擇調低重試次數或者乾脆在不能處理的狀況下拒絕請求
斷開連接四次握手
TCP
是全雙工的,在斷開鏈接時兩端都須要發送FIN
和ACK
。
第一次握手
若客戶端 A 認爲數據發送完成,則它須要向服務端 B 發送鏈接釋放請求。
第二次握手
B 收到鏈接釋放請求後,會告訴應用層要釋放 TCP 連接。而後會發送 ACK 包,並進入 CLOSE_WAIT 狀態,表示 A 到 B 的鏈接已經釋放,不接收 A 發的數據了。可是由於 TCP 鏈接時雙向的,因此 B 仍舊能夠發送數據給 A。
第三次握手
B 若是此時還有沒發完的數據會繼續發送,完畢後會向 A 發送鏈接釋放請求,而後 B 便進入 LAST-ACK 狀態。
PS:經過延遲確認的技術(一般有時間限制,不然對方會誤認爲須要重傳),能夠將第二次和第三次握手合併,延遲 ACK 包的發送。
第四次握手
爲何 A 要進入 TIME-WAIT 狀態,等待 2MSL 時間後才進入 CLOSED 狀態?
HTTP
協議是個無狀態協議,不會保存狀態
3.1 Post 和 Get 的區別
Get
請求能緩存,Post
不能Post
相對 Get
安全一點點,由於Get
請求都包含在 URL
裏,且會被瀏覽器保存歷史紀錄,Post
不會,可是在抓包的狀況下都是同樣的。Post
能夠經過 request body
來傳輸比 Get
更多的數據,Get
沒有這個技術URL
有長度限制,會影響 Get
請求,可是這個長度限制是瀏覽器規定的,不是 RFC
規定的Post
支持更多的編碼類型且不對數據類型限制3.2 常見狀態碼
2XX 成功
200 OK
,表示從客戶端發來的請求在服務器端被正確處理204 No content
,表示請求成功,但響應報文不含實體的主體部分205 Reset Content
,表示請求成功,但響應報文不含實體的主體部分,可是與 204
響應不一樣在於要求請求方重置內容206 Partial Content
,進行範圍請求3XX 重定向
301 moved permanently
,永久性重定向,表示資源已被分配了新的 URL302 found
,臨時性重定向,表示資源臨時被分配了新的 URL303 see other
,表示資源存在着另外一個 URL,應使用 GET 方法丁香獲取資源304 not modified
,表示服務器容許訪問資源,但因發生請求未知足條件的狀況307 temporary redirect
,臨時重定向,和302含義相似,可是指望客戶端保持請求方法不變向新的地址發出請求4XX 客戶端錯誤
400 bad request
,請求報文存在語法錯誤401 unauthorized
,表示發送的請求須要有經過 HTTP
認證的認證信息403 forbidden
,表示對請求資源的訪問被服務器拒絕404 not found
,表示在服務器上沒有找到請求的資源5XX 服務器錯誤
500 internal sever error
,表示服務器端在執行請求時發生了錯誤501 Not Implemented
,表示服務器不支持當前請求所須要的某個功能503 service unavailable
,代表服務器暫時處於超負載或正在停機維護,沒法處理請求3.3 HTTP 首部
通用字段 | 做用 |
---|---|
Cache-Control |
控制緩存的行爲 |
Connection |
瀏覽器想要優先使用的鏈接類型,好比 keep-alive |
Date |
建立報文時間 |
Pragma |
報文指令 |
Via |
代理服務器相關信息 |
Transfer-Encoding |
傳輸編碼方式 |
Upgrade |
要求客戶端升級協議 |
Warning |
在內容中可能存在錯誤 |
請求字段 | 做用 |
---|---|
Accept |
能正確接收的媒體類型 |
Accept-Charset |
能正確接收的字符集 |
Accept-Encoding |
能正確接收的編碼格式列表 |
Accept-Language |
能正確接收的語言列表 |
Expect |
期待服務端的指定行爲 |
From |
請求方郵箱地址 |
Host |
服務器的域名 |
If-Match |
兩端資源標記比較 |
If-Modified-Since |
本地資源未修改返回 304(比較時間) |
If-None-Match |
本地資源未修改返回 304(比較標記) |
User-Agent |
客戶端信息 |
Max-Forwards |
限制可被代理及網關轉發的次數 |
Proxy-Authorization |
向代理服務器發送驗證信息 |
Range |
請求某個內容的一部分 |
Referer |
表示瀏覽器所訪問的前一個頁面 |
TE |
傳輸編碼方式 |
響應字段 | 做用 |
---|---|
Accept-Ranges |
是否支持某些種類的範圍 |
Age |
資源在代理緩存中存在的時間 |
ETag |
資源標識 |
Location |
客戶端重定向到某個 URL |
Proxy-Authenticate |
向代理服務器發送驗證信息 |
Server |
服務器名字 |
WWW-Authenticate |
獲取資源須要的驗證信息 |
實體字段 | 做用 |
---|---|
Allow |
資源的正確請求方式 |
Content-Encoding |
內容的編碼格式 |
Content-Language |
內容使用的語言 |
Content-Length |
request body 長度 |
Content-Location |
返回數據的備用地址 |
Content-MD5 |
Base64 加密格式的內容MD5 檢驗值 |
Content-Range |
內容的位置範圍 |
Content-Type |
內容的媒體類型 |
Expires |
內容的過時時間 |
Last_modified |
內容的最後修改時間 |
DNS 的做用就是經過域名查詢到具體的 IP。
在
TCP
握手以前就已經進行了DNS
查詢,這個查詢是操做系統本身作的。當你在瀏覽器中想訪問www.google.com
時,會進行一下操做
以上介紹的是 DNS 迭代查詢,還有種是遞歸查詢,區別就是前者是由客戶端去作請求,後者是由系統配置的 DNS 服務器作請求,獲得結果後將數據返回給客戶端。
概念
實現
每種數據結構均可以用不少種方式來實現,其實能夠把棧當作是數組的一個子集,因此這裏使用數組來實現
class Stack { constructor() { this.stack = [] } push(item) { this.stack.push(item) } pop() { this.stack.pop() } peek() { return this.stack[this.getCount() - 1] } getCount() { return this.stack.length } isEmpty() { return this.getCount() === 0 } }
應用
匹配括號,能夠經過棧的特性來完成
var isValid = function (s) { let map = { '(': -1, ')': 1, '[': -2, ']': 2, '{': -3, '}': 3 } let stack = [] for (let i = 0; i < s.length; i++) { if (map[s[i]] < 0) { stack.push(s[i]) } else { let last = stack.pop() if (map[last] + map[s[i]] != 0) return false } } if (stack.length > 0) return false return true };
概念
隊列一個線性結構,特色是在某一端添加數據,在另外一端刪除數據,遵循先進先出的原則
實現
這裏會講解兩種實現隊列的方式,分別是單鏈隊列和循環隊列
class Queue { constructor() { this.queue = [] } enQueue(item) { this.queue.push(item) } deQueue() { return this.queue.shift() } getHeader() { return this.queue[0] } getLength() { return this.queue.length } isEmpty() { return this.getLength() === 0 } }
由於單鏈隊列在出隊操做的時候須要
O(n)
的時間複雜度,因此引入了循環隊列。循環隊列的出隊操做平均是O(1)
的時間複雜度
class SqQueue { constructor(length) { this.queue = new Array(length + 1) // 隊頭 this.first = 0 // 隊尾 this.last = 0 // 當前隊列大小 this.size = 0 } enQueue(item) { // 判斷隊尾 + 1 是否爲隊頭 // 若是是就表明須要擴容數組 // % this.queue.length 是爲了防止數組越界 if (this.first === (this.last + 1) % this.queue.length) { this.resize(this.getLength() * 2 + 1) } this.queue[this.last] = item this.size++ this.last = (this.last + 1) % this.queue.length } deQueue() { if (this.isEmpty()) { throw Error('Queue is empty') } let r = this.queue[this.first] this.queue[this.first] = null this.first = (this.first + 1) % this.queue.length this.size-- // 判斷當前隊列大小是否太小 // 爲了保證不浪費空間,在隊列空間等於總長度四分之一時 // 且不爲 2 時縮小總長度爲當前的一半 if (this.size === this.getLength() / 4 && this.getLength() / 2 !== 0) { this.resize(this.getLength() / 2) } return r } getHeader() { if (this.isEmpty()) { throw Error('Queue is empty') } return this.queue[this.first] } getLength() { return this.queue.length - 1 } isEmpty() { return this.first === this.last } resize(length) { let q = new Array(length) for