Python - 【Selección de socket】 Introducción al uso básico

I. Introducción

En Python, selectes un módulo para multiplexación de E/S asíncrona. Proporciona una manera fácil de monitorear múltiples descriptores de archivos (descriptores de archivos) para determinar cuáles de ellos están listos para lectura, escritura o excepciones. El uso del módulo de selección puede lograr una programación de red eficiente, como escuchar múltiples conexiones de clientes al mismo tiempo en el servidor o conectarse a múltiples servidores al mismo tiempo en el cliente.

2. Conexión directa ordinaria y conexión seleccionada.

La conexión se puede lograr utilizando métodos de conexión directa selectos y ordinarios, pero ambos tienen las siguientes diferencias:

1. Método de conexión directa:

Antes de enviar datos, primero debe establecer una conexión y luego enviar los datos a través de la función de envío. Este método solo puede manejar una conexión al mismo tiempo y no puede manejar múltiples conexiones al mismo tiempo.

2. Seleccione el método de conexión:

Utilice select para escuchar múltiples descriptores de archivos. Cuando ocurre un evento de lectura o escritura en cualquiera de los descriptores de archivos, la función de selección regresará y notificará al programa correspondiente para procesar. Este método puede manejar múltiples conexiones al mismo tiempo, mejorando el rendimiento de concurrencia del sistema.

2. Seleccione introducción

El módulo de selección expone tres funciones principales, select.select()、select.poll() 和 select.epoll()que pueden implementar diferentes mecanismos de multiplexación.

Una breve introducción a las tres funciones:

  • select.select (rlist, wlist, xlist, timeout): se utiliza para monitorear cambios en los descriptores de archivos y puede monitorear lectura, escritura y eventos anormales. selectLa función regresa cuando ocurre cualquiera de estos eventos . rlist、wlist和xlistSon la lista de descriptores de archivos legibles, escribibles y anormales que se monitorearán, y el tiempo de espera es el parámetro de tiempo de espera en segundos.
  • select.poll() : El escenario de uso es select.select()el mismo pero con mejor rendimiento y adecuado para monitorear una mayor cantidad de descriptores de archivos.
  • select.epoll(): Linux El mecanismo de multiplexación de E/S del sistema también es un mecanismo de multiplexación con buen rendimiento. Su ventaja en comparación con select.poll()
    es que puede admitir más conexiones.

Además de las tres funciones anteriores, también existen las funciones select.kevent() y select.kqueue(), que son adecuadas para sistemas FreeBSD.

Al utilizar el módulo de selección, debe prestar atención a los siguientes puntos:

  • Es mejor utilizar programas sin bloqueo socketpara evitar que el programa socketse bloquee mientras espera datos para socketpoder procesar otros datos.
  • selectLa función puede tener algunos problemas de rendimiento. Cuando es necesario monitorear una gran cantidad de descriptores de archivos al mismo tiempo, puede causar un uso elevado de la CPU, por lo que debe prestar atención al ajuste al usarla.

3. Ejemplos de código

A continuación se proporciona un ejemplo sencillo para demostrar cómo utilizar el módulo de selección para escuchar varios sockets al mismo tiempo:

Zócalo del servidor

import socket
import select

# 设置需要监听的 socket 地址和端口
ADDRESS = ("localhost", 9000)

# 创建一个服务器 socket,并绑定到指定地址和端口
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(ADDRESS)
server_socket.listen(5)

# 将 server_socket 设置为非阻塞模式
server_socket.setblocking(False)

# 存储已连接的 client_socket
connected_clients = []

# 开始监听
while True:
    # 使用 select 函数监听所有连接的 client_socket
    readable_sockets, _, _ = select.select([server_socket] + connected_clients,
                                           [], [], 1)

    # 处理所有可读的 socket
    for sock in readable_sockets:
        # 如果是 server_socket 表示有新的连接
        if sock is server_socket:
            client_socket, client_address = server_socket.accept()
            connected_clients.append(client_socket)
            print(f"New client connected: {
      
      client_address}")
        # 否则是已连接的 client_socket,需要处理收到的数据
        else:
            try:
                data = sock.recv(1024)
                if data:
                    print(f"Received data from {
      
      sock.getpeername()}: {
      
      data.decode()}")
                else:
                    # 如果收到的数据为空,表示连接已经断开,需要关闭 socket 并从 connected_clients 中移除
                    sock.close()
                    print(f"Client {
      
      sock.getpeername()} disconnected")
                    connected_clients.remove(sock)
            except Exception as e:
                # 出现异常,也需要关闭 socket 并从 connected_clients 中移除
                print(f"Error occurred while receiving data from {
      
      sock.getpeername()}: {
      
      e}")
                sock.close()
                connected_clients.remove(sock)

En este ejemplo, usamos el módulo de selección para monitorear server_sockety todo al mismo tiempo connected_clients. Cuando haya una nueva conexión client_socket, se agregará a connected_clientsla lista; cuando haya sockets legibles, sus operaciones relacionadas se procesarán según server_socketsi o .client_socket

Cabe señalar que al procesar un desconectado client_socket, es necesario cerrarlo y connected_clientseliminarlo ; de lo contrario, siempre existirá connected_clientsen, lo que provocará errores inesperados en el programa.

ClienteSocket

import socket
import select
import sys

# 创建5个socket对象,用于连接5个不同的服务端
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s5 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置服务端地址和端口号
server1_address = ('localhost', 9000)
server2_address = ('localhost', 9001)
server3_address = ('localhost', 9002)
server4_address = ('localhost', 9003)
server5_address = ('localhost', 9004)

# 将socket对象添加到列表中
sockets = [s1, s2, s3, s4, s5]

# 连接服务端
for sock, address in zip(sockets, [server1_address, server2_address, server3_address, server4_address, server5_address]):
    try:
        sock.connect(address)
    except Exception as e:
        print(f"Exception: {
      
      e}")
        sys.exit()

while True:
    # 使用select函数监控所有socket对象
    ready_to_read, _, _ = select.select(sockets, [], [])

    # 在任何一个可读socket对象中读取数据
    for sock in ready_to_read:
        try:
            data = sock.recv(1024)
            if data:
                print(f"Received: {
      
      data}")
            else:
                socket.close()
                sockets.remove(sock)
        except Exception as e:
            sockets.remove(sock)
            print(f"Exception: {
      
      e}")
            sock.close()

Monitoreamos selecttodos socketlos objetos usando funciones. Si hay datos para leer, los leemos y los imprimimos. Si ocurre una excepción al recibir datos, socketseliminamos el objeto de la lista sockety lo cerramos.

4. Varios servidores y varios clientes utilizan ejemplos de código seleccionados

Servidor

Uso de subprocesos múltiples para iniciar varios servidores
El siguiente es un ejemplo de código de servidor que escucha en 8001, 8002和8003puertos al mismo tiempo. Cuando el cliente se conecta exitosamente, enviará un mensaje de bienvenida al cliente. Cuando el cliente envíe información, regresará al cliente tal como está. Cuando el cliente cierre la conexión, el servidor también cerrará el mensaje correspondiente socket.

import socket
import threading

# 设置服务端地址和端口号
SERVER_ADDRESS = "localhost"
SERVER_PORT = 8001

# 监听的队列大小
LISTEN_QUEUE_SIZE = 5

# 消息欢迎消息
WELCOME_MESSAGE = "Welcome to server!"

# 为每个客户端建立相应的socket连接
def handle_client(client_socket, client_address):
    print(f"New connection from {
      
      client_address}")
    client_socket.send(WELCOME_MESSAGE.encode())

    while True:
        try:
            data = client_socket.recv(1024)
            if data:
                print(f"Received message from {
      
      client_address}: {
      
      data.decode()}")
                client_socket.send(data)
            else:
                # 关闭客户端连接
                client_socket.close()
                print(f"Connection closed by {
      
      client_address}")
                break
        except Exception as e:
            print(f"Error encountered while receiving data from {
      
      client_address}: {
      
      e}")
            client_socket.close()
            break

# 监听多个socket
def listen(address, connections):
    # 创建socket连接
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind(address)
    server_socket.listen(LISTEN_QUEUE_SIZE)

    print(f"Listening on {
      
      address[0]}:{
      
      address[1]}")
    while True:
        # 等待客户端连接
        client_socket, client_address = server_socket.accept()
        print(f"Accepted new connection from {
      
      client_address}")

        # 为客户端启动线程
        thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
        thread.daemon = True
        thread.start()

        # 将客户端的socket连接保存
        connections.append(client_socket)

# 启动监听
connections = []
threads = []
for port in range(SERVER_PORT, SERVER_PORT + 3):
    address = (SERVER_ADDRESS, port)
    thread = threading.Thread(target=listen, args=(address, connections))
    threads.append(thread)
    thread.start()

# 等待所有线程结束
for thread in threads:
    thread.join()

# 关闭所有连接
for connection in connections:
    connection.close()

En este código de muestra, utilizamos subprocesos múltiples en Python para monitorear simultáneamente las conexiones de los clientes en los puertos 8001, 8002 y 8003. Este método es más flexible y eficiente. La función handle_client() se utiliza para manejar cada conexión de cliente. Envía un mensaje de bienvenida al cliente y se lo devuelve tal como está cuando el cliente envía datos. La función listening() se utiliza para escuchar en un puerto e iniciar un nuevo hilo para cada conexión de cliente. Finalmente, en el hilo principal, iniciamos tres hilos para escuchar en diferentes puertos, esperamos a que finalizaran todos los hilos y cerramos todas las conexiones.

cliente

El siguiente programa cliente puede conectarse a varios servidores y escuchar los mensajes devueltos por cada servidor al mismo tiempo. El código maneja excepciones que pueden ocurrir al conectarse, enviar y recibir datos.

import socket
import time
import traceback

import select

# 设置服务端地址列表
SERVER_ADDRESSES = [("localhost", 8001), ("localhost", 8002), ("localhost", 8003)]


def set_socket(server_address):
    sockets = []
    for server_addr in server_address:
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.setblocking(True)
        sockets.append(client_socket)
    return sockets


def start_client(server_address):
    sockets = set_socket(server_address)
    # 连接服务端
    for index, client_socket in enumerate(sockets):
        server_address = SERVER_ADDRESSES[index]
        try:
            client_socket.connect(server_address)
        except Exception as e:
            print(f"Connect to {
      
      server_address} failed: {
      
      e}")
    start_listen(sockets)


def start_listen(sockets):
    # 使用select监听服务端
    while True:
        try:
            # 仅监听已连接的socket,获取到了数据则接收数据
            readable, writeable, errors = select.select(sockets, [], sockets)

            for socket in readable:
                try:
                    data = socket.recv(1024)
                    if data:
                        print(f"Received message from {
      
      socket.getpeername()}: {
      
      data.decode()}")
                    else:
                        # 当对端关闭连接时,对应的可读socket也会被认为是可写的
                        # 并且其recv方法将返回空字节流
                        sockets.remove(socket)
                except Exception as e:
                    print(f"Error encountered while receiving data: {
      
      e}")
                    sockets.remove(socket)

            for socket in errors:
                print(f"Error encountered on {
      
      socket.getpeername()}")
                sockets.remove(socket)
        except Exception as e:
            traceback.print_exc()
            time.sleep(1)
            start_client(SERVER_ADDRESSES)
            break


if __name__ == '__main__':
    # 创建socket连接
    start_client(SERVER_ADDRESSES)

5. Resumen

En la aplicación práctica, selectsu función y ventaja es que puede permitir que el programa monitoree múltiples conexiones, lo que resuelve el problema de que el método de conexión directa solo puede manejar una única conexión. Este método permite que el programa escuche otras conexiones mientras procesa una conexión, lo que mejora en gran medida el rendimiento de concurrencia del programa y es adecuado para escenarios de comunicación de red en entornos de alta concurrencia.

En resumen, selectel método es adecuado para escenarios de múltiples conexiones y puede mejorar la eficiencia de ejecución del programa. El método de conexión directa es adecuado para escenarios simples de conexión única.

Lo anterior es la introducción relevante sobre [selección de socket de Python], ¡espero que pueda serle útil!

Supongo que te gusta

Origin blog.csdn.net/qq_43030934/article/details/132626374
Recomendado
Clasificación