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