1. 程式人生 > 其它 >計算機網路 | 傳輸層篇 | 03

計算機網路 | 傳輸層篇 | 03

目錄

傳輸層篇 | 03

如果從資訊的處理角度來看,主要使用應用層提供通訊服務的。我們平時對網路進行程式設計的時候,很多時候都是直接對接傳輸層(使用傳輸層的介面來進行網路的程式設計)。因此我們說傳輸層是使用者功能的最底層,但是屬於面向通訊部分的最高層。

傳輸層的工作位置在計算機中。

對於網路中的路由器,並沒有傳輸層在工作。也就是在通訊的時候,傳輸層是工作在終端的裝置。

傳輸層的作用:管理端到端的通訊連線。

網路層的作用就是解決好虛擬的網際網路絡如何處理資料的路由,決定資料的走向。

網路層已經解決好了資料走向問題,那麼在傳輸層就不關心了,重點關心的是兩個端之間是如何進行通訊的。

傳輸層的作用就是讓兩個程序建立可以在網路中建立通訊。

作業系統中講解的程序之間的通訊是本機之間的程序通訊,不能跨網路,跨裝置。

計算機網路中將的程序通訊是可以跨網路,跨裝置的。

在計算機網路的傳輸層中需要引入埠的概念,用來標記不同的網路程序。

埠使用16bit表示(0~65535)

傳輸層主要就是學習兩個協議: TCP & UDP

UDP協議詳解

UDP相對於TCP來說是一個簡單的協議。

UDP中比較重要的概念是資料表(Datagram)。資料報是UDP協議的一個重要特徵。

資料報:應用層傳輸過來的完整資料。

UDP不會對該資料進行合併,也不會進行拆分,就是加上一個UDP頭部就向下傳遞給網路層了。

UDP的資料長度主要由應用層傳輸的資料長度決定的,應用層傳輸的資料長度長,那麼UDP的資料就長(應該僅僅加個UDP頭部,不做任何其他處理)。

可以看出在各個網路層資料報的形式。

上圖就是UDP資料報的格式。

可以看到,UDP的頭部由四部分組成,將其和IP頭部比對,UDP是一個簡單的頭部。

由於UDP的頭部很簡單,也沒有辦法保證資料會一定傳送到,而且就算資料丟失了,也沒有辦法感知到。

UDP是面向報文傳輸的,它不會對報文進行任何的處理。而是直接塞進UDP的資料中,然後就傳送出去了。

與之對應的TCP,這個後面會講解。

上面就是UDP的特點

  • UDP是無連線的
  • UDP不能保證可靠的交付
  • UDP是面向報文傳輸的
  • UDP沒有擁塞控制
  • UDP的首部開銷很小

TCP協議詳解

位元組流:流入程序或流出程序的位元組序列。

從應用層傳輸下來的資料,UDP將其看成是一塊資料,而TCP不見其看成是一塊資料,而是會將其拆分,看作是一系列的位元組流。TCP不是面向一整塊資料(面向資料報)而是面向一個位元組一個位元組來處理的。

所以在TCP中,可能先取出資料的某一段來進行傳輸,然後剩下的資料在放到第二個TCP報文來進行傳輸。

TCP會對使用者的資料進行拆分和合並操作,以保證資料可以更好的傳輸出去。

上面就是TCP的五個特點(對比UDP來寫)

  • TCP是有連線的
  • TCP提供可靠的交付
  • TCP是面向位元組流的
  • TCP有擁塞控制
  • TCP首部開銷比較大
  • TCP是全雙工通訊

除了可選的,TCP頭部的固定長度是20位元組。

TCP給每一個位元組都編號了,然後序號這裡儲存的就是這次TCP報文第一位元組的序號。

例子:

計算機收到的TCP報文的序號是501,然後資料的長度有100個位元組。計算機收到資料後,也就是501~600的位元組都收到了,那麼期望下一個傳遞迴來的序列號是601。

確認號需要配合序號一起使用。

結論:確認號為N,則表示N-1序號的資料都收到

資料偏移:真實的TCP資料偏離首部的距離

通過資料偏移,可以計算出TCP頭部的長度範圍,最少為20,4*15=60,所以TCP頭部的長度為[20,60]字。

需要有資料偏移是因為有TCP選項這一塊內容,所以要有資料偏移來記錄TCP頭部的長度。

TCP標記是非常有用的, 包含後面的三次握手,四次揮手都要不斷用到這裡的TCP標記。

視窗指明允許對方傳送的資料量,如果視窗的值為1000,那麼就表示允許對方傳送過來1000個位元組。

視窗可以和確認好結合運算,比如確認號是501,視窗的值是1000,那麼表示501~1500這麼多個位元組的資料都是可以接收的。

可靠傳輸協議的基本原理

可靠傳輸的基本原理,這裡瞭解了兩個協議:停止等待協議 & 連續ARQ協議

停止等待協議的核心是停止和等待。

連續ARQ協議的核心是滑動視窗和累計確認。

停止等待協議就如上圖, 接收方和傳送方一直處於停止等待,停止等待的過程。

第一種差錯情況: 傳送方沒有收到確認訊息,可能是傳送方發出去的訊息丟失了,那麼傳送方就會重新發送資料。

第二種差錯情況:確認訊息丟失,傳送方沒有收到確認訊息,會超時重傳。

第三種差錯情況:確認訊息沒有丟失,但是遲到了。

停止等待協議的三種可能查重情況

  • 傳送訊息在路上丟失
  • 確認訊息在路上丟失
  • 確認訊息很久才到

TCP中一共有四個定時器,這裡學習第一個定時器——超時定時器。

這裡不再一個一個位元組傳送,而是直接把視窗中的6個位元組一起傳送。

但是如果要一個位元組一個位元組確認的話,那麼相當於是沒有改進的。

例如上圖,如果第5個位元組收到了確認了,那麼就表明序號5和之前的位元組都收到了(這是確認號的作用)。

然後就可以直接向後推進,這樣就省了很多次重複確認。

視窗控制與重發控制

  1. 確認報文丟失,這個其實是不要津的,可以通過後面的確認號來進行確認判斷。

  2. 傳送的報文丟失,那麼接收方就會重複傳送幾次同樣的一個確認號,接收方連續3次收到同一個確認號,就會將所對應的資料進行重發(快速重傳)。

TCP協議的可靠傳輸

TCP的可靠傳輸是基於連續ARQ的。

選擇重傳重傳的是一個邊界,也就是一個範圍,而不是某一個位元組。

網路的速度不一定是按序的!如果先收到了25和27的確認應答,但是沒有收到23,24的話,也要從23開始重傳。

選擇重傳:TCP可以選擇一些訊息重新傳輸,而不是把所有訊息重新傳輸。

(如果是按序收到的,那麼就是連續ARQ協議,如果不是按序收到的,就可以選擇重傳!)

選擇重傳的重傳序號存在TCP選項中,最多可以存放10個序號。

TCP協議的流量控制

流量控制是TCP協議的一個特有功能。

流量控制: 接收方通過控制視窗的大小,來約束髮送方的傳送速率

TCP協議的擁塞控制

流量控制和擁塞控制的差別。一個是考慮端到端的流量控制,一個是對網路全域性的考慮。

擁塞控制主要有兩個演算法:慢啟動演算法 & 擁塞避免演算法

慢啟動先指數增長,增長到慢啟動閾值就會停止,切換到擁塞避免演算法。

網路擁塞的意思就是傳輸報文不會發生超時。

慢啟動閾值一般都是告訴你的(怎麼計算先不管了...)

上圖就是是先進行慢啟動,達到閾值之後在進行擁塞避免演算法,不斷去+1試探。

TCP連線的三次握手

上面就是上次握手的過程

-> SYN=1,seq=x
<- SYN=1,ACK=1,seq=y,ack=x+1
-> ACK=1,seq=x+1,ack=y+1

TCP的三次握手最主要是防止已過期的連線再次傳到被連線的主機。

上圖所示,如果傳送方的請求報文進行了超時重傳, 第二個報文先到了,並得到接收方迴應,如果是兩次握手的話,那麼接收方迴應時就建立了連線,傳送方收到應答報文也建立連線。

但是問題是,如果超時報文接收方迴應了,那麼傳送方收到這個應答報文,也會建立連線,那麼就會建立兩次連線,這是不對的。

為了避免上述問題,傳送方在收到接收方的應答後,可以建立連線,並再傳送一次確認報文,這樣後面收到的請求應答報文都是無效的。而接收方再收到第三次握手才開始建立連線。

TCP連線的四次揮手

為什麼接收方連續傳送兩次確認報文呢?因為結束連線是傳送方主動請求的,傳送方的資料是傳送完成了,但是接收方可能還有資料需要接收,所以第一次應答報文是:好的,我知道了,等我收完資料先。然後接收方收完資料後就會發送第二條應答報文。

傳送方收到第二次應答報文後,會進入一個等待時間2MSL(報文最長存活時間),防止應答報文沒有傳送出去,等待時間過了就進行關閉。

接收方收到傳送方的應答報文就關閉了。

一般主動釋放連線後,也是不能馬上覆用該埠的,就是因為有這個等待計時器的存在,要等待2MSL後才會釋放埠(一般是4分鐘左右)。

為什麼要設定2MSL?

  • 最後一個報文沒有確認
  • 確保傳送方的ACK可以到達接收方
  • 2MSL時間內沒有收到,則接收方會重發
  • 確保當前連線的所有報文都已經過期

套接字與套接字程式設計

服務端程式碼

import socket


def server():
    # 建立套接字
    s = socket.socket()
    # 繫結套接字
    host = 'localhost'
    port = 6666
    s.bind((host, port))

    # 監聽套接字
    s.listen(5)
    # 接收&處理訊息
    while True:
        c, addr = s.accept()
        print("Connect Addr: ", addr)
        c.send(b"Welcome!")
        c.close()


if __name__ == "__main__":
    server()

客戶端程式碼

import socket


def client(i):
    # 建立套接字
    s = socket.socket()
    # 連線套接字
    host = 'localhost'
    port = 6666
    s.connect((host, port))
    # 傳送資訊
    print("Recv msg:%s, Clinet: %d" % (s.recv(1024), i))
    s.close()


if __name__ == "__main__":
    for i in range(10):
        client(i)

網路套接字的話,不管是不是本機,都要完整的走完整個協議棧。

域套接字本適合本機的程序之間通訊,對系統資源消耗也小。