Python Socket 编程全解析
1. 协程与线程服务器响应时间对比
在特定环境下(如双核心 2 GHz 的 MacBook),基于协程的服务器和基于线程的服务器在响应时间上存在明显差异。经过 1000 次请求的测试,基于协程的服务器平均响应时间约为 1ms,而基于线程的服务器平均响应时间约为 5ms。
这一差异的原因在于,基于协程的代码一旦检测到连接就能够立即响应,而基于线程的服务器需要等待操作系统的调度才能运行。当存在 CPU 密集型线程和 Python 全局解释器锁时,服务器可能会被延迟,直到 CPU 密集型线程超出其分配的时间片。在许多系统中,时间片约为 10ms,这与上述线程响应时间的粗略测量结果相符。
2. 轮询的优缺点
轮询虽然可以降低响应时间,但如果过于频繁,会引入显著的开销。例如,在某个示例中,使用轮询的程序虽然响应时间较低,但运行完成所需的时间比不使用轮询的程序长 50% 以上。如果将代码修改为每间隔一定条件(如每六组操作)进行一次轮询,响应时间会略微增加到 1.2ms,而程序的运行时间仅比不使用轮询的程序长 3%。然而,通常很难明确知道轮询的最佳频率,往往需要对应用程序进行测量。
3. 实现并发的问题
尽管改进后的响应时间看起来是一个优势,但实现自己的并发仍然存在严重问题。例如,在执行任何阻塞操作时,任务需要特别小心。以 Web 服务器为例,当代码中存在打开并读取文件数据的操作时,整个程序可能会被冻结,尤其是在文件访问涉及磁盘寻道时,可能会导致长时间的延迟。
解决这个问题的方法是实现异步文件访问,并将其作为调度器的一个特性。对于更复杂的操作,如执行数据库查询,以异步方式完成工作变得相当复杂。一种可行的方法是在单独的线程中执行工作,并在结果可用时将其传达回任务调度器,这可以通过谨慎使用消息队列来实现。在某些系统中,存在用于异步 I/O 的底层系统调用(如 UNIX 上的 aio_* 系列函数),但截至目前,Python 库尚未提供对这些函数的访问,不过可以通过第三方模块找到相关绑定。根据经验,使用这些功能比看起来要复杂得多,而且引入的额外复杂性可能并不值得,通常让线程库处理这些问题会更好。
4. Python Socket 模块概述
4.1 模块简介
Python 的 socket 模块提供了对标准 BSD 套接字接口的访问。尽管它基于 UNIX,但该模块在所有平台上都可用。套接字接口设计通用,能够支持多种网络协议,如 Internet、TIPC、蓝牙等。其中,最常见的协议是 Internet 协议(IP),包括 TCP 和 UDP。Python 支持 IPv4 和 IPv6,但 IPv4 更为常见。
需要注意的是,该模块相对底层,直接提供了对操作系统网络功能的访问。如果编写网络应用程序,使用其他模块(如第 22 章描述的模块或本章末尾描述的 SocketServer 模块)可能会更简单。
4.2 地址族(Address Families)
部分套接字函数需要指定地址族,地址族指定了所使用的网络协议。以下是为该目的定义的常量:
| 常量 | 描述 |
| ---- | ---- |
| AF_BLUETOOTH | 蓝牙协议 |
| AF_INET | IPv4 协议(TCP、UDP) |
| AF_INET6 | IPv6 协议(TCP、UDP) |
| AF_NETLINK | Netlink 进程间通信 |
| AF_PACKET | 链路层数据包 |
| AF_TIPC | 透明进程间通信协议 |
| AF_UNIX | UNIX 域协议 |
其中,AF_INET 和 AF_INET6 最为常用,因为它们代表标准的 Internet 连接。AF_BLUETOOTH 仅在支持它的系统(通常是嵌入式系统)上可用。AF_NETLINK、AF_PACKET 和 AF_TIPC 仅在 Linux 上受支持。AF_NETLINK 用于用户应用程序和 Linux 内核之间的快速进程间通信,AF_PACKET 用于直接在数据链路层工作(如原始以太网数据包),AF_TIPC 是用于 Linux 集群的高性能进程间通信协议。
4.3 套接字类型(Socket Types)
部分套接字函数还需要指定套接字类型,套接字类型指定了在给定协议族内使用的通信类型(流或数据包)。以下是为该目的使用的常量:
| 常量 | 描述 |
| ---- | ---- |
| SOCK_STREAM | 可靠的面向连接的字节流(TCP) |
| SOCK_DGRAM | 数据报(UDP) |
| SOCK_RAW | 原始套接字 |
| SOCK_RDM | 可靠的数据报 |
| SOCK_SEQPACKET | 按顺序的连接模式记录传输 |
最常见的套接字类型是 SOCK_STREAM 和 SOCK_DGRAM,它们分别对应 Internet 协议套件中的 TCP 和 UDP。SOCK_RDM 是 UDP 的一种可靠形式,保证数据报的传递,但不保证顺序。SOCK_SEQPACKET 用于通过面向流的连接发送数据包,同时保留其顺序和数据包边界。不过,SOCK_RDM 和 SOCK_SEQPACKET 并不广泛支持,如果关注可移植性,最好避免使用它们。SOCK_RAW 用于提供对原始协议的底层访问,通常用于执行特殊用途的操作,如发送控制消息(如 ICMP 消息),使用 SOCK_RAW 通常需要超级用户或管理员权限。
并非每个协议族都支持所有的套接字类型。例如,在 Linux 上使用 AF_PACKET 嗅探以太网数据包时,不能使用 SOCK_STREAM 建立面向流的连接,而必须使用 SOCK_DGRAM 或 SOCK_RAW。对于 AF_NETLINK 套接字,SOCK_RAW 是唯一支持的类型。
5. 套接字寻址
为了在套接字上进行通信,必须指定目标地址,地址的形式取决于套接字的地址族。
5.1 AF_INET(IPv4)
对于使用 IPv4 的 Internet 应用程序,地址以元组 (host, port) 的形式指定。例如:
('www.python.org', 80)
('66.113.130.182', 25)
如果 host 为空字符串,它与 INADDR_ANY 具有相同的含义,表示任何地址,这通常用于服务器创建任何客户端都可以连接的套接字。如果 host 设置为 ‘ ‘,它与套接字 API 中的 INADDR_BROADCAST 常量具有相同的含义。
需要注意的是,当使用主机名(如 ‘www.python.org’)时,Python 使用 DNS 将主机名解析为 IP 地址。根据 DNS 的配置,每次解析可能会得到不同的 IP 地址。如果需要避免这种情况,可以使用原始 IP 地址(如 ‘66.113.130.182’)。
5.2 AF_INET6(IPv6)
对于 IPv6,地址以 4 元组 (host, port, flowinfo, scopeid) 的形式指定。其中,host 和 port 组件的工作方式与 IPv4 相同,但 IPv6 主机地址的数字形式通常由八个冒号分隔的十六进制数字组成,例如 ‘FEDC:BA98:7654:3210:FEDC:BA98:7654:3210’ 或 ‘080A::4:1’(双冒号表示用 0 填充地址组件的范围)。
flowinfo 参数是一个 32 位的数字,由 24 位的流标签(低 24 位)、4 位的优先级(接下来的 4 位)和 4 位的保留位(高 4 位)组成。流标签通常在发送方希望启用路由器的特殊处理时使用,否则,flowinfo 设置为 0。
scopeid 参数是一个 32 位的数字,仅在处理链路本地和站点本地地址时需要。链路本地地址始终以 ‘FE80:…’ 开头,用于同一局域网内的机器之间(路由器不会转发链路本地数据包),此时 scopeid 是一个接口索引,用于标识主机上的特定网络接口,可以使用 UNIX 上的 ‘ifconfig’ 命令或 Windows 上的 ‘ipv6 if’ 命令查看。站点本地地址始终以 ‘FEC0:…’ 开头,用于同一站点内的机器之间(例如,给定子网内的所有机器),此时 scopeid 是一个站点标识符数字。
如果没有提供 flowinfo 或 scopeid 的数据,IPv6 地址可以像 IPv4 一样以元组 (host, port) 的形式给出。
5.3 AF_UNIX
对于 UNIX 域套接字,地址是一个包含路径名的字符串,例如 ‘/tmp/myserver’。
5.4 AF_PACKET
对于 Linux 数据包协议,地址是一个元组 (device, protonum [, pkttype [, hatype [, addr]]]),其中 device 是一个指定设备名称的字符串(如 “eth0”),protonum 是一个整数,指定以太网协议编号(如
头文件中定义的 0x0800 表示 IP 数据包)。pkttype 是一个整数,指定数据包类型,取值为以下常量之一:
| 常量 | 描述 |
| ---- | ---- |
| PACKET_HOST | 数据包地址为本地主机 |
| PACKET_BROADCAST | 物理层广播数据包 |
| PACKET_MULTICAST | 物理层多播 |
| PACKET_OTHERHOST | 目标为不同主机的数据包,但被混杂模式的设备驱动程序捕获 |
| PACKET_OUTGOING | 源自本机但回环到数据包套接字的数据包 |
hatype 是一个整数,指定 ARP 协议中使用的硬件地址类型(如 头文件中定义的),addr 是一个字节字符串,包含硬件地址,其结构取决于 hatype 的值。对于以太网,addr 将是一个 6 字节的字符串,包含硬件地址。
5.5 AF_NETLINK
对于 Linux Netlink 协议,地址是一个元组 (pid, groups),其中 pid 和 groups 都是无符号整数。pid 是套接字的单播地址,通常与创建套接字的进程的进程 ID 相同,或者为 0 表示内核。groups 是一个位掩码,用于指定要加入的多播组。更多信息请参考 Netlink 文档。
5.6 AF_BLUETOOTH
蓝牙地址取决于所使用的协议。对于 L2CAP,地址是一个元组 (addr, psm),其中 addr 是一个字符串(如 ‘01:23:45:67:89:ab’),psm 是一个无符号整数。对于 RFCOMM,地址是一个元组 (addr, channel),其中 addr 是一个地址字符串,channel 是一个整数。对于 HCI,地址是一个 1 元组 (deviceno,),其中 deviceno 是一个整数设备编号。对于 SCO,地址是一个字符串 host。
常量 BDADDR_ANY 表示任何地址,是一个字符串 ‘00:00:00:00:00:00’,常量 BDADDR_LOCAL 是一个字符串 ‘00:00:00:ff:ff:ff’。
5.7 AF_TIPC
对于 TIPC 套接字,地址是一个元组 (addr_type, v1, v2, v3 [, scope]),其中所有字段都是无符号整数。addr_type 取值为以下之一,它也决定了 v1、v2 和 v3 的值:
| 地址类型 | 描述 |
| ---- | ---- |
| TIPC_ADDR_NAMESEQ | v1 是服务器类型,v2 是端口标识符,v3 是 0 |
| TIPC_ADDR_NAME | v1 是服务器类型,v2 是较低的端口号,v3 是较高的端口号 |
| TIPC_ADDR_ID | v1 是节点,v2 是引用,v3 是 0 |
可选的 scope 字段取值为 TIPC_ZONE_SCOPE、TIPC_CLUSTER_SCOPE 或 TIPC_NODE_SCOPE 之一。
6. 套接字模块的函数
6.1 create_connection(address [, timeout])
该函数用于建立一个 SOCK_STREAM 连接到指定地址,并返回一个已连接的套接字对象。address 是一个元组 (host, port),timeout 是一个可选的超时时间。该函数首先调用 getaddrinfo() 函数,然后尝试连接到返回的每个元组。
6.2 fromfd(fd, family, socktype [, proto])
该函数从一个整数文件描述符 fd 创建一个套接字对象。地址族、套接字类型和协议编号与 socket() 函数相同。文件描述符必须引用一个先前创建的套接字,它返回一个 SocketType 实例。
6.3 getaddrinfo(host, port [,family [, socktype [, proto [, flags]]]])
该函数根据主机和端口信息返回一个包含打开套接字连接所需信息的元组列表。host 是一个包含主机名或数字 IP 地址的字符串,port 是一个数字或表示服务名称的字符串(如 “http”、”ftp”、”smtp”)。每个返回的元组由五个元素组成 (family, socktype, proto, canonname, sockaddr),其中 family、socktype 和 proto 的值与传递给 socket() 函数的值相同,canonname 是一个表示主机规范名称的字符串,sockaddr 是一个包含套接字地址的元组,如前面关于 Internet 地址部分所述。
例如:
>>> getaddrinfo("www.python.org",80)
[(2,2,17,'',('194.109.137.226',80)), (2,1,6,'',('194.109.137.226',80))]
在这个例子中,getaddrinfo() 返回了两个可能的套接字连接信息,第一个(proto=17)是 UDP 连接,第二个(proto=6)是 TCP 连接。getaddrinfo() 的额外参数可以用于缩小选择范围,例如:
>>> getaddrinfo("www.python.org",80,AF_INET,SOCK_STREAM)
[(2,1,6,'',('194.109.137.226',80))]
特殊常量 AF_UNSPEC 可以用于地址族,以查找任何类型的连接,例如:
>>> getaddrinfo("www.python.org","http", AF_UNSPEC, SOCK_STREAM)
[(2,1,6,'',('194.109.137.226',80))]
getaddrinfo() 函数具有非常通用的用途,适用于所有支持的网络协议(IPv4、IPv6 等)。如果关注兼容性和支持未来协议,特别是如果打算支持 IPv6,建议使用该函数。
6.4 getdefaulttimeout()
该函数返回默认的套接字超时时间(以秒为单位)。如果返回值为 None,表示没有设置超时时间。
6.5 getfqdn([name])
该函数返回指定名称的完全限定域名。如果省略 name,则默认使用本地机器。例如,getfqdn(“foo”) 可能返回 “foo.quasievil.org”。
6.6 gethostbyname(hostname)
该函数将主机名(如 ‘www.python.org’)转换为 IPv4 地址,并以字符串形式返回,如 ‘132.151.1.90’。该函数不支持 IPv6。
6.7 gethostbyname_ex(hostname)
该函数将主机名转换为 IPv4 地址,但返回一个元组 (hostname, aliaslist, ipaddrlist),其中 hostname 是主要主机名,aliaslist 是同一地址的替代主机名列表,ipaddrlist 是同一主机上同一接口的 IPv4 地址列表。例如:
>>> gethostbyname_ex('www.python.org')
('fang.python.org', ['www.python.org'], ['194.109.137.226'])
该函数不支持 IPv6。
6.8 gethostname()
该函数返回本地机器的主机名。
6.9 gethostbyaddr(ip_address)
该函数根据 IP 地址返回与 gethostbyname_ex() 相同的信息。如果 ip_address 是 IPv6 地址(如 ‘FEDC:BA98:7654:3210:FEDC:BA98:7654:3210’),则返回有关 IPv6 的信息。
6.10 getnameinfo(address, flags)
该函数根据套接字地址将地址转换为一个 2 元组 (host, port),具体取决于 flags 的值。address 是一个指定地址的元组(如 (‘www.python.org’,80)),flags 是以下常量的按位或:
| 常量 | 描述 |
| ---- | ---- |
| NI_NOFQDN | 不使用本地主机的完全限定名称 |
| NI_NUMERICHOST | 以数字形式返回地址 |
| NI_NAMEREQD | 需要主机名,如果地址没有 DNS 条目则返回错误 |
| NI_NUMERICSERV | 返回的端口以包含端口号的字符串形式返回 |
| NI_DGRAM | 指定查找的服务是数据报服务(UDP)而不是 TCP(默认) |
该函数的主要目的是获取有关地址的额外信息。例如:
>>> getnameinfo(('194.109.137.226',80),0)
('fang.python.org', 'http')
>>> getnameinfo(('194.109.137.226',80),NI_NUMERICSERV)
('fang.python.org','80')
6.11 getprotobyname(protocolname)
该函数将 Internet 协议名称(如 ‘icmp’)转换为一个可以传递给 socket() 函数第三个参数的协议编号(如 IPPROTO_ICMP 的值)。如果协议名称未被识别,则引发 socket.error 异常。通常,该函数仅用于原始套接字。
6.12 getservbyname(servicename [, protocolname])
该函数将 Internet 服务名称和协议名称转换为该服务的端口号。例如:
>>> getservbyname('ftp', 'tcp')
21
协议名称(如果提供)应该是 ‘tcp’ 或 ‘udp’。如果 servicename 与任何已知服务不匹配,则引发 socket.error 异常。
6.13 getservbyport(port [, protocolname])
该函数是 getservbyname() 的逆函数,根据数字端口号返回服务名称(如果有)。例如:
>>> getservbyport(21, 'tcp')
'ftp'
协议名称(如果提供)应该是 ‘tcp’ 或 ‘udp’。如果端口号没有对应的服务名称,则引发 socket.error 异常。
6.14 has_ipv6
这是一个布尔常量,如果支持 IPv6,则为 True。
6.15 htonl(x)
该函数将 32 位整数从主机字节序转换为网络字节序(大端字节序)。
6.16 htons(x)
该函数将 16 位整数从主机字节序转换为网络字节序(大端字节序)。
6.17 inet_aton(ip_string)
该函数将以字符串形式提供的 IPv4 地址(如 ‘135.128.11.209’)转换为 32 位的打包二进制格式,用于地址的原始编码。返回值是一个包含二进制编码的四字符字符串。该函数不支持 IPv6。
6.18 inet_ntoa(packedip)
该函数将二进制打包的 IPv4 地址转换为使用标准点分表示法的字符串(如 ‘135.128.11.209’)。packedip 是一个包含 IP 地址原始 32 位编码的四字符字符串。该函数不支持 IPv6。
6.19 inet_ntop(address_family, packed_ip)
该函数将表示 IP 网络地址的打包二进制字符串 packed_ip 转换为字符串(如 ‘123.45.67.89’)。address_family 是地址族,通常是 AF_INET 或 AF_INET6。该函数可用于从原始字节缓冲区(如底层网络数据包的内容)获取网络地址字符串。
6.20 inet_pton(address_family, ip_string)
该函数将 IP 地址(如 ‘123.45.67.89’)转换为打包字节字符串。address_family 是地址族,通常是 AF_INET 或 AF_INET6。该函数可用于将网络地址编码为原始二进制数据包。
6.21 ntohl(x)
该函数将 32 位整数从网络字节序(大端字节序)转换为主机字节序。
6.22 ntohs(x)
该函数将 16 位整数从网络字节序(大端字节序)转换为主机字节序。
6.23 setdefaulttimeout(timeout)
该函数设置新创建的套接字对象的默认超时时间。timeout 是一个以秒为单位的浮点数,值为 None 表示没有设置超时时间(默认值)。
6.24 socket(family, type [, proto])
该函数使用给定的地址族、套接字类型和协议编号创建一个新的套接字。family 是地址族,type 是套接字类型,如前面所述。要打开一个 TCP 连接,可以使用 socket(AF_INET, SOCK_STREAM);要打开一个 UDP 连接,可以使用 socket(AF_INET, SOCK_DGRAM)。该函数返回一个 SocketType 实例。
协议编号通常省略(默认值为 0),通常仅用于原始套接字(SOCK_RAW),并设置为一个取决于所使用地址族的常量。以下是 Python 可能为 AF_INET 和 AF_INET6 定义的所有协议编号:
| 常量 | 描述 |
| ---- | ---- |
| IPPROTO_AH | IPv6 认证头 |
| IPPROTO_BIP | Banyan VINES |
| IPPROTO_DSTOPTS | IPv6 目的选项 |
| IPPROTO_EGP | 外部网关协议 |
| IPPROTO_EON | ISO CNLP(无连接网络协议) |
| IPPROTO_ESP | IPv6 封装安全有效载荷 |
| IPPROTO_FRAGMENT | IPv6 分段头 |
| IPPROTO_GGP | 网关到网关协议(RFC823) |
| IPPROTO_GRE | 通用路由封装(RFC1701) |
| IPPROTO_HELLO | Fuzzball HELLO 协议 |
| IPPROTO_HOPOPTS | IPv6 逐跳选项 |
| IPPROTO_ICMP | IPv4 ICMP |
| IPPROTO_ICMPV6 | IPv6 ICMP |
| IPPROTO_IDP | XNS IDP |
| IPPROTO_IGMP | 组管理协议 |
| IPPROTO_IP | IPv4 |
| IPPROTO_IPCOMP | IP 有效载荷压缩协议 |
| IPPROTO_IPIP | IP 嵌套 IP |
| IPPROTO_IPV4 | IPv4 头 |
| IPPROTO_IPV6 | IPv6 头 |
| IPPROTO_MOBILE | IP 移动性 |
| IPPROTO_ND | 网络磁盘协议 |
| IPPROTO_NONE | IPv6 无下一个头 |
| IPPROTO_PIM | 协议无关多播 |
| IPPROTO_PUP | 施乐 PARC 通用数据包(PUP) |
| IPPROTO_RAW | 原始 IP 数据包 |
| IPPROTO_ROUTING | IPv6 路由头 |
| IPPROTO_RSVP | 资源预留 |
| IPPROTO_TCP | TCP |
| IPPROTO_TP | OSI 传输协议(TP - 4) |
| IPPROTO_UDP | UDP |
| IPPROTO_VRRP | 虚拟路由器冗余协议 |
| IPPROTO_XTP | 快速传输协议 |
以下是与 AF_BLUETOOTH 一起使用的协议编号:
| 常量 | 描述 |
| ---- | ---- |
| BTPROTO_L2CAP | 逻辑链路控制和适配协议 |
| BTPROTO_HCI | 主机/控制器接口 |
| BTPROTO_RFCOMM | 电缆替换协议 |
| BTPROTO_SCO | 同步面向连接链路 |
6.25 socketpair([family [, type [, proto ]]])
该函数使用给定的地址族、套接字类型和协议选项创建一对连接的套接字对象,这些选项的含义与 socket() 函数相同。该函数仅适用于 UNIX 域套接字(family = AF_UNIX),type 可以是 SOCK_DGRAM 或 SOCK_STREAM。如果 type 是 SOCK_STREAM,则创建一个称为流管道的对象。proto 通常为 0(默认值)。该函数的主要用途是在 os.fork() 创建的进程之间建立进程间通信。例如,父进程可以调用 socketpair() 创建一对套接字,然后调用 os.fork(),父进程和子进程可以使用这些套接字进行通信。
7. 套接字对象的方法
套接字由
SocketType
类型的实例表示,以下是套接字对象
s
可用的方法:
7.1 s.accept()
接受一个连接,并返回一个元组
(conn, address)
,其中
conn
是一个新的套接字对象,可用于在连接上发送和接收数据,
address
是连接另一端套接字的地址。
7.2 s.bind(address)
将套接字绑定到一个地址。地址的格式取决于地址族,在大多数情况下,它是一个
(hostname, port)
形式的元组。对于 IP 地址,空字符串表示
INADDR_ANY
,字符串
'<broadcast>'
表示
INADDR_BROADCAST
。
INADDR_ANY
主机名(空字符串)用于表示服务器允许在系统的任何 Internet 接口上进行连接,常用于多宿主服务器。
INADDR_BROADCAST
主机名(
'<broadcast>'
)用于套接字发送广播消息。
7.3 s.close()
关闭套接字。套接字在被垃圾回收时也会自动关闭。
7.4 s.connect(address)
连接到远程套接字的指定地址。地址的格式取决于地址族,通常是一个
(hostname, port)
元组。如果发生错误,会引发
socket.error
异常。如果连接到同一台计算机上的服务器,可以使用
'localhost'
作为主机名。
7.5 s.connect_ex(address)
与
connect(address)
类似,但成功时返回 0,失败时返回
errno
的值。
7.6 s.fileno()
返回套接字的文件描述符。
7.7 s.getpeername()
返回套接字所连接的远程地址。通常返回值是一个
(ipaddr, port)
元组,但这取决于所使用的地址族。并非所有系统都支持此方法。
7.8 s.getsockname()
返回套接字自身的地址。通常是一个
(ipaddr, port)
元组。
7.9 s.getsockopt(level, optname [, buflen])
返回套接字选项的值。
level
定义了选项的级别,可以是
SOL_SOCKET
表示套接字级别的选项,或者是一个协议编号(如
IPPROTO_IP
)表示与协议相关的选项。
optname
选择一个特定的选项。如果省略
buflen
,则假定是一个整数选项,并返回其整数值。如果提供了
buflen
,则指定用于接收选项的缓冲区的最大长度,该缓冲区以字节字符串的形式返回,需要调用者使用
struct
模块或其他方法来解码其内容。
以下是 Python 定义的套接字选项:
7.9.1 常用的
SOL_SOCKET
级别选项
| 选项名称 | 值 | 描述 |
|---|---|---|
| SO_ACCEPTCONN | 0, 1 | 确定套接字是否接受连接 |
| SO_BROADCAST | 0, 1 | 允许发送广播数据报 |
| SO_DEBUG | 0, 1 | 确定是否记录调试信息 |
| SO_DONTROUTE | 0, 1 | 绕过路由表查找 |
| SO_ERROR | int | 获取错误状态 |
| SO_EXCLUSIVEADDRUSE | 0,1 |
防止其他套接字强制绑定到相同的地址和端口,禁用
SO_REUSEADDR
选项
|
| SO_KEEPALIVE | 0, 1 | 定期探测连接的另一端,如果半开则终止连接 |
| SO_LINGER | linger |
如果发送缓冲区包含数据,在
close()
时停留。
linger
是一个包含两个 32 位整数
(onoff, seconds)
的打包二进制字符串
|
| SO_OOBINLINE | 0, 1 | 将带外数据放入输入队列 |
| SO_RCVBUF | int | 接收缓冲区的大小(以字节为单位) |
| SO_RCVLOWAT | int |
在
select()
将套接字返回为可读之前读取的字节数
|
| SO_RCVTIMEO | timeval |
接收调用的超时时间(以秒为单位)。
timeval
是一个包含两个 32 位无符号整数
(seconds, microseconds)
的打包二进制字符串
|
| SO_REUSEADDR | 0, 1 | 允许本地地址重用 |
| SO_REUSEPORT | 0, 1 | 允许多个进程绑定到相同的地址,只要所有进程都设置了此套接字选项 |
| SO_SNDBUF | int | 发送缓冲区的大小(以字节为单位) |
| SO_SNDLOWAT | int |
在
select()
将套接字返回为可写之前发送缓冲区中可用的字节数
|
| SO_SNDTIMEO | timeval |
发送调用的超时时间(以秒为单位),
timeval
描述同
SO_RCVTIMEO
|
| SO_TYPE | int | 获取套接字类型 |
| SO_USELOOPBACK | 0, 1 | 路由套接字获取其发送内容的副本 |
7.9.2
IPPROTO_IP
级别选项
| 选项名称 | 值 | 描述 |
|---|---|---|
| IP_ADD_MEMBERSHIP | ip_mreg |
加入多播组(仅设置)。
ip_mreg
是一个包含两个 32 位 IP 地址
(multiaddr, localaddr)
的打包二进制字符串,其中
multiaddr
是多播地址,
localaddr
是本地接口的 IP 地址
|
| IP_DROP_MEMBERSHIP | ip_mreg |
离开多播组(仅设置),
ip_mreg
同上
|
| IP_HDRINCL | int | 数据中包含 IP 头 |
| IP_MAX_MEMBERSHIPS | int | 最大多播组数量 |
| IP_MULTICAST_IF | in_addr |
外出接口。
in_addr
是一个包含 32 位 IP 地址的打包二进制字符串
|
| IP_MULTICAST_LOOP | 0,1 | 回环 |
| IP_MULTICAST_TTL | uint8 |
生存时间。
uint8
是一个包含 1 字节无符号字符的打包二进制字符串
|
| IP_OPTIONS | ipopts |
IP 头选项。
ipopts
是一个不超过 44 字节的打包二进制字符串,其内容在 RFC 791 中描述
|
| IP_RECVDSTADDR | 0,1 | 接收数据报时接收 IP 目的地址 |
| IP_RECVOPTS | 0,1 | 接收数据报时接收所有 IP 选项 |
| IP_RECVRETOPTS | 0,1 | 接收响应时接收 IP 选项 |
| IP_RETOPTS | 0,1 |
与
IP_RECVOPTS
相同,不处理选项,不填充时间戳或路由记录选项
|
| IP_TOS | int | 服务类型 |
| IP_TTL | int | 生存时间 |
7.9.3
IPPROTO_IPV6
级别选项
| 选项名称 | 值 | 描述 |
|---|---|---|
| IPV6_CHECKSUM | 0,1 | 让系统计算校验和 |
| IPV6_DONTFRAG | 0,1 | 如果数据包超过 MTU 大小,不进行分段 |
| IPV6_DSTOPTS | ip6_dest |
目的选项。
ip6_dest
是一个
(next, len, options)
形式的打包二进制字符串,其中
next
是一个 8 位整数,表示下一个头的选项类型;
len
是一个 8 位整数,指定头的长度(以 8 字节为单位,不包括前 8 字节);
options
是编码后的选项
|
| IPV6_HOPLIMIT | int | 跳数限制 |
| IPV6_HOPOPTS | ip6_hbh |
逐跳选项。
ip6_hbh
的编码与
ip6_dest
相同
|
| IPV6_JOIN_GROUP | ip6_mreq |
加入多播组。
ip6_mreq
是一个包含
(multiaddr, index)
的打包二进制字符串,其中
multiaddr
是 128 位的 IPv6 多播地址,
index
是本地接口的 32 位无符号整数接口索引
|
| IPV6_LEAVE_GROUP | ip6_mreq | 离开多播组 |
| IPV6_MULTICAST_HOPS | int | 多播数据包的跳数限制 |
| IPV6_MULTICAST_IF | int | 外出多播数据包的接口索引 |
| IPV6_MULTICAST_LOOP | 0,1 | 将外出多播数据包回送到本地应用程序 |
| IPV6_NEXTHOP | sockaddr_in6 |
设置外出数据包的下一跳地址。
sockaddr_in6
是一个包含 C
sockaddr_in6
结构的打包二进制字符串,通常在
<netinet/in.h>
中定义
|
| IPV6_PKTINFO | ip6_pktinfo |
数据包信息结构。
ip6_pktinfo
是一个包含
(addr, index)
的打包二进制字符串,其中
addr
是 128 位的 IPv6 地址,
index
是 32 位无符号整数的接口索引
|
| IPV6_RECVDSTOPTS | 0,1 | 接收目的选项 |
| IPV6_RECVHOPLIMIT | 0,1 | 接收跳数限制 |
| IPV6_RECVHOPOPTS | 0,1 | 接收逐跳选项 |
| IPV6_RECVPKTINFO | 0,1 | 接收数据包信息 |
| IPV6_RECVRTHDR | 0,1 | 接收路由头 |
| IPV6_RECVTCLASS | 0,1 | 接收流量类 |
| IPV6_RTHDR | ip6_rthdr |
路由头。
ip6_rthdr
是一个包含
(next, len, type, segleft, data)
的打包二进制字符串,其中
next
、
len
、
type
和
segleft
都是 8 位无符号整数,
data
是路由数据,详见 RFC 2460
|
| IPV6_RTHDRDSTOPTS | ip6_dest | 路由选项头之前的目的选项头 |
| IPV6_RECVPATHMTU | 0,1 |
启用接收
IPV6_PATHMTU
辅助数据项
|
| IPV6_TCLASS | int | 流量类 |
| IPV6_UNICAST_HOPS | int | 单播数据包的跳数限制 |
| IPV6_USE_MIN_MTU | -1,0,1 | 路径 MTU 发现。1 为所有目的地禁用, -1 仅为多播目的地禁用 |
| IPV6_V6ONLY | 0,1 | 仅连接到其他 IPv6 节点 |
7.9.4
SOL_TCP
级别选项
| 选项名称 | 值 | 描述 |
|---|---|---|
| TCP_CORK | 0,1 | 如果设置,不发送部分帧 |
| TCP_DEFER_ACCEPT | 0,1 | 仅当套接字上有数据到达时唤醒监听器 |
| TCP_INFO | tcp_info |
返回一个包含套接字信息的结构,
tcp_info
是特定于实现的
|
| TCP_KEEPCNT | int | 在丢弃连接之前,TCP 应发送的最大保持活动探测次数 |
| TCP_KEEPIDLE | int |
如果设置了
TCP_KEEPALIVE
选项,在 TCP 开始发送保持活动探测之前,连接应空闲的时间(以秒为单位)
|
| TCP_KEEPINTVL | int | 保持活动探测之间的时间(以秒为单位) |
| TCP_LINGER2 | int |
孤立的
FIN_WAIT2
状态套接字的生命周期
|
| TCP_MAXSEG | int | 外出 TCP 数据包的最大段大小 |
| TCP_NODELAY | 0,1 | 如果设置,禁用 Nagle 算法 |
| TCP_QUICKACK | 0,1 | 如果设置,立即发送 ACK,禁用 TCP 延迟 ACK 算法 |
| TCP_SYNCNT | int | 在中止连接请求之前,SYN 重传的次数 |
| TCP_WINDOW_CLAMP | int | 设置通告的 TCP 窗口大小的上限 |
7.10 s.gettimeout()
返回当前的超时值(如果有)。以秒为单位返回一个浮点数,如果没有设置超时,则返回
None
。
7.11 s.ioctl(control, option)
在 Windows 上提供对
WSAIoctl
接口的有限访问。
control
唯一支持的值是
SIO_RCVALL
,用于捕获网络上所有接收到的 IP 数据包,这需要管理员权限。
option
可以使用以下值:
| 选项 | 描述 |
| ---- | ---- |
| RCVALL_OFF | 防止套接字接收所有 IPv4 或 IPv6 数据包 |
| RCVALL_ON | 启用混杂模式,允许套接字接收网络上所有 IPv4 或 IPv6 数据包。接收到的数据包类型取决于套接字地址族,不捕获与其他网络协议(如 ARP)相关的数据包 |
| RCVALL_IPLEVEL | 接收网络上接收到的所有 IP 数据包,但不启用混杂模式。这将捕获针对主机的任何配置 IP 地址的所有 IP 数据包 |
7.12 s.listen(backlog)
开始监听传入的连接。
backlog
指定操作系统在拒绝连接之前应排队的最大挂起连接数。该值至少应为 1,对于大多数应用程序,5 就足够了。
7.13 s.makefile([mode [, bufsize]])
创建一个与套接字关联的文件对象。
mode
和
bufsize
的含义与内置的
open()
函数相同。文件对象使用通过
os.dup()
创建的套接字文件描述符的副本,因此文件对象和套接字对象可以独立关闭或被垃圾回收。套接字
s
不应设置超时,也不应配置为非阻塞模式。
7.14 s.recv(bufsize [, flags])
从套接字接收数据,数据以字符串形式返回。要接收的最大数据量由
bufsize
指定。
flags
提供有关消息的额外信息,通常省略(默认值为 0)。如果使用,通常设置为以下常量之一(取决于系统):
| 常量 | 描述 |
| ---- | ---- |
| MSG_DONTROUTE | 绕过路由表查找(仅发送) |
| MSG_DONTWAIT | 非阻塞操作 |
| MSG_EOR | 表示消息是记录中的最后一个,通常仅在
SOCK_SEQPACKET
套接字上发送数据时使用 |
| MSG_PEEK | 查看数据但不丢弃(仅接收) |
| MSG_OOB | 接收/发送带外数据 |
| MSG_WAITALL | 在读取到请求的字节数之前不返回(仅接收) |
7.15 s.recv_into(buffer [, nbytes [, flags]])
与
recv()
相同,只是数据写入支持缓冲区接口的对象
buffer
中。
nbytes
是要接收的最大字节数,如果省略,则最大大小取自缓冲区大小。
flags
的含义与
recv()
相同。
7.16 s.recvfrom(bufsize [, flags])
与
recv()
方法类似,但返回值是一个元组
(data, address)
,其中
data
是包含接收到的数据的字符串,
address
是发送数据的套接字的地址。可选的
flags
参数的含义与
recv()
相同。该函数主要与 UDP 协议一起使用。
7.17 s.recvfrom_info(buffer [, nbytes [, flags]])
与
recvfrom()
相同,但接收到的数据存储在缓冲区对象
buffer
中。
nbytes
指定要接收的最大字节数,如果省略,则最大大小取自缓冲区大小。
flags
的含义与
recv()
相同。
7.18 s.send(string [, flags])
将字符串
string
中的数据发送到已连接的套接字。可选的
flags
参数的含义与
recv()
相同。返回发送的字节数,可能少于
string
中的字节数。如果发生错误,会引发异常。
7.19 s.sendall(string [, flags])
将字符串
string
中的数据发送到已连接的套接字,尝试在返回之前发送所有数据。成功时返回
None
,失败时引发异常。
flags
的含义与
send()
相同。
7.20 s.sendto(string [, flags], address)
将数据发送到套接字。
flags
的含义与
recv()
相同。
address
是一个
(host, port)
形式的元组,指定远程地址。套接字不应已经连接。返回发送的字节数。该函数主要与 UDP 协议一起使用。
7.21 s.setblocking(flag)
如果
flag
为 0,将套接字设置为非阻塞模式;否则,将套接字设置为阻塞模式(默认)。在非阻塞模式下,如果
recv()
调用未找到任何数据或
send()
调用无法立即发送数据,将引发
socket.error
异常。在阻塞模式下,这些调用会阻塞直到可以继续。
7.22 s.setsockopt(level, optname, value)
设置给定套接字选项的值。
level
和
optname
的含义与
getsockopt()
相同。
value
可以是一个整数或表示缓冲区内容的字符串。在后一种情况下,调用者需要确保字符串包含正确的数据。套接字选项的名称、值和描述见
getsockopt()
。
7.23 s.settimeout(timeout)
设置套接字操作的超时时间。
timeout
是一个以秒为单位的浮点数,值为
None
表示没有超时。如果发生超时,将引发
socket.timeout
异常。一般来说,应在创建套接字后尽快设置超时时间,因为它们可以应用于建立连接的操作(如
connect()
)。
7.24 s.shutdown(how)
关闭连接的一半或两半。如果
how
为 0,禁止进一步接收;如果
how
为 1,禁止进一步发送;如果
how
为 2,禁止进一步发送和接收。
除了这些方法,套接字实例
s
还有以下只读属性,对应于传递给
socket()
函数的参数:
| 属性 | 描述 |
| ---- | ---- |
| s.family | 套接字地址族(如
AF_INET
) |
| s.proto | 套接字协议 |
| s.type | 套接字类型(如
SOCK_STREAM
) |
8. 套接字模块的异常
套接字模块定义了以下异常:
8.1 error
该异常用于套接字或地址相关的错误,返回一个元组
(errno, mesg)
,包含底层系统调用返回的错误。继承自
IOError
。
8.2 herror
用于地址相关的错误,返回一个元组
(herrno, hmesg)
,包含错误编号和错误消息。继承自
error
。
8.3 gaierror
在
getaddrinfo()
和
getnameinfo()
函数中,用于地址相关的错误。错误值是一个元组
(errno, mesg)
,其中
errno
是一个错误编号,
mesg
是一个包含消息的字符串。
errno
设置为以下常量之一:
| 常量 | 描述 |
| ---- | ---- |
| EAI_ADDRFAMILY | 地址族不支持 |
| EAI_AGAIN | 名称解析临时失败 |
| EAI_BADFLAGS | 无效标志 |
| EAI_BADHINTS | 错误提示 |
| EAI_FAIL | 名称解析不可恢复的失败 |
| EAI_FAMILY | 主机不支持的地址族 |
| EAI_MEMORY | 内存分配失败 |
| EAI_NODATA | 节点名称没有关联的地址 |
| EAI_NONAME | 没有提供节点名称或服务名称 |
| EAI_PROTOCOL | 协议不支持 |
| EAI_SERVICE | 套接字类型不支持的服务名称 |
| EAI_SOCKTYPE | 套接字类型不支持 |
| EAI_SYSTEM | 系统错误 |
8.4 timeout
当套接字操作超时时引发该异常,仅在使用
setdefaulttimeout()
函数或套接字对象的
settimeout()
方法设置了超时时间时才会发生。异常值是一个字符串
'timeout'
,继承自
error
。
9. 示例
9.1 TCP 连接示例
在前面的内容中已经有简单的 TCP 连接示例,下面给出一个简单的 UDP 回显服务器示例:
# UDP 消息服务器
# 从任何地方接收小数据包并打印出来
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 10000))
while True:
data, address = s.recvfrom(256)
print("Received a connection from %s" % str(address))
s.sendto(b"echo:" + data, address)
9.2 示例流程 mermaid 图
graph TD;
A[创建 UDP 套接字] --> B[绑定地址和端口];
B --> C[进入循环];
C --> D[接收数据和地址];
D --> E[打印连接信息];
E --> F[发送回显数据];
F --> C;
通过以上内容,我们全面了解了 Python 中
socket
模块的使用,包括协程与线程服务器的性能差异、轮询的优缺点、套接字的地址族、类型、寻址方式、模块函数、套接字对象的方法、异常处理以及实际示例应用等方面。掌握这些知识,能够帮助我们更好地进行网络编程。
超级会员免费看
695

被折叠的 条评论
为什么被折叠?



