面试题答案
一键面试对事件驱动编程模型的理解
事件驱动编程模型是一种编程范式,程序的执行流程由外部事件(如网络请求、用户输入等)来驱动。在这种模型中,程序会持续监听各种事件,当某个事件发生时,对应的事件处理函数会被调用。这种模型避免了传统多线程或多进程编程中由于线程/进程上下文切换带来的开销,适用于处理大量并发连接但每个连接I/O操作较少的场景。
Linux环境下epoll机制
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用I/O接口select/poll的增强版本。它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
epoll相比于select和poll的优势
- 支持的文件描述符数量:
- select通常受限于FD_SETSIZE(一般为1024)。
- poll理论上没有限制,但在实际应用中也会因内存等因素受限。
- epoll在Linux 2.6内核以后,理论上支持的文件描述符数量仅受限于系统内存。
- 事件通知方式:
- select和poll采用轮询的方式检查文件描述符上是否有事件发生,时间复杂度为O(n),随着文件描述符数量增多,效率会显著下降。
- epoll采用回调机制,当有事件发生时,内核会将事件添加到就绪列表中,应用程序通过epoll_wait获取就绪事件,时间复杂度为O(1)。
- 内存拷贝:
- select和poll每次调用都需要将文件描述符集合从用户空间拷贝到内核空间,返回时又从内核空间拷贝回用户空间。
- epoll通过epoll_ctl注册文件描述符到内核中,之后内核与用户空间共享这个数据结构,无需重复拷贝。
基于epoll实现简单并发网络服务器的伪代码示例
import socket
import selectors
# 创建一个默认的Selector对象
sel = selectors.DefaultSelector()
# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 8888))
server_socket.listen(100)
server_socket.setblocking(False)
# 将server_socket注册到selector,监听读事件
sel.register(server_socket, selectors.EVENT_READ, data=None)
def accept_wrapper(sock):
conn, addr = sock.accept() # 接受新连接
print(f"Accepted connection from {addr}")
conn.setblocking(False)
# 注册新连接的读事件和数据处理函数
sel.register(conn, selectors.EVENT_READ, data=echo_handler)
def echo_handler(conn, mask):
data = conn.recv(1024) # 读取数据
if data:
print(f"Received {data!r} from {conn.getpeername()}")
conn.sendall(data) # 回显数据
else:
print(f"Closing connection to {conn.getpeername()}")
sel.unregister(conn) # 取消注册
conn.close() # 关闭连接
while True:
events = sel.select(timeout=None) # 等待事件发生
for key, mask in events:
if key.data is None:
accept_wrapper(key.fileobj)
else:
callback = key.data
callback(key.fileobj, mask)
关键步骤和数据结构
- 关键步骤:
- 创建socket并设置为非阻塞模式:
server_socket.setblocking(False)
,这样在接受连接和读取数据时不会阻塞主线程。 - 注册socket到epoll:
sel.register(server_socket, selectors.EVENT_READ, data=None)
,将服务器socket注册到epoll实例,监听读事件。 - 处理新连接:在
accept_wrapper
函数中,接受新连接并将其也注册到epoll,同时设置为非阻塞模式。 - 处理读事件:在
echo_handler
函数中,读取客户端发送的数据并回显,如果数据读完则关闭连接并取消注册。 - 事件循环:
while True
循环中,通过sel.select(timeout=None)
等待事件发生,然后根据事件类型调用相应的处理函数。
- 创建socket并设置为非阻塞模式:
- 数据结构:
- Selector对象:
sel
是selectors.DefaultSelector
的实例,它管理所有注册的文件描述符及其对应的事件和处理函数。 - socket对象:
server_socket
和conn
分别代表服务器socket和客户端连接socket,用于网络通信。
- Selector对象: