C語言中文網 目錄

socket發送和接受數據(基于UDP協議)詳解

< 上一頁UDP協議是什么 UDP多點廣播下一頁 >

程序在創建 socket 時,可通過 type 參數指定該 socket 的類型,如果將該參數指定為 SOCK_DGRAM,則意味著創建基于 UDP 協議的 socket。

在創建了基于UDP 協議的 socket 之后,程序可以通過如下兩個方法來發送和接收數據:
  • socket.sendto(bytes, address):將 bytes 數據發送到 address 地址。
  • socket.recvfrom(bufsize[, flags]):接收數據。該方法可以同時返回 socket 中的數據和數據來源地址。

從這兩個方法的介紹可以看出,使用 UDP 協議的 socket 在發送數據時必須使用 sendto() 方法,這是因為程序必須指定發送數據的目標地址(通過 address 參數指定);使用 UDP 協議的 socket 在接收數據時,既可使用普通的 recv() 方法,也可使用 recvfrom() 方法。如果程序需要得到數據報的來源,則應該使用 recvfrom() 方法。

從上面的介紹不難看出,由于 UDP 協議沒有建立虛擬鏈路,因此程序使用 socket 發送數據報時,scoket 并不知道將該數據報發送到哪里,必須通過 sendto() 方法的 address 參數來指定數據報的目的地,這個目的地的地址會被附加在所發送的數據報上。

就像碼頭并不知道每個集裝箱的目的地一樣,碼頭只是將這些集裝箱發送出去,而集裝箱本身包含了該集裝箱的目的地。

程序在使用 UDP 協議進行網絡通信時,實際上并沒有明顯的服務器端和客戶端,因為雙方都需要先建立一個 socket 對象,用來接收或發送數據報。但在實際編程中,通常具有固定 IP 地址和端口的 socket 對象所在的程序被稱為服務器,因此該 socket 應該調用 bind() 方法被綁定到指定 IP 地址和端口,這樣其他 socket(客戶端 socket)才可向服務器端 socket(綁定了固定 IP 地址和端口的 socket)發送數據報,而服務器端 socket 就可以接收這些客戶端數據報。

當服務器端(也可以是客戶端)接收到一個數據報后,如果想向該數據報的發送者“反饋”一些信息,此時就必須獲取數據報的“來源信息”,這就到了 recvfrom() 方法“閃亮登場”的時候,該方法不僅可以獲取 socket 中的數據,也可以獲取數據的來源地址,程序就可以通過該來源地址來“反饋”信息。

一般來說,服務器端 socket 的 IP 地址和端口應該是固定的,因此客戶端程序可以直接向服務器端 socket 發送數據,但服務器端無法預先知道各客戶端 socket 的 IP 地址和端口,因此必須調用 recvfrom() 方法來獲取客戶端 socket 的 IP 地址和端口。

下面程序使用 UDP 協議的 socket 實現了 C/S 結構的網絡通信。本程序的服務器端通過循環 1000 次來讀取 socket 中的數據報,每當讀取到內容之后,便向該數據報的發送者發送一條信息。服務器端程序的代碼如下:
import socket

PORT = 30000;
# 定義每個數據報的大小最大為4KB
DATA_LEN = 4096;
# 定義一個字符串數組,服務器端發送該數組的元素
books = ("瘋狂Python講義",
    "瘋狂Kotlin講義",
    "瘋狂Android講義",
    "瘋狂Swift講義")
# 通過type屬性指定創建基于UDP協議的socket
s = socket.socket(type=socket.SOCK_DGRAM)
# 將該socket綁定到本機的指定IP和端口
s.bind(('192.168.1.88', PORT))
# 采用循環接收數據
for i in range(1000):
    # 讀取s中的數據的數據的發送地址
    data, addr = s.recvfrom(DATA_LEN)
    # 將接收到的內容轉換成字符串后輸出
    print(data.decode('utf-8'))
    # 從字符串數組中取出一個元素作為發送數據
    send_data = books[i % 4].encode('utf-8')
    # 將數據報發送給addr地址
    s.sendto(send_data, addr)
s.close()
上面程序中的代碼就是使用 UDP 協議的 socket 發送和接收數據報的關鍵代碼,該程序可以接收 1000 個客戶端發送過來的數據。

客戶端程序的代碼與此類似,客戶端采用循環不斷地讀取用戶的鍵盤輸入內容,每當讀取到用戶輸入的內容后,就將該內容通過數據報發送出去;接下來再讀取來自 socket 中的信息(也就是來自服務器端的數據)??蛻舳顺绦虻拇a如下:
import socket

PORT = 30000;
# 定義每個數據報的大小最大為4KB
DATA_LEN = 4096;
DEST_IP = "192.168.1.88";
# 通過type屬性指定創建基于UDP協議的socket
s = socket.socket(type=socket.SOCK_DGRAM)
# 不斷地讀取鍵盤輸入
while True:
    line = input('')
    if line is None or line == 'exit':
        break
    data = line.encode('utf-8')
    # 發送數據報
    s.sendto(data, (DEST_IP, PORT))
    # 讀取socket中的數據
    data = s.recv(DATA_LEN)
    print(data.decode('utf-8'))
s.close()
上面程序中的代碼就是使用 UDP 協議的 socket 發送和接收數據報的關鍵代碼,這些代碼與服務器端程序的代碼基本相似。而客戶端與服務器端的唯一區別在于,服務器端的 IP 地址和端口是固定的,所以客戶端可以直接將數據報發送給服務器端;而服務器端則需要根據所接收到的數據報來決定“反饋”數據報的目的地。

讀者可能會發現,在使用 UDP 協議進行網絡通信時,服務器端無須也無法保存每個客戶端的狀態,客戶端把數據報發送到服務器端后,完全有可能立即退出。但不管客戶端是否退出,服務器端都無法知道客戶端的狀態。

當使用 UDP 協議時,如果想讓一個客戶端發送的聊天信息被轉發到其他所有的客戶端也是可以的,程序可以考慮在服務器端使用 list 列表來保存所有的客戶端信息,每當接收到一個客戶端的數據報之后,程序檢查該數據報的來源地址是否在 list 列表中,如果不在就將該來源地址添加到 list 列表中。這樣又涉及一個問題,可能有些客戶端發送一個數據報之后,永久地退出了程序,但服務器端依然會將該客戶端的 IP 地址和端口保存在 list 列表中。

因此,程序還需要定義一個定時器,定期檢查每個客戶端有多長時間沒向服務器端發送數據了,如果超過一定的時間(比如 10 分鐘)該客戶端還沒有發送數據,則服務器端就將該客戶端的 IP 地址和端口從 list 列表中刪除……總之,這種方式需要處理的問題比較多,編程比較煩瑣。幸好 UDP 協議還支持多點廣播,Python 也為 UDP 協議的多點廣播提供了支持。
< 上一頁UDP協議是什么 UDP多點廣播下一頁 >

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo