Python网络编程(socket)

网络编程指的是:在程序中实现两台计算机之间的通信。Python提供了大量网络编程的工具和库,本文重点学习socket和select模块。

网络编程涉及许多关于TCPIP的基础知识,本文默认对这些知识已经了解了,不再对TCPIP相关的知识进行学习。

socket模块

这个模块提供了访问 BSD 套接字 的接口。在所有现代 Unix 系统、Windows、macOS 和其他一些平台上可用。

socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或应答网络请求,使主机间或一台计算机上的进程间可以通信。socket模块提供了标准的网络接口,可以访问底层操作系统socket接口的全部方法。

创建套接字socket

class socket.socket(family=AF_INET , type=SOCK_STREAM , proto=0 , fileno=None)

使用给定的地址族、套接字类型和协议号创建一个新的套接字。

  • *family:*地址族应为 AF_INET (默认值), AF_INET6, AF_UNIX, AF_CAN, AF_PACKET 或 AF_RDS 之一。
  • *type:*套接字类型应为 SOCK_STREAM (默认值), SOCK_DGRAM, SOCK_RAW 或其他可能的 SOCK_ 常量之一。
  • *proto:*协议号通常为零并且可以省略,或在协议族为 AF_CAN 的情况下,协议应为 CAN_RAW, CAN_BCM, CAN_ISOTP 或 CAN_J1939 之一。
  • 如果指定了 fileno,那么将从这一指定的文件描述符中自动检测 family、type 和 proto 的值。如果调用本函数时显式指定了 family、type 或 proto 参数,可以覆盖自动检测的值。这只会影响 Python 表示诸如 socket.getpeername() 一类函数的返回值的方式,而不影响实际的操作系统资源。与 socket.fromfd() 不同,fileno 将返回原先的套接字,而不是复制出新的套接字。这有助于在分离的套接字上调用 socket.close() 来关闭它。

socket.socketpair([family [, type [, proto]]])

构建一对已连接的套接字对象,使用给定的地址簇、套接字类型和协议号。地址簇、套接字类型和协议号与上述 socket() 函数相同。默认地址簇为 AF_UNIX (需要当前平台支持,不支持则默认为 AF_INET )。

可以理解为 创建了两个socket, 比喻为一个server的 socket,一个client的socket,这两个socket是已经connected连接状态

是全双工模式,也就是每个socket都能收发,比喻为*server.send--->client.recv,和 client.send* --->server.recv********

python 复制代码
import socket
import time
import os
from multiprocessing import Process


def servlet(sock :socket.socket):
    while True:
        msg = sock.recv(1024)
        print(f'servlet{os.getpid()},recv:{msg=}')
        if msg.decode() == 'quit':
            break
        sock.send(msg.upper())


def client(sock :socket.socket):
    sock.send(f'client{os.getpid()}, hello world!'.encode())
    msg = sock.recv(1024)
    print(msg)

    sock.send('I love you forever!'.encode())
    msg = sock.recv(1024)
    print(msg)

    sock.send('quit'.encode())
    time.sleep(1)


if __name__ == '__main__':
    s, c = socket.socketpair()

    p1 = Process(target=servlet, args=(s,))
    p2 = Process(target=client, args=(c,))
    p1.start()
    p2.start()
    p2.join()
    p2.join()

    s.close()
    c.close()

'''
servlet34196,recv:msg=b'client34197, hello world!'
b'CLIENT34197, HELLO WORLD!'
servlet34196,recv:msg=b'I love you forever!'
b'I LOVE YOU FOREVER!'
servlet34196,recv:msg=b'quit'
'''

socket.create_connection(address , timeout=GLOBAL_DEFAULT , source_address=None , * , all_errors=False)

  • 连接到一个在互联网 address (以 (host, port) 2 元组表示) 上侦听的 TCP 服务,并返回套接字对象。 这是一个相比 socket.connect() 层级更高的函数:如果 host 是非数字的主机名,它将尝试将其解析为 AF_INET 和 AF_INET6,然后依次尝试连接到所有可能的地址直到连接成功。 这使编写兼容 IPv4 和 IPv6 的客户端变得很容易。
  • 传入可选参数 timeout 可以在套接字实例上设置超时(在尝试连接前)。如果未提供 timeout,则使用由 getdefaulttimeout() 返回的全局默认超时设置。
  • 如果提供了 source_address,它必须为二元组 (host, port),以便套接字在连接之前绑定为其源地址。如果 host 或 port 分别为 '' 或 0,则使用操作系统默认行为。
  • 当无法创建连接时,将会引发一个异常。 在默认情况下,它将是来自列表中最后一个地址的异常。 如果 all_errors 为 True,它将是一个包含所有尝试错误的 ExceptionGroup。

socket.create_server(address , * , family=AF_INET , backlog=None , reuse_port=False , dualstack_ipv6=False)

创建socket监听服务。

family 应当为 AF_INET 或 AF_INET6。 backlog 是传递给 socket.listen() 的队列大小;当未指定时,将选择一个合理的默认值。 reuse_port 指定是否要设置 SO_REUSEPORT 套接字选项。

如果 dualstack_ipv6 为 true 且平台支持,则套接字能接受 IPv4 和 IPv6 连接,否则将抛出 ValueError 异常。大多数 POSIX 平台和 Windows 应该支持此功能。启用此功能后,socket.getpeername() 在进行 IPv4 连接时返回的地址将是一个(映射到 IPv4 的)IPv6 地址。

socket.fromfd(fd , family , type , proto=0)

复制文件描述符 fd (一个由文件对象的 fileno() 方法返回的整数),然后从结果中构建一个套接字对象。地址簇、套接字类型和协议号与上述 socket() 函数相同。文件描述符应指向一个套接字,但不会专门去检查------如果文件描述符是无效的,则对该对象的后续操作可能会失败。本函数很少用到,但是在将套接字作为标准输入或输出传递给程序(如 Unix inet 守护程序启动的服务器)时,可以使用本函数获取或设置套接字选项。套接字将处于阻塞模式。

套接字对象

创建套接字后,就可以使用套接字对象。

套接字对象的主要方法和属性有:

|-----------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 方法和属性名 | 说明 |
| socket.bind(address) | 将套接字绑定到 address。 |
| socket.listen([backlog]) | 启动一个服务器用于接受连接。如果指定 backlog,则它最低为 0(小于 0 会被置为 0),它指定系统允许暂未 accept 的连接数,超过后将拒绝新连接。未指定则自动设为合理的默认值。 |
| socket.accept() | 接受一个连接。此 socket 必须绑定到一个地址上并且监听连接。返回值是一个 (conn, address) 对,其中 conn 是一个 的套接字对象,用于在此连接上收发数据,address 是连接另一端的套接字所绑定的地址。 |
| socket.connect(address) | 连接到 address 处的远程套接字。 |
| socket.connect_ex(address) | 类似于 connect(address),但是对于 C 级别的 connect() 调用返回的错误,本函数将返回错误指示器,而不是抛出异常(对于其他问题,如"找不到主机",仍然可以抛出异常)。如果操作成功,则错误指示器为 0,否则为 errno 变量的值。这对支持如异步连接很有用。 |
| socket.detach() | 将套接字对象置于关闭状态,而底层的文件描述符实际并不关闭。返回该文件描述符,使其可以重新用于其他目的。 |
| socket.close() | 将套接字标记为关闭。 |
| socket.dup() | 创建套接字的副本。 |
| socket.fileno() | 返回套接字的文件描述符 |
| socket.get_inheritable() | 获取套接字文件描述符或套接字句柄的 可继承标志 :如果子进程可以继承套接字则为 True,否则为 False。 |
| socket.getpeername() | 返回套接字连接到的远程地址。 |
| socket.getsockname() | 返回套接字本身的地址。 |
| socket.getsockopt(level , optname [, buflen]) | 返回指定套接字选项的值(参阅 Unix 手册页 getsockopt(2) )。所需的符号常量( SO_* 等)已定义在本模块中。如果未指定 buflen,则认为该选项值为整数,由本函数返回该整数值。如果指定 buflen,则它定义了用于存放选项值的缓冲区的最大长度,且该缓冲区将作为字节对象返回。对缓冲区的解码工作由调用者自行完成(针对编码为字节串的 C 结构,其解码方法请参阅可选的内置模块 struct )。 |
| socket.getblocking() | 如果套接字处于阻塞模式,返回 True,非阻塞模式返回 False。 |
| socket.gettimeout() | 返回套接字操作相关的超时秒数(浮点数),未设置超时则返回 None。它反映最后一次调用 setblocking() 或 settimeout() 后的设置。 |
| socket.ioctl(control , option) | |
| socket.makefile(mode='r' , buffering=None , * , encoding=None , errors=None , newline=None) | 返回与套接字关联的 文件对象。返回的对象的具体类型取决于 makefile() 的参数。这些参数的解释方式与内置的 open() 函数相同,其中 mode 的值仅支持 'r' (默认),'w' 和 'b'。 套接字必须处于阻塞模式,它可以有超时,但是如果发生超时,文件对象的内部缓冲区可能会以不一致的状态结尾。 关闭 makefile() 返回的文件对象不会关闭原始套接字,除非所有其他文件对象都已关闭且在套接字对象上调用了 socket.close()。 |
| socket.recv(bufsize [, flags]) | 从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。可选参数 flags 的含义请参阅 Unix 手册页 recv(2),它默认为零。 |
| socket.recvfrom(bufsize [, flags]) | 从套接字接收数据。返回值是一对 (bytes, address),其中 bytes 是字节对象,表示接收到的数据,address 是发送端套接字的地址。可选参数 flags 的含义请参阅 Unix 手册页 recv(2) ,它默认为零。( address 的格式取决于地址簇 ------ 参见上文) |
| socket.recvmsg(bufsize [, ancbufsize [, flags]]) | 从套接字接收普通数据(至多 bufsize 字节)和辅助数据。ancbufsize 参数设置用于接收辅助数据的内部缓冲区的大小(以字节为单位),默认为 0,表示不接收辅助数据。可以使用 CMSG_SPACE() 或 CMSG_LEN() 计算辅助数据缓冲区的合适大小,无法放入缓冲区的项目可能会被截断或丢弃。flags 参数默认为 0,其含义与 recv() 中的相同。 返回值是一个四元组: (data, ancdata, msg_flags, address)。data 项是一个 bytes 对象,用于保存接收到的非辅助数据。ancdata 项是零个或多个元组 (cmsg_level, cmsg_type, cmsg_data) 组成的列表,表示接收到的辅助数据(控制消息):cmsg_level 和 cmsg_type 是分别表示协议级别和协议类型的整数,而 cmsg_data 是保存相关数据的 bytes 对象。msg_flags 项由各种标志按位或组成,表示接收消息的情况,详细信息请参阅系统文档。如果接收端套接字断开连接,则 address 是发送端套接字的地址(如果有),否则该值无指定。 在某些系统上,可以使用 sendmsg() 和 recvmsg() 通过 AF_UNIX 套接字在进程之间传递文件描述符。 当使用此功能时 (通常仅限于 SOCK_STREAM 套接字), recvmsg() 将在其附带数据中返回 (socket.SOL_SOCKET, socket.SCM_RIGHTS, fds) 形式的项,其中 fds 是一个代表新文件描述符的原生as a binary array of the native C int 类型的二进制数组形式的 bytes 对象。 如果 recvmsg() 在系统调用返回后引发了异常,它将首先尝试关闭通过此机制接收到的任何文件描述符。 对于仅接收到一部分的辅助数据项,一些系统没有指示其截断长度。如果某个项目可能超出了缓冲区的末尾,recvmsg() 将发出 RuntimeWarning,并返回其在缓冲区内的部分,前提是该对象被截断于关联数据开始后。 |
| socket.recvmsg_into(buffers [, ancbufsize [, flags]]) | 从套接字接收普通数据和辅助数据,其行为与 recvmsg() 相同,但将非辅助数据分散到一系列缓冲区中,而不是返回新的字节对象。buffers 参数必须是可迭代对象,它迭代出可供写入的缓冲区(如 bytearray 对象),这些缓冲区将被连续的非辅助数据块填充,直到数据全部写完或缓冲区用完为止。在允许使用的缓冲区数量上,操作系统可能会有限制( sysconf() 的 SC_IOV_MAX 值)。ancbufsize 和 flags 参数的含义与 recvmsg() 中的相同。 返回值为四元组: (nbytes, ancdata, msg_flags, address),其中 nbytes 是写入缓冲区的非辅助数据的字节总数,而 ancdata、msg_flags 和 address 与 recvmsg() 中的相同。 |
| socket.recvfrom_into(buffer [, nbytes [, flags]]) | 从套接字接收数据,将其写入 buffer 而不是创建新的字节串。返回值是一对 (nbytes, address),其中 nbytes 是收到的字节数,address 是发送端套接字的地址。可选参数 flags 的含义请参阅 Unix 手册页 recv(2),它默认为零。 |
| socket.recv_into(buffer [, nbytes [, flags]]) | 从套接字接收至多 nbytes 个字节,将其写入缓冲区而不是创建新的字节串。如果 nbytes 未指定(或指定为 0),则接收至所给缓冲区的最大可用大小。返回接收到的字节数。可选参数 flags 的含义请参阅 Unix 手册页 recv(2),它默认为零。 |
| socket.send(bytes [, flags]) | 发送数据给套接字。本套接字必须已连接到远程套接字。可选参数 flags 的含义与上述 recv() 中的相同。本方法返回已发送的字节数。应用程序要负责检查所有数据是否已发送,如果仅传输了部分数据,程序需要自行尝试传输其余数据。 |
| socket.sendall(bytes [, flags]) | 发送数据给套接字。本套接字必须已连接到远程套接字。可选参数 flags 的含义与上述 recv() 中的相同。与 send() 不同,本方法持续从 bytes 发送数据,直到所有数据都已发送或发生错误为止。成功后会返回 None。出错后会抛出一个异常,此时并没有办法确定成功发送了多少数据。 |
| socket.sendto(bytes , address) socket.sendto(bytes , flags , address) | 发送数据给套接字。本套接字不应连接到远程套接字,而应由 address 指定目标套接字。可选参数 flags 的含义与上述 recv() 中的相同。本方法返回已发送的字节数。 |
| socket.sendmsg(buffers [, ancdata [, flags [, address]]]) | 将普通数据和辅助数据发送给套接字,将从一系列缓冲区中收集非辅助数据,并将其拼接为一条消息。buffers 参数指定的非辅助数据应为可迭代的 字节类对象 (如 bytes 对象),在允许使用的缓冲区数量上,操作系统可能会有限制( sysconf() 的 SC_IOV_MAX 值)。ancdata 参数指定的辅助数据(控制消息)应为可迭代对象,迭代出零个或多个 (cmsg_level, cmsg_type, cmsg_data) 元组,其中 cmsg_level 和 cmsg_type 是分别指定协议级别和协议类型的整数,而 cmsg_data 是保存相关数据的字节类对象。请注意,某些系统(特别是没有 CMSG_SPACE() 的系统)可能每次调用仅支持发送一条控制消息。flags 参数默认为 0,与 send() 中的含义相同。如果 address 指定为除 None 以外的值,它将作为消息的目标地址。返回值是已发送的非辅助数据的字节数。 |
| socket.sendmsg_afalg([msg , ]* , op [, iv [, assoclen [, flags]]]) | 为 AF_ALG 套接字定制的 sendmsg() 版本。可为 AF_ALG 套接字设置模式、IV、AEAD 关联数据的长度和标志位。 |
| socket.sendfile(file , offset=0 , count=None) | 使用高性能的 os.sendfile 发送文件,直到达到文件的 EOF 为止,返回已发送的字节总数。file 必须是一个以二进制模式打开的常规文件对象。如果 os.sendfile 不可用(如 Windows)或 file 不是常规文件,将使用 send() 代替。offset 指示从哪里开始读取文件。如果指定了 count,它确定了要发送的字节总数,而不会持续发送直到达到文件的 EOF。返回时或发生错误时,文件位置将更新,在这种情况下,file.tell() 可用于确定已发送的字节数。套接字必须为 SOCK_STREAM 类型。不支持非阻塞的套接字。 |
| socket.set_inheritable(inheritable) | 设置套接字文件描述符或套接字句柄的 可继承标志。 |
| socket.setblocking(flag) | 设置套接字为阻塞或非阻塞模式:如果 flag 为 false,则将套接字设置为非阻塞,否则设置为阻塞。 |
| socket.settimeout(value) | 为阻塞套接字的操作设置超时。value 参数可以是非负浮点数,表示秒,也可以是 None。如果赋为一个非零值,那么如果在操作完成前超过了超时时间 value,后续的套接字操作将抛出 timeout 异常。如果赋为 0,则套接字将处于非阻塞模式。如果指定为 None,则套接字将处于阻塞模式。 |
| socket.setsockopt(level, optname, value: int) socket.setsockopt(level, optname, value: buffer) socket.setsockopt(level, optname, None, optlen: int) | 设置给定套接字选项的值(参阅 Unix 手册页 setsockopt(2) )。所需的符号常量( SO_* 等)已定义在本 socket 模块中。该值可以是整数、None 或表示缓冲区的 字节类对象。在后一种情况下,由调用者确保字节串中包含正确的数据位(关于将 C 结构体编码为字节串的方法,请参阅可选的内置模块 struct )。当 value 设置为 None 时,必须设置 optlen 参数。这相当于调用 setsockopt() C 函数时使用了 optval=NULL 和 optlen=optlen 参数。 |
| socket.shutdown(how) | 关闭一半或全部的连接。如果 howSHUT_RD,则后续不再允许接收。如果 howSHUT_WR,则后续不再允许发送。如果 howSHUT_RDWR,则后续的发送和接收都不允许。 |
| socket.share(process_id) | 复制套接字,并准备将其与目标进程共享。目标进程必须以 process_id 形式提供。然后可以利用某种形式的进程间通信,将返回的字节对象传递给目标进程,还可以使用 fromshare() 在新进程中重新创建套接字。一旦本方法调用完毕,就可以安全地将套接字关闭,因为操作系统已经为目标进程复制了该套接字 |
| socket.family | 套接字的协议簇。 |
| socket.type | 套接字的类型。 |
| socket.proto | 套接字的协议。 |

构建服务

构建服务需要以下几个步骤:

  1. 创建socket对象;
  2. 使用bind()绑定服务器地址和端口;
  3. 使用listen()启动监听队列,监听客户端的连接请求;
  4. 使用accept获取客户端的连接,这个方法会阻塞住,等到客户端有连接时才会返回;
  5. 获得客户端连接的socket后,使用客户端的连接进行数据交互(recv()、send())
python 复制代码
# 导入 socket、sys 模块
import socket
import time
import sys
# 创建 socket 对象
serversocket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
print(f'{host=}')
port = 9999
# 绑定端口
serversocket.bind(('127.0.0.1', port))
# 设置最大连接数,超过后排队
serversocket.listen(5)

while True:
    # 建立客户端连接
    clientsocket,addr = serversocket.accept()
    print("连接地址: %s" % str(addr))
    while True:
        msg = clientsocket.recv(1024)
        print(f'recv:{msg=}')
        if msg.decode() == 'quit':
            break
        clientsocket.send(msg.upper())
serversocket.close()
time.sleep(1)

构建客户端

相比服务端,客户端比较简单:

  1. 创建socket对象;
  2. 通过connect()去连接服务端的端口;
  3. 使用客户端的socket进行数据交互(recv()、send())。
python 复制代码
# 导入 socket、sys 模块
import socket
import sys
import time

# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口
port = 9999
# 连接服务,指定主机和端口
s.connect(('127.0.0.1', port))
# 接收小于 1024 字节的数据
s.send('hello world!'.encode())
msg = s.recv(1024)
print(msg)

print('-----')
s.send('I love you forever!'.encode())
msg = s.recv(1024)
print(msg)
time.sleep(1)

s.send('quit'.encode())
time.sleep(1)
s.close()

其他函数

socket 模块还提供多种网络相关的函数

|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 函数名 | 说明 |
| socket.close(fd) | 关闭一个套接字文件描述符。 |
| socket.getaddrinfo(host , port , family=0 , type=0 , proto=0 , flags=0) | 将 host/port 参数转换为 5 元组的序列,其中包含创建(连接到某服务的)套接字所需的所有参数。host 是域名,是字符串格式的 IPv4/v6 地址或 None。port 是字符串格式的服务名称,如 'http' 、端口号(数字)或 None。传入 None 作为 host 和 port 的值,相当于将 NULL 传递给底层 C API。 可以指定 family、type 和 proto 参数,以缩小返回的地址列表。向这些参数分别传入 0 表示保留全部结果范围。flags 参数可以是 AI_* 常量中的一个或多个,它会影响结果的计算和返回。例如,AI_NUMERICHOST 会禁用域名解析,此时如果 host 是域名,则会抛出错误。 本函数返回一个列表,其中的 5 元组具有以下结构: (family, type, proto, canonname, sockaddr) 在这些元组中,family, type, proto 都是整数且其作用是被传入 socket() 函数。 如果 AI_CANONNAME 是 flags 参数的一部分则 canonname 将是表示 host 规范名称的字符串;否则 canonname 将为空。 sockaddr 是一个描述套接字地址的元组,其具体格式取决于返回的 family (对于 AF_INET 为 (address, port) 2 元组,对于 AF_INET6 则为 (address, port, flowinfo, scope_id) 4 元组),其作用是被传入 socket.connect() 方法。 |
| socket.getfqdn([name]) | 返回 name 的完整限定域名。 如果 name 被省略或为空,则将其解读为本地主机。 要查找完整限定名称,将先检查 gethostbyaddr() 所返回的主机名,然后是主机的别名(如果存在)。 包括句点的第一个名称将会被选择。 对于没有完整限定域名而提供了 name 的情况,则会将其原样返回。 如果 name 为空或等于 '0.0.0.0',则返回来自 gethostname() 的主机名。 |
| socket.gethostbyname(hostname) | 将主机名转换为 IPv4 地址格式。IPv4 地址以字符串格式返回,如 '100.50.200.5'。如果主机名本身是 IPv4 地址,则原样返回。更完整的接口请参考 gethostbyname_ex()。 gethostbyname() 不支持 IPv6 名称解析,应使用 getaddrinfo() 来支持 IPv4/v6 双协议栈。 |
| socket.gethostbyname_ex(hostname) | 将一个主机名转换为 IPv4 地址格式的扩展接口。 返回一个 3 元组 (hostname, aliaslist, ipaddrlist) 其中 hostname 是主机的首选主机名,aliaslist 是同一地址的备选主机名列表(可能为空),而 ipaddrlist 是同一主机上同一接口的 IPv4 地址列表(通常为单个地址但并不总是如此)。 gethostbyname_ex() 不支持 IPv6 名称解析,应当改用 getaddrinfo() 来提供 IPv4/v6 双栈支持。 |
| socket.gethostname() | 返回一个字符串,包含当前正在运行 Python 解释器的机器的主机名。 |
| socket.gethostbyaddr(ip_address) | 返回一个 3 元组 (hostname, aliaslist, ipaddrlist) 其中 hostname 是响应给定 ip_address 的首选主机名,aliaslist 是同一地址的备选主机名列表(可能为空),而 ipaddrlist 是同一主机上同一接口的 IPv4/v6 地址列表(很可能仅包含一个地址)。 要查询完整限定域名,请使用函数 getfqdn()。 gethostbyaddr() 同时支持 IPv4 和 IPv6。 |
| socket.getnameinfo(sockaddr , flags) | 将套接字地址 sockaddr 转换为一个 2 元组 (host, port)。 根据 flags 的设置,结果可能包含 host 中的完整限定域名或数字形式的地址。 类似地,port 可以包含字符串形式的端口名或数字形式的端口号。 对于 IPv6 地址,如果 sockaddr 包含有意义的 scope_id ,则 %scope_id 会被附加到主机部分。 这种情况通常发生在多播地址上。 |
| socket.getprotobyname(protocolname) | 将一个互联网协议名称 (如 'icmp') 转换为能被作为 (可选的) 第三个参数传给 socket() 函数的常量。 这通常仅对以 "raw" 模式 (SOCK_RAW) 打开的套接字来说是必要的;对于正常的套接字模式,当该协议名称被省略或为零时会自动选择正确的协议。 |
| socket.getservbyname(servicename [, protocolname]) | 将一个互联网服务名称和协议名称转换为该服务的端口号。 如果给出了可选的协议名称,它应为 'tcp''udp',否则将匹配任意的协议。 |
| socket.getservbyport(port [, protocolname]) | 将一个互联网端口号和协议名称转换为该服务的服务名称。 如果给出了可选的协议名称,它应为 'tcp''udp',否则将匹配任意的协议。 |
| socket.ntohl(x) | 将 32 位正整数从网络字节序转换为主机字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 4 字节交换操作。 |
| socket.ntohs(x) | 将 16 位正整数从网络字节序转换为主机字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 2 字节交换操作。 |
| socket.htonl(x) | 将 32 位正整数从主机字节序转换为网络字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 4 字节交换操作。 |
| socket.htons(x) | 将 16 位正整数从主机字节序转换为网络字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 2 字节交换操作。 |
| socket.inet_aton(ip_string) | 将一个 IPv4 地址从以点号分为四段的字符串格式(例如 '123.45.67.89')转换为 32 位的紧凑二进制格式,长度为四个字符的字节串对象。 这在与使用标准 C 库并且需要 in_addr 类型对象的程序通信时很有用处,该类型就是此函数所返回的 32 位的紧凑二进制格式 C 类型。 |
| socket.inet_ntoa(packed_ip) | 将一个 32 位紧凑 IPv4 地址 (长度为四个字节的 bytes-like object) 转换为标准的以点号四分段字符串表示形式 (例如 '123.45.67.89')。 这在与使用标准 C 库并且需要 in_addr 类型对象的程序通信时很有用处,该类型就是此函数接受作为参数的 32 位的紧凑二进制格式 C 类型。 如果传入本函数的字节序列长度不是 4 个字节,则抛出 OSError。inet_ntoa() 不支持 IPv6,在 IPv4/v6 双协议栈下应使用 inet_ntop() 来代替。 |
| socket.inet_pton(address_family , ip_string) | 将基于特定地址族字符串格式的 IP 地址转换为紧凑的二进制格式。 inet_pton() 在一个库或网络协议需要 in_addr (类似于 inet_aton()) 或 in6_addr 类型的对象时很有用处。 目前 address_family 支持 AF_INET 和 AF_INET6。如果 IP 地址字符串 ip_string 无效,则抛出 OSError。注意,具体什么地址有效取决于 address_family 的值和 inet_pton() 的底层实现。 |
| socket.inet_ntop(address_family , packed_ip) | 将一个紧凑的 IP 地址 (长度为多个字节的 bytes-like object) 转换为标准的基于特定地址族的字符串表示形式 (例如 '7.10.0.5' 或 '5aef:2b::8')。 inet_ntop() 在一个库或网络协议返回 in_addr (类似于 inet_ntoa()) 或 in6_addr 类型的对象时很有用处。 目前 address_family 支持 AF_INET 和 AF_INET6。如果字节对象 packed_ip 与指定的地址簇长度不符,则抛出 ValueError。针对 inet_ntop() 调用的错误则抛出 OSError。 |
| socket.CMSG_LEN(length) | 返回给定 length 所关联数据的辅助数据项的总长度(不带尾部填充)。此值通常用作 recvmsg() 接收一个辅助数据项的缓冲区大小,但是 RFC 3542 要求可移植应用程序使用 CMSG_SPACE(),以此将尾部填充的空间计入,即使该项在缓冲区的最后。如果 length 超出允许范围,则抛出 OverflowError。 |
| socket.CMSG_SPACE(length) | 返回 recvmsg() 所需的缓冲区大小,以接收给定 length 所关联数据的辅助数据项,带有尾部填充。接收多个项目所需的缓冲区空间是关联数据长度的 CMSG_SPACE() 值的总和。如果 length 超出允许范围,则抛出 OverflowError。 请注意,某些系统可能支持辅助数据,但不提供本函数。还需注意,如果使用本函数的结果来设置缓冲区大小,可能无法精确限制可接收的辅助数据量,因为可能会有其他数据写入尾部填充区域。 |
| socket.getdefaulttimeout() | 返回用于新套接字对象的默认超时(以秒为单位的浮点数)。值 None 表示新套接字对象没有超时。首次导入 socket 模块时,默认值为 None。 |
| socket.setdefaulttimeout(timeout) | 设置用于新套接字对象的默认超时(以秒为单位的浮点数)。首次导入 socket 模块时,默认值为 None。可能的取值及其各自的含义请参阅 settimeout()。 |
| socket.sethostname(name) | 将计算机的主机名设置为 name。如果权限不足将抛出 OSError。 |
| socket.if_nameindex() | 返回一个列表,包含网络接口(网卡)信息二元组(整数索引,名称字符串)。系统调用失败则抛出 OSError。 |
| socket.if_nametoindex(if_name) | 返回网络接口名称相对应的索引号。如果没有所给名称的接口,则抛出 OSError。 |
| socket.if_indextoname(if_index) | 返回网络接口索引号相对应的接口名称。如果没有所给索引号的接口,则抛出 OSError。 |
| socket.send_fds(sock , buffers , fds [, flags [, address]]) | 将文件描述符列表 fds 通过一个 AF_UNIX 套接字 sock 进行发送。 fds 形参是由文件描述符构成的序列。 请查看 sendmsg() 获取这些形参的文档。 |
| socket.recv_fds(sock , bufsize , maxfds [, flags]) | 接收至多 maxfds 个来自 AF_UNIX 套接字 sock 的文件描述符。 返回 (msg, list(fds), flags, addr)。 请查看 recvmsg() 获取有些形参的文档。 |

selectors模块

简单的网络编程可以通过建立socket,然后bind()、listen,最后通过accept接收客户端的请求,但是在实际中,网络的请求是大量的、并发的,这种同步模式的处理,很难支持大并发的网络请求。

selectors模块允许高层级且高效率的 I/O 复用,它建立在 select 模块原型的基础之上。 推荐用户改用此模块,除非他们希望对所使用的 OS 层级原型进行精确控制。

它定义了一个 BaseSelector 抽象基类,以及多个实际的实现 (KqueueSelector, EpollSelector...),它们可被用于在多个文件对象上等待 I/O 就绪通知。 在下文中,"文件对象" 是指任何具有 fileno() 方法的对象,或是一个原始文件描述符。

DefaultSelector 是一个指向当前平台上可用的最高效实现的别名:这应为大多数用户的默认选择。

类的层次结构:

复制代码
BaseSelector
+-- SelectSelector
+-- PollSelector
+-- EpollSelector
+-- DevpollSelector
+-- KqueueSelector

上面的子类是对各个不同的平台的支持,如EpollSelector仅支持 Linux 2.5.44 或更高版本,KqueueSelector仅支持 BSD系统等,建议使用DefaultSelector,它会自动选择合适平台的最高效实现。

主要方法和对象

register()

BaseSelector.register(fileobj , events , data=None)

注册一个用于选择的文件对象,在其上监视 I/O 事件。

fileobj 是要监视的文件对象。 它可以是整数形式的文件描述符或者具有 fileno() 方法的对象。 events 是要监视的事件的位掩码。 data 是一个不透明对象。

这将返回一个新的 SelectorKey 实例,或在出现无效事件掩码或文件描述符时引发 ValueError,或在文件对象已被注册时引发 KeyError。

events 一个位掩码,指明哪些 I/O 事件要在给定的文件对象上执行等待。 它可以是以下模块级常量的组合:

常量 含意
selectors.EVENT_READ 可读
selectors.EVENT_WRITE 可写

SelectorKey对象

SelectorKey 是一个 namedtuple,用来将文件对象关联到其下层的文件描述符、选定事件掩码和附加数据等。

namedtuple:可以通过字段名来获取属性值,同样也可以通过索引和迭代获取值的元组。

属性有:

fileobj:已注册的文件对象。

fd:下层的文件描述符/socket。

events:必须在此文件对象上被等待的事件。

data:可选的关联到此文件对象的不透明数据:例如,这可被用来存储各个客户端的会话 ID。

unregister()

BaseSelector.unregister(fileobj)

注销对一个文件对象的选择,移除对它的监视。 在文件对象被关闭之前应当先将其注销。

fileobj 必须是之前已注册的文件对象。

这将返回已关联的 SelectorKey 实例,或者如果 fileobj 未注册则会引发 KeyError。 It will raise ValueError 如果 fileobj 无效(例如它没有 fileno() 方法或其 fileno() 方法返回无效值)。

modify()

BaseSelector.modify(fileobj , events , data=None)

更改已注册文件对象所监视的事件或所附带的数据。

这等价于 BaseSelector.unregister(fileobj) 加 BaseSelector.register(fileobj, events, data),区别在于它可以被更高效地实现。

这将返回一个新的 SelectorKey 实例,或在出现无效事件掩码或文件描述符时引发 ValueError,或在文件对象未被注册时引发 KeyError。

select()

BaseSelector.select(timeout=None)

核心函数。

等待直到有已注册的文件对象就绪,或是超过时限。

如果 timeout > 0,这指定以秒数表示的最大等待时间。 如果 timeout <= 0,调用将不会阻塞,并将报告当前就绪的文件对象。 如果 timeout 为 None,调用将阻塞直到某个被监视的文件对象就绪。

这将返回由 (key, events) 元组构成的列表,每项各表示一个就绪的文件对象。

key 是对应于就绪文件对象的 SelectorKey 实例。 events 是在此文件对象上等待的事件位掩码。

close()

关闭选择器。

必须调用这个方法以确保下层资源会被释放。 选择器被关闭后将不可再使用。

get_key()

BaseSelector.get_key(fileobj)

返回关联到某个已注册文件对象的键。

此方法将返回关联到文件对象的 SelectorKey 实例,或在文件对象未注册时引发 KeyError。

get_map()

返回从文件对象到选择器键的映射。

这将返回一个将已注册文件对象映射到与其相关联的 SelectorKey 实例的 Mapping 实例。

构建服务器

python 复制代码
import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data.upper())  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('127.0.0.1', 9999))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

一个测试的客户端:

python 复制代码
# 导入 socket、sys 模块
import socket
import sys
import time

# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口
port = 9999
# 连接服务,指定主机和端口
s.connect(('127.0.0.1', port))
# 接收小于 1024 字节的数据
s.send('hello world!'.encode())
msg = s.recv(1024)
print(msg)

print('-----')
s.send('I love you forever!'.encode())
msg = s.recv(1024)
print(msg)
time.sleep(1)

s.send(''.encode())
time.sleep(1)
s.close()
相关推荐
会员源码网3 分钟前
理财源码开发:单语言深耕还是多语言融合?看完这篇不踩坑
网络·个人开发
米羊1211 小时前
已有安全措施确认(上)
大数据·网络
Fcy6481 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满1 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠1 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥1 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey9031 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
ManThink Technology2 小时前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
珠海西格电力科技3 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
QT.qtqtqtqtqt3 小时前
未授权访问漏洞
网络·安全·web安全