Python网络编程(socket)

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

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

socket模块

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

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

创建套接字socket

class socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=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

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(addresstimeout=GLOBAL_DEFAULTsource_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_INETbacklog=Nonereuse_port=Falsedualstack_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(fdfamilytypeproto=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(leveloptname[, buflen]) 返回指定套接字选项的值(参阅 Unix 手册页 getsockopt(2) )。所需的符号常量( SO_* 等)已定义在本模块中。如果未指定 buflen,则认为该选项值为整数,由本函数返回该整数值。如果指定 buflen,则它定义了用于存放选项值的缓冲区的最大长度,且该缓冲区将作为字节对象返回。对缓冲区的解码工作由调用者自行完成(针对编码为字节串的 C 结构,其解码方法请参阅可选的内置模块 struct )。
socket.getblocking() 如果套接字处于阻塞模式,返回 True,非阻塞模式返回 False
socket.gettimeout() 返回套接字操作相关的超时秒数(浮点数),未设置超时则返回 None。它反映最后一次调用 setblocking() 或 settimeout() 后的设置。
socket.ioctl(controloption)
socket.makefile(mode='r'buffering=None*encoding=Noneerrors=Nonenewline=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(bytesaddress)

socket.sendto(bytesflagsaddress)

发送数据给套接字。本套接字不应连接到远程套接字,而应由 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(fileoffset=0count=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) 关闭一半或全部的连接。如果 how 为 SHUT_RD,则后续不再允许接收。如果 how 为 SHUT_WR,则后续不再允许发送。如果 how 为 SHUT_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())
# 导入 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())。
# 导入 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(hostportfamily=0type=0proto=0flags=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(sockaddrflags)

将套接字地址 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_familyip_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_familypacked_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(sockbuffersfds[, flags[, address]]) 将文件描述符列表 fds 通过一个 AF_UNIX 套接字 sock 进行发送。 fds 形参是由文件描述符构成的序列。 请查看 sendmsg() 获取这些形参的文档。
socket.recv_fds(sockbufsizemaxfds[, 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(fileobjeventsdata=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(fileobjeventsdata=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 实例。

构建服务器

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)

一个测试的客户端:

# 导入 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()

你可能感兴趣的:(网络,服务器,运维)