摘要:報文主體并不是一定要有的。緩存緩存作用減少了冗余的數據傳輸,節省了網費。當資源發生改變時,也隨之發生變化。本人水平有限,有不足之處,望大家指出改正。
前言
或許你在面試時遇到過這樣的問題:從輸入URL到瀏覽器顯示頁面發生了什么?
簡單的回答就是:
DNS解析
TCP建立連接
發送HTTP請求
服務器處理請求
如果有緩存直接讀緩存
沒有緩存返回響應內容
TCP斷開連接
瀏覽器解析渲染頁面
如果你覺得這樣回答過于簡單,不如來深入了解一下吧。
網絡基礎在此之前,先了解一下TCP/IP基礎知識。
TCP/IP參考模型
早期的TCP/IP模型是一個四層結構,從下往上依次是網絡接口層、互聯網層、傳輸層和應用層,后來將網絡接口層劃分為了物理層和數據鏈路層
應用層(Application)提供網絡與用戶應用軟件之間的接口服務
傳輸層(Transimission)提供建立、維護和取消傳輸連接功能,負責可靠地傳輸數據(PC)
傳輸層有兩個性質不同的協議:TCP(傳輸控制協議)和UDP(用戶數據報協議)
網絡層(Network)處理網絡間路由,確保數據及時傳送(路由器)
數據鏈路層(DataLink)負責無錯傳輸數據,確認幀、發錯重傳等(交換機)
物理層(Physics)提供機械、電氣、功能和過程特性(網卡、網線、雙絞線、同軸電纜、中繼器)
各層常用協議這里可以看到HTTP協議是構建于TCP之上,屬于應用層協議。
具體過程 1. DNS解析DNS服務是和HTTP協議一樣位于應用層的協議,提供域名到IP地址的解析服務。
得到IP地址后就可以建立連接了,這里還有兩個知識需要了解:
持久連接
持久連接(也稱為HTTP keep-alive)的特點是,只要任意一段沒有提出斷開連接,就保持TCP連接狀態。
管線化
持久連接建立后就可以使用管線化發送了,可以同時并發多個請求,不用等待一個接一個的響應。(在這里我想到了流的pipe方法。)
大致說一下:
計算機通過端口號識別訪問哪個服務,比如http;源端口號進行隨機端口,目的端口決定哪個程序進行接收
數據序號和確認序號用于保障傳輸數據的完整性和順序
需要注意的是TCP的連接、傳輸和斷開都受六個控制位的指揮(比如三次握手和四次揮手)
PSH(push急迫位)緩存區將滿,立刻速度傳輸
RST(reset重置位)連接斷了重新連接
URG(urgent緊急位)緊急信號
ACK(acknowlegement確認)為1就表示確認號
SYN(synchronous建立聯機)同步序號位 TCP建立連接時將這個值設為1
用戶數據存儲了應用層生成的HTTP報文
了解了這些,那么開始講重點
2.2 TCP三次握手和四次揮手三次握手
客戶端先發送一個帶SYN標志的數據包給服務器端
服務器收到后,回傳一個帶有SYN/ACK標志的數據包表示確認收到
客戶端再發送一個帶SYN/ACK標志的數據包,代表握手結束
四次揮手
客戶端向服務器發出了FIN報文段
服務器收到后,回復一個ACK應答
服務器也向客戶端發送一個FIN報文段,隨后關閉了服務器端的連接
客戶端收到之后,又向服務器回復一個ACK應答,過了一段計時等待,客戶端也關閉了連接(計時等待是為了確認服務器端已正常關閉)
四次揮手并不是必然的,當服務器已經沒有內容發給客戶端了,就直接發送FIN報文段,這樣就變成了三次揮手。3. HTTP請求/響應 3.1 HTTP報文
HTTP報文大致可分為報文首部和報文主體兩塊,兩者由空行(就相當于用了兩個換行符rnrn)來劃分。報文主體并不是一定要有的。
3.1.1 請求報文常用請求行方法:
GET 獲取資源
POST 向服務器端發送數據,傳輸實體主體
PUT 傳輸文件
HEAD 獲取報文首部
DELETE 刪除文件
OPTIONS 詢問支持的方法
TRACE 追蹤路徑
3.1.2 響應報文說到響應報文,就必要談到狀態碼:
2XX 成功
200(OK) 客戶端發過來的數據被正常處理
204(Not Content) 正常響應,沒有實體
206(Partial Content) 范圍請求,返回部分數據,響應報文中由Content-Range指定實體內容
3XX 重定向
301(Moved Permanently) 永久重定向
302(Found) 臨時重定向,規范要求方法名不變,但是都會改變
303(See Other) 和302類似,但必須用GET方法
304(Not Modified) 狀態未改變 配合(If-Match、If-Modified-Since、If-None_Match、If-Range、If-Unmodified-Since) (通常緩存會返回304狀態碼)
4XX 客戶端錯誤
400(Bad Request) 請求報文語法錯誤
401 (unauthorized) 需要認證
403(Forbidden) 服務器拒絕訪問對應的資源
404(Not Found) 服務器上無法找到資源
5XX 服務器端錯誤
500(Internal Server Error) 服務器故障
503(Service Unavailable) 服務器處于超負載或正在停機維護
3.1.3 首部通用首部
首部字段名 | 說明 |
---|---|
Cache-Control | 控制緩存行為 |
Connection | 連接的管理 |
Date | 報文日期 |
Pragma | 報文指令 |
Trailer | 報文尾部的首部 |
Trasfer-Encoding | 指定報文主體的傳輸編碼方式 |
Upgrade | 升級為其他協議 |
Via | 代理服務器信息 |
Warning | 錯誤通知 |
請求首部
首部字段名 | 說明 |
---|---|
Accept | 用戶代理可處理的媒體類型 |
Accept-Charset | 優先的字符集 |
Accept-Encoding | 優先的編碼 |
Accept-Langulage | 優先的語言 |
Authorization | Web認證信息 |
Expect | 期待服務器的特定行為 |
From | 用戶的電子郵箱地址 |
Host | 請求資源所在的服務器 |
If-Match | 比較實體標記 |
If-Modified-Since | 比較資源的更新時間 |
If-None-Match | 比較實體標記 |
If-Range | 資源未更新時發送實體Byte的范圍請求 |
If-Unmodified-Since | 比較資源的更新時間(和If-Modified-Since相反) |
Max-Forwards | 最大傳輸條數 |
Proxy-Authorization | 代理服務器需要客戶端認證 |
Range | 實體字節范圍請求 |
Referer | 請求中的URI的原始獲取方 |
TE | 傳輸編碼的優先級 |
User-Agent | HTTP客戶端程序的信息 |
響應首部
首部字段名 | 說明 |
---|---|
Accept-Ranges | 是否接受字節范圍 |
Age | 資源的創建時間 |
ETag | 資源的匹配信息 |
Location | 客戶端重定向至指定的URI |
Proxy-Authenticate | 代理服務器對客戶端的認證信息 |
Retry-After | 再次發送請求的時機 |
Server | 服務器的信息 |
Vary | 代理服務器緩存的管理信息 |
www-Authenticate | 服務器對客戶端的認證 |
實體首部
首部字段名 | 說明 |
---|---|
Allow | 資源可支持的HTTP方法 |
Content-Encoding | 實體的編碼方式 |
Content-Language | 實體的自然語言 |
Content-Length | 實體的內容大小(字節為單位) |
Content-Location | 替代對應資源的URI |
Content-MD5 | 實體的報文摘要 |
Content-Range | 實體的位置范圍 |
Content-Type | 實體主體的媒體類型 |
Expires | 實體過期時間 |
Last-Modified | 資源的最后修改時間 |
創建HTTP服務端
let http = require("http"); let app = http.createServer((req, res) => {// req是可讀流/res是可寫流 // 獲取請求報文信息 let method = req.method;// 方法 let httpVersion = req.httpVersion;// HTTP版本 let url = req.url; let headers = req.headers; console.log(method, httpVersion, url, headers); // 獲取請求體(如果請求體的數據大于64k,data事件會被觸發多次) let buffers = []; req.on("data", data => { buffers.push(data); }) req.on("end", () => { console.log(Buffer.concat(buffers).toString()); res.write("hello"); res.end("world"); }) }) // 監聽服務器事件 app.on("connection", socket => { console.log("建立連接"); }); app.on("close", () => { console.log("服務器關閉") }); app.on("error", err => { console.log(err); }); app.listen(3000, () => { console.log("server is starting on port 3000"); });
創建客戶端
let http = require("http"); let options = { hostname: "localhost", port: 3000, path: "/", method: "GET", // 設置實體首部 告訴服務端我當前要給你發什么樣的數據 headers: { "content-Type": "application/x-www-form-urlencoded", "Content-Length": 15 } } let req = http.request(options); req.on("response", res => { res.on("data", chunk => { console.log(chunk.toString()); }); }); req.end("name=js&&age=22")
然后使用node運行我們的客戶端
說了這么多,你可能已經大致了解了
從輸入URL到瀏覽器顯示頁面發生了什么,不用多說,我們再來看一下緩存。
減少了冗余的數據傳輸,節省了網費。
減少了服務器的負擔, 大大提高了網站的性能
加快了客戶端加載網頁的速度
4.2 緩存分類強制緩存:說白了就是第一次請求數據時,服務端將數據和緩存規則一并返回,下一次請求時瀏覽器直接根據緩存規則進行判斷,有就直接讀緩存數據庫,不用連接服務器;沒有,再去找服務器。
對比緩存,顧名思義,需要進行比較判斷是否可以使用緩存。
瀏覽器第一次請求數據時,服務器會將緩存標識與數據一起返回給客戶端,客戶端將二者備份至緩存數據庫中。
再次請求數據時,客戶端將備份的緩存標識發送給服務器,服務器根據緩存標識進行判斷,判斷成功后,返回304狀態碼,通知* 客戶端比較成功,可以使用緩存數據。
4.3 請求流程從上張圖我們可以看到,判斷緩存是否可用,有兩種方式
ETag是實體標簽的縮寫,根據實體內容生成的一段hash字符串,可以標識資源的狀態。當資源發生改變時,ETag也隨之發生變化。ETag是Web服務端產生的,然后發給瀏覽器客戶端。
Last-Modified是此資源的最后修改時間,
如果客戶端在請求到的資源中發現實體首部里有Last-Modified聲明,再次請求就會在頭里帶上if-Modified-Since字段
服務端收到請求后發現if-Modified-Since字段則與被請求資源的最后修改時間進行對比
說了這么多,不如直接來實現一下緩存
通過最后修改時間來判斷緩存是否可用
let http = require("http"); let url = require("url"); let path = require("path"); let fs = require("fs"); let mime = require("mime"); let app = http.createServer((req, res) => { // 根據url獲取客戶端要請求的文件路徑 let { parsename } = url.parse(req.url); let p = path.join(__dirname, "public", "." + pathname); // fs.stat()用來讀取文件信息,文件最后修改時間就是stat.ctime fs.stat(p, (err, stat) => { if (!err) { let since = req.headers["if-modified-since"];//客戶端發來的文件最后修改時間 if (since) { if (since === stat.ctime.toUTCString()) {//最后修改時間相等,讀緩存 res.statusCode = 304; res.end(); } else { sendFile(req, res, p, stat);//最后修改時間不相等,返回新內容 } } else { sendError(res); } } }) }) function sendError(res) { res.statusCode = 404; res.end(); } function sendFile(req, res, p, stat) { res.setHeader("Cache-Control", "no-cache");// 設置通用首部字段 控制緩存行為 res.setHeader("Last-Modified", stat.ctime.toUTCString());// 實體首部字段 資源最后修改時間 res.setHeader("Content-Type", mime.getType(p) + ";charset=utf8") fs.createReadStream(p).pipe(res); } app.listen(3000, () => { console.log("server is starting on port 3000"); });
最后修改時間存在問題:
1. 某些服務器不能精確得到文件的最后修改時間, 這樣就無法通過最后修改時間來判斷文件是否更新了。
2. 某些文件的修改非常頻繁,在秒以下的時間內進行修改. Last-Modified只能精確到秒。
3. 一些文件的最后修改時間改變了,但是內容并未改變。 我們不希望客戶端認為這個文件修改了。
4. 如果同樣的一個文件位于多個CDN服務器上的時候內容雖然一樣,修改時間不一樣。
通過ETag來判斷緩存是否可用
ETag就是根據文件內容來判斷,說白了就是采用MD5(md5并不叫加密算法,它不可逆,應該叫摘要算法)產生信息摘要,用摘要來進行比對。
let http = require("http"); let url = require("url"); let path = require("path"); let fs = require("fs"); let mime = require("mime"); // crypto是node.js中實現加密和解密的模塊 具體詳解請自行了解 let crypto = require("crypto"); let app = http.createServer((req, res) => { // 根據url獲取客戶端要請求的文件路徑 let { parsename } = url.parse(req.url); let p = path.join(__dirname, "public", "." + pathname); // fs.stat()用來讀取文件信息,文件最后修改時間就是stat.ctime fs.stat(p, (err, stat) => { let md5 = crypto.createHash("md5");//創建md5對象 let rs = fs.createReadStream(p); rs.on("data", function (data) { md5.update(data); }); rs.on("end", () => { let r = md5.digest("hex"); // 對文件進行md5加密 // 下次就拿最新文件的加密值 和客戶端請求來比較 let ifNoneMatch = req.headers["if-none-match"]; if (ifNoneMatch) { if (ifNoneMatch === r) { res.statusCode = 304; res.end(); } else { sendFile(req, res, p, r); } } else { sendFile(req, res, p, r); } }); }) }); function sendError(res) { res.statusCode = 404; res.end(); } function sendFile(req, res, p, stat) { res.setHeader("Cache-Control", "no-cache");// 設置通用首部字段 控制緩存行為 res.setHeader("Etag", r);// 響應首部字段 資源的匹配信息 res.setHeader("Content-Type", mime.getType(p) + ";charset=utf8") fs.createReadStream(p).pipe(res); } app.listen(3000, () => { console.log("server is starting on port 3000"); });最后
想深入學習http的同學,我推薦一本書《圖解HTTP》。
本人水平有限,有不足之處,望大家指出改正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94579.html
摘要:先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學,大學期間開始自學前端開發,在今年春招實習和秋招的時候投了一些公司,拿到一些京東拼多多虎牙等,總體來說還算滿意,特地寫一篇文章來總結一下面試的那些套路。 showImg(https://segmentfault.com/img/remote/1460000011897700); 先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學...
摘要:先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學,大學期間開始自學前端開發,在今年春招實習和秋招的時候投了一些公司,拿到一些京東拼多多虎牙等,總體來說還算滿意,特地寫一篇文章來總結一下面試的那些套路。 showImg(https://segmentfault.com/img/remote/1460000011897700); 先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學...
摘要:先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學,大學期間開始自學前端開發,在今年春招實習和秋招的時候投了一些公司,拿到一些京東拼多多虎牙等,總體來說還算滿意,特地寫一篇文章來總結一下面試的那些套路。 showImg(https://segmentfault.com/img/remote/1460000011897700); 先介紹一下本人應屆前端開發一枚,非科班出身,專業是化學...
摘要:擴展閱讀收集的前端面試題和答案前端開發面試題史上最全的前端面試題匯總及答案前端工程師手冊協議工作原理協議運行機制的概述 本書的 GitHub 地址:https://github.com/todayqq/PH... 對于大公司,很少會有全棧工程師這個崗位,全棧是個花哨的詞,對于現在比較熱門的技術,不論是 Vue 還是 Laravel,只要智商不差,看著文檔,都能寫出一個 CURD 來,...
摘要:前端篇收集的前端面試題和答案前端開發面試題史上最全的前端面試題匯總及答案前端工程師手冊協議工作原理協議運行機制的概述協議篇原理原理解析的工作原理與的區別理解后端篇年的面試總結垃圾回收機制面向對象設計淺談說清楚是什么和的區別索引原理及慢查 前端篇 收集的前端面試題和答案 前端開發面試題 史上最全的web前端面試題匯總及答案 前端工程師手冊 HTTP協議:工作原理 SSL/TLS協議運行...
摘要:地址每次面試多多少少都會被問到等等之類協議,協議相關的問題也可以說是面試必備,所以我把這些知識單獨收集成了一篇文章。即標志位和標志位均為。發送完畢后,服務器端進入狀態。認證服務器對客戶端進行認證以后,確認無誤,同意發放令牌。 Git 地址:https://github.com/todayqq/PH... 每次面試多多少少都會被問到 HTTP、HTTPS、TCP、Socket、 OAu...
閱讀 3469·2021-09-02 09:53
閱讀 1793·2021-08-26 14:13
閱讀 2750·2019-08-30 15:44
閱讀 1313·2019-08-30 14:03
閱讀 1962·2019-08-26 13:42
閱讀 3014·2019-08-26 12:21
閱讀 1302·2019-08-26 11:54
閱讀 1899·2019-08-26 10:46