目录
前言
一、文件 I/O
1.基本文件 I/O 操作
1.1打开文件
1.2读取文件内容 (read)
1.3写入文件 (write)
1.4关闭文件 (close)
2.文件指针
二、多路复用 I/O
1.常用的多路复用 I/O 模型
1.1select
1.2poll
1.3epoll
2.使用 select、poll 和 epoll 进行简单的 I/O 监控
3.select、poll 和 epoll 三种 I/O 多路复用模型的对比
前言
- 文件 I/O 是程序与外部数据交互的基础,可以通过读取和写入文件实现数据持久化。文件 I/O 的操作相对简单,适用于普通的数据读写场景。
- 多路复用 I/O 则是一种高级 I/O 操作模式,能够有效地处理多个文件描述符的 I/O 事件,广泛应用于网络编程中,特别是在高并发服务器中发挥着重要作用。常用的多路复用 I/O 模型包括
select
、poll
和epoll
。
一、文件 I/O
文件 I/O(Input/Output,输入/输出)指的是计算机程序与文件进行交互的过程,这些文件通常存储在磁盘上。文件 I/O 是程序与外部数据交互的基础,能够实现数据的存储、读取和处理。
1.基本文件 I/O 操作
1.1打开文件
- 在进行文件操作之前,需要先打开文件。
open
函数用于打开文件,并返回一个文件对象,供后续操作使用。 - 语法:
file_object = open(file_name, mode)
- 常用模式:
"r"
: 读取(默认)"w"
: 写入(会覆盖原有文件)"a"
: 追加(在文件末尾追加内容)"b"
: 二进制模式(如"rb"
,"wb"
等)"+"
: 更新模式(读写都可以,如"r+"
,"w+"
)
1.2读取文件内容 (read)
- 打开文件后,可以使用
read()
、readline()
或readlines()
方法读取文件内容。 read(size)
:读取文件中size
个字节内容,如果不指定size
,则读取整个文件。readline()
:按行读取文件,返回文件中的一行。readlines()
:返回文件中的所有行,结果是一个包含每行内容的列表。
with open("example.txt", "r") as file:content = file.read()print(content)
1.3写入文件 (write)
- 使用
write()
方法将字符串写入文件中。如果文件是以"w"
模式打开的,写入时会覆盖文件内容。 - 如果想逐行写入,可以使用
writelines()
方法,这个方法接受一个字符串列表,每个字符串将作为一行写入文件。
with open("example.txt", "w") as file:file.write("Hello, World!\n")file.writelines(["Line 1\n", "Line 2\n"])
1.4关闭文件 (close)
操作完成后,应当使用 close()
关闭文件,以释放系统资源。不过,使用 with
语句可以自动关闭文件。
file = open("example.txt", "r")
# 执行操作
file.close() # 关闭文件
2.文件指针
- 文件指针是指示文件中当前读写位置的标记。
tell()
:获取文件指针当前位置。seek(offset, from_what)
:移动文件指针到指定位置。offset
表示偏移量,from_what
表示参考点(0: 文件开头, 1: 当前位置, 2: 文件结尾)。
二、多路复用 I/O
多路复用 I/O 是一种高级 I/O 操作模式,允许一个进程同时监听多个文件描述符(如文件、网络连接等),并根据事件发生情况进行相应处理。它的优势在于避免了传统阻塞 I/O 操作的等待时间,提升了并发处理的效率。
1.常用的多路复用 I/O 模型
1.1select
select
是一种跨平台的 I/O 多路复用接口,能够监控一组文件描述符(包括套接字),当其中的一个或多个描述符准备好进行 I/O 操作时,select
返回并指示哪些描述符可以进行操作。- 它的基本思想是通过单个系统调用等待多个文件描述符中的任何一个变得可读、可写或有错误发生。
import select
import socket# 创建 socket 对象
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8080))
sock.listen(5)inputs = [sock]
while True:readable, writable, exceptional = select.select(inputs, [], [])for s in readable:if s is sock:conn, addr = s.accept()inputs.append(conn)else:data = s.recv(1024)if data:print("Received:", data.decode())else:inputs.remove(s)s.close()
1.2poll
poll
是select
的改进版本,克服了select
的一些限制(如文件描述符数量限制)。poll
使用一个 poll 对象来监控多个文件描述符。它的行为和select
类似,但性能更好,特别是在监控大量描述符时。
import select
import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8080))
sock.listen(5)poller = select.poll()
poller.register(sock, select.POLLIN)fd_to_socket = {sock.fileno(): sock}
while True:events = poller.poll()for fd, flag in events:s = fd_to_socket[fd]if flag & select.POLLIN:if s is sock:conn, addr = s.accept()poller.register(conn, select.POLLIN)fd_to_socket[conn.fileno()] = connelse:data = s.recv(1024)if data:print("Received:", data.decode())else:poller.unregister(fd)s.close()del fd_to_socket[fd]
1.3epoll
epoll
是 Linux 提供的一种高效的多路复用 I/O 模型,适用于大量并发连接场景。相比select
和poll
,epoll
更加高效,因为它采用了事件通知机制,避免了轮询的开销。epoll
可以分为水平触发(Level-Triggered,LT)和边缘触发(Edge-Triggered,ET)两种模式,ET 模式更加高效,但编程更加复杂。
import select
import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8080))
sock.listen(5)epoll = select.epoll()
epoll.register(sock.fileno(), select.EPOLLIN)fd_to_socket = {sock.fileno(): sock}
while True:events = epoll.poll()for fd, event in events:s = fd_to_socket[fd]if event & select.EPOLLIN:if s is sock:conn, addr = s.accept()epoll.register(conn.fileno(), select.EPOLLIN)fd_to_socket[conn.fileno()] = connelse:data = s.recv(1024)if data:print("Received:", data.decode())else:epoll.unregister(fd)s.close()del fd_to_socket[fd]
2.使用 select
、poll
和 epoll
进行简单的 I/O 监控
import select
import socket
import sysdef create_server_socket():server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.bind(('localhost', 8080))server_socket.listen(5)return server_socketdef handle_select(server_socket):inputs = [server_socket]while True:readable, writable, exceptional = select.select(inputs, [], [])for s in readable:if s is server_socket:conn, addr = s.accept()inputs.append(conn)else:data = s.recv(1024)if data:s.send(data)else:inputs.remove(s)s.close()def handle_poll(server_socket):poller = select.poll()poller.register(server_socket, select.POLLIN)fd_to_socket = {server_socket.fileno(): server_socket}while True:events = poller.poll()for fd, flag in events:s = fd_to_socket[fd]if flag & select.POLLIN:if s is server_socket:conn, addr = s.accept()poller.register(conn.fileno(), select.POLLIN)fd_to_socket[conn.fileno()] = connelse:data = s.recv(1024)if data:s.send(data)else:poller.unregister(fd)s.close()del fd_to_socket[fd]def handle_epoll(server_socket):epoll = select.epoll()epoll.register(server_socket.fileno(), select.EPOLLIN)fd_to_socket = {server_socket.fileno(): server_socket}while True:events = epoll.poll()for fd, event in events:s = fd_to_socket[fd]if event & select.EPOLLIN:if s is server_socket:conn, addr = s.accept()epoll.register(conn.fileno(), select.EPOLLIN)fd_to_socket[conn.fileno()] = connelse:data = s.recv(1024)if data:s.send(data)else:epoll.unregister(fd)s.close()del fd_to_socket[fd]if __name__ == "__main__":if len(sys.argv) != 2:print("Usage: python3 server.py <select|poll|epoll>")sys.exit(1)mode = sys.argv[1]server_socket = create_server_socket()if mode == "select":handle_select(server_socket)elif mode == "poll":handle_poll(server_socket)elif mode == "epoll":handle_epoll(server_socket)else:print("Invalid mode. Use 'select', 'poll', or 'epoll'.")sys.exit(1)
3.select
、poll
和 epoll
三种 I/O 多路复用模型的对比
select
适用于小规模并发应用,跨平台支持广泛,但在处理大量文件描述符时性能较差。poll
解决了select
的文件描述符限制问题,适合中小规模并发连接,但在性能上不如epoll
。epoll
是处理大规模并发连接的最佳选择,具有出色的扩展性和性能,但仅在 Linux 系统上可用,并且编程复杂度较高。