使用 Python 中的 select 模块处理多个连接

71 阅读2分钟

在编写一个网络导向的 Python 应用时,为了实现更好的性能和可扩展性,我想要使用非阻塞套接字和一个事件驱动的服务器。在阅读了一些示例后,我遇到了以下代码段:

import select
import socket
import sys

host = ''
port = 50000
backlog = 5
size = 1024
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host,port))
server.listen(backlog)
input = [server,sys.stdin]
running = 1
while running:
    inputready,outputready,exceptready = select.select(input,[],[])

    for s in inputready:

        if s == server:
            # handle the server socket
            client, address = server.accept()
            input.append(client)

        elif s == sys.stdin:
            # handle standard input
            junk = sys.stdin.readline()
            running = 0

        else:
            # handle all other sockets
            data = s.recv(size)
            if data:
                s.send(data)
            else:
                s.close()
                input.remove(s)
server.close()

其中,我遇到了以下几个问题:

  • inputready,outputready,exceptready = select.select(input,[],[]) 中,select() 函数返回了三个可能为空的列表,分别对应可读的、可写的和异常的套接字。第一参数是包含服务器套接字和标准输入的列表,但我不明白为什么在 else 块中,我们可以在不将其添加到第二个参数列表的情况下向新套接字发送数据。
  • 我们只在输入列表中循环,为什么不循环输出列表来检查哪些套接字可以写入?

2、解决方案

  • select() 函数可以用来监听多个套接字,并返回那些有活动的套接字。因此,在 else 块中,我们可以直接向新套接字发送数据,而不需要将其添加到第二个参数列表中。
  • 我们只在输入列表中循环,因为我们只对可读的套接字感兴趣。也就是那些可以从中读取数据的套接字。对于那些可写的套接字,我们不需要采取任何行动,因为我们可以随时向它们发送数据。

select 模块提供了以下三个函数:

  • select.select() 函数:用于监听多个套接字,并返回那些有活动的套接字。
  • select.poll() 函数:用于同时执行多个套接字操作,它比 select() 函数更有效。
  • select.epoll() 函数:用于同时执行多个套接字操作,它比 select() 函数和 poll() 函数更有效。

建议使用 select.epoll() 函数,因为它比其他两个函数更有效。

以下是一些关于 select() 函数的示例代码:

# 创建一个服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 8080))
server_socket.listen(5)

# 创建一个列表,其中包含服务器套接字和所有连接的套接字
sockets = [server_socket]

# 循环等待连接
while True:
    # 使用 select() 函数监听套接字
    ready_sockets, _, _ = select.select(sockets, [], [])

    # 处理服务器套接字
    if server_socket in ready_sockets:
        # 接受一个新的连接
        client_socket, client_address = server_socket.accept()

        # 将新的连接添加到列表中
        sockets.append(client_socket)

    # 处理连接的套接字
    for socket in ready_sockets:
        if socket != server_socket:
            # 从套接字中读取数据
            data = socket.recv(1024)

            # 如果没有数据,则关闭套接字
            if not data:
                socket.close()
                sockets.remove(socket)

            # 否则,将数据发送回客户端
            else:
                socket.send(data)