網路程式設計原理與UDP實現

PsgQ發表於2020-12-05

如何傳送資料包?


Q:當應用程式產生資料的時候,需要去構造資料包併傳送到網路上去,但是由誰負責處理呢?

A:現代作業系統負責資料包得構造與傳送,應用程式只需提供資料。 當應用程式產生資料時,應用程式將資料交給OS核心,然後在OS核心新增各層的首部,構建好資料包,然後交給網路卡,傳送到網路中去。


Q:應用程式如何向OS 傳送資料呢?

A: OS為程式提供了一個介面,即socket API,類似於系統呼叫函式。


Q:通過socket API,只需要提供資料嗎?

A:並不,還需要告訴OS 核心,目的埠是什麼,傳輸層使用哪種協議(UDP/TCP),目的IP地址。


Q:為什麼不需要告訴源埠?

A:因為源埠是當資料進入到OS核心時,OS會為這個應用程式隨機開放一個埠,作為源埠。當伺服器端收到資料,進行返回時,也從這個埠進行返回。同樣源IP地址,OS核心也是選一個網路卡出去,這個網路卡的地址作為源IP地址。


詳情可見下圖:

網路圖

Q:加工資料是包括什麼?

A:依次包括新增傳輸層首部,網路層首部,所以在這裡需要告訴傳輸層的目的埠,以及使用的協議,以及網路層的目的IP地址。然後交給網路卡驅動程式,構造資料鏈路層頭部,以及物理層。若想繼續詳細瞭解網路卡的操作,可參考:網路卡工作原理詳解從網路卡傳送資料再談TCP/IP協議—網路傳輸速度計算-網路卡構造



瞭解到上述資訊,知道我們要告訴OS 目的IP地址,目的埠,以及資料。

下面是使用python網路程式設計客戶端的程式碼:

import  socket

ip="127.0.0.1"
port=8090
data = b"hello PsgQ"
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.sendto(data,(ip,port))


Q: socket函式裡面的引數是什麼意思?

A: socket.AF_INET表示使用IPv4。socket.SOCK_DGRAM表示傳輸層使用UDP。


Q:為什麼data=b"hello PsgQ"?

A: 因為在網路中傳輸的是位元組流,b字首將字串轉換成bytes。對於英文字母可以這樣轉換,但對於中文字串不行,因為b後面的字串必須是ascii碼可以表示的。若要傳遞中文字串,須data=bytes('哈哈',encoding='UTF-8')

具體可參考字串和編碼


c語言版程式碼隨後更新。


如何接收資料包?


網路卡驅動程式收到資料,作出校驗以及檢查完相應的目的MAC地址,交到OS核心。

OS核心 網路層模組 進行解封裝,檢視IP的目的地址,是不是發給自己的,丟棄IP地址不是自己的資料包。然後檢視傳輸層使用的是什麼協議,假如是UDP,之後交給UDP模組處理。

UDP獲得資料包後,檢視目的埠,發給相應的埠。


Q:應用程式怎麼去獲得資料阿?

A:每個應用程式想要去接收資料,都需要與相應的埠進行繫結。比如HTTP,預設80埠,DNS預設使用53埠等。即OS核心交付給相應的埠,就意味著交付到某個應用程式(通過socket API)。相當於告訴核心,任何時候只要有資料到達這個埠,可以把資料交給我。具體過程是,OS 核心交給相應的埠的緩衝區,這個緩衝區是與埠進行繫結的。應用程式可從這個緩衝區讀取資料。

實際情況可能是:當沒有資料時,這個應用程式的socket API會被阻塞掉,但當資料包到達時,OS核心會去啟用這個程式。


如下圖:

接收資料包

從上述資訊瞭解到,我們在server端要進行繫結埠,因為可能有多個網路卡,每個網路卡IP地址不同,所以我們也要繫結IP地址等。


下面是使用python網路程式設計伺服器端的程式碼:

import  socket

ip="0.0.0.0"
port = 8090
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind((ip,port))

while True:
    data,(ip,port)=sock.recvfrom(1024)
    data=data.decode('ascii')
    print("clinet:{},port:{}".format(ip,port))
    print("Received:{}".format(data))

Q: ip ="0.0.0.0"?

A: 表示 資料包的目的地址可以是任意一個網路卡的地址。

Q: data=data.decode('ascii')

A: 因為網路上使用的是位元組流,前面客戶端將字串轉換成了位元組流,在這裡使用ascii碼進行解碼,使位元組流重又轉換成字串格式。


注意:呼叫bind函式 ,引數是(ip,port)


最終執行結果:

image-20201205135106608

相關文章