[Network] 11. TCP 與 UDP,含 python 實現

 · 4 mins read

TCP 和 UDP 的區別以及優缺點?

▍簡述

  • TCP:有連線,所以握手過程會消耗資源,過程為可靠連線,不會丟失資料,適合大資料量交換
  • UDP:非可靠連線,會丟包,沒有校驗,速度快,無須握手過程
 TCPUDP
是否連線面向連線面向非連線
傳輸可靠性可靠的不可靠的
應用場合傳輸大量資料少量資料
速度


▍TCP

  • 是網際網路上最常用的協定,這種協定較為可靠 TCP 為每個封包分配一個唯一的識別碼和一個序號,這些號碼能讓接收端識別封包的完整性,以及封包的順序。
  • 當接收端收到封包後,如果順序正確,會向發送端傳送一個確認信號(Acknowledgement),以此確認接收端已經收到封包。
  • 發送端傳送另一個封包。
  • 如果封包遺失或發送順序錯誤,接收端會保持沈默,不發送確認信號。這表示發送端需要重新傳送封包。


▍UDP

  • 不需要唯一識別碼和序號就能完成相同的工作。
  • 這種協定以串流方式傳送資料,發送端不會等待接收端的確認信號,會繼續不斷發送封包資料。
  • UDP 幾乎沒有錯誤修正功能,也不在乎封包遺失,因此很容易出錯,但傳輸速度比 TCP 更快。
  • 串流媒體、VoIP 語音、網路遊戲等服務經常使用 UDP 協定,這網路應用不太需要可靠性機制


參考資料:https://nordvpn.com/zh-tw/blog/tcp-udp-bijiao/


Tcp 連線數限制

在 Linux 系統中,如果兩個機器要通訊,那麼相互之間需要建立 TCP 連線,為了讓雙方互相認識,Linux 系統用一個四元組來唯一標識一個 TCP 連線: {local ip, local port, remote ip, remote port},即本機 IP、本機埠、遠端 IP、遠端埠,IP 和埠就相當於小區地址和門牌號,只有拿到這些資訊,通訊的雙方才能互相認知。

在 Linux 系統中,表示埠號(port)的變數佔 16 位,這就決定了埠號最多有 2的16次方個,即65,536個,另外埠 0 有特殊含義不給使用,這樣每個伺服器最多就有 65,535 個埠可用。因此,65,535 代表 Linux 系統支援的 TCP 埠號數量,在 TCP 建立連線時會使用。

▍Linux 伺服器只作為客戶端

這時候每發起一個 TCP 請求,系統就會指定一個空間的本地埠給你用,而且是獨佔式的,不會被別的 TCP 連線搶走,這樣最多可以建立 65535 個連線,每個連線都與不同的伺服器進行互動。

▍Linux 伺服器只作為服務端

這種場景下,服務端就會固定的監聽本地埠 port,等著客戶端來向它發起請求。為了計算簡單,我們假設伺服器端的 IP 跟埠是多對一的,這樣 TCP 四元組裡面就有 remote ip 和 remote port 是可變的,因此最大支援建立 TCP 個數為 2的32次方(IP地址是32位的)乘以 2的16次方(port是16位的)等於 2的48次方。


參考:https://www.juduo.cc/club/550281.html


強行關閉客戶端和服務器之間的連接?

在 socket 通信過程中不算循環檢測一個全局變量(開關標記變量),一旦標記變量變為關閉,則調用 socket 的 close 方法,循環結束,從而達到關閉連接的目的。



python 實現

此章節以 Server 端和 Client 端做 TCP 和 UDP 比較區別


Server 端怎麼收取用戶的訊息

  • TCP-Server 需要監聽用戶訊息,流程:
    1. 建立 socket:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM),並指定 socket.AF_INET (TCP) 的通訊協定
    2. 綁定 socket 到本地 IP 與 port:s.bind()
    3. (TCP才有)開始監聽:s.listen()
    4. (TCP才有)等待與接受客戶端的請求連線:s.accept()
    5. 接收客戶端傳來的資料:s.recv()
    6. 傳送給對方發送資料:s.send()s.sendall()
    7. 傳輸完畢後,關閉 socket:s.close()
      import socket
    
      # 1.創建socket對象
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    
      # 2.將socket綁定到指定地址
      address = ('127.0.0.1', 10140)  
      s.bind(address)
    
      # 3.接收連接請求
      s.listen(5) 
    
      # 4.等待客戶請求一個連接
      # 調用accept方法時,socket會進入“waiting”狀態。
      # 接受方法返回一個包含所有元素的元組(連接,地址)。
      # 第一個元素連接是新的socket對象,服務器必須通過它與客戶通信;
      # 第二個元素地址是客戶的互聯網地址。
      ss, addr = s.accept()
      print 'got connect from', addr
    
      # 5.處理:服務器和客戶端通過send和recv通信方法
      # 發送方法返回已發送的字節個數。
      # 調用接收時間,服務器必須指定一個還原,它對應可通過本次方法調用來接收的最大數據量。
      # recv 方法在接收數據時會進入“阻塞”狀態,最終返回一個字符串,用它表示收到的數據。
      # 如果發送的數據量超過了接收所允許的,數據會被截短。
      # 多餘的數據將緩衝於接收端以後調用recv時,多餘的數據會從刪除刪除。
      while True: ra = ss.recv(512) print 'client:', ra ss.send('received')
    
      ss.close() #传输结束,关闭连接
      s.close()
    
  • UDP-Server:不需要監聽訊息
      import socket
    
      address = ('127.0.0.1', 10141)
      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      s.bind(address)
    
      while True: data, addr = s.recvfrom(2048) print "received:", data, "from", addr
    
      s.close()
    


Client 端怎麼發送訊息

  • TCP Client 流程:
    • 建立socket:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    • 連線至遠端地址:s.connect()
    • 傳送資料:s.send()s.sendall()
    • 接收資料:s.recv()
    • 傳輸完畢後,關閉socket:s.close()
      import socket
    
      target_host = 'www.google.com'
      target_port= 80
    
      # 建立一個 socket 物件(引數 AF_INET 表示標準 IPv4 地址或主機名,SOCK_STREAM 表示 TCP 客戶端)
      client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
      # 將客戶端連線到伺服器
      client.connect((target_host, target_port))
    
      # 向伺服器傳送資料
      client.send("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
    
      # 接收返回的資料
      response = client.recv(4096)
    
      # 列印返回資料
      print(response)
    
  • UDP Client 流程:
    • 因為 UDP 是一個無連線狀態的傳輸協議,所以不需要在此之前呼叫 connect() 函式。
      import socket
    
      target_host = '127.0.0.1'
      target_port= 80
    
      # 建立一個 socket 物件(引數 SOCK_DGRAM 表示 UDP 客戶端)
      client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
      # 向伺服器傳送資料
      client.sendto("AAABBBCCC", (target_host, target_port))
    
      # 接收返回的資料
      data, addr = client.recvfrom(4096)
    
      # 列印返回資料
      print(data)