9.11. Socket 物件¶
傳輸層的 Python 介面是 socket.socket 類別。一個 socket 代表一次網路對話的其中一個端點——一個位址、一個連接埠,以及該對話所使用的協定(UDP 或 TCP)。硬體控制章節開啟了 UART 實例以在線路上通訊;本節則開啟 socket 實例以在網路上通訊。形式是相同的;只是底層的服務能力強大得多。
9.11.1. 建立 socket¶
有三個引數用來描述一個 socket:它說哪一種 位址族(address family)、它提供哪一種 socket 類型,以及它使用哪一種 協定。預設值涵蓋了本節其餘部分所使用的情況::
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # IPv4 UDP
應用在幾乎每種情況下都會在兩個常數之間做選擇:
AF_INET-- IPv4 位址(一個數字 IP 加上一個連接埠)。最常見的選擇。AF_INET6是 IPv6 的對應物。SOCK_STREAM-- 一個 TCP 連線,亦即可靠的位元組串流。SOCK_DGRAM是 UDP 的對應物。
第三個引數(協定)保持為零,這會根據前兩者挑選正確的預設值。完整的建構函式記載於 socket --- socket 模組。
9.11.2. socket 上的位址¶
socket 位址是一個 (host, port) 元組::
("192.168.1.50", 80)
("0.0.0.0", 8000)
host 是字串形式的 IP 位址。port 則是 連接埠 中所涵蓋的 16 位元整數。
有少數幾個 特殊 的 host 字串值得了解:
"0.0.0.0"表示「這台裝置上的每一個 IPv4 介面」。繫結到此位址的伺服器會接受傳往相機所持有的任何位址的連線。"127.0.0.1"是 localhost——傳往它的流量永遠不會離開該裝置。在測試時很有用。"255.255.255.255"是本地廣播位址。傳往它的 UDP 資料報會送往本地網段上的每一台裝置。
像 "example.com" 這樣的網域名稱在 socket 位址中 並非 有效的 host 字串。它們必須先被解析為 IP;名稱與 DNS 涵蓋了執行此操作的 getaddrinfo() 呼叫。
9.11.3. 兩種角色¶
socket 的生命週期取決於它位於對話的哪一側。一個 用戶端 socket 會呼叫 connect()(對 UDP 而言,則只是 sendto())以與已知的伺服器通訊。一個 伺服器 socket 會呼叫 bind() 以宣告佔用一個連接埠,接著呼叫 listen() 與 accept()(對 TCP 而言)或 recvfrom()(對 UDP 而言)以接收進來的流量。
兩種情況都使用相同的 socket 建構函式;只有之後所呼叫的方法不同。接下來的三個頁面逐一介紹實際的模式:
UDP socket -- 傳送與接收資料報。
TCP socket -- TCP 用戶端與伺服器。
使用 asyncio 的 Socket -- 以上的全部,但置於
asyncio事件迴圈之中。
9.11.4. 關閉 socket¶
每個 socket 都持有一小部分作業系統狀態(一個連接埠保留、緩衝區、連線的 TCP 狀態)。當應用使用完畢時,close() 會釋放該狀態。一個被遺忘的 socket 是一個會逐漸累積的緩慢洩漏;在一個會開啟連線的迴圈中,漏掉一個 close 最終會耗盡相機可用 socket 的資源池。
最乾淨的模式是 with 陳述式::
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(addr)
s.send(b"GET / HTTP/1.0\r\n\r\n")
...
# socket is closed automatically here, even on error
Socket 實作了 Python 概覽中所涵蓋的情境管理器協定,因此 with 區塊保證無論該區塊是正常結束還是因引發例外而結束,close() 都會被呼叫。
9.11.5. socket 參考¶
本頁與接下來的頁面以敘述的形式逐一介紹該 API。關於每個方法、每個旗標以及該模組所公開的每個常數的完整引數層級參考,請參閱 socket --- socket 模組。該參考也是查閱本節未涵蓋的較不常見操作(socket 選項、多播群組成員資格、IPv6 範圍 ID)的地方。