第二章 計算機網絡面試核心

目錄

OSI七層架構

TCP/IP協議

1. TCP
(1). 三次握手
  • SYN Flood攻擊
  • TCP報文頭
  • 為什么要進行三次握手
(2). 四次揮手
  • TIME_WAIT
  • CLOSE_WAIT
  • 為什么要進行四次揮手
(3). 滑動窗口
(4). TCP和UDP的區別
2. HTTP
(1) 請求結構
(2) 響應結構
(3) 請求/響應步驟
(4) 瀏覽器鍵入URL經歷的流程
(5) 狀態碼
(6) GET和POST的區別
(7) Cookie和Session的區別
(8) HTTP和HTTPS的區別
3. Socket

一、網絡基礎知識詳解

1. OSI開放式互聯參考模型

  • 第七層:應用層
  • 第六層:表示層
  • 第五層:會話層
  • 第四層:傳輸層
  • 第三層:網絡層
  • 第二層:數據鏈路層
  • 第一層:物理層

第一層:物理層

機械、電子、定時接口通信信道上的原始比特流傳輸。

首先要解決兩臺物理機的通信需求,具體就是機器A往機器B發送比特流,機器B收到比特流。
物理層主要定義了物理設備的標準,如網線的類型,光纖的接口類型,各種傳輸介質的傳輸速率等。它的主要作用是傳輸比特流,即我們所謂的0101二進制數據,將他們轉化為電流強弱來進行傳輸,到達目的地后再轉化為0101機器碼,也就是我們常說的數模轉換與模數轉換。
這一層的數據叫做比特。網卡工作在這一層。

第二層:數據鏈路層

物理尋址,同時將原始比特流轉變為邏輯傳輸線路。

在傳輸比特流的過程中,會產生錯傳,數據傳輸不完整的可能。數據鏈路層應運而生,數據鏈路層定義了如何格式化數據以進行傳輸以及如何控制對物理介質的訪問。這一層通常還提供錯誤檢測和糾正,以確保數據傳輸的可靠性。
本層將比特數據組成了幀。交換機工作在這一層。對幀解碼,并根據幀中包含的信息把數據發送到正確接收方。

第三層:網絡層

控制子網的運行,如邏輯編址,分組傳輸,路由選擇。

隨著網絡節點的不斷增加,點對點通信是要經過多個節點的,那么如何找到目標節點,如何選擇最佳路徑,變成了首要需求,此時便有了網絡層。
主要功能是將網絡地址翻譯為對應的物理地址,并決定如何將數據從發送發路由到接受方。網絡層通過綜合考慮發送優先權,網絡擁塞程度,服務質量以及可選路由的花費來決定從一個網絡中節點A到另一個網絡中節點B的最佳路徑。
由于網絡層處理并智能指導數據傳送路由器連接網絡各段,路由器屬于網絡層。此層的數據被稱為數據包。本層需要關注的協議是TCP/IP協議里面的IP協議。

第四層:傳輸層

接受上一層的數據,在必要的時候把數據進行分割,并將這些數據交給網絡層,切保證這些數據段有效到達對端。

隨著網絡通信需求的進一步擴大,通信過程中需要發送大量的數據。如海量文件傳輸等,可能需要很長時間。而網絡在通信的過程中會中斷好多次,此時為了保證傳輸大量文件時的準確性,需要對發送出去的數據進行切分,切割為一個一個的段落,即segment進行發送,那么其中一個段落丟失了該怎們辦,要不要重傳,每個段落要按照順序到達么?這個便是傳輸層需要考慮的問題了。
傳輸層解決了主機間的數據傳輸,數據間的傳輸可以是不同網絡的,并且傳輸層解決了傳輸質量的問題,該層成為OSI模型中最重要的一層。傳輸協議同時進行流量控制,或是基于可接受方接受數據的快慢程度,規定適當的發送速率,除此之外,傳輸層按照網絡能處理的最大尺寸,將較長的數據包進行強制分割。例如,以太網無法接受大于1500字節的數據包,發送方節點的傳輸層將數據分割成較小的數據片,同時對每一數據片安排一序列號,以便數據到達接受方節點的傳輸層時能以正確的順序重組,該過程稱為排序。傳輸層中我們需要關注的協議有TCP/IP協議中的TCP協議和UDP協議。

第五層:會話層

不同機器上的用戶之間建立及管理會話。

現在我們已經保證給正確的計算機發送正確的封裝過后的信息了,現在需要建立一個自動收發包自動尋址的功能,于是發明了會話層。
會話層的作用就是建立和管理應用程序之間的通信。

第六層:表示層

信息的語法語義以及他們的關聯,如加密解密、轉換翻譯、壓縮與解壓縮。

現在我能保證應用程序自動收發包和尋址,但我要用linux給windows發包,兩個系統語法不一致,于是需要表示層,幫我們解決不同系統之間的通信語法問題。在表示層,數據將按照網絡能理解的方案進行格式化,這種格式化也因所使用的網絡類型不同而不同。

第七層:應用層

此時,雖然發送方知道自己發送的是什么東西,轉化成字節數據后有多長,但接受方肯定不知道,所以應用層的網絡協議誕生了。他規定發送方和接收方必須使用一個固定長度的消息頭,消息頭必須使用某種固定的組成。而且消息頭里必須記錄消息長度等一系列信息,以方便接收方能夠正確的解析發送方發送的數據。應用層旨在讓你更方便的應用同一網絡中接收到的數據,至于數據的傳遞,沒有該層,你也可以直接在兩臺電腦間進行,只是傳來傳去只是1和0組成的數組。
該層需要我們重點關注的是與之相對應的TCP/IP協議中的HTTP協議。

2. 數據解析

先自上而下,后自下而上處理數據頭部


數據解析.png

3. TCP/IP

OSI是一個定義良好的協議規范集,并有許多可選部分完成類似的任務,它定義了開放系統的層次結構,層次之間相互關系以及各層所包括的可能任務。是作為一個框架來協調和組織各層所提供的服務。

OSI的 “實現” :TCP/IP四層架構參考模型

TCP/IP.png

先自上而下,后自下而上處理數據頭部


image.png

二、TCP的三次握手

1. IP協議與TCP協議

IP協議是無連接的通信協議,它不會占用兩臺正在通信的計算機之間的通信線路,這樣IP就降低了對網絡線路的需求,每條線可以同時滿足許多不同計算機之間,IP的通信需要,通過IP消息或者其他數據會被分割為較小的獨立的包,并通過因特網在計算機之間傳送,IP負責將每個包路由至它的目的地,但是IP協議沒有做任何事情來確認數據包是否按順序發送,或者包是否被破壞,所以IP數據包是不可靠的,需要由它的上層協議來做出控制。

2. 傳輸控制協議TCP(傳輸層的協議)

  • 面向連接的、可靠的、基于字節流的傳輸層通信協議。
  • 將應用層的數據流分割成報文段并發送給目標節點的TCP層。
    報文段的長度通常受該計算機連接的網絡的數據鏈路層的最大傳輸單元(MTU)的限制。
    TCP將結果包傳輸給IP層,由它來通過網絡將包傳輸給目標節點的TCP層。
  • 數據包都有序號,對方收到則發送ACK確認,未收到則重傳。
  • 使用校驗和來校驗數據在傳輸過程中是否有誤。
    (使用奇偶校驗頭函數)

3. TCP報文頭

image.png

image.png
  • Source Port:源端口 2字節

  • Destination Port:目的端口 2字節
    注釋:兩個進程在計算機內部進行通信的方法:

    1. 管道
    2. 內存共享
    3. 信號量
    4. 消息隊列

    ip 地址加協議加端口號唯一標識網絡中的一個進程,稱為套接字

  • Sequence Number:Seq序號 4字節
    注釋: TCP流中傳輸的每個字節都按順序編號,例如一段報文的序號字段值是107,而攜帶的數據共有100個字段,如果有下一個報文段的話,它的序號值就是107+100=207。

  • Acknoledgment Number: ACK確認號 4字節
    注釋:期望收到對方下一個報文的第一個數據字節的序號,例如B收到了A發送過來的報文,其序列號字段為301,數據長度是200字節,這表明了B正確的收到了A發送的到序號500為止的數據,B期望收到A的下一個數據序號是501,于是B在發送給A的確認報文段中會把ACK確認號置為501。

  • Offset:數據偏移
    注釋:由于頭部有可選字段,長度不固定,因此它指出TCP報文的數據距離TCP報文的起始處有多遠。

  • Resverved: 保留域、
    注釋:保留今后使用的,到目前都會被標為0

  • TCP Flags:控制位
    注釋:主要由八個標志位來組成,每一個標志位表示一個控制功能。CEUAPRSF

    • URG:緊急指針標志,為1表示緊急指針有效,為0則忽略緊急指針
    • ACK: 確認序號標志,為1表示確認號有效,為0表示報文中不含確認信息,忽略確認號信息,上確認號是否有效就是通過該標志位控制的。
    • PSH:push標志,為1表示是帶有push標志的數據,指示接收方在接到該報文以后應盡快將這個報文段交給應用程序,而不是在緩沖區排隊。
    • RST:重置鏈接標志,用于重置因為主機崩潰或其他原因而出現錯誤的鏈接,或者用于拒絕非法的報文段和拒絕鏈接請求。
    • SYN:同步序列號,用于建立連接過程,在連接請求中,SYN=1和ACK=0表示該數據段沒有使用捎帶的確認域,而鏈接應答捎帶一個確認即SYN=1和ACK=1。
    • FIN:finish標志,用于釋放連接,為1表示數據方已經沒有數據發送了,即關閉本方數據流。
  • Windows: 窗口
    注釋*滑動窗口的大小,用來告知發送端和接收端的緩存大小,以此控制發送發送數據我的速率,從而達到流量控制。

  • Checksum:校驗和
    注釋:指的是奇偶校驗,此校驗和是對整個TCP報文段包括TCP頭部和TCP數據以16進行計算所得,又發送方計算存儲,并由接收端進行驗證。

  • Urgent Pointer:緊急指針
    注釋:URG為1時有效,指出本報文中緊急數據的字節數。

  • TCP Options:可選項
    注釋:長度可變,定義一些其他的可選參數。

當應用程序希望通過TCP與另一個應用程序通信時,它會發送一個通信請求,這個請求必須被送達一個確切的地址,在雙方握手之后,TCP將在兩個應用之間建立一個全雙工的通信,將占用兩個計算機之間的的通信線路直到他被一方或雙方關閉為止。

4. 說說TCP的三次握手

“握手”是為了建立連接,TCP三次握手的流程如下:
在TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接。

第一次握手:建立連接時,客戶端發送SYN包(seq = x)到服務器,并進入SYN_SEND狀態,等待服務器確認;

第二次握手:服務器收到SYN包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此時包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

image.png
  1. 客戶端和服務器都出去CLOSED的狀態。
  2. 客戶端主動打開。
  3. 服務端被動打開,創建傳輸控制塊TCB,時刻準備其他客戶進程發送過來的連接請求,此時服務端進入LISTEN監聽狀態。
  4. 客戶端創建傳輸控制塊TCB,然后向服務器發出鏈接請求報文,即SYN=1,并選擇一個初識序列號seq=x,x是任意的正整數值,客戶端進入SYN-SENT同步已發送狀態。此時發送過去的數據包即報文段會被稱為SYN報文段,不能攜帶數據,但是要消耗掉一個序號,這是第一次握手。
  5. 服務器接收的請求報文之后,如果同意鏈接,則發出確認報文,確認報文中包含TCP Flags中兩個位的字段,一個是ACK=1,另一個是SYN=1,這里的ack=x+1,并初始化一個序列號號seq=y,此時服務器就進入了SYN-RCVD同步已收到的狀態,這個報文也不能攜帶數據,并且同樣需要消耗一個序號,這是第二次握手。
  6. 客戶端接收到確認報文之后,還要向服務器給出一個確認,確認報文的ACK=1,ack=y+1,seq=x+1,并且該ACK報文段可以攜帶數據,客戶端就進入ESTAB-LISHED已建立連接的狀態。
  7. 服務器接收到確認后也會進入到ESTAB-LISHED狀態,雙方就可以開始通信。

5. 為什么需要三次握手才能建立起來連接

為了初始化Sequence Number的初識值。通信的雙方要互相通對方自己的初始化的Sequence Number,這個號要作為以后通信的序號,以保證應用層接收到的數據不會因為網絡上的傳輸問題而亂序,即TCP會用這個序號來拼接數據,因此在服務器回發它的Sequence Number即第二次握手之后,還需要發送確認報文給服務器告知服務器說客戶端已經收到你發送的Sequence Number了。

6. 首次握手的隱患---SYN超時

問題起因分析

  • Server收到Client的SYN,回復SYN-ACK的時候,客戶端掉線,未收到ACK確認,此連接將處于中間狀態,即沒有成功也沒有失敗。
  • Server不斷重試直至超時,Linux默認等待63秒才斷開連接(1s => 2s => 4s => 8s => 16s => 32s)。

這會導致SYN Flood風險,惡意程序會給服務器發送SYN報文,發完就下線,于是服務器需要默認等63秒才會斷開連接,這樣攻擊者就會將服務器的SYN連接的隊列耗盡,讓正常的連接請求不能處理。

針對SYN Flood的防護措施

  • SYN隊列滿后,通過tcp_syncookies參數回發SYN Cookie
    源地址端口,目標地址端口,和時間戳造出一個特別的Sequence Number回發回去,這個Sequence Number簡稱SYN Cookie
  • 若為正常連接則client會回發SYN Cookie,直接建立連接

7. 建立連接后,Client出現故障怎么辦

?;顧C制

  • 向對方發送保活探測報文,如果未收到響應則繼續發送(經過一個提前設定好的包活時間間隔)
  • 嘗試次數達到?;钐綔y次數仍未收到響應則中斷連接

三、TCP的四次揮手

1. TCP采用四次揮手來釋放連接

“揮手“是為了終止連接,TCP四次揮手的流程圖如下(假設客戶端主動觸發CLOSE):


image.png
  • 第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態;
  • 第二次揮手:Server收到FIN后,發送一個ACK給Client,確認序號為收到的序號+1(與SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態;
  • 第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態;
  • 第四次揮手:Client收到FIN后,Client進入TIME_WAIT狀態,接著發送一個ACK給Server,確認序號為收到的序號+1,Server進入CLOSE狀態,完成四次揮手。

2. 為什么會有TIME_WAIT(2MSL)狀態

原因

  • 確保有足夠的時間讓對方收到ACK包,如果對方沒有收到ACK,對方就會觸發被動端重發FIN包,一來一去正好是兩個MSL。
  • 避免新舊連接混淆,有些路由器會緩存ip數據包,如果連接被重用了,那么這些延遲收到的包就會跟新連接混在一起。

4. 為什么需要四次握手才能斷開連接

全雙工的意思是允許數據在兩個方向上同時傳輸,即在同一時間服務器可以發送數據給客戶端,客戶端也可以發送數據給服務器。
因為全雙工,發送方和接受方都需要FIN報文和ACK報文,也就是發送方和接受方各只需兩次揮手即可,只不過有一方是被動的,所以就成了四次揮手。

5. 服務器出現大量CLOSE_WAIT狀態的原因

問題的其中一個表現是客戶端一直在請求,但是返回給客戶端的信息是異常的,服務器壓根也沒有收到請求。
服務器保持大量的CLOSE-WAIT只有一個原因,就是在對方發送一個FIN報文之后,程序這邊沒有進一步發送ACK,或者FIN報文已確認,換句話說,就是對方關閉socket連接,我方忙于讀或寫,沒有及時關閉連接。

  • 檢查代碼,特別是釋放資源的bug。
  • 檢查配置,特別是處理請求的線程配置。

一旦CLOSE-WAIT很多,就需要排查問題,因為對于linux服務器來說,會為每個用戶分配優先的文件句柄數,而我們的連接也是和文件句柄一一對應的,而CLOSE-WAIT狀態如果一直被保持,那么以為著對應數目的通道一直被占用著,一旦達到上限,則新的請求就無法被處理,接著就就是大量的too many open files異常,接著就是軟件服務器的崩潰,極有可能讓我們的tomcat,nginx,apache服務器崩潰掉。

四、TCP和UDP的區別

1. UDP簡介

用戶數據協議(User Datagram Protoco)

UDP報文結構

image.png
  • Source Port:源端口
  • Destination:目標端口
  • Length:數據包長度
  • Checksum:奇偶校驗值
  • data octets:用戶數據

UDP的特點

簡單的報文結構意味著UDP不像TCP那樣支持錯誤重傳,滑動窗口等精細控制。

  • 面向非連接
    UDP是一個非連接的協議,傳輸數據之前源端和終端無建立連接,當它想傳送時,就簡單的去抓取來自應用程序的數據,并盡可能快的把它扔到網絡上,在發送端,UDP傳送數據的速度,僅僅是受應用程序生成數據的速度,計算機的能力和傳輸帶寬的限制,在接收端,UDP把每個消息段放到隊列中,應用程序每次從應用中讀取一個消息段。
  • 不維護連接狀態,支持同時像多個客戶端傳輸相同的消息
    由于傳輸數據不建立連接,因此,也就不需要維護連接狀態,包括收發狀態等,因此一臺服務器可同時向多個客戶機傳輸相同的消息。
  • 數據包包頭只有8個字節。額外開銷較小
    UPD信息包的包頭很短,只有8個字節,相對于TCP的20個字節信息包的額外開銷小很多。
  • 吞吐量只受限于數據生成速率、傳輸速率以及機器性能。
    吞吐量不受擁擠控制算法的調節,只受應用軟件生成數據的速率、傳輸帶寬、源端和終端主機性能的限制。
  • 盡最大努力交付,不保證可靠交付,不需要維持復雜的連接狀態表
  • UDP面向報文,不對應用程序提交的報文信息進行拆分或者合并
    發送方對應用程序交下來的報文在添加首部后就向下交付給IP層,既不拆分,也不合并,而是保留這些報文的邊界,因此,應用程序需要選擇合適的報文大小,也就是說UDP將絕大多數的控制交由我們的上層去解決。

2. TCP和UDP的區別

TCP和UDP是OSI中的運輸層協議,TCP提供可靠的通信傳輸;UDP常被用于讓網絡和細節控制交給應用層的通信傳輸。

  • 面向連接 VS 無連接
    TCP有三次握手的過程;UDP適合消息的多播發布,從單個點向多個點傳輸信息
  • 可靠性
    TCP是可靠的,利用握手、確認和重傳機制提供了可靠性保證;而UDP則可能會丟失,不知道到底有沒有被接收
  • 有序性
    TCP利用序列號保證了消息包的順序交付,到達可能無序,但TCP最終會排序;而UDP不具備有序性。
  • 速度
    TCP速度比較慢,因為要創建連接,保證消息的可靠性和有序性等,需要做額外的很多事情;UDP則更適合做對速度比較敏感的應用,比如在線視頻媒體,電視廣播,多人在線游戲等。
  • 量級
    TCP屬于重量級的;UDP屬于輕量級;體現在源數據的頭大小。

五、TCP的滑動窗口

1. RTT和RTO

RTT:發送一個數據包到收到對應的ACK,所花費的時間

簡單來說就是我發送一個數據包,對方回一個ACK,當我接到ACK之后就能計算出從我發出包到接到回應過了多久,這個時間就是RTT,RTT的計算很簡單,就是一個時間差。

RTO:重傳時間間隔

TCP在發送一個數據包之后,會啟動一個重傳定時器,而RTO就是這個定時器的重傳時間。 再通俗的講就是我一開始預先算一個定時器時間,如果你回復了ACK,那重傳定時器就自動失效,也就是說不用重傳了,如果沒有回復給我ACK,然后RTO定時器的時間又到了,我就重傳。
由于RTO是本次發送當前數據包所預估的超時時間,那么RTO就需要一個很好的算法來統計,來更好的預測這次超時時間,RTO不是固定寫死的配置,而是經過RTT計算出來的,有了RTT才能計算出RTO?;赗TO,我們便有了重傳機制,才能支撐起滑動窗口。

2. 滑動窗口

前面我們提到,TCP要將數據拆分成段進行發送,出于效率和傳輸速度的考慮,我們不可能等一段一段數據去發送,等到上一段數據被確認之后再發送下一段數據,這個效率是非常低的,我們是要實現對數據的批量發送,TCP必須要解決可靠傳輸以及包亂序的問題,所以TCP需要知道網絡實際的數據處理帶寬或是數據處理速度,這樣,才不會引起網絡擁塞,導致丟包。

TCP使用滑動窗口做流量控制與亂序重排

  • 保證TCP的可靠性
  • 保證TCP的流控特性

前面我們學的TCP報文頭里面有一個字段叫做Window,用于接收方通知發送方自己還有多少緩沖區可以接受數據,發送方根據接受方的處理能力來發送數據,不會導致接收方處理不過來,這便是流量控制。
同時,滑動窗口機制還體現了TCP面向字節流的設計思路。

窗口數據的計算過程

窗口數據的計算過程

如圖所示,左圖是TCP協議的發送端緩沖區,右圖是接收端緩沖區,左邊往右邊發數據,兩個圖中下面的長方形表示要發送的數據流,里面假設裝滿了數據,并且需要按照順序從左向右發送或者接收,假設對應的數據段位置序號也是從左到右去增長的。

對于發送方來講
  • LastByteAcked指向收到的連續最大的ACK的位置,也就是從左端算起連續已經被接收端的程序發送ACK回執確認已收到的SeqNum
  • LastByteSent指向已發送的最后一個字節的位置,該位置只是發出去了但是還沒有收到ACK的回應
  • LastByteWritten指向上層應用已寫完的最后一個位置,即當前程序已經準備好的需要發送的最新的一個數據段。
對于接受方來講
  • LastByteRead指向上層應用已經讀完的最后一個字節的位置
  • 而NextByteExpected指向收到的連續最大的Seq的位置,即已經收到但是還沒有發送回執
  • 而LastByteRcvd指向已收到的最后一個字節的位置

可以看到NextByteExpected和LastByteRcvd中間有一些Seq還沒有到達,對應的是空白的區域,此時,咱們可以根據上面的數值計算出接收方的AdvertisedWindow的大小,之后回發給發送方讓其計算出發送方的剩余可發送的數據大小,即EffectiveWindow的大小。

  • AdvertisedWindow=MaxRcvBuffer-(LastByteRcvd - LastByteRead)
    • MaxRcvBuffer: 接收方能接收的最大數據量,接收端緩存池的大小。
    • LastByteRcvd - LastByteRead: 接收方已為接收到的數據或者還沒有接收到的預訂數據留出來的空間,當前這些空間已經占據了一定的緩存。

我們將最大的緩存減去這些已經占據的緩存就得出我們還能夠接收的數據量,繼而我們就能夠將AdvertisedWindow告知發送端,而發送方根據ACK中的AdvertisedWindow的值就需要保證LastByteSent-LastByteAcked<=AdvertisedWindow,即已發送且待確認的數據量要小于接收方的Window大小

  • EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)

3. TCP的滑動窗口基本原理

TCP會話的發送方

TCP會話的發送方

對于TCP會話的發送方,任何時候在其發送緩存內的數據都可以分為4類

  1. 已經發送并且得到端的回應的。
  2. 已經發送但還沒有得到端的回應的
  3. 未發送但對端允許發送的
  4. 未發送且由于達到了window的大小對端不允許發送的數據

2,3組成的連續空間稱為發送窗口

滑動窗口的變化

當收到接收方新的ACK對于發送窗口中后續字節的確認時,發送窗口就會進行滑動,滑動原理如圖所示。

我們先從原滑動窗窗口說起,由虛線部分組成,前面我們已經知道,滑動窗口里面包含了已經發送但是還沒有收到接收端已經確認的數據,以及還沒有發送,但是允許向接收方發送的數據,咱們假設原先的滑動窗口的邊界是從32到51,我們假設已發送但未被確認的序號是32到40,而41到51未發送但對端允許發送的,此時只有收到對端大于32的序號的ACK,即收到32到40之間的某個ACK序號回執的時候,咱們的滑動窗口才會發生移動,而此時序號大于或者等于52的數據,即窗口外的數據是不能發送的,這里我們假設收到了對端序號為36的ACK回執,則滑動窗口會向右移動4位到36這個地方,繼而我們的程序就能發送序號為52到55的數據了。

TCP會話的接收方

TCP會話的接收方

對于TCP會話的接收方來講,在某一時刻,在他的接收緩存內會存在三種狀態

  1. 已接收并且已發送回執的狀態
  2. 未接收但是可以接收也就是準備接收的狀態
  3. 未接收并且不能接收的狀態,因為達到了窗口的閾值了

由于ACK直接由TCP?;貜?,默認沒有應用延遲,不存在已接收但是未回復ACK的這種狀態,其中未接收并且準備接收的這段空間就稱為接收窗口,由于接收窗口的滑動機制和前面發送方的一致,這里不作重復講解。

TCP最基本的傳輸可靠性來源于確認重傳機制,TCP的滑動窗口的可靠性也是建立在確認重傳基礎上的,發送窗口只有收到接收端對于本段發送窗口內字節的ACK確認才會移動滑動窗口的左邊界,接收窗口只有在前面所有段都確認的情況下,才會移動左邊界,當在前面還有字節未接收但收到后面字節的情況下,窗口是不會移動的,并不對后續字節確認,以此確保對端會對這些數據進行重傳,以上便是滑動窗口的基本原理。

滑動窗口的大小可以依據一定的策略進行動態調整,例如會根據自身處理能力的變化通過本端TCP接收窗口大小的控制來實現對端的發送窗口進行流量限制。

六、HTTP相關

1. HTTP簡介

超文本傳輸協議,是屬于應用層的協議,是一個基于響應與請求無狀態的應用層的協議,常基于TCP的連接方式。HTTP1.1中給出一種持續連接的方式,keeplive,絕大多數的web開發都數是構建在HTTP協議之上的web應用。

超文本傳輸協議的主要特點

  • 支持客戶/服務器模式
    HTTP協議工作于客戶端/服務端架構之上,瀏覽器作為HTTP客戶端通過url向HTTP服務端向Web服務器發送所有請求,Web服務器根據接收到的請求向客戶端發送響應信息。


    客戶/服務器模式
  • 簡單快速
    客戶端向服務器請求服務的時候,只需傳送請求方法和路徑,請求方法常用的有GET,POST等,每種方法規定了客戶端與服務器聯系的類型不同,由于HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。
  • 靈活
    HTTP允許傳輸任意類型的數據對象,正在傳輸的類型由Content-type加以標記。
  • 無連接
    限制每次連接只處理一個請求,服務器處理完客戶的請求并收到客戶的應答之后即斷開連接,采用這種方式可以節省傳輸時間,從HTTP1.1起,默認使用了長連接,即服務器需要等待一定時間后,才斷開連接,以保證連接特性,雖然目前的一些技術如keeplive使用了長連接優化效率,但這些都是屬于HTTP請求之外的,也就是說在每個獨立的HTTP請求中,你是無法知道當前的HTTP是否處于長連接的狀態,你始終得認為HTTP請求在結束后連接就會關閉,這是HTTP的特性,至于下層實現是否在請求結束后關閉連接,都不會改變這個特性,長連接可以理解為下層實現對上層透明。
  • 無狀態
    HTTP協議是無狀態協議,無狀態是指協議對于事物處理沒有記憶能力,缺少狀態意味著后續處理需要前面信息則必須被重傳這樣可能導致每次傳送的數據量增大,另一方面在服務器不需要先前信息時,他的應答就較快。

2. HTTP請求結構

HTTP請求結構

請求行,請求頭部,空行,數據四個部分組成。

請求行

  • 請求方法:POST,GET,PUT等等
  • URL
  • 協議版本:HTTP1.0、HTTP1.1等等

請求頭部(若干個)

  • 名字: 值
    名字是大小寫無關的,這些報頭用于設置HTTP請求的一些參數,例如HOST表示被請求資源的主機和端口號,Content-Type等等

空行

必須要有冪級數數據為空,用空行來表示頭部發送結束。

請求的數據

只在POST里面用到,表示要上傳的數據體,和頭部之間有個空行

3. HTTP響應結構

發送請求報文后,正常的情況下能收到其響應報文。服務器接收并處理客戶端發過來的請求后會返回一個HTTP的響應消息,即我們的響應報文。


HTTP響應結構

狀態行,響應頭部,響應正文

狀態行

  • 協議版本
  • 狀態碼
  • 狀態碼描述
  • 換行符

響應頭部(若干個)

  • 頭部字段名: 值

響應正文

4. HTTP簡介

HTTP協議定義了Web客戶端如何從Web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶端,HTTP協議采用了請求響應模型,客戶端向服務器發送一個請求報文,請求報文包含一個請求的方法,URL,協議版本,請求頭部,和請求數據,服務器以一個狀態行作為響應,響應的內容包括協議的版本,成功或者錯誤代碼,服務器信息,響應頭部和響應數據。

5. 請求/響應步驟

  • 客戶端連接到Web服務器;
  • 發送HTTP請求;
  • 服務器接收并返回HTTP響應;
  • 釋放連接TCP連接;
  • 客戶端瀏覽器解析HTML內容。

6. 在瀏覽器地址鍵入URL,按下回車之后經歷的流程?

  • DNS解析
    首先瀏覽器會依據url逐層查詢DNS服務器緩存,解析url中的域名所對應的ip地址,DNS從近到遠依次是瀏覽器緩存->系統緩存->路由器緩存->IPS服務器緩存->根域名服務器緩存->頂級域名服務器緩存,從哪個緩存找對對應的ip則直接返回,不在查詢后面的緩存。
  • TCP連接
    找到ip地址之后,會根據該ip地址和對應端口(默認80)和服務器建立TCP連接。
  • HTTP請求
    瀏覽器發出讀取文件的HTTP請求,該請求將發送給服務器
  • 服務器處理請求并返回HTTP報文
    服務器對瀏覽器請求作出相應,并把對應的帶有html文本的http響應報文發送給瀏覽器。
  • 瀏覽器解析渲染頁面
  • 連接結束

7. HTTP狀態碼

五中可能取值

  • 1xx:指示信息--表示請求已接收,繼續處理;
  • 2xx:成功--表示請求已被成功接收、理解、接受;
  • 3xx:重定向--要完成請求必須進行更進一步的操作;
  • 4xx:客戶端錯誤--請求有語法錯誤或請求無法實現;
  • 5xx:服務器錯誤--服務器未能實現合法的請求。

常見狀態碼

  • 200 OK:正常返回信息;
  • 400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解;
  • 401 Unauthorized:請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用;
  • 403 Forbidden:服務器收到請求,但是拒絕提供服務;
  • 404 Not Found:請求資源不存在;
  • 500 Internal Server Error:服務器發生不可預期的錯誤;
  • 503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常。

8. GET請求和POST請求的區別

從三個層面來解答

  • Http報文層面:GET將請求信息放在URL后面,請求信息和URL之間以問號隔開,請求信息的格式為鍵值對,POST放在報文體中,想獲得請求信息必須獲得報文。
    因此安全性較GET要高一些。但要獲得報文體中的請求信息也很容易,安全性上其實無太多區別。具體解決傳輸過程中的安全問題,還要靠https。

  • 數據庫層面:GET符合冪等性和安全性,POST不符合。
    冪等性:對數據進行一次操作或者多次操作的結果是一致的,則認為符合冪等性。
    安全性:對數據的操作沒有改變數據庫中的數據,則認為符合安全性。

  • 其他層面:GET可以被緩存、被存儲,而POST不行。

9. Cookie和Session的區別

Cookie簡介

Cookie技術是客戶端的解決方案

  • 是由服務器發給客戶端的特殊信息,以文本的形式存放在客戶端。
    客戶端每次向服務器發送請求的時候都會帶上這些特殊的信息(Http響應頭中)。
  • 客戶端再次請求的時候,會把Cookie回發
  • 服務器接收到后,會解析Cookie生成與客戶端相對應的內容。

Cookie的設置以及發送過程

Cookie的設置以及發送過程

Session簡介

  • 服務器端的機制,在服務器上保存的信息。
  • 解析客戶端請求并操作session id,按需求保存狀態信息。

Session的實現方式

  • 使用Cookie來實現


    Cookie實現
  • 使用URL回寫來實現
    服務器在發送給瀏覽器的所有鏈接中都寫到sessionId的參數,這樣客戶端點擊任何一個鏈接都會把sessionId帶回服務器。如果直接輸入url來請求資源,那么session是匹配不到的。

Tomcat存在Cookie和url回寫兩種機制,如果支持Cookie就使用Cookie,如果不支持Cookie就一直使用url回寫。

Cookie和Session的區別

  • Cookie數據存放在客戶的瀏覽器上,Session數據放在服務器上;
  • Session相對于Cookie更安全
  • 若考慮減輕服務器負擔,應當使用Cookie。

七、HTTP和HTTPS的區別

1. HTTPS簡介

超文本傳輸安全協議


HTTP和HTTPS的區別

HTTPS是一種以計算機網絡安全通信為目的的傳輸協議,在HTTP下面加入了SSL層,從而具有了保護交換數據隱私以及完整性,還有提供了對于網站服務器身份認證的功能,簡單來說,它就是安全版的HTTP協議。

2. SSL(Security Sockets Layer,安全套接層)

  • 為網絡通信提供安全及數據完整性的一種安全協議;
  • SSL位于TCP與各應用層之間,是操作系統對外的API,SSL3.0后更名為TLS;
  • 采用身份驗證數據加密保證網絡通信的安全和數據的完整性

3. 加密的方式

  • 對稱加密:加密和解密都使用同一個密鑰。
    此種加密效率高;
  • 非對稱加密:加密使用的密鑰和解密使用的密鑰是不同的。
    分別稱為公鑰和私鑰,公鑰和算法都是公開的,私鑰是保密的。此種算法效率較低,但是安全性超強。由于其加密特性,其加密長度有限。如區塊鏈技術很多使用非對稱加密。
  • 哈希算法:將任意長度的信息轉換為固定長度的值,算法不可逆。
    常見的是MD5算法。
  • 數字簽名:證明某個消息或者文件是某人發出/認同的
    簽名就是在信息的后面加上一段內容,這些內容是經過哈希后的值,可以證明信息沒有被修改過。
    哈希值都會經過哈希后也就是簽名后和信息一起發送,以保證這個哈希值不被修改。

4. HTTPS數據傳輸流程

HTTPS使用證書加各種加密方式進行加密。

HTTPS在進行數據傳輸之前,會與網站服務器與Web瀏覽器進行一次握手,在握手時確定雙方的加密密碼信息,具體過程如下:

  • 瀏覽器將支持的加密算法信息發送給服務器(A1S對稱加密,ECC對稱加密);
  • 服務器選擇一套瀏覽器支持的加密算法,以證書的形式回發瀏覽器(證書發布的CA機構,證書發布的有效期,公鑰,證書所有者,簽名等等);
    注:CA機構是具備證書頒發資格的權威機構。
  • 瀏覽器驗證證書合法性,并結合證書公鑰加密信息發送給服務器;
    如果證書受到瀏覽器信任,則在地址欄會有標志性顯示,否則就會顯示不受信的標識。當證書受信之后,Web瀏覽器會隨機生成一串密碼,并使用證書中的公鑰加密,之后就是使用約定好的哈希算法握手消息并生成隨機數對消息進行加密,再將之前生成的消息回發給服務器。
  • 服務器使用私鑰解密信息,驗證哈希,加密響應消息回發瀏覽器;
  • 瀏覽器解密響應消息,并對消息進行驗真,之后進行加密交換數據。

5. HTTP和HTTPS的區別

  • HTTPS需要到CA申請CA申請證書,HTTP不需要;
  • HTTPS密文傳輸,HTTP明文傳輸;
  • 連接方式不同,HTTPS默認使用443端口,HTTP使用80端口;
  • HTTPS=HTTP+加密+認證+完整性保護,較HTTP安全。

6. HTTPS真的安全么?

  • 瀏覽器默認填充http://,請求需要進行跳轉,有被劫持的風險;
  • 可以使用HSTS(HTTP Strict Transport Security)優化。

八、Socket相關

1. Socket簡介

兩個進程需要通信,最基本的一個前提是能夠唯一的標識一個進程,在本地進程通信中,我們可以使用pid來唯一標識一個進程,但pid只在本地唯一,網絡中的兩個進程pid沖突的幾率還是有的,這時候我們需要另辟蹊徑了,我們知道ip層的ip地址可以唯一標識一臺主機,而TCP協議和端口號可以唯一標識主機的一個進程,這樣,我們可以利用ip地址+協議+端口號來唯一標識網絡中的一個進程,能夠唯一標識網絡中的進程后,他們就可以利用Socket進行通信了。

什么是Socket呢?

Socket是對TCP/IP協議的抽象,是操作系統對外開放的接口。


Socket

2. Socket通信流程

image.png

服務器首先創建socket,為socket綁定ip地址和端口號,服務器監聽端口號的請求,隨時準備接收客戶端發來的連接,這時候服務器的socket只是listen并沒有打開,此時我們假設客戶端創建并打開了socket,并根據服務器的ip地址和端口號嘗試去連接服務器的socket,服務器的socket接收到客戶端的socket請求被動打開開始接收客戶端的請求,直到客戶端返回連接信息,這時候服務器的socket進入到阻塞狀態(accept方法需要一直等待客戶端返回連接信息后才返回,同時開始接收下一個客戶端的連接請求),客戶端在連接成功之后就會向服務器發送連接狀態信息,服務端在接收到客戶端的連接信息之后就會將accept方法返回,并提示連接成功,之后客戶就可以向socket寫入信息了,服務器就能收到并且讀取相關的信息,最后在發送完數據后,客戶端就會關閉socket,緊接著服務端也要關閉socket。以上就是整個socket通信流程。

3. Socket相關面試題

編寫一個網絡應用程序,有客戶端與服務器端,客戶端向服務器發送一個字符串,服務器收到該字符串后將其打印到命令行上,然后向客戶端返回該字符串的長度,最后,客戶端輸出服務器端返回的該字符串的長度,分別用TCP和UDP兩種方式去實現。

SocketUtil.java

import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author ShiXinXin
 * @date 2019-09-11 20:07
 */
public class SocketUtil {
    /**
     * 輸入流轉String
     *
     * @param inputStream 輸入流
     * @return 流對應的字符串
     */
    public static String getStringFromStream(InputStream inputStream) {
        byte[] buff = new byte[1024];
        int len = 0;
        try {
            len = inputStream.read(buff);
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
        return new String(buff, 0, len);
    }

    /**
     * 對象轉byte數組
     *
     * @param object 對象
     * @return 對應的byte數組
     */
    public static byte[] getByteFromObject(Object object) {
        return String.valueOf(object).getBytes();
    }

    /**
     * 從數據包中讀取數據
     *
     * @param packet 數據包
     * @return 其中的字符串
     */
    public static String getStringFromPacket(DatagramPacket packet) {
        byte[] data = packet.getData();
        return new String(data, 0, packet.getLength());
    }

    /**
     * 創建upd發送數據包
     *
     * @param object 數據
     * @param ip     ip地址
     * @param port   端口號
     * @return 數據包
     */
    public static DatagramPacket createSendDatagramPacket(Object object, String ip, int port) {
        byte[] buff = String.valueOf(object).getBytes();
        InetAddress address = null;
        try {
            address = InetAddress.getByName(ip);
        } catch (UnknownHostException e) {
            throw new RuntimeException("參數錯誤");
        }
        return new DatagramPacket(buff, buff.length, address, port);
    }

    /**
     * 創建upd發送數據包
     *
     * @param object 發送對象
     * @param packet 數據包
     * @return 數據包
     */
    public static DatagramPacket createSendDatagramPacket(Object object, DatagramPacket packet) {
        byte[] buff = String.valueOf(object).getBytes();
        return new DatagramPacket(buff, buff.length, packet.getAddress(), packet.getPort());
    }

    /**
     * 創建接收數據包
     *
     * @return 數據包
     */
    public static DatagramPacket createReceiveDatagramPacket() {
        byte[] data = new byte[100];
        return new DatagramPacket(data, data.length);
    }
}

1. TCP實現

TcpServer.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author ShiXinXin
 * @date 2019-09-11 19:33
 */
public class TcpServer {
    public static void main(String[] args) throws IOException {
        // 初始化一個Socket
        ServerSocket serverSocket = new ServerSocket(65000);
        while (true) {
            // 開始接受數據
            Socket socket = serverSocket.accept();
            // 接受到數據創建一個線程繼續接收
            new LengthCalculator(socket).start();
        }
    }
}

LengthCalculator.java

import com.xinxin.socket.utils.SocketUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author ShiXinXin
 * @date 2019-09-11 19:35
 */
public class LengthCalculator extends Thread {
    private Socket socket;

    public LengthCalculator(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 獲取socket的輸入流與輸出流
            OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = socket.getInputStream();

            // 從輸入流讀數據
            String content = SocketUtil.getStringFromStream(inputStream);
            System.out.println(content);

            // 將要發送的數據寫入輸出流
            outputStream.write(SocketUtil.getByteFromObject(content.length()));

            // 關閉所有鏈接
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TcpClient.java

import com.xinxin.socket.utils.SocketUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import static com.xinxin.socket.utils.SocketUtil.getByteFromObject;

/**
 * @author ShiXinXin
 * @date 2019-09-11 19:40
 */
public class TcpClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 65000);
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();

        // 將要發送的數據寫入輸出流
        outputStream.write(SocketUtil.getByteFromObject("Hello World"));

        // 打印輸入流得到的數據
        System.out.println(SocketUtil.getStringFromStream(inputStream));

        // 關閉所有鏈接
        inputStream.close();
        outputStream.close();
        socket.close();
    }
}

2. UDP實現

UdpServer.java

import com.xinxin.socket.utils.SocketUtil;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * @author ShiXinXin
 * @date 2019-09-11 19:54
 */
public class UdpServer {
    public static void main(String[] args) throws IOException {
        // 創建udp Socket
        DatagramSocket socket = new DatagramSocket(65001);
        while (true) {
            // 初始化一個接收包
            DatagramPacket packet = SocketUtil.createReceiveDatagramPacket();
            // 使用packet接收數據
            socket.receive(packet);
            // 接收到數據則初始化一個線程
            new LengthCalculator(socket, packet).start();
        }
    }
}

LengthCalculator.java

import com.xinxin.socket.utils.SocketUtil;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author ShiXinXin
 * @date 2019-09-11 20:08
 */
public class LengthCalculator extends Thread {
    private DatagramSocket socket;
    private DatagramPacket packet;

    public LengthCalculator(DatagramSocket socket, DatagramPacket packet) {
        this.socket = socket;
        this.packet = packet;
    }

    @Override
    public void run() {
        try {
            // 從包中獲取數據
            String content = SocketUtil.getStringFromPacket(packet);
            System.out.println(content);

            // 創建一個數據包,并發送數據包
            socket.send(SocketUtil.createSendDatagramPacket(content.length(), packet));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UdpClient.java

import com.xinxin.socket.utils.SocketUtil;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author ShiXinXin
 * @date 2019-09-11 20:00
 */
public class UdpClient {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();

        DatagramPacket packet = SocketUtil.createSendDatagramPacket("你好,世界!", "127.0.0.1", 65001);
        socket.send(packet);

        DatagramPacket receivedPacket = SocketUtil.createReceiveDatagramPacket();
        socket.receive(receivedPacket);

        String content = SocketUtil.getStringFromPacket(receivedPacket);
        System.out.println(content);
    }
}

九、網絡知識總結

OSI七層架構

TCP/IP協議

1. TCP
(1). 三次握手
  • SYN Flood攻擊
  • TCP報文頭
  • 為什么要進行三次握手
(2). 四次揮手
  • TIME_WAIT
  • CLOSE_WAIT
  • 為什么要進行四次揮手
(3). 滑動窗口
(4). TCP和UDP的區別
2. HTTP
(1) 請求結構
(2) 響應結構
(3) 請求/響應步驟
(4) 瀏覽器鍵入URL經歷的流程
(5) 狀態碼
(6) GET和POST的區別
(7) Cookie和Session的區別
(8) HTTP和HTTPS的區別
3. Socket
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容