一、轮询机制概念
在操作系统中,用户态轮询机制是一种等待系统中某个资源就绪的方式,它通常用于非阻塞式I/O操作。这种机制允许用户进程在等待I/O操作完成时继续执行其他任务,而不是一直阻塞等待。用户进程可以使用系统调用将I/O操作请求提交到内核,然后轮询(通过反复检查状态)该请求是否完成。
在用户态轮询机制中,用户进程使用非阻塞式I/O操作来提交请求,然后在请求完成前反复检查请求的状态是否已经就绪。如果请求还未完成,则继续执行其他任务,一旦请求完成,则返回操作系统,并从内核中读取数据。此时,用户进程再次运行,并在需要时再次提交新的I/O请求。
用户态轮询机制的优点在于可以提高系统的吞吐量,因为不需要等待I/O操作完成,可以先执行其他任务。此外,它也可以帮助避免阻塞,从而提高系统的响应能力,因为用户进程可以在I/O操作等待期间继续执行其他任务。但是,它也会消耗更多的CPU时间,因为用户进程需要不断检查I/O请求的状态。
二、Python实现轮询机制
在Python中,实现轮询机制可以使用select模块或更高级别的asyncio库。
使用select模块:
server
[root@localhost python]# cat selectTCP.py
import select
import socket
host = "192.168.6.211"
port = 8888
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(10)
inputs = [server]
while True:
ready_for_reading, _, _ = select.select(inputs, [], [])
for sock in ready_for_reading:
if sock == server:
# 如果是server socket,表示有新的客户端连接请求
client, address = server.accept()
inputs.append(client)
else:
# 如果是client socket,表示有数据可读
data = sock.recv(1024)
if data:
# 处理数据
print(data)
else:
# 关闭socket
inputs.remove(sock)
sock.close()
[root@localhost python]# cat tcp_clientTrue.py
import socket
HOST = '192.168.6.211'
PORT = 8888
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
message = 'Hello from client!'
client_socket.send(message.encode('utf-8'))
response = client_socket.recv(1024).decode('utf-8')
print(f'Received response from server: {response}')
client_socket.close()
以上示例程序使用select模块实现了一个简单的TCP服务器。在主循环中,程序调用select.select()方法来等待socket的读就绪事件。如果是server socket,表示有新的客户端连接请求;如果是client socket,表示有数据可读。
使用asyncio库:
import asyncio
import socket
host = "192.168.6.1"
port = 8888
async def handle_client(reader, writer):
while True:
data = await reader.read(1024)
if not data:
# 关闭流
writer.close()
break
# 处理数据
print(data)
async def run_server():
# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# server.bind((host, port))
# server.listen(10)
# server.setblocking(False)
# while True:
# client, address = await asyncio.start_server(handle_client, host, port)
# asyncio.create_task(handle_client(client))
server = await asyncio.start_server(handle_client, host, port)
async with server:
await server.serve_forever()
asyncio.run(run_server())
[root@localhost python]# cat tcp_clientPC.py
import socket
HOST = '192.168.6.1'
PORT = 8888
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
message = 'Hello from client!'
client_socket.send(message.encode('utf-8'))
response = client_socket.recv(1024).decode('utf-8')
print(f'Received response from server: {response}')
client_socket.close()
以上示例程序使用asyncio库实现了一个简单的TCP服务器。在主循环中,程序调用asyncio.sock_accept()方法来异步等待客户端连接请求。一旦有客户端连接上,程序创建一个任务来处理客户端数据的读取和处理。在任务中,使用asyncio.sock_recv()方法来异步等待客户端数据的读取。如果没有数据,则关闭socket。
附:报错说明
报错一:
AttributeError: module 'asyncio' has no attribute 'sock_accept'
这个错误可能是因为在Python 3.8及之后的版本中,asyncio
模块中的sock_accept
方法已经被废弃。可以改用asyncio.start_server()
方法来实现异步监听客户端连接请求,并在有新连接时调用回调函数处理客户端数据。
以下是一个使用asyncio.start_server()
方法实现的简单TCP服务器示例:
import asyncio
import socket
host = "localhost"
port = 5000
async def handle_client(reader, writer):
while True:
data = await reader.read(1024)
if not data:
# 关闭流
writer.close()
break
# 处理数据
pass
async def run_server():
server = await asyncio.start_server(handle_client, host, port)
async with server:
await server.serve_forever()
asyncio.run(run_server())
在上面的示例中,asyncio.start_server()
方法会异步监听客户端连接请求,并在有新连接时自动调用handle_client()
方法来处理客户端数据。在handle_client()
方法内部,代码使用await reader.read()
方法来异步等待客户端数据的读取。如果没有数据,则关闭流。asyncio
库自动管理流的关闭,因此不需要手动关闭。最后,使用await server.serve_forever()
方法来使服务器一直运行下去。
报错二:
ValueError: Neither host/port nor sock were specified
这个错误通常是因为在使用asyncio.start_server()
方法时,没有正确指定host
和port
参数。
以下是一个使用asyncio.start_server()
方法实现的简单TCP服务器示例,其中正确指定了host
和port
参数:
import asyncio
async def handle_client(reader, writer):
while True:
data = await reader.read(1024)
if not data:
# 关闭流
writer.close()
break
# 处理数据
pass
async def run_server():
host = "localhost"
port = 5000
server = await asyncio.start_server(handle_client, host, port)
async with server:
await server.serve_forever()
asyncio.run(run_server())
在上面的示例中,host
和port
参数被正确指定为"localhost"
和5000
,因此不会出现ValueError: Neither host/port nor sock were specified
的错误。
报错三:
OSError: [Errno 10048] error while attempting to bind on address ('127.0.0.1', 8888): 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
这个错误通常是因为在使用asyncio.start_server()
方法时,指定的端口已经被占用了。
解决这个问题最简单的方法是使用一个没有被占用的端口。另外,也可以通过以下两种方法解决这个问题:
- 关闭占用端口的程序或服务:使用
lsof -i :端口号
命令查看占用该端口的程序或服务,然后结束该程序或服务; - 修改使用的端口号:可以使用一个未被占用的端口号替换原来的端口号。
以下是一个修改端口号的示例:
import asyncio
async def handle_client(reader, writer):
while True:
data = await reader.read(1024)
if not data:
# 关闭流
writer.close()
break
# 处理数据
pass
async def run_server():
host = "localhost"
port = 8889 # 修改为未被占用的端口号
server = await asyncio.start_server(handle_client, host, port)
async with server:
await server.serve_forever()
asyncio.run(run_server())
在上面的示例中,我们将端口号从8888
修改为8889
,可以避免OSError: [Errno 10048] error while attempting to bind on address ('127.0.0.1', 8888): 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
错误的发生。
报错四:
AttributeError: module 'asyncio' has no attribute 'run'
asyncio.run()
是在Python 3.7中引入的新方法,用于运行一个async函数。如果在较早的Python版本中使用asyncio.run()
方法,则会出现AttributeError: module 'asyncio' has no attribute 'run'
错误。
要解决这个问题,可以采用以下两种方法之一:
- 将Python版本升级到3.7或更高版本,以便使用
asyncio.run()
方法。 - 在较早的Python版本中,使用
asyncio.get_event_loop().run_until_complete()
方法来运行async函数。
以下是使用第二种方法修改的示例代码:
import asyncio
async def main():
# 你的async方法代码
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在上面的示例中,我们使用asyncio.get_event_loop().run_until_complete()
方法来运行main()
async函数,而不是使用asyncio.run()
方法。这将解决AttributeError: module 'asyncio' has no attribute 'run'
错误。
报错五:
async with server:
AttributeError: __aexit__
这个错误通常发生在使用一个不支持上下文管理器的对象时,而Python中的async with
语法需要对象支持上下文管理器。因此,解决这个问题的方法是检查使用的对象是否支持上下文管理器。
如果使用的对象本身不支持上下文管理器,可以考虑使用其他方法来替代async with
语法。例如,使用asyncio.ensure_future()
或asyncio.create_task()
来运行异步任务,这两个方法都不需要上下文管理器。
如果对象本身支持上下文管理器,但是出现了__aexit__
属性缺失的错误,那么可能是因为使用的是Python 3.6及以下版本。在这些版本中,async with
语法不支持异步上下文管理器。要解决这个问题,可以考虑升级Python版本或者使用其他方法来实现异步上下文管理器,例如使用async_generator
库提供的asynccontextmanager
装饰器。