9.11. 套接字对象

传输层的 Python 接口是 socket.socket 类。一个套接字代表一次网络会话的一个端点——一个地址、一个端口,以及该会话所基于的协议(UDP 或 TCP)。硬件控制章节打开 UART 实例来在导线上通信;本节则打开 socket 实例来在网络上通信。形态是相同的;只是底层服务的能力要强大得多。

9.11.1. 创建套接字

描述一个套接字需要三个参数:它使用哪种地址族、提供哪种套接字类型,以及使用哪种协议。其默认值涵盖了本节其余部分所用到的情形:

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. 套接字上的地址

套接字地址是一个 (host, port) 元组:

("192.168.1.50", 80)
("0.0.0.0", 8000)

host 是以字符串形式表示的 IP 地址。port 是 端口 中介绍的 16 位整数。

有少数几个特殊的主机字符串值得了解:

  • "0.0.0.0" 表示“本设备上的每一个 IPv4 接口”。绑定到该地址的服务器会在摄像头所持有的任意地址上接受连接。

  • "127.0.0.1"本地回环(localhost)——发往它的流量永远不会离开本设备。在测试时很有用。

  • "255.255.255.255" 是本地广播地址。发往它的 UDP 数据报会发送给本地网段上的每一个设备。

诸如 "example.com" 这样的域名在套接字地址中不是有效的主机字符串。它们必须先被解析为 IP;名称与 DNS 介绍了完成此事的 getaddrinfo() 调用。

9.11.3. 两种角色

套接字的生命周期取决于它处于会话的哪一侧。客户端套接字调用 connect()(对 UDP 而言,则只需 sendto())来与一个已知的服务器通信。服务器套接字调用 bind() 来占用一个端口,然后通过 listen()accept()(用于 TCP)或 recvfrom()(用于 UDP)来接收传入的流量。

两种情形使用的都是同一个 socket 构造函数;只是此后所调用的方法不同。接下来的三个页面将逐一讲解实际的模式:

9.11.4. 关闭套接字

每个套接字都持有一小块操作系统状态(一个端口预留、若干缓冲区、该连接的 TCP 状态)。当应用程序用完它后,close() 会释放这块状态。被遗忘的套接字是一种会日积月累的缓慢泄漏;在一个不断打开连接的循环里,遗漏 close 最终会耗尽摄像头可用套接字的资源池。

最简洁的模式是使用 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

套接字实现了 Python 概览中介绍的上下文管理器协议,因此 with 代码块保证无论该代码块是正常退出还是因抛出异常而退出,都会调用 close()

9.11.5. socket 参考

本页和接下来的几页以叙述的形式逐一讲解该 API。如需该模块所暴露的每个方法、每个标志和每个常量的完整参数级参考,请参阅 socket --- socket 模块。该参考也是查阅本节未涉及的较不常见操作(套接字选项、多播组成员、IPv6 作用域 ID)的地方。