17.2. 套接字—低级网络接口

该模块提供对 BSD * socket *接口的访问。它在所有现代 Unix 系统,Windows,Mac OS X,BeOS,OS/2 以及可能的其他平台上都可用。

Note

由于对 os 套接字 API 进行了调用,因此某些行为可能取决于平台。

有关套接字编程(用 C 语言编写)的介绍,请参见以下文章:UNIX 程序员手册中的 Stuart Sechrest 撰写的 4.3BSD 进程间通信入门教程和 Samuel J. Leffler 等人的 Sam 4.3 JSD 的 Advanced 4.3BSD 进程间通信教程。 ,补充文件 1(PS1:7 和 PS1:8 部分)。各种与套接字相关的系统调用的特定于平台的参考资料也是有关套接字语义细节的有价值的信息来源。对于 Unix,请参考手册页。对于 Windows,请参阅 WinSock(或 Winsock 2)规范。对于支持 IPv6 的 API,Reader 可能希望参考 RFC 3493标题为 IPv6 的基本套接字接口扩展。

Python 接口是 Unix 系统调用和库接口的直接转译,用于将套接字转换为 Python 的面向对象样式:socket()函数返回* socket 对象*,其方法实现了各种套接字系统调用。参数类型比 C 接口中的级别更高:与 Python 文件上的read()write()操作一样,接收操作的缓冲区分配是自动的,发送操作的缓冲区长度是隐式的。

套接字地址表示如下:AF_UNIX地址系列使用单个字符串。对(host, port)用于AF_INET地址族,其中* host 是一个字符串,表示 Internet 域表示法中的主机名(如'daring.cwi.nl')或 IPv4 地址(如'100.50.200.5'),而 port 是整数。对于AF_INET6地址族,使用四 Tuples(host, port, flowinfo, scopeid),其中 flowinfo scopeid 表示 C 中struct sockaddr_in6中的sin6_flowinfosin6_scope_id成员。对于socket模块方法,仅出于向后兼容性,可以Ellipsis flowinfo scopeid * 。但是请注意,Ellipsis* scopeid *可能会导致在处理作用域 IPv6 地址时出现问题。当前不支持其他地址族。将根据创建套接字对象时指定的地址系列自动选择特定套接字对象所需的地址格式。

对于 IPv4 地址,可以接受两种特殊形式而不是主机地址:空字符串表示INADDR_ANY,而字符串'<broadcast>'表示INADDR_BROADCAST。该行为不适用于 IPv6 以实现向后兼容性,因此,如果要使用 Python 程序支持 IPv6,则可能要避免这些行为。

如果在 IPv4/v6 套接字地址的* host 部分中使用主机名,则该程序可能会显示不确定的行为,因为 Python 使用从 DNS 解析返回的第一个地址。套接字地址将根据 DNS 解析和/或主机配置的结果不同地解析为实际的 IPv4/v6 地址。对于确定性行为,请在 host *部分使用数字地址。

2.5 版中的新Function:AF_NETLINK 套接字表示为对pid, groups

2.6 版中的新Function:使用AF_TIPC地址系列也可提供仅 Linux 对 TIPC 的支持。 TIPC 是一种开放的,非基于 IP 的网络协议,旨在用于群集计算机环境中。地址由 Tuples 表示,并且字段取决于地址类型。一般的 Tuples 形式为(addr_type, v1, v2, v3 [, scope]),其中:

如果* addr_type TIPC_ADDR_NAMESEQ,则 v1 为服务器类型, v2 为下端口号, v3 *为上端口号。

如果* addr_type TIPC_ADDR_ID,则 v1 是节点, v2 是引用,并且 v3 *应该设置为 0.

所有错误都会引发异常。无效参数类型和内存不足条件的正常异常可以被提出。与套接字或地址语义相关的错误会引发错误socket.error

passsetblocking()支持非阻塞模式。passsettimeout()支持基于超时的通用化。

模块socket导出以下常量和函数:

在 2.6 版中更改:socket.error现在是IOError的子类。

附带的值是Pair(h_errno, string),代表库调用返回的错误。 * string 表示hstrerror() C 函数返回的 h_errno *的描述。

2.3 版的新Function。

2.6 版的新Function。

2.6 版的新Function。

2.3 版的新Function。

传递可选的* timeout 参数将在try连接之前在套接字实例上设置超时。如果未提供 timeout *,则使用getdefaulttimeout()返回的全局默认超时设置。

如果提供,则* source_address *必须为 2Tuples(host, port),以便套接字在连接之前绑定为其源地址。如果主机或端口分别为''或 0,则将使用 os 默认行为。

2.6 版的新Function。

在 2.7 版中进行了更改:添加了* source_address *。

可以选择指定* family socktype proto 参数,以缩小返回的地址列表。默认情况下,它们的值为0,这意味着将选择整个结果范围。 * flags 参数可以是AI_*常量中的一个或多个,并将影响结果的计算和返回方式。其默认值为0。例如,AI_NUMERICHOST将禁用域名解析,并且如果 host *是域名,则会引发错误。

该函数返回具有以下结构的 5Tuples 列表:

(family, socktype, proto, canonname, sockaddr)

在这些 Tuples 中,* family socktype proto 都是整数,并应传递给socket()函数。如果AI_CANONNAME flags 参数的一部分,则* canonname 将是代表 host 规范名称的字符串;否则 canonname *将为空。 * sockaddr 是一个描述套接字地址的 Tuples,其格式取决于返回的 family *(对于AF_INET(address, port) 2Tuples,对于AF_INET6(address, port, flow info, scope id) 4Tuples),并打算传递给socket.connect()方法。

以下示例为端口 80 上与example.org的虚拟 TCP 连接获取地址信息(如果未启用 IPv6,结果可能会在您的系统上有所不同):

>>> socket.getaddrinfo("example.org", 80, 0, 0, socket.IPPROTO_TCP)
[(10, 1, 6, '', ('2606:2800:220:1:248:1893:25c8:1946', 80, 0, 0)),
 (2, 1, 6, '', ('93.184.216.34', 80))]

2.2 版中的新Function。

2.0 版中的新Function。

如果您想知道当前机器的 IP 地址,则可以使用gethostbyname(gethostname())。该操作假设主机存在有效的地址到主机 Map,并且该假设并不总是成立。

注意:gethostname()并不总是返回完全限定的域名。使用getfqdn()(请参见上文)。

2.2 版中的新Function。

2.4 版的新Function。

inet_aton()还接受少于三个点的字符串;有关详细信息,请参见 Unix 手册* inet(3)*。

如果传递给此Function的 IPv4 地址字符串无效,则将引发socket.error。请注意,确切有效的内容取决于inet_aton()的基础 C 实现。

inet_aton()不支持 IPv6,而应使用inet_pton()代替 IPv4/v6 双协议栈。

如果传递给此函数的字符串长度不完全是 4 个字节,则将引发socket.errorinet_ntoa()不支持 IPv6,而应使用inet_ntop()代替 IPv4/v6 双栈。

可用性:Unix(可能不是所有平台)。

2.3 版的新Function。

可用性:Unix(可能不是所有平台)。

2.3 版的新Function。

2.3 版的新Function。

2.3 版的新Function。

See also

  • Module SocketServer

  • 简化编写 Web Service 器的类。

  • Module ssl

  • 套接字对象的 TLS/SSL 包装器。

17.2.1. 套接字对象

套接字对象具有以下方法。除了makefile(),它们对应于适用于套接字的 Unix 系统调用。

Note

从历史上看,此方法接受了AF_INET个地址的Pair参数,而不仅仅是一个 Tuples。这从来不是故意的,并且在 Python 2.0 和更高版本中不再可用。

Note

close()释放与连接关联的资源,但不一定立即关闭连接。如果要及时关闭连接,请在close()之前致电shutdown()

Note

从历史上看,此方法接受了AF_INET个地址的Pair参数,而不仅仅是一个 Tuples。这从来不是故意的,并且在 Python 2.0 和更高版本中不再可用。

Note

从历史上看,此方法接受了AF_INET个地址的Pair参数,而不仅仅是一个 Tuples。这从来不是故意的,并且在 Python 2.0 和更高版本中不再可用。

在 Windows 下,不能在可以使用文件 Descriptors 的地方使用此方法返回的小整数(例如os.fdopen())。 Unix 没有此限制。

ioctl()方法是 WSAIoctl 系统接口的受限接口。有关更多信息,请参考Win32 documentation

在其他平台上,可以使用通用的fcntl.fcntl()fcntl.ioctl()Function。他们接受套接字对象作为第一个参数。

2.6 版的新Function。

套接字必须处于阻止模式(不能有超时)。可选的* mode bufsize *参数的解释方式与内置file()函数的解释方式相同。

Note

在 Windows 上,不能在期望带有文件 Descriptors 的文件对象(例如subprocess.Popen()的流参数)使用makefile()创建的类似文件的对象。

Note

为了与硬件和网络的实际情况达到最佳匹配,* bufsize *的值应为 2 的相对较小的幂,例如 4096.

2.5 版的新Function。

2.5 版的新Function。

2.3 版的新Function。

2.3 版的新Function。

有关套接字阻塞和超时的一些注意事项:套接字对象可以处于以下三种模式之一:阻塞,非阻塞或超时。套接字始终以阻止模式创建。在阻止模式下,操作将阻止直到完成或系统返回错误(例如连接超时)。在非阻塞模式下,如果操作无法立即完成,则操作将失败(不幸的是,错误取决于系统)。在超时模式下,如果无法在为套接字指定的超时时间内完成操作,或者系统返回错误,则操作将失败。 setblocking()方法只是某些settimeout()调用的简写。

超时模式在内部将套接字设置为非阻塞模式。阻塞和超时模式在引用相同网络端点的文件 Descriptors 和套接字对象之间共享。结果是,仅当套接字处于阻塞模式时才可以使用makefile()方法返回的文件对象。在超时或非阻塞模式下无法立即完成的文件操作将失败。

请注意,connect()操作受超时设置的约束,通常建议在调用connect()之前将settimeout()调用或将超时参数传递给create_connection()。无论任何 Python 套接字超时设置如何,系统网络堆栈都可能返回其自身的连接超时错误。

注意,没有方法read()write();请使用不带* flags 参数的recv()send()

套接字对象还具有与赋予socket构造函数的值相对应的这些(只读)属性。

2.5 版的新Function。

2.5 版的新Function。

2.5 版的新Function。

17.2.2. Example

这是使用 TCP/IP 协议的四个最小示例程序:一台服务器,该服务器回显它收到的所有数据(仅为一个 Client 端提供服务),以及一个使用它的 Client 端。请注意,服务器必须执行 Sequencessocket()bind()listen()accept()(可能重复accept()以为多个 Client 端提供服务),而 Client 端仅需要 Sequencessocket()connect()。还要注意,服务器不在正在侦听的套接字上sendall()/recv(),而是在accept()返回的新套接字上。

前两个示例仅支持 IPv4.

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.sendall(data)
conn.close()
# Echo client program
import socket

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

接下来的两个示例与以上两个示例相同,但同时支持 IPv4 和 IPv6.服务器端将监听第一个可用的地址族(它应该监听两个)。在大多数支持 IPv6 的系统上,IPv6 优先,并且服务器可能不接受 IPv4 流量。Client 端将try连接到由于名称解析而返回的所有地址,并将流量发送到成功连接的第一个地址。

# Echo server program
import socket
import sys

HOST = None               # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
                              socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error as msg:
        s = None
        continue
    try:
        s.bind(sa)
        s.listen(1)
    except socket.error as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()
# Echo client program
import socket
import sys

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error as msg:
        s = None
        continue
    try:
        s.connect(sa)
    except socket.error as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
s.sendall('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

最后一个示例显示了如何在 Windows 上使用原始套接字编写一个非常简单的网络嗅探器。该示例需要 Management 员特权才能修改接口:

import socket

# the public network interface
HOST = socket.gethostbyname(socket.gethostname())

# create a raw socket and bind it to the public interface
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((HOST, 0))

# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# receive a package
print s.recvfrom(65565)

# disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

多次运行示例,两次执行之间的延迟太短,可能导致此错误:

socket.error: [Errno 98] Address already in use

这是因为先前的执行使套接字处于TIME_WAIT状态,因此无法立即重用。

为了防止这种情况,设置了一个socket标志socket.SO_REUSEADDR

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))

SO_REUSEADDR标志告诉内核重用处于TIME_WAIT状态的本地套接字,而不必 await 其自然超时到期。

首页