Linux| |對於UDP的學習

千秋,發表於2020-12-30

UDP


# 前序

UDP(使用者資料包協議)沒有連線的,是面向資料包的,是不可靠

# 套接字

就是IP地址+埠號

IP地址:4位元組

埠號:2位元組,也就是說範圍是0~65535

  • 埠號分為

  • 知名埠號

    • 0--1023:http,ssh,ftp,telnet等一些協議埠號都是固定的,對於作業系統來說是不能對其進行分配的

    • 一些固定的埠號

    1. ssh伺服器,使用22埠

    2. ftp伺服器,使用21埠

    3. telnet伺服器,使用23埠

    4. http伺服器,使用80埠

    5. https伺服器,使用443埠

  • 作業系統動態分配的埠號

    • 客戶端伺服器的埠號,這個範圍的埠號作業系統可以對其進行分配

  • 檢視埠號

  •  
    1. less /etc/services

    2. //就可以檢視Linux下所有的埠號了

     

IP地址的理解:

  • IP地址用來標識一個主機

埠號的理解:

  • 埠號就是用來告訴作業系統要對於那一個程式進行操作,也就是說埠號就是用來標識一個程式

  • 一個埠號只可被一個程式所佔用,但是一個程式可以擁有多個埠號,也就是程式和埠號是一對多的關係

  • 當我們寫一個程式使用埠號的時候,要避開這些知名埠號

【問題】

  1. 一個程式是否可以bind多個埠號呢?

    • 可以,因為一個程式可以開啟多個檔案描述符,而每一個檔案描述符都對應著一個埠號,所以一個程式可以繫結多個埠號

  2. 一個埠號是否可以被多個程式bind?

    • 不可以

    • 如果一個程式先繫結一個埠號,然後再fork一個子程式,這樣的話就實現了多個程式繫結一個埠號,但是不同的程式繫結同一個埠號是不可以

    • TIME_WAIT狀態,伺服器不能立即重啟也說明不用程式不能同時繫結同一個埠號

  3. 多個程式可以監聽同一個埠號嗎?

    • 可以。監聽之前要進行建立套接字->繫結ip::埠號->監聽。我們可以在bind之前使用setsockopt函式,設定套接字選項,其中就包括REUSEADDR這個選項,表明多個程式可以複用bind函式中指定的地址和埠號

所以套接字就可以準確的標識一臺主機上的一個程式,從而完成計算機之間的通訊

計算機之間的通訊:

  • 主機A的某個程式與主機B上的另一個程式進行通訊

 

# 網路位元組序轉換

對於資料在網路中傳輸的時候有著自己遵循的傳輸規則大端傳輸

對於主機上的資料的傳輸序列有著兩種:

  • 大端:即高位位元組序放在低地址上

  • 小端:即低位位元組序放在低地址上

  • 傳輸:均是先傳輸低地址上的資料然後是高地址上的資料

所以對於主機上的資料傳輸的時候傳輸到網路上的時候有可能導致資料錯誤(例如主機上是小端的時候,所以需要進行轉換)

轉換函式:

 
  1. #include <arpa/inet.h>

  2. uint32_t htonl(uint32_t hostlong);

  3. uint16_t htons(uint16 hostshort);

  4. uint32_t ntohl(uint32_t netlong);

  5. uint16_t ntohs(uint16_t netshort);

h:表示主機host name

n:表示網路network

l:表示4位元組long

s:表示2位元組short

 

# 地址轉換函式

  • 字串轉化為in_addr

    • in_addr_t inet_addr(const char* strptr)

  • in_addr轉化為字串

    • char* inet_ntoa(struct in_addr inaddr)

    • 具有不可重入性,也就是不可多次呼叫,因為該函式自己在靜態區開闢一塊空間用來存放IP地址字串的

 

# UDP協議

UDP協議端格式

插圖:UDP協議端格式

  • 16為UDP長度,表示整個資料包(UDP首部+UDP資料)的最大長度(64KB)

  • 檢驗和:如果校驗和出錯,就會直接丟棄(檢驗的是把首部和資料部分一起都檢驗)

    • 校驗值首先在資料傳送方通過特殊的演算法計算得出,在傳遞到接收方之後,還要在重新計算。如果某個資料包在傳輸過程中被第三方篡改或者由於線路噪音等原因受到損壞,傳送和接收方的校驗計算值將不會相符,由此UDP協議可以檢驗是否出錯。

  • 源埠號:在對方回信是選用,不需要時可用全0

  • 目的埠號:在終點交付報時必須要用到

  • 長度:UDP使用者資料包的長度,其最小值是8(僅有首部)

UDP的特點

  • 無連線:直到對端的IP和埠號就直接進行傳輸,不需要建立連線

  • 不可靠:沒有確認機制,沒有重傳機制;因為沒有網路故障該段無法傳送到對方,UDP協議層也不會給應用層返回任何錯誤資訊

  • 面向資料包:不能夠靈活的控制讀寫資料的次數和數量

  • 控制選項較少,資料傳輸過程中延遲小,資料傳輸效率高

面向資料包

  • 應用層交給UDP多長的報文,UDP原樣傳送,既不會拆分也不會合並

  • 例:用UDP傳輸100個位元組的資料

    • 如果傳送端呼叫一次sendto,傳送100個位元組。那麼接收端也必須呼叫對應的一次recvfrom,接收100位元組;而不能迴圈呼叫10次recvfrom,每次傳送10個位元組

UDP的快取區

  • UDP沒有傳送快取區,呼叫sendto之後會直接交給核心,由核心·將資料傳給網路層協議進行後續的傳輸動作。因為UDP是不面向連線的,所以沒有重發機制,也就不需要傳送快取區將已經傳送的資料儲存下來為了傳送失敗進行重傳做準備

  • UDP具有接收快取區。但是這個接收快取區不能保證收到的UDP報的順序和傳送UDP報的順序一致;如果快取區滿了,在到達的UDP資料就會被丟棄

UDP的Socket既能讀,也能寫,全雙工

UDP的使用注意事項

  • UDP協議首部中有一個16位的最大長度,也就是說一個UDP能傳輸的資料的最大長度是64K(包含UDP首部)。但是64K在當今的網際網路環境下,是一個非常小的數字。如果我們需要傳輸的資料超過64K,就需要應用層手動的分包,多次傳送,並在接收端拼裝

  • UDP首部中校驗和的計算方法有些特殊。在計算校驗和時,要在UDP使用者資料包之前增加12個位元組的偽首部

  • 偽首部既不向下傳輸也不想上遞送,而僅僅是為了計算校驗和

  • 與IP資料包的校驗和只檢驗IP資料包的首部不同,UDP的校驗和是把首部和資料部分一起都檢驗

偽首部:

插圖:偽首部

基於UDP的應用層的協議

  • NFS:網路檔案系統

  • TFTP:簡單檔案傳輸檔案協議

  • DHCP:動態主機配置協議

  • DNS:域名解析協議

面試題用UDP實現可靠傳輸?

參考TCP的可靠性機制,在應用層實現類似的邏輯

  • 引用序列號,保證資料順序

  • 引入確認應答,確保對端收到了資料

  • 引入超時重傳,如果隔一段時間沒有應答,就重發資料

 

1. 對於socket函式的使用

1.1 函式原型

 
  1. int socket(int domain, int type, int protocol);

  2. domain: 領域

  3. AF_INET:IPV4

  4. AF_INET6:IPV6

  5. type: 型別

  6. SOCK_STREAM

  7. SOCK_DGARM

  8. protocol: 協議

1.2 函式的作用

在通訊領域中建立一個未被繫結的套接字,並且返回一個檔案描述符,可以在以後對套接字進行操作的函式呼叫中使用

 

2. 對於bind函式的使用

2.1 函式原型

int bind(int socket, const struct sockaddr* address, socklen_t address_len);

2.2. 函式的作用

該函式採用先前建立好的套接字來對於IP地址以及埠號進行繫結,也就是表示該套接字可以標識出在一個網路中一臺確定的主機並且主機中的程式

 

3. 對於recvfrom函式的使用

3.1 函式原型

 
  1. ssize_t recvfrom(int socket, void* restrict buffer, size_t length,

  2.                 int flags, struct sockaddr* restrict address,

  3.                socklen_t* restrict address_len);

  4. socket:要接受那一個套接字的訊息

  5. buffer:用來接收訊息的快取區

  6. length:接收的訊息的長度

  7. flags:型別

  8. address:空指標或者儲存傳送資訊的sockaddr結構

  9. addless_len:指定地址引數指向的sockaddr結構的長度

  10. 3.2 函式的作用

3.2 函式的作用  

用來接收從socket套接字傳送來的訊息。該套接字的sockaddr結構也知道

 

4. 對於sendto函式的使用

4.1 函式原型

 
  1. ssize_t recvfrom(int socket, const void* message, size_t length,

  2.                 int flags, const struct sockaddr* dest_addr,

  3.                socklen_t* dest_len);

4.2 函式的作用

該函式是socket套接字從dest_addr出接收訊息

 

5. 擴充套件知識

5.1 netstat

netstat是一個用來監控TCP/IP網路非重要工具

語法:netstat [選項]

功能:檢視網路狀態

選項:

  • -a,顯示所有連線的Socket

  • -c,持續列出網路狀態

  • -n,直接使用ip地址,而不通過域名伺服器,也就是顯示為數字

  • -l,顯示監控中的伺服器的Socket,僅列出監聽(Listen)狀態下的Socket

  • -p,顯示正在使用Socket的程式的識別碼和名稱(PID/Program name)

  • -t,顯示TCP傳輸協議的連線狀況

  • -u,顯示UDP傳輸協議的連線狀況

  • -v,顯示指令執行過程

  • -V,顯示版本資訊

  • -x,顯示UNIX傳輸協議的連線狀況

  • -s,顯示網路工作資訊統計表

  • -h,線上幫助

 

5.2 pidof

檢視伺服器程式id是非常方面

語法:pisdof [程式名]

功能:通過程式名,檢視程式id

 

5.3 scp命令

基於ssh登入進行的網路安全的遠端檔案拷貝命令

例:要將自己當前路徑下的clinet檔案傳送到主機IP為192.168.153.140的home目錄下

scp ./clinet root@192.168.153.140:/home

 

 

5.4 關於防火牆的命令

  • 啟動:systemctl start firewalld

  • 關閉:systemctl stop firewalld

  • 檢視狀態:systemctl status firewalld

  • 開機禁用:systemctl disable firewalld

  • 開機啟用:systemctl enable firewalld

# 對於UDP書寫伺服器的思路

由於UDP是無連線的,所以對於兩個處於同一區域網下計算機的程式之間通訊,所以是不需要兩臺計算機之間的程式進行連線的,對於UDP使用的介面是需要包含知道從哪裡接收訊息的,要傳送訊息到哪裡的。

  • 實現本地通訊

    • 伺服器

      • 只需要伺服器建立一個套接字

      • 使該套接字對於本地地址(127.0.0.1)進行繫結,並且繫結一個埠號(1024--65535)就行了

        • 繫結本地地址是為了對於本地計算機的兩個程式程式通訊,而繫結埠號是為了繫結一個程式,是為了對於客戶端進行傳送訊息到伺服器的時候,可以找到伺服器

      • 然後就接受客戶端發來的訊息

      • 對於客戶端的訊息進行處理然後就可以再次將處理後的訊息進行返回

      • 插圖:伺服器流程

    • 客戶端

      • 繫結一個套接字

        • 為了繫結一個程式,可以和伺服器進行通訊,將訊息傳送過去的時候要讓伺服器知道是哪一個程式再和他程式通訊

      • 客戶端只需要向伺服器傳送訊息

      • 然後再次從客戶端接收訊息就好了,不需要考慮要進行連線

      • 插圖:客戶端流程

  • 實現處於同一區域網下的不同主機間進行通訊

    • 伺服器

      • 和本地通訊的一致,只是對於套接字繫結的ip地址不一樣了

      • 也對於套接字要繫結該區域網的ip地址以及一個埠號不需要在繫結本地地址(127.0.0.1)

        • 這樣的話處於同一區域網下的計算機的程式就可以進行通訊了

    • 客戶端

      • 對於客戶端來說沒有任何改變,仍然是隻需要知道伺服器的ip和埠號就行了

 

# 對於UDP伺服器要注意的問題

  • 啟動客戶端

    • 啟動客戶端的時候必須給客戶端輸入一個ip地址和埠號,這個ip地址和埠號也就是要知道客戶端要傳送訊息給哪一個伺服器進行傳送

  • 啟動伺服器

    • 必須要給伺服器繫結一個ip地址和埠號,也就是要注意該伺服器處於該計算機上的哪一個程式上

相關文章