計算機網路基礎知識點快速複習手冊

qqxx6661發表於2019-02-18

在這裡插入圖片描述

前言

本文快速回顧了計算機網路書本中常考的的知識點,用作面試複習,事半功倍。

主要內容有:計算機網路體系結構,TCP與UDP,UDP/TCP實現DEMO程式碼

面試知識點複習手冊

全複習手冊文章導航

全複習手冊文章導航(CSDN)

已釋出知識點複習手冊

-----正文開始-----

基礎

計算機網路體系結構

在這裡插入圖片描述

1. 五層協議

  • 應用層為特定應用程式提供資料傳輸服務,例如 HTTP、DNS 等。資料單位為報文

  • 運輸層 :提供的是程式間的通用資料傳輸服務。由於應用層協議很多,定義通用的運輸層協議就可以支援不斷增多的應用層協議。運輸層包括兩種協議:

    • 傳輸控制協議TCP,提供面向連線、可靠的資料傳輸服務,資料單位為報文段;TCP 主要提供完整性服務.
    • 使用者資料包協議UDP,提供無連線、盡最大努力的資料傳輸服務,資料單位為使用者資料包。UDP 主要提供及時性服務。
  • 網路層為主機間提供資料傳輸服務,而運輸層協議是為主機中的程式提供服務。網路層把運輸層傳遞下來的報文段或者使用者資料包封裝成分組

  • 資料鏈路層:網路層針對的還是主機之間的資料傳輸服務,而主機之間可以有很多鏈路,鏈路層協議就是為同一鏈路的節點提供服務。資料鏈路層把網路層傳來的分組封裝成幀

  • 物理層 :考慮的是怎樣在傳輸媒體上傳輸資料位元流,而不是指具體的傳輸媒體。物理層的作用是儘可能遮蔽傳輸媒體和通訊手段的差異,使資料鏈路層感覺不到這些差異

資料包->分組->幀->位元流

2. 七層協議

其中表示層和會話層用途如下:

  • 表示層 :資料壓縮、加密以及資料描述。這使得應用程式不必擔心在各臺主機中表示/儲存的內部格式不同的問題。

  • 會話層 :建立及管理會話。

五層協議沒有表示層和會話層,而是將這些功能留給應用程式開發者處理。

3. 資料在各層之間的傳遞過程

在向下的過程中,需要新增下層協議所需要的首部或者尾部,而在向上的過程中不斷拆開首部和尾部。

路由器只有下面三層協議,因為路由器位於網路核心中,不需要為程式或者應用程式提供服務,因此也就不需要運輸層和應用層。

4. TCP/IP

它只有四層,相當於五層協議中資料鏈路層和物理層合併為網路介面層。

現在的 TCP/IP 體系結構不嚴格遵循 OSI 分層概念,應用層可能會直接使用 IP 層或者網路介面層

TCP/IP 協議族是一種沙漏形狀,中間小兩邊大,IP 協議在其中佔用舉足輕重的地位。

在這裡插入圖片描述

物理層/資料鏈路層/網路層

知識點偏通訊理論的多一些,可以放在後面複習

傳輸層

TCP與UDP的特點

使用者資料包協議 UDP(User Datagram Protocol):無連線的,盡最大可能交付,沒有擁塞控制,面向報文

對於應用程式傳下來的報文不合並也不拆分,只是新增 UDP 首部,支援一對一、一對多、多對一和多對多的互動通訊。

傳輸控制協議 TCP(Transmission Control Protocol)是有連線的,提供可靠交付,有流量控制,擁塞控制,面向位元組流

把應用層傳下來的報文看成位元組流,把位元組流組織成大小不等的資料塊,每一條 TCP連線只能是點對點的(一對一)。

總結(TCP和UDP的區別):

1)TCP提供面向連線的傳輸;UDP提供無連線的傳輸

2)TCP提供可靠的傳輸(有序,無差錯,不丟失,不重複);UDP提供不可靠的傳輸

3)TCP面向位元組流的傳輸,因此它能將資訊分割成組,並在接收端將其重組;UDP是面向資料包的傳輸,沒有分組開銷。

4)TCP提供擁塞控制和流量控制機制;UDP不提供擁塞控制和流量控制機制。

5)TCP只能是點對點的(一對一)。UDP支援一對一、一對多、多對一和多對多的互動通訊。

首部格式

UDP

在這裡插入圖片描述

首部欄位只有 8 個位元組,包括源埠、目的埠、長度、檢驗和。12 位元組的偽首部是為了計算檢驗和臨時新增的。

TCP

在這裡插入圖片描述

  • 序號 :用於對位元組流進行編號,例如序號為 301,表示第一個位元組的編號為 301,如果攜帶的資料長度為 100 位元組,那麼下一個報文段的序號應為 401。
  • 確認號 :期望收到的下一個報文段的序號。例如 B 正確收到 A 傳送來的一個報文段,序號為 501,攜帶的資料長度為 200 位元組,因此 B 期望下一個報文段的序號為 701,B 傳送給 A 的確認報文段中確認號就為 701。
  • 資料偏移 :指的是資料部分距離報文段起始處的偏移量,實際上指的是首部的長度。
  • 確認 ACK :當 ACK=1 時確認號欄位有效,否則無效。TCP 規定,在連線建立後所有傳送的報文段都必須把 ACK 置 1。
  • 同步 SYN :在連線建立時用來同步序號。當 SYN=1,ACK=0 時表示這是一個連線請求報文段。若對方同意建立連線,則響應報文中 SYN=1,ACK=1。
  • 終止 FIN :用來釋放一個連線,當 FIN=1 時,表示此報文段的傳送方的資料已傳送完畢,並要求釋放連線。
  • 視窗 :視窗值作為接收方讓傳送方設定其傳送視窗的依據。之所以要有這個限制,是因為接收方的資料快取空間是有限的。

TCP拆包粘包

如果客戶端連續不斷的向服務端傳送資料包時,服務端接收的資料會出現兩個資料包粘在一起的情況。

分包機制一般有兩個通用的解決方法:

1,特殊字元控制

2,在包頭首都新增資料包的長度

如果使用netty的話,就有專門的編碼器和解碼器解決拆包和粘包問題了。

tips:

UDP沒有粘包問題,但是有丟包和亂序。不完整的包是不會有的,收到的都是完全正確的包。傳送的資料單位協議是UDP報文或使用者資料包,傳送的時候既不合並,也不拆分。

三次握手四次揮手:

blog.csdn.net/qzcsu/artic…

三次握手

(1)第一步:源主機A的TCP向主機B發出連線請求報文段,其首部中的SYN(同步)標誌位應置為1,表示想與目標主機B進行通訊,**併傳送一個同步序列號X(例:SEQ=100)進行同步,表明在後面傳送資料時的第一個資料位元組的序號是X+1(即101)**。SYN同步報文會指明客戶端使用的埠以及TCP連線的初始序號。

(2)第二步:目標主機B的TCP收到連線請求報文段後,如同意,則發回確認。在確認報中應將ACK位和SYN位置1,表示客戶端的請求被接受。確認號應為X+1(圖中為101),同時也為自己選擇一個序號Y。

(3)第三步:源主機A的TCP收到目標主機B的確認後要向目標主機B給出確認,其ACK置1,確認號為Y+1,而自己的序號為X+1。**TCP的標準規定,SYN置1的報文段要消耗掉一個序號。**
  

執行客戶程式的源主機A的TCP通知上層應用程式,連線已經建立。當源主機A向目標主機B傳送第一個資料包文段時,**其序號仍為X+1,因為前一個確認報文段並不消耗序號。**
  

當執行服務程式的目標主機B的TCP收到源主機A的確認後,也通知其上層應用程式,連線已經建立。至此建立了一個全雙工的連線。
複製程式碼

在這裡插入圖片描述

三次握手的原因

如果採用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受到了那條失效報文並且回覆了確認報文,但是客戶端不會再次發出確認。由於伺服器收不到確認,就知道客戶端並沒有請求連線。

如果使用的是兩次握手建立連線,假設有這樣一種場景,客戶端傳送了第一個請求連線並且沒有丟失,只是因為在網路結點中滯留的時間太長了,由於TCP的客戶端遲遲沒有收到確認報文,以為伺服器沒有收到,此時重新向伺服器傳送這條報文,此後客戶端和伺服器經過兩次握手完成連線,傳輸資料,然後關閉連線。此時此前滯留的那一次請求連線,網路通暢了到達了伺服器,這個報文字該是失效的,但是,兩次握手的機制將會讓客戶端和伺服器再次建立連線,這將導致不必要的錯誤和資源的浪費。

四次揮手

  1. 客戶端程式發出連線釋放報文,並且停止傳送資料。釋放資料包文首部,FIN=1,其序列號為seq=u(等於前面已經傳送過來的資料的最後一個位元組的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶資料,也要消耗一個序號。

  2. 伺服器收到連線釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP伺服器通知高層的應用程式,客戶端向伺服器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有資料要傳送了,但是伺服器若傳送資料,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。

  3. 客戶端收到伺服器的確認請求後,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待伺服器傳送連線釋放報文(在這之前還需要接受伺服器傳送的最後的資料)。

  4. 伺服器將最後的資料傳送完畢後,就向客戶端傳送連線釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,伺服器很可能又傳送了一些資料,假定此時的序列號為seq=w,此時,伺服器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。

  5. 客戶端收到伺服器的連線釋放報文後,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連線還沒有釋放,必須經過2∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。

  6. 伺服器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB後,就結束了這次的TCP連線。可以看到,伺服器結束TCP連線的時間要比客戶端早一些。

在這裡插入圖片描述

等待 2MSL(最長報文段壽命) 的原因

書中解釋:

在這裡插入圖片描述

TCP採用四次揮手關閉連線如圖所示為什麼建立連線協議是三次握手,而關閉連線卻是四次握手呢?

建立連線的時候, 伺服器在LISTEN狀態下,收到建立連線請求的SYN報文後,把ACK和SYN放在一個報文裡傳送給客戶端。

而關閉連線時,伺服器收到對方的FIN報文時,僅僅表示對方不再傳送資料了但是還能接收資料,而自己也未必全部資料都傳送給對方了,所以己方可以立即關閉,也可以傳送一些資料給對方後,再傳送FIN報文給對方來表示同意現在關閉連線,因此,己方ACK和FIN一般都會分開傳送,從而導致多了一次。

如果已經建立了連線,但是客戶端突然出現故障了怎麼辦?

TCP還設有一個保活計時器,顯然,客戶端如果出現故障,伺服器不能一直等下去,白白浪費資源。伺服器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設定為2小時,若兩小時還沒有收到客戶端的任何資料,伺服器就會傳送一個探測報文段,以後每隔75分鐘傳送一次。若一連傳送10個探測報文仍然沒反應,伺服器就認為客戶端出了故障,接著就關閉連線。

Time-wait狀態好處和壞處

好處

上方所述兩點

壞處

高併發下,埠都處在timewait很快就用完埠。

解決方法:

  • (阿里手冊)調小 TCP 協議的 time_ wait 超時時間。net . ipv 4. tcp _ fin _ timeout = 30
  • tcp_tw_reuse 這個引數作用是當新的連線進來的時候,可以複用處於TIME_WAIT的socket。預設值是0。
  • tcp_tw_recycle和tcp_timestamps 預設TIME_WAIT的超時時間是2倍的MSL,但是MSL一般會設定的非常長。如果tcp_timestamps是關閉的,開啟tcp_tw_recycle是沒用的。但是一般情況下tcp_timestamps是預設開啟的,所以直接開啟就有用了。

blog.chinaunix.net/xmlrpc.php?…

  • 對於客戶端

    作為客戶端因為有埠65535問題,TIME_OUT過多直接影響處理能力,開啟tw_reuse 即可解決,不建議同時開啟tw_recycle,幫助不大。

  • 對於服務端

    1. 開啟tw_reuse無效

    2. 線上環境 tw_recycle 最好不要開啟

      伺服器處於NAT 負載後,或者客戶端處於NAT後(這是一定的事情,基本公司家庭網路都走NAT);公網服務開啟就可能造成部分連線失敗,內網的話到時可以視情況開啟;像我所在公司對外服務都放在負載後面,負載會把timestamp 選項都給關閉,所以就算開啟也不起作用。

    3. 伺服器TIME_WAIT 高怎麼辦

      不像客戶端有埠限制,處理大量TIME_WAIT Linux已經優化很好了,每個處於TIME_WAIT 狀態下連線記憶體消耗很少,而且也能通過tcp_max_tw_buckets = 262144 配置最大上限,現代機器一般也不缺這點記憶體。

    4. 高併發伺服器建議調小 TCP 協議的 time_wait 超時時間。240s調整至30s(阿里Java規約)

什麼情況下會出現RST包

看這篇就夠了:blog.csdn.net/eric0318/ar…

TCP 滑動視窗

在這裡插入圖片描述

視窗是快取的一部分,用來暫時存放位元組流

傳送方和接收方各有一個視窗,接收方通過 TCP 報文段中的視窗欄位告訴傳送方自己的視窗大小,傳送方根據這個值和其它資訊設定自己的視窗大小。

傳送視窗內的位元組都允許被髮送,接收視窗內的位元組都允許被接收。

  • 如果傳送視窗左部的位元組已經傳送並且收到了確認,那麼就將傳送視窗向右滑動一定距離,直到左部第一個位元組不是已傳送並且已確認的狀態;

  • 接收視窗的滑動類似,接收視窗左部位元組已經傳送確認並交付主機,就向右滑動接收視窗。

接收視窗只會對視窗內最後一個按序到達的位元組進行確認,例如接收視窗已經收到的位元組為 {31, 34, 35},其中 {31} 按序到達,而 {32, 33} 就不是,因此只對位元組 31 進行確認。傳送方得到一個位元組的確認之後,就知道這個位元組之前的所有位元組都已經被接收。

TCP 可靠傳輸(超時重傳)

TCP 使用超時重傳來實現可靠傳輸:如果一個已經傳送的報文段在超時時間內沒有收到確認,那麼就重傳這個報文段。

一個報文段從傳送再到接收到確認所經過的時間稱為往返時間 RTT,加權平均往返時間 RTTs 計算如下:

image

超時時間 RTO 應該略大於 RTTs,TCP 使用的超時時間計算如下:

image

其中 RTTd 為偏差。

TCP 流量控制

流量控制是為了控制傳送方傳送速率,保證接收方來得及接收。

接收方傳送的確認報文中的視窗欄位可以用來控制傳送方視窗大小,從而影響傳送方的傳送速率。將視窗欄位設定為 0,則傳送方不能傳送資料。

TCP 擁塞控制

在這裡插入圖片描述

在這裡插入圖片描述

  • 擁塞控制主要包含以下2個內容:

    (1)慢開始,擁塞避免

    (2)快重傳,快恢復

傳送方需要維護一個叫做擁塞視窗(cwnd)的狀態變數,注意擁塞視窗與傳送方視窗的區別:擁塞視窗只是一個狀態變數,實際決定傳送方能傳送多少資料的是傳送方視窗。

為了便於討論,做如下假設:

  • 接收方有足夠大的接收快取,因此不會發生流量控制;
  • 雖然 TCP 的視窗基於位元組,但是這裡設視窗的大小單位為報文段。

1. 慢開始與擁塞避免

傳送的最初執行慢開始,令 cwnd=1,傳送方只能傳送 1 個報文段;當收到確認後,將 cwnd 加倍,因此之後傳送方能夠傳送的報文段數量為:2、4、8 ...

注意到慢開始每個輪次都將 cwnd 加倍,這樣會讓 cwnd 增長速度非常快,從而使得傳送方傳送的速度增長速度過快,網路擁塞的可能也就更高。設定一個慢開始門限 ssthresh,當 cwnd >= ssthresh 時,進入擁塞避免,每個輪次只將 cwnd 加 1。

如果出現了超時,則令 ssthresh = cwnd/2,然後重新執行慢開始。

2. 快重傳與快恢復

在接收方,要求每次接收到報文段都應該傳送對已收到有序報文段的確認,例如已經接收到 M1 和 M2,此時收到 M4,應當傳送對 M2 的確認。

快重傳

在傳送方,如果收到三個重複確認,那麼可以確認下一個報文段丟失,例如收到三個 M2 ,則 M3 丟失。此時執行快重傳,立即重傳下一個報文段。

快恢復

在快重傳情況下,只是丟失個別報文段,而不是網路擁塞,因此執行快恢復,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此時直接進入擁塞避免。

慢開始和快恢復的快慢指的是 cwnd 的設定值,而不是 cwnd 的增長速率。慢開始 cwnd 設定為 1,而快恢復 cwnd 設定為 ssthresh。

UDP/TCP程式碼DEMO

經典:blog.csdn.net/column/deta…

TCP:blog.csdn.net/ns_code/art…

UDP:blog.csdn.net/ns_code/art…

TCP

TCP連線的建立步驟

客戶端向伺服器端傳送連線請求後,就被動地等待伺服器的響應。典型的TCP客戶端要經過下面三步操作:

  • 1、建立一個Socket例項:建構函式向指定的遠端主機和埠建立一個TCP連線;
  • 2.通過套接字的I/O流與服務端通訊;
  • 3、使用Socket類的close方法關閉連線。

服務端的工作是建立一個通訊終端,並被動地等待客戶端的連線。典型的TCP服務端執行如下兩步操作:

  • 1、建立一個ServerSocket例項並指定本地埠,用來監聽客戶端在該埠傳送的TCP連線請求;
  • 2、重複執行:
    • 1)呼叫ServerSocket的accept()方法以獲取客戶端連線,並通過其返回值建立一個Socket例項;
    • 2)為返回的Socket例項開啟新的執行緒,並使用返回的Socket例項的I/O流與客戶端通訊;
    • 3)通訊完成後,使用Socket類的close()方法關閉該客戶端的套接字連線。

Demo

ServerDemo.java

/**
 * sinture.com Inc.
 * Copyright (c) 2016-2018 All Rights Reserved.
 */
package test.socketDemo.TCP;

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

/**
 * @author yzd
 * @version Id: ServerDemo.java, v 0.1 2018年07月10日 12:36 yzd Exp $
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 服務端在20006埠監聽客戶端請求的TCP連線
        ServerSocket server = new ServerSocket(20000);
        Socket client = null;
        boolean f = true;
        while(f){
            // 等待客戶端的連線,如果沒有獲取連線
            client = server.accept();
            System.out.println("與客戶端連線成功!");
            // 為每個客戶端連線開啟一個執行緒
            new Thread(new ServerThread(client)).start();

        }
    }
}
複製程式碼

ServerThread.java

/**
 * sinture.com Inc.
 * Copyright (c) 2016-2018 All Rights Reserved.
 */
package test.socketDemo.TCP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/**
 * @author yzd
 * @version Id: ServerThread.java, v 0.1 2018年07月10日 13:41 yzd Exp $
 */
public class ServerThread implements Runnable {

    private Socket client = null;
    public ServerThread(Socket client){
        this.client = client;
    }

    @Override
    public void run() {
        try{
            //獲取Socket的輸出流,用來向客戶端傳送資料
            PrintStream out = new PrintStream(client.getOutputStream());
            //獲取Socket的輸入流,用來接收從客戶端傳送過來的資料
            BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
            boolean flag = true;
            while (flag){
                //接收從客戶端傳送過來的資料
                String str = buf.readLine();
                if(str == null || "".equals(str)){
                    flag = false;
                }else {
                    if("bye".equals(str)){
                        flag = false;
                    }else{
                        //將接收到的字串前面加上echo,傳送到對應的客戶端
                        out.println("echo:" + str);
                    }
                }
            }
            out.close();
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

ClientDemo.java

/**
 * sinture.com Inc.
 * Copyright (c) 2016-2018 All Rights Reserved.
 */
package test.socketDemo.TCP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
 * @author yzd
 * @version Id: ClientDemo.java, v 0.1 2018年07月10日 14:05 yzd Exp $
 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //客戶端請求與本機在20006埠建立TCP連線
        Socket client = new Socket("127.0.0.1", 20000);
        client.setSoTimeout(10000);
        //獲取鍵盤輸入
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        //獲取Socket的輸出流,用來傳送資料到服務端
        PrintStream out = new PrintStream(client.getOutputStream());
        //獲取Socket的輸入流,用來接收從服務端傳送過來的資料
        BufferedReader buf =  new BufferedReader(new InputStreamReader(client.getInputStream()));
        boolean flag = true;
        while(flag){
            System.out.print("輸入資訊:");
            String str = input.readLine();
            //傳送資料到服務端
            out.println(str);
            if("bye".equals(str)){
                flag = false;
            }else{
                try{
                    //從伺服器端接收資料有個時間限制(系統自設,也可以自己設定),超過了這個時間,便會丟擲該異常
                    String echo = buf.readLine();
                    System.out.println(echo);
                }catch(SocketTimeoutException e){
                    System.out.println("Time out, No response");
                }
            }
        }
        input.close();
        if(client != null){
            //如果建構函式建立起了連線,則關閉套接字,如果沒有建立起連線,自然不用關閉
            client.close();	//只關閉socket,其關聯的輸入輸出流也會被關閉
        }

    }
}
複製程式碼

UDP

UDP的通訊建立的步驟

客戶端要經過下面三步操作:

  • 1、建立一個DatagramSocket例項,可以有選擇地對本地地址和埠號進行設定,如果設定了埠號,則客戶端會在該埠號上監聽從伺服器端傳送來的資料

  • 2、使用DatagramSocket例項的send()和receive()方法來傳送和接收DatagramPacket例項,進行通訊;

  • 3、通訊完成後,呼叫DatagramSocket例項的close()方法來關閉該套接字。

UDP服務端要經過下面三步操作:

  • 1、建立一個DatagramSocket例項,指定本地埠號,並可以有選擇地指定本地地址,此時,伺服器已經準備好從任何客戶端接收資料包文

  • 2、使用DatagramSocket例項的receive()方法接收一個DatagramPacket例項,當receive()方法返回時,資料包文就包含了客戶端的地址,這樣就知道了回覆資訊應該傳送到什麼地方;

  • 3、使用DatagramSocket例項的send()方法向伺服器端返回DatagramPacket例項。

Demo

這裡有一點需要注意:

UDP程式在receive()方法處阻塞,直到收到一個資料包文或等待超時。由於UDP協議是不可靠協議,如果資料包在傳輸過程中發生丟失,那麼程式將會一直阻塞在receive()方法處,這樣客戶端將永遠都接收不到伺服器端傳送回來的資料,但是又沒有任何提示。為了避免這個問題,我們在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,並指定重發資料包的次數,如果每次阻塞都超時,並且重發次數達到了設定的上限,則關閉客戶端。

ClientDemo.java

/**
 * sinture.com Inc.
 * Copyright (c) 2016-2018 All Rights Reserved.
 */
package test.socketDemo.UDP;

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

/**
 * @author yzd
 * @version Id: ClientDemo.java, v 0.1 2018年07月10日 14:46 yzd Exp $
 */
public class ClientDemo {
    public static final int TIMEOUT = 5000;
    public static final int MAXNUM = 5;

    public static void main(String[] args) throws IOException {
        String str_send = "Hello UDPServer";
        byte[] buf = new byte[1024];
        //客戶端在9000埠監聽接收到的資料
        DatagramSocket ds = new DatagramSocket(9000);
        InetAddress loc = InetAddress.getLocalHost();
        //定義用來傳送資料的DatagramPacket例項
        DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),loc,3000);
        //定義用來接收資料的DatagramPacket例項
        DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
        //資料發向本地3000埠
        ds.setSoTimeout(TIMEOUT);               //設定接收資料時阻塞的最長時間
        int tries = 0;                         //重發資料的次數
        boolean receivedResponse = false;     //是否接收到資料的標誌位
        //直到接收到資料,或者重發次數達到預定值,則退出迴圈
        while(!receivedResponse && tries<MAXNUM){
            //傳送資料
            ds.send(dp_send);
            System.out.println("Client send message succeed.");
            try{
                //接收從服務端傳送回來的資料
                ds.receive(dp_receive);
                //如果接收到的資料不是來自目標地址,則丟擲異常
                if(!dp_receive.getAddress().equals(loc)){
                    throw new IOException("Received packet from an unknown source");
                }
                //如果接收到資料。則將receivedResponse標誌位改為true,從而退出迴圈
                receivedResponse = true;
            }catch(InterruptedIOException e){
                //如果接收資料時阻塞超時,重發並減少一次重發的次數
                tries += 1;
                System.out.println("Time out," + (MAXNUM - tries) + " more tries..." );
            }
        }
        if(receivedResponse){
            System.out.println("client received data from server:");
            String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) +
                    " from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
            System.out.println(str_receive);
            //由於dp_receive在接收了資料之後,其內部訊息長度值會變為實際接收的訊息的位元組數,
            //所以這裡要將dp_receive的內部訊息長度重新置為1024
            dp_receive.setLength(1024);
        }else{
            //如果重發MAXNUM次資料後,仍未獲得伺服器傳送回來的資料,則列印如下資訊
            System.out.println("No response -- give up.");
        }
        ds.close();
    }
}
複製程式碼

ServerDemo.java

/**
 * sinture.com Inc.
 * Copyright (c) 2016-2018 All Rights Reserved.
 */
package test.socketDemo.UDP;

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

/**
 * @author yzd
 * @version Id: ServerDemo.java, v 0.1 2018年07月10日 15:12 yzd Exp $
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        String str_send = "Hello UDPclient";
        byte[] buf = new byte[1024];
        //服務端在3000埠監聽接收到的資料
        DatagramSocket ds = new DatagramSocket(3000);
        //接收從客戶端傳送過來的資料
        DatagramPacket dp_receive = new DatagramPacket(buf, 1024);
        System.out.println("Server is on,Waiting for client to send data......");
        boolean f = true;
        while(f){
            //伺服器端接收來自客戶端的資料
            ds.receive(dp_receive);
            System.out.println("Server received data from client:");
            String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength()) +
                    " from " + dp_receive.getAddress().getHostAddress() + ":" + dp_receive.getPort();
            System.out.println(str_receive);
            //資料發動到客戶端的3000埠
            DatagramPacket dp_send= new DatagramPacket(str_send.getBytes(),str_send.length(),dp_receive.getAddress(),9000);
            ds.send(dp_send);
            System.out.println("Server send message succeed.");
            //由於dp_receive在接收了資料之後,其內部訊息長度值會變為實際接收的訊息的位元組數,
            //所以這裡要將dp_receive的內部訊息長度重新置為1024
            dp_receive.setLength(1024);
        }
        ds.close();
    }
}
複製程式碼

應用層

瀏覽器從輸入URL地址到最終顯示內容的過程

DNS查詢對應ip過程

首先查詢瀏覽器自身的DNS快取,如果有這個域名對映且沒過期(TTL)則直接向該IP傳送HTTP請求,否則下一步

查詢本地作業系統hosts快取,如果有且沒過期,拿出來使用完成DNS解析,否則下一步

查詢本地DNS域名伺服器

如果不可以由該伺服器解析,則把請求發至根域名伺服器,解析該域名是由誰來授權管理,返回頂級域名伺服器的IP地址

本地DNS伺服器聯絡頂級域名伺服器。

頂級域名伺服器如果無法解析,則找下一級DNS伺服器,並把IP發給本地DNS伺服器。

以此類推,在DNS域名解析的過程中,使用UDP協議進行不可靠傳輸,不需要三次握手,傳輸需要的內容較少,使用UDP更快。

在網頁開發過程中儘量減少對DNS域名的解析,天貓,淘寶等使用進行dns延遲快取

www.cnblogs.com/sjm19910902…

HTTP請求過程

  1. 建立TCP連線

  2. 傳送請求

    一旦建立了TCP連線,Web瀏覽器就會向Web伺服器傳送請求命令。例如:GET/sample/hello.jsp HTTP/1.1。

  3. 傳送請求頭資訊

    瀏覽器傳送其請求命令之後,還要以頭資訊的形式向Web伺服器傳送一些別的資訊,之後瀏覽器傳送了一空白行來通知伺服器,它已經結束了該頭資訊的傳送

  4. 伺服器應答

    客戶機向伺服器發出請求後,伺服器會客戶機回送應答, HTTP/1.1 200 OK ,應答的第一部分是協議的版本號和應答狀態碼。

  5. 伺服器傳送應答頭資訊

    正如客戶端會隨同請求傳送關於自身的資訊一樣,伺服器也會隨同應答向使用者傳送關於它自己的資料及被請求的文件。

  6. 伺服器向瀏覽器傳送資料

    Web伺服器向瀏覽器傳送頭資訊後,它會傳送一個空白行來表示頭資訊的傳送到此為結束,接著,它就以Content-Type應答頭資訊所描述的格式傳送使用者所請求的實際資料。

*7. Web伺服器關閉TCP連線

一般情況下,一旦Web伺服器向瀏覽器傳送了請求資料,它就要關閉TCP連線,然後如果瀏覽器或者伺服器在其頭資訊加入了這行程式碼:Connection:keep-alive TCP連線在傳送後將仍然保持開啟狀態,於是,瀏覽器可以繼續通過相同的連線傳送請求。保持連線節省了為每個請求建立新連線所需的時間,還節約了網路頻寬。

如果是第一次訪問請求該網址

瀏覽器傳送HTTP請求,請求頭包括:

  • 請求方法(Request Method)
  • 協議版本
  • 客戶端資訊(User-Agent)
  • connect
  • 請求內容等
  • host

如果順利訪問:客戶端返回200狀態碼

返回資訊包括:

  • 返回內容
  • expires設定快取過期時間
  • contentType返回內容型別
  • contentLength
  • status
  • Etage該快取的版本號
  • contentEncoding
  • Date
  • cache-control
  • set-cookie設定本域名下瀏覽器的cookie
  • lastModified

如果是第二次

瀏覽器則發出http請求時

  • 帶上cookie傳送
  • if-Modified-since(匹配前一次請求時返回的last-modified)
  • if-None-match(匹配前一次請求時返回的Etag)
  • 如果資源沒有被修改則返回304狀態碼。

但是如果前一次請求瀏覽器設定expires,則瀏覽器首先會檢查快取中的資源,如果在設定的expires時間之內則不會再次傳送請求。

lastModified代表伺服器最後修改時間,精確到秒。expires資源過期時間,精確到秒。Etag則代表資源的版本號,每次修改資源Etag就會變。不同資源的Etag不同。

如果正確訪問

瀏覽器根據返回content-type,解析伺服器返回的資料

  • 瀏覽器解析html檔案時,每次遇到frame、img、link、javascript都會重新傳送一個http請求

  • javascript下載完後就會立即執行阻塞瀏覽器的渲染以及繪製

  • 所以一般js連結放在最後,但是很多瀏覽器都會優先下載js檔案和css檔案,所以如果js沒有對dom操作,儘量defer延遲載入js檔案

  • css在文件頭,防止因為css樣式改變導致瀏覽器多次重繪或者回流,是頁面閃動卡頓。

js和css儘量使用外鍊形式,減少DOM結構的長度和複雜度,減少瀏覽器解析html檔案的時間。

dom節點儘量別深度巢狀,css少使用多層選擇器。

頁面減少http請求的個數,多個圖片使用圖片dataURI編碼或則圖片精靈進行合併、css檔案壓縮合並、js檔案壓縮合並。配置localhost之後就不會走dns了

-----正文結束-----

本文參考

github.com/CyC2018/CS-…

github.com/linw7/Skill…

www.jianshu.com/p/674fb7ec1…

有刪減,修改,補充額外增加內容

本作品採用知識共享署名-非商業性使用 4.0 國際許可協議進行許可。

關注我

我是蠻三刀把刀,目前為後臺開發工程師。主要關注後臺開發,網路安全,Python爬蟲等技術。

來微信和我聊聊:yangzd1102

Github:github.com/qqxx6661

原創部落格主要內容

  • 筆試面試複習知識點手冊
  • Leetcode演算法題解析(前150題)
  • 劍指offer演算法題解析
  • Python爬蟲相關技術分析和實戰
  • 後臺開發相關技術分析和實戰

同步更新以下部落格

1. Csdn

blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發、面試助攻手冊

2. 知乎

www.zhihu.com/people/yang…

擁有專欄:碼農面試助攻手冊

3. 掘金

juejin.im/user/5b4801…

4. 簡書

www.jianshu.com/u/b5f225ca2…

個人公眾號:Rude3Knife

個人公眾號:Rude3Knife

如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章