网络编程指的是:在程序中实现两台计算机之间的通信。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
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 是连接另一端的套接 |