1. bio
# bio_server.py
import socket
import threading
def sub_conn(conn):
while True:
result = conn.recv(5).decode("utf-8")
if result == "end" or not result:
break
print(result)
conn.close()
server = socket.socket()
host = socket.gethostname()
server.bind((host, 9999))
server.listen(5)
while True:
conn, addr = server.accept()
print(conn)
threading.Thread(target=sub_conn, args=(conn,)).start()
# client.py
import socket
client = socket.socket(family=socket. AF_INET, type=socket.SOCK_STREAM)
host = socket.gethostname()
client.connect((host, 9999))
while True:
data = input("客户端发送数据:").strip()
print(client.send(data.encode("utf-8")))
if data == "end":
client.close()
break
2. nio
# server.py
python
import socket
# 创建 TCP 服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 9999))
server_socket.listen(5)
server_socket.setblocking(False)
# 客户端套接字列表
client_sockets = []
print("服务器启动,等待连接...")
while True:
try:
# 接受新连接
client_socket, client_address = server_socket.accept()
client_socket.setblocking(False)
print(f"接受来自 {client_address} 的连接")
client_sockets.append(client_socket)
except BlockingIOError:
pass
# 处理现有连接
for client_socket in client_sockets:
try:
data = client_socket.recv(1024)
if data:
print(f"接收到来自 {client_socket.getpeername()} 的消息:{data.decode()}")
# 可以在这里处理接收到的数据,比如回复客户端
else:
print(f"客户端 {client_socket.getpeername()} 断开连接")
client_socket.close()
client_sockets.remove(client_socket)
except BlockingIOError:
pass
# client.py
import socket
import sys
# 创建 TCP 客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 9999))
client_socket.setblocking(False)
print("连接到服务器...")
while True:
try:
# 发送消息
message = sys.stdin.readline().strip()
if message:
client_socket.sendall(message.encode())
except BlockingIOError:
pass
try:
# 接收服务器响应
data = client_socket.recv(1024)
if data:
print(f"收到来自服务器的消息:{data.decode()}")
except BlockingIOError:
pass
这些示例演示了如何使用非阻塞套接字实现一个简单的 TCP 服务器和客户端。在服务器端,通过将 server_socket 设置为非阻塞,并在接受连接和处理数据时捕获 BlockingIOError 异常来实现非阻塞 I/O;在客户端,同样将 client_socket 设置为非阻塞,并在发送消息和接收服务器响应时捕获 BlockingIOError 异常。
3. select/poll
# select_server.py
import select
import socket
# 创建 TCP 服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 9999))
server_socket.listen(5)
# 创建一个列表,用于存储要监视的文件描述符
inputs = []
while True:
# 使用 select 函数监视文件描述符的状态变化
readable, _, _ = select.select(inputs, [], [])
# 遍历可读的文件描述符列表
for readable_socket in readable:
if readable_socket is server_socket:
# 如果可读的文件描述符是服务器套接字,表示有新连接请求
client_socket, client_address = server_socket.accept()
print(f"接受来自 {client_address} 的连接")
inputs.append(client_socket) # 将新连接的客户端套接字添加到监视列表中
else:
# 如果可读的文件描述符是客户端套接字,表示有数据可读
data = readable_socket.recv(1024)
if not data:
# 如果客户端关闭连接,则从监视列表中移除该套接字
print(f"客户端 {readable_socket.getpeername()} 断开连接")
inputs.remove(readable_socket)
readable_socket.close()
else:
# 处理接收到的数据
print(f"接收到来自 {readable_socket.getpeername()} 的消息:{data.decode()}")
# 关闭服务器套接字
server_socket.close()
"""
在此示例中,使用 select.select() 函数来监视 inputs 列表中的文件描述符,当其中任何一个变为可读时,select.select() 函数就会返回可读的文件描述符列表 readable。然后根据可读的文件描述符是服务器套接字还是客户端套接字来执行相应的操作。
请注意,此示例仅演示了如何使用 select 模块进行基本的 TCP 服务器端编程,实际应用中可能还需要添加异常处理、超时处理等额外逻辑。
"""
# client.py
import select
import socket
import sys
# 创建 TCP 客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_address = ('localhost', 9999)
client_socket.connect(server_address)
# 创建输入列表,包括标准输入和客户端套接字
inputs = [sys.stdin, client_socket]
while True:
# 使用 select 函数监视文件描述符的状态变化
readable, _, _ = select.select(inputs, [], [])
# 遍历可读的文件描述符列表
for readable_socket in readable:
if readable_socket is sys.stdin:
# 如果可读的文件描述符是标准输入,读取用户输入并发送到服务器
message = sys.stdin.readline().strip()
client_socket.sendall(message.encode())
elif readable_socket is client_socket:
# 如果可读的文件描述符是客户端套接字,表示从服务器接收到数据
data = client_socket.recv(1024)
if not data:
# 如果服务器关闭连接,则退出循环
print("服务器关闭连接")
sys.exit()
else:
# 打印接收到的数据
print(f"收到来自服务器的消息:{data.decode()}")
# 关闭客户端套接字
client_socket.close()
"""
在此示例中,客户端首先连接到服务器,然后创建一个输入列表,其中包括标准输入和客户端套接字。然后使用 select.select() 函数来监视输入列表中的文件描述符,当其中任何一个变为可读时,select.select() 函数就会返回可读的文件描述符列表 readable。然后根据可读的文件描述符是标准输入还是客户端套接字来执行相应的操作。
客户端会不断监听用户输入和从服务器接收数据,并将用户输入发送到服务器。如果从服务器接收到的数据为空,则表示服务器关闭了连接,客户端会退出循环并关闭客户端套接字。
"""
4. epoll
# epoll_server.py
"""
使用 select 虽然在较小规模的情况下是有效的,但是在大规模连接下性能会有限。epoll 是 Linux 中更高效的 I/O 多路复用机制,可以显著提升性能。以下是使用 epoll 实现的简单 TCP 服务器示例:
"""
import socket
import select
# 创建 TCP 服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 9999))
server_socket.listen(5)
# 创建 epoll 对象
epoll = select.epoll()
# 将服务器套接字注册到 epoll 中,并设置为监听读事件
epoll.register(server_socket.fileno(), select.EPOLLIN)
connections = {} # 存储客户端连接信息
while True:
# 等待事件发生
events = epoll.poll()
# 处理事件
for fileno, event in events:
if fileno == server_socket.fileno():
# 如果是服务器套接字上有事件发生,表示有新连接
client_socket, client_address = server_socket.accept()
print(f"接受来自 {client_address} 的连接")
# 将新连接的客户端套接字注册到 epoll 中,并设置为监听读事件
epoll.register(client_socket.fileno(), select.EPOLLIN)
connections[client_socket.fileno()] = client_socket
elif event & select.EPOLLIN:
# 如果是客户端套接字上有数据可读
client_socket = connections[fileno]
data = client_socket.recv(1024)
if not data:
# 如果客户端关闭连接,则从 epoll 中注销套接字,并关闭连接
print(f"客户端 {client_socket.getpeername()} 断开连接")
epoll.unregister(fileno)
client_socket.close()
del connections[fileno]
else:
# 处理接收到的数据
print(f"接收到来自 {client_socket.getpeername()} 的消息:{data.decode()}")
# 关闭服务器套接字和 epoll 对象
server_socket.close()
epoll.close()
"""
在此示例中,首先创建了一个 epoll 对象,并将服务器套接字注册到 epoll 中以监听读事件。然后进入事件循环,使用 epoll.poll() 等待事件发生。一旦事件发生,就根据事件类型进行处理。如果是新连接事件,则接受连接并将新连接的客户端套接字注册到 epoll 中。如果是客户端套接字上有数据可读,则接收数据并进行处理。最后,记得在程序结束时关闭服务器套接字和 epoll 对象。
"""
# client.py
"""
在客户端使用 epoll 的场景相对较少,因为 epoll 主要用于服务器端的高并发连接管理。但是,你可以使用 epoll 在客户端实现一些高级的 I/O 操作,比如同时监听多个套接字的可读或可写事件。
以下是一个简单的示例,展示了如何在客户端使用 epoll 同时监听标准输入和与服务器的套接字:
"""
import socket
import select
import sys
# 创建 TCP 客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 9999))
# 创建 epoll 对象
epoll = select.epoll()
# 将标准输入和客户端套接字注册到 epoll 中,并设置为监听读事件
epoll.register(sys.stdin.fileno(), select.EPOLLIN)
epoll.register(client_socket.fileno(), select.EPOLLIN)
while True:
# 等待事件发生
events = epoll.poll()
# 处理事件
for fileno, event in events:
if fileno == sys.stdin.fileno():
# 如果是标准输入上有事件发生,读取用户输入并发送到服务器
message = sys.stdin.readline().strip()
client_socket.sendall(message.encode())
elif fileno == client_socket.fileno():
# 如果是客户端套接字上有事件发生,表示从服务器接收到数据
data = client_socket.recv(1024)
if not data:
# 如果服务器关闭连接,则退出循环
print("服务器关闭连接")
sys.exit()
else:
# 打印接收到的数据
print(f"收到来自服务器的消息:{data.decode()}")
# 关闭客户端套接字和 epoll 对象
client_socket.close()
epoll.close()
"""
在此示例中,我们将标准输入和客户端套接字都注册到 epoll 中,并设置为监听读事件。然后进入事件循环,使用 epoll.poll() 等待事件发生。一旦事件发生,就根据事件类型进行相应的处理。如果是标准输入上有事件发生,则读取用户输入并发送到服务器;如果是客户端套接字上有事件发生,则接收从服务器发送过来的数据并进行处理。
"""
5 信号驱动I/O
# server.py
import signal
import socket
import os
# SIGIO 信号处理函数
def handle_sigio(signum, frame):
print("收到 SIGIO 信号,数据准备就绪")
# 安装 SIGIO 信号处理函数
signal.signal(signal.SIGIO, handle_sigio)
# 创建 TCP 服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 8888))
server_socket.listen(5)
# 设置套接字为非阻塞模式
server_socket.setblocking(False)
# 将套接字的属主设置为当前进程,以便接收 SIGIO 信号
pid = os.getpid()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVUID, pid)
# 将套接字设置为接收SIGIO信号
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVSIG, 1)
# 套接字加入进程的信号接收组
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SIGNAL, signal.SIGIO)
print("等待数据准备就绪...")
# 进入主循环
while True:
try:
# 接受连接
client_socket, client_address = server_socket.accept()
print(f"接受来自 {client_address} 的连接")
# 接收数据
data = client_socket.recv(1024)
if data:
print(f"收到来自 {client_address} 的消息:{data.decode()}")
# 可以在这里对收到的数据进行处理,并向客户端发送响应
response = "消息已收到,谢谢!"
client_socket.send(response.encode())
# 关闭客户端套接字
client_socket.close()
except IOError:
# 在非阻塞模式下,如果没有数据准备就绪,会抛出IOError异常,这是正常的
pass
# 关闭服务器套接字
server_socket.close()
# client.py
import socket
import os
import signal
import time
# SIGIO 信号处理函数
def handle_sigio_client(signum, frame):
print("客户端收到 SIGIO 信号")
# 安装 SIGIO 信号处理函数
signal.signal(signal.SIGIO, handle_sigio_client)
# 创建 TCP 客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8888))
# 设置套接字为非阻塞模式
client_socket.setblocking(False)
# 将套接字的属主设置为当前进程,以便接收 SIGIO 信号
pid = os.getpid()
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVUID, pid)
# 将套接字设置为接收SIGIO信号
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVSIG, 1)
# 套接字加入进程的信号接收组
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SIGNAL, signal.SIGIO)
print("发送数据给服务器...")
# 发送数据给服务器
client_socket.send("Hello, server!".encode())
# 等待一段时间,以便收到服务器的响应
time.sleep(2)
# 接收服务器的响应
response = client_socket.recv(1024)
print("收到服务器的响应:", response.decode())
# 关闭客户端套接字
client_socket.close()
这两个代码段分别是服务器端和客户端的代码。它们共享了相同的信号处理函数 handle_sigio(),以便在数据准备就绪时执行相应的操作。在服务器端,我们等待连接并处理来自客户端的数据。在客户端,我们先连接到服务器,然后发送数据,并等待服务器的响应。
5. aio
用户进程在调用 aio_read 等异步 I/O 系统调用后,会立即返回,而不会阻塞等待数据准备好。在内核数据准备好后,内核会将数据复制给用户进程,并向其发送通知。这种方式下,用户进程在整个 I/O 操作的两个阶段都是非阻塞的,可以继续执行其他任务。
下面是一个简单的 Python 示例,演示了如何使用 aio_read 等异步 I/O 系统调用实现这种模式:
此段代码没测试过,但逻辑没问题。
# server.py
import socket
import os
import asyncio
import ctypes
# 定义 aio_read 函数的回调函数类型
CallbackType = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, ctypes.c_ulong)
# 定义 aio_read 函数的参数和返回值类型
libc = ctypes.CDLL(None)
aio_read = libc.aio_read
aio_read.argtypes = [ctypes.c_void_p] * 4
aio_read.restype = ctypes.c_int
# 定义异步读取数据的协程函数
async def async_read_data(client_socket):
buffer_size = 1024
buffer = bytearray(buffer_size)
# 定义 aio_read 的回调函数
def aio_read_callback(fd, status, nbytes):
if nbytes > 0:
print("接收到数据:", buffer[:nbytes].decode())
else:
print("连接关闭")
# 将回调函数转换为 C 函数指针
aio_read_callback = CallbackType(aio_read_callback)
# 设置异步 I/O 请求
aio_req = ctypes.c_void_p()
aio_read(ctypes.c_int(client_socket.fileno()), ctypes.byref(buffer), buffer_size, aio_req, aio_read_callback)
# 等待异步读取完成
while True:
await asyncio.sleep(0.1)
if aio_req.value == 0:
break
# 定义服务端函数
async def server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8888))
server_socket.listen(1)
print("等待客户端连接...")
client_socket, addr = server_socket.accept()
print("客户端已连接:", addr)
# 异步接收客户端发送的数据
await async_read_data(client_socket)
# 关闭连接
client_socket.close()
server_socket.close()
# 运行服务端
asyncio.run(server())
# client.py
import socket
import os
import ctypes
import time
# 定义 aio_read 函数的回调函数类型
CallbackType = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, ctypes.c_ulong)
# 定义 aio_read 函数的参数和返回值类型
libc = ctypes.CDLL(None)
aio_read = libc.aio_read
aio_read.argtypes = [ctypes.c_void_p] * 4
aio_read.restype = ctypes.c_int
# 定义异步读取数据的协程函数
async def async_read_data(client_socket):
buffer_size = 1024
buffer = bytearray(buffer_size)
# 定义 aio_read 的回调函数
def aio_read_callback(fd, status, nbytes):
if nbytes > 0:
print("接收到数据:", buffer[:nbytes].decode())
else:
print("连接关闭")
# 将回调函数转换为 C 函数指针
aio_read_callback = CallbackType(aio_read_callback)
# 设置异步 I/O 请求
aio_req = ctypes.c_void_p()
aio_read(ctypes.c_int(client_socket.fileno()), ctypes.byref(buffer), buffer_size, aio_req, aio_read_callback)
# 等待异步读取完成
while True:
await asyncio.sleep(0.1)
if aio_req.value == 0:
break
# 定义客户端函数
async def client():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8888))
print("已连接到服务端")
# 发送消息给服务端
message = "Hello, Server!"
client_socket.sendall(message.encode())
print("已发送消息:", message)
# 异步接收服务端返回的消息
await async_read_data(client_socket)
# 关闭连接
client_socket.close()
# 运行客户端
asyncio.run(client())