http1.1归敬、http2.0和http3.0的演進(jìn)

http1.1

http1.1的優(yōu)點(diǎn)

  • 1. 簡(jiǎn)單

HTTP 基本的報(bào)文格式就是 header + body,頭部信息也是 key-value 簡(jiǎn)單文本的形式,易于理解汪茧,降低了學(xué)習(xí)和使用的門檻椅亚。

  • 2. 靈活和易于擴(kuò)展

HTTP 協(xié)議里的各類請(qǐng)求方法、URI/URL舱污、狀態(tài)碼玩郊、頭字段等每個(gè)組成要求都沒(méi)有被固定死办斑,都允許開(kāi)發(fā)人員自定義和擴(kuò)充挽鞠。

同時(shí) HTTP 由于是工作在應(yīng)用層( OSI 第七層)闹究,則它下層可以隨意變化,比如:

  • HTTPS 就是在 HTTP 與 TCP 層之間增加了 SSL/TLS安全傳輸層珠插;
  • HTTP/1.1 和 HTTP/2.0 傳輸協(xié)議使用的是 TCP 協(xié)議惧磺,而到了 HTTP/3.0 傳輸協(xié)議改用了 UDP 協(xié)議
  • 3. 應(yīng)用廣泛和跨平臺(tái)

互聯(lián)網(wǎng)發(fā)展至今捻撑,HTTP 的應(yīng)用范圍非常的廣泛磨隘,從臺(tái)式機(jī)的瀏覽器到手機(jī)上的各種 APP,從看新聞顾患、刷貼吧到購(gòu)物番捂、理財(cái)、吃雞江解,HTTP 的應(yīng)用遍地開(kāi)花设预,同時(shí)天然具有跨平臺(tái)的優(yōu)越性。

http1.1的缺點(diǎn)

缺點(diǎn)很明顯

    1. 無(wú)狀態(tài)雙刃劍

無(wú)狀態(tài)的好處犁河,因?yàn)榉?wù)器不會(huì)去記憶 HTTP 的狀態(tài)鳖枕,所以不需要額外的資源來(lái)記錄狀態(tài)信息,這能減輕服務(wù)器的負(fù)擔(dān)呼股,能夠把更多的 CPU 和內(nèi)存用來(lái)對(duì)外提供服務(wù)耕魄。

無(wú)狀態(tài)的壞處画恰,既然服務(wù)器沒(méi)有記憶能力彭谁,它在完成有關(guān)聯(lián)性的操作時(shí)會(huì)非常麻煩。

對(duì)于無(wú)狀態(tài)的問(wèn)題允扇,解法方案有很多種缠局,其中比較簡(jiǎn)單的方式用 Cookie技術(shù)和Session技術(shù),而Session又是基于Cookie實(shí)現(xiàn)的考润,這里不過(guò)多介紹倆張方案的實(shí)現(xiàn)狭园。

    1. 明文傳輸

明文意味著在傳輸過(guò)程中的信息,是可方便閱讀的糊治,比如 Wireshark 抓包都可以直接肉眼查看唱矛,為我們調(diào)試工作帶了極大的便利性。

這正是這樣,HTTP 的所有信息都暴露在了光天化日下绎谦,相當(dāng)于信息裸奔,也就是http傳輸非常不安全管闷。

    1. 傳輸過(guò)程數(shù)據(jù)可能被篡改

由于數(shù)據(jù)沒(méi)有進(jìn)行加密,傳輸過(guò)程都是明文窃肠,則很有可能被中間站攔截并篡改包个,所以http無(wú)法證明報(bào)文的完整性,且容易被篡改冤留。

    1. 無(wú)法驗(yàn)證通訊雙方身份

不驗(yàn)證通信雙方的身份碧囊,因此有可能遭遇偽裝并劫持流量。

對(duì)于無(wú)狀態(tài)這一點(diǎn)特點(diǎn)來(lái)說(shuō)纤怒,不管是http1.1還是http2.0和http3.0都保持這一特點(diǎn)糯而,因?yàn)檫@一特點(diǎn)并不是完全是一個(gè)缺點(diǎn)。

而對(duì)于明文傳輸泊窘、保證不了報(bào)文的完整性無(wú)法驗(yàn)證雙方身份的這3個(gè)缺點(diǎn)歧蒋,https基本都解決啦,https是在應(yīng)用層和傳輸層中間增加了一層SSL/TSL協(xié)議,https具體解決這3個(gè)問(wèn)題的實(shí)現(xiàn)方式這里也不過(guò)多介紹州既。但是需要注意的一點(diǎn)是普通的https僅驗(yàn)證了服務(wù)端的身份(通過(guò)CA證書(shū))谜洽,其實(shí)并沒(méi)有驗(yàn)證客戶端的身份,所以有的https服務(wù)仍然可以通過(guò)抓包工具獲取吴叶。

你可能也發(fā)現(xiàn)了阐虚,有的https服務(wù)可以被抓包,有的卻抓不到蚌卤,原因就是看https服務(wù)是否開(kāi)啟的雙向驗(yàn)證实束,開(kāi)始了雙向驗(yàn)證之后不僅是在服務(wù)端需要按照證書(shū),客戶端也需要安裝證書(shū)逊彭,也就是只有開(kāi)啟了雙向驗(yàn)證的https來(lái)可以驗(yàn)證雙方身份咸灿,且無(wú)法被抓到工具獲取

http1.1的性能如何

HTTP 協(xié)議是基于 TCP/IP侮叮,并且使用了「請(qǐng)求 - 應(yīng)答」的通信模式避矢,所以性能的關(guān)鍵就在這兩點(diǎn)里。

「請(qǐng)求 - 應(yīng)答」的通信模式是在同一個(gè)TCP連接里客戶端發(fā)出一個(gè)請(qǐng)求之后只能等待該請(qǐng)求被響應(yīng)之后囊榜,客戶端才可以發(fā)生下一個(gè)請(qǐng)求审胸,如果上個(gè)請(qǐng)求一直沒(méi)有被響應(yīng),那么就是會(huì)被阻塞卸勺,可以發(fā)現(xiàn)http的性能的關(guān)鍵就在于此砂沛。

    1. 長(zhǎng)鏈接

早期 HTTP/1.0 性能上的一個(gè)很大的問(wèn)題,那就是每發(fā)起一個(gè)請(qǐng)求曙求,都要新建一次 TCP 連接(三次握手)碍庵,請(qǐng)求完成后映企,都要斷開(kāi)TCP鏈接(4次揮手),俗稱短鏈接静浴,而且是串行請(qǐng)求卑吭,做了無(wú)謂的 TCP 連接建立和斷開(kāi),增加了很大的通信開(kāi)銷马绝。

為了解決上述 TCP 連接問(wèn)題豆赏,HTTP/1.1 提出了長(zhǎng)連接的通信方式,也叫持久連接富稻,且是http1.1的默認(rèn)方式了掷邦。這種方式的好處在于減少了 TCP 連接的重復(fù)建立斷開(kāi)所造成的額外開(kāi)銷,減輕了服務(wù)器端的負(fù)載椭赋。

短連接的特點(diǎn)是:只有任意一方執(zhí)行完代碼或者任意一方顯示的明確提出斷開(kāi)連接時(shí)則會(huì)斷開(kāi)鏈接抚岗。

長(zhǎng)連接的特點(diǎn)是:雙方代碼執(zhí)行完成后并不會(huì)斷開(kāi)鏈接,而只要任意一端顯示的明確提出斷開(kāi)連接哪怔,才會(huì)進(jìn)行4次揮手?jǐn)嚅_(kāi)TCP連接宣蔚。

    1. 管道傳輸

HTTP/1.1 默認(rèn)采用了長(zhǎng)連接的方式,這使得管道(pipeline)網(wǎng)絡(luò)傳輸成為了可能认境。

即可在同一個(gè) TCP 連接里面胚委,客戶端可以發(fā)起多個(gè)請(qǐng)求只要第一個(gè)請(qǐng)求發(fā)出去了叉信,不必等其回來(lái)亩冬,就可以發(fā)第二個(gè)請(qǐng)求出去,可以減少整體的響應(yīng)時(shí)間硼身。

還記得開(kāi)頭說(shuō)的「請(qǐng)求 - 應(yīng)答」的通信模式嗎硅急?該模式下同一個(gè)tcp客戶端只能同時(shí)發(fā)送一個(gè)請(qǐng)求,只有該請(qǐng)求被響應(yīng)之后才可以發(fā)送下一個(gè)請(qǐng)求佳遂,這種模式對(duì)應(yīng)http的性能影響很大营袜,而管道傳輸其實(shí)就是想解決這樣問(wèn)題的,但是遺憾的是管道傳輸并沒(méi)有本質(zhì)上解決上述的問(wèn)題丑罪,原因繼續(xù)往下看荚板。

    1. 隊(duì)頭阻塞

舉例來(lái)說(shuō),客戶端需要請(qǐng)求兩個(gè)資源巍糯。以前的做法是啸驯,在同一個(gè) TCP 連接里面客扎,先發(fā)送 A 請(qǐng)求祟峦,然后等待服務(wù)器做出回應(yīng),收到后再發(fā)出 B 請(qǐng)求徙鱼。那么宅楞,管道機(jī)制則是允許瀏覽器同時(shí)發(fā)出 A 請(qǐng)求和 B 請(qǐng)求针姿,如下圖:


但是服務(wù)器必須按照接收請(qǐng)求的順序發(fā)送對(duì)這些管道化請(qǐng)求的響應(yīng)。

如果服務(wù)端在處理 A 請(qǐng)求時(shí)耗時(shí)比較長(zhǎng)厌衙,那么后續(xù)的請(qǐng)求的處理都會(huì)被阻塞住距淫,這稱為「隊(duì)頭堵塞」。所以管道傳輸只解決了請(qǐng)求隊(duì)頭阻塞而沒(méi)有解決響應(yīng)隊(duì)頭阻塞婶希。也就是說(shuō)管道傳輸其實(shí)很雞肋榕暇,沒(méi)什么卵用。

TIP:實(shí)際上 HTTP/1.1 管道化技術(shù)不是默認(rèn)開(kāi)啟喻杈,而且瀏覽器基本都沒(méi)有支持彤枢。

有沒(méi)有想過(guò)為什么響應(yīng)時(shí)必須按照請(qǐng)求的順序返回呢?如果沒(méi)有按照請(qǐng)求的順序返回會(huì)發(fā)生什么情況呢筒饰?我舉個(gè)??缴啡,如果沒(méi)有按照請(qǐng)求順序響應(yīng)會(huì)發(fā)生什么結(jié)果:

如果有倆個(gè)請(qǐng)求a和b,請(qǐng)求a先到達(dá)服務(wù)器瓷们,但由于某種原因?qū)е耡的響應(yīng)被阻塞业栅,而請(qǐng)求b隨后到達(dá)服務(wù)器并得到了及時(shí)的響應(yīng),那么返回的響應(yīng)的順序其實(shí)發(fā)生了顛倒谬晕。

客戶端可能會(huì)錯(cuò)誤地將屬于請(qǐng)求a和響應(yīng)b關(guān)聯(lián)碘裕,此時(shí)數(shù)據(jù)就會(huì)發(fā)生了錯(cuò)亂。這就導(dǎo)致客戶端難以確定哪個(gè)請(qǐng)求對(duì)應(yīng)哪個(gè)響應(yīng)攒钳,從而引發(fā)數(shù)據(jù)錯(cuò)亂娘汞。

這就是為什么要求響應(yīng)和請(qǐng)求的順序要一致,一致時(shí)就是可以順序匹配請(qǐng)求和響應(yīng)夕玩,將不會(huì)出現(xiàn)上述的數(shù)據(jù)錯(cuò)亂問(wèn)題你弦。

本質(zhì)原因就是請(qǐng)求和響應(yīng)不能一一對(duì)應(yīng)而只能按照順序來(lái)匹配,可以繼續(xù)往下看燎孟,http2.0是怎么解決這個(gè)對(duì)應(yīng)的問(wèn)題的禽作。

http2.0

可以發(fā)現(xiàn)http1.1的性能其實(shí)很一般,http2.0對(duì)其做了很多改進(jìn)揩页,使性能發(fā)生了質(zhì)的提升旷偿。廢話不多說(shuō),上圖爆侣。



那 HTTP/2 相比 HTTP/1.1 性能上的改進(jìn):

  • 內(nèi)置TLS協(xié)議萍程。
  • 頭部壓縮:使用靜態(tài)表、動(dòng)態(tài)表和HPack實(shí)現(xiàn)兔仰。
  • 二進(jìn)制格式:Header+Body都使用二進(jìn)制傳輸茫负。
  • 并發(fā)傳輸:使用Stream、fream實(shí)現(xiàn)乎赴,Stream是實(shí)現(xiàn)并發(fā)傳輸?shù)年P(guān)鍵忍法。
  • 服務(wù)器主動(dòng)推送資源潮尝。

頭部壓縮

HTTP 協(xié)議的報(bào)文是由「Header + Body」構(gòu)成的,對(duì)于 Body 部分饿序,HTTP/1.1協(xié)議可以使用頭字段 「Content-Encoding」指定 Body 的壓縮方式勉失,比如用gzip 壓縮,這樣可以節(jié)約帶寬原探,但報(bào)文中的另外一部分 Header乱凿,是沒(méi)有針對(duì)它的優(yōu)化手段。

HTTP/1.1 報(bào)文中 Header 部分存在的問(wèn)題:

  • 含很多固定的字段咽弦,比如 Cookie告匠、User Agent、Accept 等离唬,這些字段加起來(lái)也高達(dá)幾百字節(jié)甚至上千字節(jié)后专,所以有必要壓縮
  • 大量的請(qǐng)求和響應(yīng)的報(bào)文里有很多字段和字段值都是重復(fù)输莺,這樣會(huì)使得大量帶寬被這些冗余的數(shù)據(jù)占用了戚哎,所以有必須要避免重復(fù)性
  • 字段是 ASCII 編碼的嫂用,雖然易于人類觀察型凳,但效率低,所以有必要改成二進(jìn)制編碼嘱函;

HTTP/2 對(duì) Header 部分做了大改造甘畅,把以上的問(wèn)題都解決了。

HTTP/2 沒(méi)使用常見(jiàn)的 gzip 壓縮方式來(lái)壓縮頭部往弓,而是開(kāi)發(fā)了HPACK 算法疏唾,HPACK 算法主要包含三個(gè)組成部分:

  • 靜態(tài)字典
  • 動(dòng)態(tài)字典
  • Huffman 編碼(壓縮算法)

客戶端和服務(wù)器兩端都會(huì)建立和維護(hù)「字典」,用長(zhǎng)度較小的索引號(hào)表示重復(fù)的字符串函似,再用Huffman 編碼壓縮數(shù)據(jù)槐脏,可達(dá)到 50%~90% 的高壓縮率。

靜態(tài)字典

首先TCP連接建立后撇寞,客戶端和服務(wù)端都會(huì)有一張靜態(tài)字典顿天,它是寫入到 HTTP/2 框架里的,不會(huì)變化的蔑担,靜態(tài)表里共有61 組牌废,如下圖:


表中的Index 表示索引(Key),Header Value表示索引對(duì)應(yīng)的 Value啤握,Header Name表示字段的名字鸟缕,比如 Index 為 2 代表Header頭中method: GET,Index 為 8 代表Header頭中的狀態(tài)碼 Status :200恨统。

你可能注意到叁扫,表中有的 Index 沒(méi)有對(duì)應(yīng)的 Header Value三妈,這是因?yàn)檫@些 Value 并不是固定的而是變化的畜埋,這些 Value 都會(huì)經(jīng)過(guò) Huffman 編碼后莫绣,才會(huì)發(fā)送出去,具體是怎么實(shí)現(xiàn)的呢悠鞍?繼續(xù)往下看:

我們來(lái)看個(gè)具體的例子对室,比如Header頭中下面這個(gè) server 頭部字段,在 HTTP/1.1 的形式如下:

server: nghttpx\r\n

先給出結(jié)論:在http1.1中算上冒號(hào)空格和末尾的\r\n咖祭,共占用了17 字節(jié)掩宜,而使用了靜態(tài)表Huffman 編碼,可以將它壓縮成 8 字節(jié)么翰,壓縮率大概 47%牺汤。

根據(jù) RFC7541 規(guī)范,如果頭部字段屬于靜態(tài)表范圍浩嫌,并且Value變化的檐迟,整個(gè)頭部格式如下圖:


我抓了個(gè) HTTP/2 協(xié)議的網(wǎng)絡(luò)包,你可以從下圖看到码耐,高亮部分就是 server: nghttx 頭部字段的二進(jìn)制數(shù)據(jù)追迟,只用了 8 個(gè)字節(jié)而已。

對(duì)照著頭部格式來(lái)一步一步分析:

  1. 如果頭部字段屬于靜態(tài)表范圍骚腥,并且Value 是變化時(shí)敦间,第一個(gè)字節(jié)的前倆位固定為01,后6位是頭部字段server在靜態(tài)表中的索引值,也就是54束铭,轉(zhuǎn)化為二進(jìn)制為110110,拼接起來(lái)之后第一個(gè)字節(jié)為01110110廓块。

  2. 第二個(gè)字節(jié)中的第一個(gè)位H表示Value 是否經(jīng)過(guò) Huffman 編碼1表示經(jīng)過(guò) Huffman 編碼契沫,0則相反剿骨。后面的7個(gè)bit位表示 Value 的長(zhǎng)度,也就是nghttx經(jīng)過(guò)huffman編碼后的長(zhǎng)度為7,也就是0111埠褪,至于為什么是7浓利,繼續(xù)看下面,這里寫暫且認(rèn)為是7,拼接上首位1后钞速,第二個(gè)字節(jié)表示為10000111贷掖。

  3. 接著計(jì)算nghttx的長(zhǎng)度。value的值是通過(guò)huffman編碼算出來(lái)的渴语,而Huffman 編碼的原理是將高頻出現(xiàn)的信息用「較短」的編碼表示苹威,從而縮減字符串長(zhǎng)度。

    于是驾凶,在統(tǒng)計(jì)大量的 HTTP 頭部后牙甫,HTTP/2 根據(jù)出現(xiàn)頻率將 ASCII 碼編改為了Huffman 編碼表掷酗,可以在 RFC7541 文檔找到這張靜態(tài) Huffman 表,我就不把表的全部?jī)?nèi)容列出來(lái)了窟哺,我只列出字符串 nghttpx 中每個(gè)字符對(duì)應(yīng)的 Huffman 編碼泻轰,如下圖:


    通過(guò)查表后,字符串 nghttpx的 Huffman 編碼在下圖看到且轨,共6個(gè)字節(jié)浮声,每一個(gè)字符的 Huffman 編碼,我用相同的顏色將他們對(duì)應(yīng)起來(lái)了旋奢,最后的 7 位是補(bǔ)位的泳挥。

    最終,server 頭部的二進(jìn)制數(shù)據(jù)對(duì)應(yīng)的靜態(tài)頭部格式如下:

動(dòng)態(tài)字典

靜態(tài)表只包含了61高頻出現(xiàn)在頭部的字符串至朗,不在靜態(tài)表范圍內(nèi)的頭部字符串就要自行構(gòu)建動(dòng)態(tài)表屉符,它的 Index62 起步,會(huì)在編碼解碼的時(shí)候隨時(shí)更新锹引,也就是靜態(tài)字典喝動(dòng)態(tài)字典是結(jié)合使用的矗钟。

比如,第一次發(fā)送請(qǐng)求時(shí)的request頭部中的「Cookie」字段數(shù)據(jù)有上百個(gè)字節(jié)粤蝎,經(jīng)過(guò) Huffman 編碼發(fā)送出去后真仲,客戶端就會(huì)更新自己的動(dòng)態(tài)字典,添加一個(gè)Index 號(hào) 62的數(shù)據(jù)初澎。

當(dāng)服務(wù)器收到請(qǐng)求之后會(huì)更新自己的動(dòng)態(tài)表秸应,也添加一個(gè)新的 Index 號(hào) 62

那么在下一次請(qǐng)求的時(shí)候碑宴,就不用重復(fù)發(fā)這個(gè)字段的數(shù)據(jù)了软啼,只用發(fā) 1個(gè)字節(jié)的 Index 號(hào)就好了,因?yàn)殡p方都可以根據(jù)自己的動(dòng)態(tài)表獲取到字段的數(shù)據(jù)延柠。

細(xì)心的人可能發(fā)現(xiàn)祸挪,如果客戶端請(qǐng)求發(fā)出后,由于網(wǎng)絡(luò)原因服務(wù)端并沒(méi)有收到請(qǐng)求贞间,此時(shí)會(huì)出現(xiàn)的情況是客戶端已經(jīng)更新Index為62的記錄贿条,而服務(wù)并沒(méi)有更新。
如果此時(shí)客戶端再次請(qǐng)求時(shí)增热,攜帶的是62的Index整以,而服務(wù)端收到62之后不清楚是什么意思,因?yàn)榉?wù)端并沒(méi)有存儲(chǔ)62的數(shù)據(jù)峻仇,此時(shí)就會(huì)出現(xiàn)問(wèn)題公黑,
這是http2.0的一個(gè)潛在問(wèn)題,http3.0對(duì)該問(wèn)題進(jìn)行了修復(fù)。

需要注意的是:新建的連接初始化時(shí)只有靜態(tài)表凡蚜,只有在同一個(gè)連接上后續(xù)的請(qǐng)求時(shí)才會(huì)動(dòng)態(tài)的增加動(dòng)態(tài)字典人断,連接銷毀時(shí)對(duì)應(yīng)的動(dòng)態(tài)字典也就隨之消失。如果消息字段在 1 個(gè)連接上只發(fā)送了 1 次朝蜘,或者重復(fù)傳輸時(shí)恶迈,字段總是略有變化,動(dòng)態(tài)表就無(wú)法被充分利用了芹务。

因此蝉绷,隨著在同一 HTTP/2 連接上發(fā)送的報(bào)文越來(lái)越多鸭廷,客戶端和服務(wù)器雙方的「字典」積累的越來(lái)越多枣抱,理論上最終每個(gè)頭部字段都會(huì)變成 1 個(gè)字節(jié)的 Index,這樣便避免了大量的冗余數(shù)據(jù)的傳輸辆床,大大節(jié)約了帶寬佳晶。

理想很美好,現(xiàn)實(shí)很骨感讼载。動(dòng)態(tài)表越大轿秧,占用的內(nèi)存也就越大,如果占用了太多內(nèi)存咨堤,是會(huì)影響服務(wù)器性能的菇篡,因此 Web 服務(wù)器都會(huì)提供類似 http2_max_requests的配置,用于限制一個(gè)連接上能夠傳輸?shù)恼?qǐng)求數(shù)量一喘,避免動(dòng)態(tài)表無(wú)限增大驱还,請(qǐng)求數(shù)量到達(dá)上限后,就會(huì)關(guān)閉 HTTP/2 連接來(lái)釋放內(nèi)存凸克。

綜上议蟆,HTTP/2 頭部的編碼通過(guò)「靜態(tài)表、動(dòng)態(tài)表萎战、Huffman 編碼」共同完成的咐容。

二進(jìn)制

HTTP/2 厲害的地方在于將 HTTP/1 的文本格式改成二進(jìn)制格式傳輸數(shù)據(jù),極大提高了 HTTP 傳輸效率蚂维,而且二進(jìn)制數(shù)據(jù)使用位運(yùn)算能高效解析戳粒。

二進(jìn)制數(shù)據(jù)傳輸?shù)幕締挝皇?code>二進(jìn)制幀,即為fream,下圖為fream的結(jié)構(gòu)

  1. 幀開(kāi)頭的前 3 個(gè)字節(jié)表示幀數(shù)據(jù)(Frame Playload)的長(zhǎng)度虫啥。
  2. 幀長(zhǎng)度后面的一個(gè)字節(jié)是表示幀的類型蔚约,HTTP/2 總共定義了 10 種類型的幀,一般分為數(shù)據(jù)幀控制幀兩類孝鹊,如下表格:

    我們主要關(guān)注數(shù)據(jù)幀,我們知道http2.0中的·Header+Body·都是使用·二進(jìn)制幀·來(lái)實(shí)現(xiàn)炊琉,如果幀的類型為HEADRERS則表示該幀的數(shù)據(jù)為·Header數(shù)據(jù)·,如果幀的類型為DATA則表示該幀的數(shù)據(jù)為Body數(shù)據(jù)。
    幀的類型主要的作用是表明該數(shù)據(jù)是什么類型的數(shù)據(jù)苔咪。
  3. 幀類型后面的一個(gè)字節(jié)是標(biāo)志位锰悼,可以保存 8 個(gè)標(biāo)志位,用于攜帶簡(jiǎn)單的控制信息团赏,比如:
    • END_HEADERS 表示頭數(shù)據(jù)結(jié)束標(biāo)志箕般,相當(dāng)于 HTTP/1 里頭后的空行(“\r\n”);
    • END_Stream 表示單方向數(shù)據(jù)發(fā)送結(jié)束舔清,后續(xù)不會(huì)再有數(shù)據(jù)幀丝里。
    • PRIORITY 表示流的優(yōu)先級(jí);

標(biāo)志位的作用是非常重要的体谒,想象一個(gè)http請(qǐng)求被分成了多個(gè)幀數(shù)據(jù)發(fā)生,服務(wù)端在接受到這么多幀數(shù)據(jù)的時(shí)杯聚,可以根據(jù)數(shù)據(jù)幀的類別區(qū)別出哪些幀是Header數(shù)據(jù)哪些是Body數(shù)據(jù)。

但是一個(gè)完整的Header數(shù)據(jù)或者Body數(shù)據(jù)是被分成多個(gè)幀數(shù)據(jù)發(fā)送的抒痒,服務(wù)端是當(dāng)收到幀類別為HEADRERS且標(biāo)志位為END_HEADERS時(shí)表示該請(qǐng)求的header數(shù)據(jù)已經(jīng)全部接受完成了幌绍,可以處理header請(qǐng)求了

同理當(dāng)收到幀類別為DATA且標(biāo)識(shí)位為END_Stream時(shí)表示該請(qǐng)求的body數(shù)據(jù)全部發(fā)生完成了故响,可以處理body請(qǐng)求了傀广。

可以發(fā)現(xiàn)標(biāo)志位的作用非常重要,但是有沒(méi)有發(fā)現(xiàn)一點(diǎn)彩届,如果幀數(shù)據(jù)發(fā)生順序錯(cuò)亂伪冰,會(huì)發(fā)生嚴(yán)重的問(wèn)題,比如當(dāng)收到了一個(gè)幀類別為HEADRERS且標(biāo)志位為END_HEADERS的幀數(shù)據(jù)時(shí)樟蠕,就代表著header頭數(shù)據(jù)發(fā)生完畢了贮聂,但是由于網(wǎng)絡(luò)原因?qū)е缕渲械囊粋€(gè)header幀數(shù)據(jù)又發(fā)生過(guò)來(lái)了,這不出現(xiàn)很重的問(wèn)題了嗎坯墨?我已經(jīng)認(rèn)為header數(shù)據(jù)發(fā)生完畢了寂汇,然后過(guò)一會(huì),你發(fā)過(guò)來(lái)一個(gè)header數(shù)據(jù)捣染,我該怎么處理啊骄瓣,全亂了。

所以同一個(gè)stream里的幀數(shù)據(jù)是嚴(yán)格要求順序的發(fā)送的耍攘,不可以亂序發(fā)送的榕栏,時(shí)序問(wèn)題由tcp的順序性保證(序列號(hào))。

  1. 幀頭的最后 4 個(gè)字節(jié)是流標(biāo)識(shí)符(Stream ID),最高位被保留不用蕾各,只有 31 位可以使用扒磁,因此流標(biāo)識(shí)符的最大值是 2^31,大約是 21 億式曲,它的作用是用來(lái)標(biāo)識(shí)該 Frame 屬于哪個(gè) Stream妨托,接收方可以根據(jù)這個(gè)信息從亂序的幀(這里的亂序是指所屬不同Stream的Fream是亂序的缸榛,但是同一個(gè)Stream內(nèi)的Fream肯定是有序的)里找到相同 Stream ID 的幀,從而有序組裝信息兰伤。

如果你不懂Stream和Fream的關(guān)系内颗,那么繼續(xù)往下看

并發(fā)傳輸

知道了 HTTP/2 的幀結(jié)構(gòu)后,我們?cè)賮?lái)看看它是如何實(shí)現(xiàn)并發(fā)傳輸?shù)摹?/p>

我們都知道 HTTP/1.1 的實(shí)現(xiàn)是基于請(qǐng)求-響應(yīng)模型的敦腔。同一個(gè)TCP連接中均澳,HTTP 完成一個(gè)事務(wù)(請(qǐng)求與響應(yīng)),才能處理下一個(gè)事務(wù)符衔,也就是說(shuō)在發(fā)出請(qǐng)求等待響應(yīng)的過(guò)程中找前,是沒(méi)辦法做其他事情的,如果響應(yīng)遲遲不來(lái)判族,那么后續(xù)的請(qǐng)求是無(wú)法發(fā)送的躺盛,也造成了隊(duì)頭阻塞的問(wèn)題,這也是http1.1的性能關(guān)鍵五嫂。

而 HTTP/2 就很牛逼了颗品,引出了 Stream 概念肯尺,多個(gè) Stream 可復(fù)用在一條 TCP 連接沃缘。實(shí)現(xiàn)了在同一個(gè)TCP連接上可以并發(fā)多個(gè)請(qǐng)求和響應(yīng)。

為了理解 HTTP/2 的并發(fā)是怎樣實(shí)現(xiàn)的则吟,我們先來(lái)理解 HTTP/2 中的 Stream槐臀、Message、Frame 這 3 個(gè)概念氓仲。


你可以從上圖中看到:

  • 1 個(gè) TCP 連接包含一個(gè)或者多個(gè) Stream水慨,Stream 是 HTTP/2 并發(fā)的關(guān)鍵技術(shù)。一個(gè)TCP連接由相同的四元組組成敬扛,即源ip晰洒、源端口、目標(biāo)ip和目標(biāo)端口啥箭,也就是客戶端并發(fā)請(qǐng)求谍珊,服務(wù)端也可以并發(fā)響應(yīng);
  • Stream 里可以包含 1 個(gè)或多個(gè) Message急侥,Message 對(duì)應(yīng) HTTP/1 中的請(qǐng)求或響應(yīng)砌滞,由 HTTP 頭部和包體構(gòu)成;
  • Message 里包含一條或者多個(gè) Frame坏怪,F(xiàn)rame 是 HTTP/2 最小單位贝润,以二進(jìn)制壓縮格式存放 HTTP/1 中的內(nèi)容(頭部和包體);

我估計(jì)你看到這里還是云里霧里的铝宵,為什么Stream能實(shí)現(xiàn)并發(fā)傳輸呢打掘?Stream到底是個(gè)什么呢?

想知道這個(gè)問(wèn)題的答案,先來(lái)回顧下http1.1中開(kāi)啟管道傳輸之后為什么只支持客戶端并發(fā)請(qǐng)求而不支持服務(wù)端的并發(fā)響應(yīng)呢尊蚁?根本原因就是服務(wù)端的響應(yīng)和客戶端的請(qǐng)求對(duì)應(yīng)不上唯绍,只能根據(jù)請(qǐng)求和響應(yīng)的順序來(lái)匹配。

Stream其實(shí)是一個(gè)唯一的ID標(biāo)識(shí)枝誊,在同一個(gè)tcp連接中的 Stream ID 是不能復(fù)用的况芒,只能順序遞增Stream可以理解為并不是一個(gè)真實(shí)存在的東西叶撒,就是一個(gè)唯一的自增ID绝骚。

我們知道幀數(shù)據(jù)Fream是數(shù)據(jù)傳輸?shù)幕締挝唬?code>Fream結(jié)構(gòu)中有個(gè)流標(biāo)識(shí)符是用來(lái)表示該Fream所屬的Stream ID祠够。

客戶端的每次請(qǐng)求都會(huì)分配一個(gè)唯一的Stream ID压汪。比如客戶端發(fā)出一個(gè)請(qǐng)求,Stream ID為123古瓤,而請(qǐng)求體中Header+Body會(huì)被分割成多個(gè)Fream止剖,且這些Fream的標(biāo)識(shí)符都將是123;當(dāng)服務(wù)端響應(yīng)數(shù)據(jù)時(shí)落君,也會(huì)將響應(yīng)體中的Header+Body分割成多個(gè)Fream穿香,且這些響應(yīng)體的所有Fream的標(biāo)識(shí)符也同樣是123

看到這里绎速,我想你應(yīng)該明白了Stream是怎么實(shí)現(xiàn)并發(fā)傳輸?shù)牧税善せ瘛ttp1.1中由于不知道返回的響應(yīng)是屬于哪個(gè)請(qǐng)求的,所以只能默認(rèn)按照順序匹配纹冤。而http2.0中請(qǐng)求和響應(yīng)共用一個(gè)Stream ID,這樣就可以將請(qǐng)求和響應(yīng)進(jìn)行精準(zhǔn)匹配啦洒宝。

因此,我們可以得出個(gè)結(jié)論:多個(gè) Stream 跑在一條 TCP 連接萌京,同一個(gè) HTTP 請(qǐng)求與響應(yīng)是跑在同一個(gè) Stream 中雁歌,HTTP 消息可以由多個(gè) Frame 構(gòu)成, 一個(gè) Frame 可以由多個(gè) TCP 報(bào)文構(gòu)成知残。

在 HTTP/2 連接上靠瞎,不同 Stream 的幀是可以亂序發(fā)送的(因此可以并發(fā)不同的 Stream ),因?yàn)槊總€(gè)幀的頭部會(huì)攜帶 Stream ID 信息橡庞,所以接收端可以通過(guò) Stream ID 有序組裝成 HTTP 消息较坛,而同一 Stream 內(nèi)部的幀必須是嚴(yán)格有序的,在上面幀數(shù)據(jù)結(jié)構(gòu)介紹標(biāo)志位的說(shuō)明為什么必須保持有序

比如下圖扒最,服務(wù)端并行亂序的地發(fā)送了兩個(gè)響應(yīng): Stream 1 和 Stream 3丑勤,這兩個(gè) Stream 都是跑在一個(gè) TCP 連接上,客戶端收到后吧趣,會(huì)根據(jù)相同的 Stream ID 有序組裝成 HTTP 消息,并將組裝完成的消息發(fā)送給相同Stream ID 的請(qǐng)求法竞。


客戶端和服務(wù)器雙方都可以建立 Stream耙厚,因?yàn)?code>服務(wù)端可以主動(dòng)推送資源給客戶端。

客戶端建立的 Stream 必須是奇數(shù)號(hào)岔霸,而服務(wù)器建立的 Stream 必須是偶數(shù)號(hào)薛躬。

同一個(gè)連接中的 Stream ID 是不能復(fù)用的,只能順序遞增呆细。幀結(jié)構(gòu)中可以發(fā)現(xiàn)型宝,流標(biāo)識(shí)符(Stream ID),只有 31 位可以使用絮爷,因此流標(biāo)識(shí)符的最大值是 2^31趴酣,大約是 21 億,所以當(dāng) Stream ID 耗盡時(shí),需要發(fā)一個(gè)控制幀 GOAWAY坑夯,用來(lái)優(yōu)雅關(guān)閉 TCP 連接岖寞。

服務(wù)器主動(dòng)推送資源

HTTP/1.1 不支持服務(wù)器主動(dòng)推送資源給客戶端,都是由客戶端向服務(wù)器發(fā)起請(qǐng)求后柜蜈,才能獲取到服務(wù)器響應(yīng)的資源仗谆。

比如,客戶端通過(guò) HTTP/1.1 請(qǐng)求從服務(wù)器那獲取到了 HTML 文件淑履,而 HTML 可能還需要依賴 CSS 來(lái)渲染頁(yè)面隶垮,這時(shí)客戶端還要再發(fā)起獲取 CSS 文件的請(qǐng)求,需要兩次消息往返鳖谈,如下圖左邊部分:


如上圖右邊部分岁疼,在 HTTP/2 中,客戶端在訪問(wèn) HTML 時(shí)缆娃,服務(wù)器可以直接主動(dòng)推送 CSS 文件,減少了消息傳遞的次數(shù)瑰排,減少了網(wǎng)絡(luò)耗時(shí)贯要。

在 Nginx 中,如果你希望客戶端訪問(wèn) /test.html 時(shí)椭住,服務(wù)器直接推送 /test.css崇渗,那么可以這么配置:

location /test.html { 
  http2_push /test.css; 
}

http2.0存在的缺點(diǎn)

http2.0相對(duì)于http1.1性能確實(shí)提升了很多,但是仍然還存在一些問(wèn)題

  • 對(duì)頭阻塞
  • TCP和TLS的握手延時(shí)
  • 網(wǎng)絡(luò)遷移需要重新建立連接

TCP 對(duì)頭阻塞

HTTP/2 多個(gè)請(qǐng)求是跑在一個(gè) TCP 連接中的京郑,那么當(dāng) TCP 丟包時(shí)宅广,整個(gè) TCP 都要等待重傳,那么就會(huì)阻塞該 TCP 連接中的所有請(qǐng)求些举。

比如下圖中跟狱,Stream 2 有一個(gè) TCP 報(bào)文丟失了,那么即使收到了 Stream 3 和 Stream 4 的 TCP 報(bào)文,應(yīng)用層也是無(wú)法讀取讀取的,相當(dāng)于阻塞了 Stream 3 和 Stream 4 請(qǐng)求座掘。


因?yàn)镠TTP2.0是基于TCP 是字節(jié)流協(xié)議昔案,TCP 層必須保證收到的字節(jié)數(shù)據(jù)是完整且有序的欣范,如果序列號(hào)較低的 TCP 段在網(wǎng)絡(luò)傳輸中丟失了戒悠,即使序列號(hào)較高的 TCP 段已經(jīng)被接收了烦衣,也會(huì)被阻塞在傳輸層嚷闭,應(yīng)用層也無(wú)法從內(nèi)核中讀取到這部分?jǐn)?shù)據(jù)纵寝,只有當(dāng)所有的TCP段都被接受成功之后论寨,才會(huì)將數(shù)據(jù)組裝之后發(fā)送給應(yīng)用層應(yīng)用層才會(huì)讀取數(shù)據(jù)爽茴。

可以發(fā)現(xiàn)出現(xiàn)阻塞的本質(zhì)原因是:阻塞出現(xiàn)在了傳輸層政基,而導(dǎo)致應(yīng)用層讀不到數(shù)據(jù)。
那么如果讓阻塞出現(xiàn)在應(yīng)用層而不要出現(xiàn)在網(wǎng)絡(luò)層是不是就可以避免對(duì)頭阻塞了闹啦,可以看下http3.0的實(shí)現(xiàn)沮明。

可以發(fā)現(xiàn)Http2.0出現(xiàn)對(duì)頭阻塞的場(chǎng)景只發(fā)送在Tcp丟包的場(chǎng)景下。

可以發(fā)現(xiàn)http2.0不管怎么優(yōu)化也解決不了TCP的對(duì)頭阻塞問(wèn)題窍奋,除非把TCP協(xié)議換了荐健, 這是TCP 協(xié)議固有的問(wèn)題×瞻溃可以接續(xù)往下看http3.0是怎么解決TCP對(duì)頭阻塞的江场。

TCP和TLS的握手延時(shí)

發(fā)起 HTTP 請(qǐng)求時(shí),需要經(jīng)過(guò) TCP 三次握手TLS 四次握手(TLS 1.2)的過(guò)程窖逗,因此共需要 3 個(gè) RTT 的時(shí)延才能發(fā)出請(qǐng)求數(shù)據(jù)址否。

另外,TCP 由于具有「擁塞控制」的特性碎紊,所以剛建立連接的 TCP 會(huì)有個(gè)「慢啟動(dòng)」的過(guò)程佑附,它會(huì)對(duì) TCP 連接產(chǎn)生“減速”效果,給人的感覺(jué)就是突然卡頓了一下仗考。

網(wǎng)絡(luò)遷移需要重新建立連接

一個(gè) TCP 連接是由四元組(源 IP 地址音同,源端口,目標(biāo) IP 地址秃嗜,目標(biāo)端口)確定的权均,這意味著如果 IP 地址或者端口變動(dòng)了,就會(huì)導(dǎo)致需要 TCP 與 TLS 重新握手锅锨,這不利于移動(dòng)設(shè)備切換網(wǎng)絡(luò)的場(chǎng)景叽赊,比如 4G 網(wǎng)絡(luò)環(huán)境切換成 WiFi。

這些問(wèn)題都是 TCP 協(xié)議固有的問(wèn)題必搞,無(wú)論應(yīng)用層的 HTTP/2 在怎么設(shè)計(jì)都無(wú)法逃脫必指。要解決這個(gè)問(wèn)題,就必須把傳輸層協(xié)議替換成 UDP顾画,這個(gè)大膽的決定取劫,HTTP/3 做了匆笤!

http3.0

http2.0有3個(gè)缺陷,http3.0都給解決了谱邪,接下來(lái)看下是怎么一步一步解決的炮捧。



可以發(fā)現(xiàn)http3.0有幾個(gè)特點(diǎn)

  • 傳輸層由TCP改用成UDP協(xié)議。我們深知惦银,UDP 是一個(gè)簡(jiǎn)單咆课、不可靠的傳輸協(xié)議,而且是 UDP 包之間是無(wú)序的扯俱,也沒(méi)有依賴關(guān)系书蚪。而且,UDP 是不需要連接的迅栅,也就不需要握手和揮手的過(guò)程殊校,所以天然的就比 TCP 快。
  • 應(yīng)用層新增QUIC協(xié)議: 雖然UDP是不具備可靠性等特性的读存,但是UDP的上層協(xié)議QUID为流,它具有類似 TCP 的連接管理、擁塞窗口让簿、流量控制的網(wǎng)絡(luò)特性敬察,相當(dāng)于將不可靠傳輸?shù)?UDP 協(xié)議變成“可靠”的了,所以不用擔(dān)心數(shù)據(jù)包丟失的問(wèn)題尔当。
  • TLS協(xié)議內(nèi)置在了QUIC協(xié)議中莲祸。避免了TLS的4次握手。
  • 由HTTP2.0中的HPACK編碼格式改為了QPACK編碼格式椭迎。

無(wú)隊(duì)頭阻塞

QUIC 協(xié)議也有類似 HTTP/2 Stream 與多路復(fù)用的概念锐帜,也是可以在同一條連接上并發(fā)傳輸多個(gè) Stream的,這個(gè)優(yōu)點(diǎn)http3.0是繼承了的侠碧。

在來(lái)回憶下抹估,為什么HTTP2.0會(huì)有TCP對(duì)頭阻塞?
主要原因是:當(dāng)tcp發(fā)送丟包時(shí),tcp所在的傳輸層會(huì)發(fā)生阻塞弄兜,進(jìn)而應(yīng)用層也發(fā)生阻塞。只有組裝完成所有tcp段之后瓷式,應(yīng)用層才會(huì)讀取替饿。

而上面的http3.0介紹中TCP改成了UDP,而UDP并不是對(duì)數(shù)據(jù)的連續(xù)性,時(shí)序性等問(wèn)題進(jìn)行驗(yàn)證贸典,所以傳輸層肯定是不會(huì)發(fā)生阻塞了视卢。

因?yàn)閁DP是不可靠的,所以在應(yīng)用層實(shí)現(xiàn)了QUIC協(xié)議來(lái)保證消息的可靠性廊驼。http2.0和http3.0對(duì)比可發(fā)現(xiàn)一點(diǎn):阻塞發(fā)生的層級(jí)由http2.0的傳輸層變更到了http3.0的應(yīng)用層据过。正是由于這一變更惋砂,如果http3.0請(qǐng)求時(shí)發(fā)生丟包則應(yīng)用層只會(huì)阻塞本請(qǐng)求,后續(xù)的請(qǐng)求是不會(huì)阻塞的绳锅,應(yīng)用層都是可以讀取到的西饵;同理,如果ttp3.0響應(yīng)時(shí)發(fā)生丟包則應(yīng)用層也只會(huì)阻塞本響應(yīng)鳞芙,后續(xù)的響應(yīng)是不會(huì)阻塞的眷柔,應(yīng)用層都是可以讀取到的。

所以原朝,QUIC 連接上的多個(gè) Stream 之間并沒(méi)有依賴驯嘱,都是獨(dú)立的,某個(gè)流發(fā)生丟包了喳坠,只會(huì)影響該流鞠评,其他流不受影響。

更快的連接建立

對(duì)于 HTTP/1 和 HTTP/2 協(xié)議壕鹉,TCP 和 TLS 是分層的剃幌,分別屬于內(nèi)核實(shí)現(xiàn)的傳輸層、OpenSSL 庫(kù)實(shí)現(xiàn)的表示層御板,因此它們難以合并在一起锥忿,需要分批次來(lái)握手,先 3次TCP 握手怠肋,再 4次TLS 握手敬鬓。

HTTP/3 在傳輸數(shù)據(jù)前雖然需要 QUIC 協(xié)議握手,這個(gè)握手過(guò)程只需要 1 RTT笙各,握手的目的是為確認(rèn)雙方的「連接 ID」钉答,連接遷移就是基于連接 ID 實(shí)現(xiàn)的。

但是 HTTP/3 的 QUIC 協(xié)議并不是與 TLS 分層杈抢,而是 QUIC 內(nèi)部包含了 TLS数尿,它在自己的幀會(huì)攜帶 TLS 里的“記錄”,再加上 QUIC 使用的是 TLS 1.3惶楼,因此僅需 1 個(gè) RTT 就可以「同時(shí)」完成建立連接與密鑰協(xié)商右蹦,甚至在第二次連接的時(shí)候,應(yīng)用數(shù)據(jù)包可以和 QUIC 握手信息(連接信息 + TLS 信息)一起發(fā)送歼捐,達(dá)到0-RTT的效果何陆。

至于為什么第二次連接時(shí)為 0-RTT,接著繼續(xù)看下面豹储。

網(wǎng)絡(luò)遷移

在前面我們提到贷盲,基于 TCP 傳輸協(xié)議的 HTTP 協(xié)議,由于是通過(guò)四元組(源 IP剥扣、源端口巩剖、目的 IP铝穷、目的端口)確定一條 TCP 連接。



那么當(dāng)移動(dòng)設(shè)備的網(wǎng)絡(luò)從 4G 切換到 WiFi 時(shí)佳魔,意味著 IP 地址變化了曙聂,那么就必須要斷開(kāi)連接,然后重新建立連接吃引,而建立連接的過(guò)程包含 TCP 三次握手和 TLS 四次握手的時(shí)延筹陵,以及 TCP 慢啟動(dòng)的減速過(guò)程,給用戶的感覺(jué)就是網(wǎng)絡(luò)突然卡頓了一下镊尺,因此連接的遷移成本是很高的朦佩。

QUIC 協(xié)議沒(méi)有用四元組的方式來(lái)“綁定”連接,而是通過(guò)連接 ID 來(lái)標(biāo)記通信的兩個(gè)端點(diǎn)庐氮,客戶端和服務(wù)器可以各自選擇一組 ID 來(lái)標(biāo)記自己语稠,因此即使移動(dòng)設(shè)備的網(wǎng)絡(luò)變化后,導(dǎo)致 IP 地址變化了弄砍,只要仍保有上下文信息(比如連接 ID仙畦、TLS 密鑰等),就可以“無(wú)縫”地復(fù)用原連接音婶,消除重連的成本慨畸,沒(méi)有絲毫卡頓感,達(dá)到了連接遷移的功能衣式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寸士,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碴卧,更是在濱河造成了極大的恐慌弱卡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件住册,死亡現(xiàn)場(chǎng)離奇詭異婶博,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)荧飞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門凡人,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叹阔,你說(shuō)我怎么就攤上這事划栓。” “怎么了条获?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蒋歌。 經(jīng)常有香客問(wèn)我帅掘,道長(zhǎng)委煤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任修档,我火速辦了婚禮碧绞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吱窝。我一直安慰自己讥邻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布院峡。 她就那樣靜靜地躺著兴使,像睡著了一般。 火紅的嫁衣襯著肌膚如雪照激。 梳的紋絲不亂的頭發(fā)上发魄,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音俩垃,去河邊找鬼励幼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛口柳,可吹牛的內(nèi)容都是我干的苹粟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跃闹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嵌削!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辣卒,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掷贾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后荣茫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體想帅,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年啡莉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了港准。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咧欣,死狀恐怖浅缸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魄咕,我是刑警寧澤衩椒,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響毛萌,放射性物質(zhì)發(fā)生泄漏苟弛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一阁将、第九天 我趴在偏房一處隱蔽的房頂上張望膏秫。 院中可真熱鬧,春花似錦做盅、人聲如沸缤削。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亭敢。三九已至,卻和暖如春腊尚,著一層夾襖步出監(jiān)牢的瞬間吨拗,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工婿斥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劝篷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓民宿,卻偏偏與公主長(zhǎng)得像娇妓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子活鹰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容