大锤python日记(基于TCP/IP协议,使用单线程,非堵塞,epoll方式实现HTTP的服务器)

51 阅读3分钟

Epoll是Linux操作系统中的一个I/O事件通知机制,它可以用于监视文件描述符上的事件。使用epoll可以构建高效的网络服务器,并且可以同时处理多个客户端连接。Python3中有一个名为select的标准库,它可以用来管理文件描述符上的事件,包括socket。

以下是一个基本的使用epoll实现单线程HTTP服务器的示例代码:

import socket
import select

# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置该socket对象的选项,使得可以重用地址(在服务器关闭后立即重新启动时避免端口占用)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定IP地址和端口号
server_socket.bind(('localhost', 8080))

# 监听连接
server_socket.listen()

# 设置服务端套接字为非堵塞I/O
server_socket.setblocking(False)

# 创建epoll对象
epoll = select.epoll()

# 注册server_socket到epoll对象,以便监听新连接事件
epoll.register(server_socket.fileno(), select.EPOLLIN)

# connections字典用于保存已连接的客户端socket对象 
connections = {}  # {文件描述符:client_socket}

while True:
    # 等待事件发生,每次最多处理一个事件
    events = epoll.poll(1)

    for fileno, event in events:
        # 处理新连接
        if fileno == server_socket.fileno():  # 判断是否是服务器的文件描述符,如果是 调用accept()
            # 接受新连接,创建新的客户端socket对象
            connection, address = server_socket.accept()
            
            # 客户端也设置为非堵塞I/O
            connection.setblocking(False)
            
            # 将新的客户端socket对象注册到epoll对象中,以便监听其读事件
            epoll.register(connection.fileno(), select.EPOLLIN)
            
            # 将新连接保存到connections字典中
            connections[connection.fileno()] = connection
        
        # 处理已连接的客户端发送的数据
        elif event & select.EPOLLIN:
            # 读取客户端发送的数据
            data = connections[fileno].recv(1024).decode("utf-8")
            print(data)
            
            # 响应客户端请求,发送HTTP响应消息
            connections[fileno].sendall(b'HTTP/1.1 200 OK\nContent-Type: text/html\n\nHello World!')
            
            # 将socket对象的事件类型修改为写事件,以便处理下一步操作
            epoll.modify(fileno, select.EPOLLOUT)
        
        # 处理已连接的客户端关闭连接
        elif event & select.EPOLLHUP:
            # 从epoll对象中注销该socket对象
            epoll.unregister(fileno)
            
            # 关闭连接,并且从connections字典中删除该连接
            connections[fileno].close()
            del connections[fileno]
        
        # 处理已经写入完毕的连接
        elif event & select.EPOLLOUT:
            # 将socket对象的事件类型修改为读事件,以便监听客户端发来的新数据
            epoll.modify(fileno, select.EPOLLIN)

在这个例子中,我们创建了一个socket对象,并将其绑定到本地IP地址和8080端口。然后使用epoll注册该socket对象以便监听新的客户端连接。当有新的连接时,我们将其注册到epoll对象中,以便处理客户端发送的数据。我们使用select.EPOLLIN指定socket上的读事件和select.EPOLLOUT指定socket上的写事件。

在接收到来自客户端的请求消息并根据请求内容生成响应消息之后,我们使用sendall()方法将响应消息发送回客户端。在此过程中,我们使用epoll修改socket对象上的事件类型以便处理下一个事件。

此外,由于该服务器只包含单个线程,因此它可能会成为性能瓶颈,无法同时处理大量连接请求。这时可以使用多线程或多进程技术来扩展服务器性能,以便同时处理更多连接请求。