HOWTO · Python
Python 中的套接字程式設計:初學者指南
本文討論 Python 中的套接字程式設計。我們已經討論了使用 Python 中的套接字程式設計使用 TCP 協議和 UDP 協議進行通訊。
本頁內容
Python 的 socket 程式設計可讓兩個程序透過稱為套接字的通訊端點交換資料。本指南使用標準程式庫中的 socket 模組,建立本機 TCP 和 UDP 用戶端/伺服器通訊範例,並說明各協定適用的情境。無須安裝額外套件。
Python Socket 程式設計:概覽與設定
伺服器會等待用戶端;用戶端則會主動向伺服器發起通訊。在以下範例中,兩者都在同一台電腦上執行,並使用 127.0.0.1 和相同的連接埠。socket.socket() 建構函式會建立套接字。TCP 使用 socket.SOCK_STREAM,UDP 則使用 socket.SOCK_DGRAM。傳送前必須先將文字編碼為位元組,接收後再解碼。
請在同一個終端機中,將下方每個完整程式碼區塊各執行一次。每個指令碼會先在背景執行緒啟動伺服器,再由同一個指令碼充當用戶端,透過迴環介面交換一則訊息,最後關閉套接字。
建立 TCP 伺服器和用戶端
TCP 使用面向連線、可靠且有序的位元組串流。伺服器會繫結至位址和連接埠、呼叫 listen(),再等待 accept() 接受連線。用戶端則連線至相同的位址和連接埠。連線建立後,兩端都能傳送和接收位元組。sendall() 會傳送提供的緩衝區中的所有位元組,而 recv(1024) 最多會讀取 1,024 個位元組。TCP 讀取操作不會標示應用程式訊息的邊界,因此較大型的通訊協定必須自行定義訊息框架,例如使用長度前綴或分隔符號。
執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
import socket
import threading
HOST = "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
probe.bind((HOST, 0))
PORT = probe.getsockname()[1]
ready = threading.Event()
def serve():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((HOST, PORT))
server.listen(1)
ready.set()
connection, _ = server.accept()
with connection:
message = connection.recv(1024).decode("utf-8")
connection.sendall(("Received: " + message).encode("utf-8"))
thread = threading.Thread(target=serve)
thread.start()
ready.wait()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
client.connect((HOST, PORT))
client.sendall(b"Hello over TCP")
reply = client.recv(1024).decode("utf-8")
thread.join()
print(reply)
作業系統會指派可用的本機連接埠,讓範例不必使用固定連接埠,也能避免連接埠衝突。背景執行緒讓伺服器可以在用戶端連線時接受連線;交換完成後,兩個套接字都會由各自的情境管理器關閉。
建立 UDP 伺服器和用戶端
UDP 會傳送彼此獨立的資料包,不會建立 TCP 式的連線。伺服器會繫結至位址,並透過 recvfrom() 等待資料;此方法會同時傳回收到的位元組和傳送端位址。用戶端會使用伺服器位址呼叫 sendto() 傳送資料,再透過 recvfrom() 接收回覆及傳送端位址。以下本機範例使用相同的迴環位址,並讓作業系統自動選擇伺服器連接埠。
執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
import socket
import threading
HOST = "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
probe.bind((HOST, 0))
PORT = probe.getsockname()[1]
ready = threading.Event()
def serve():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server:
server.bind((HOST, PORT))
ready.set()
message, client_address = server.recvfrom(1024)
reply = ("Received: " + message.decode("utf-8")).encode("utf-8")
server.sendto(reply, client_address)
thread = threading.Thread(target=serve)
thread.start()
ready.wait()
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client:
client.sendto(b"Hello over UDP", (HOST, PORT))
reply, server_address = client.recvfrom(1024)
thread.join()
print(reply.decode("utf-8"))
資料包會傳送至指定的目的地位址和連接埠,但 UDP 不保證資料包一定抵達,也不保證多個資料包依序抵達。範例會先將訊息位元組解碼,再建立回覆,因此回覆中呈現的是可讀文字,而不是 Python 的位元組表示形式。
如何選擇 TCP 或 UDP
當應用程式需要有序的位元組串流,以及傳輸層的傳遞與重傳行為時,請選擇 TCP。應用程式仍須處理連線失敗並定義訊息框架;TCP 不保證對端應用程式會處理收到的訊息。若工作適合使用彼此獨立的資料包,而且應用程式可以容忍資料遺失或順序錯亂,或能自行處理這些情況,便可選擇 UDP。UDP 並非天生較快:效能取決於網路、工作負載,以及應用程式所需的可靠性功能。只要套接字和網路設定允許,UDP 可以用於廣播;但它不能普遍取代可靠的用戶端/伺服器通訊。
結論
socket 模組提供兩種協定所需的基本工具。TCP 範例使用 SOCK_STREAM、accept()、sendall() 和 recv(),透過連線交換資料;UDP 範例則使用 SOCK_DGRAM、sendto() 和 recvfrom(),交換指定目的地的資料包。先從本機用戶端和伺服器開始,再依照應用程式所需的資料傳遞行為選擇 TCP 或 UDP。