Python - Introduction à l'utilisation de base de [Socket select]

Introduction

En Python, selectest un module de multiplexage d'E/S asynchrone. Il fournit un moyen simple de surveiller plusieurs descripteurs de fichiers pour déterminer lesquels d'entre eux sont prêts à être lus, écrits ou font l'objet d'une exception. À l'aide du module de sélection, vous pouvez mettre en œuvre une programmation réseau efficace, telle que la surveillance simultanée de plusieurs connexions client côté serveur ou la connexion de plusieurs serveurs côté client en même temps.

2. Connexion directe ordinaire et utilisation de la connexion sélectionnée

La connexion peut être réalisée à l'aide des méthodes de connexion directe sélective et ordinaire, mais les deux présentent les différences suivantes :

1. Méthode de connexion directe :

Avant d'envoyer des données, vous devez d'abord établir une connexion, puis envoyer les données via la fonction d'envoi. Cette méthode ne peut gérer qu’une seule connexion à la fois et ne peut pas gérer plusieurs connexions en même temps.

2. Sélectionnez la méthode de connexion :

Vous pouvez utiliser select pour surveiller plusieurs descripteurs de fichiers. Lorsqu'un événement de lecture ou d'écriture se produit sur l'un des descripteurs de fichiers, la fonction select reviendra et notifiera le programme correspondant pour le traitement. Cette méthode peut gérer plusieurs connexions en même temps, améliorant ainsi les performances de concurrence du système.

2. Sélectionnez l'introduction

Le module select expose trois fonctions principales, select.select()、select.poll() 和 select.epoll()qui peuvent implémenter différents mécanismes de multiplexage.

Une brève introduction aux trois fonctions :

  • select.select(rlist, wlist, xlist, timeout) : utilisé pour surveiller les modifications dans les descripteurs de fichiers et peut surveiller la lecture, l'écriture et les événements anormaux. selectLa fonction est renvoyée lorsque l'un de ces événements se produit . rlist、wlist和xlistIl s'agit de la liste des descripteurs de fichiers lisibles, inscriptibles et anormaux à surveiller, et timeout est le paramètre de délai d'attente en secondes.
  • select.poll() : Le scénario d'utilisation est select.select()le même mais avec de meilleures performances et adapté à la surveillance d'un plus grand nombre de descripteurs de fichiers.
  • select.epoll() : Linux Le mécanisme de multiplexage d'E/S sous le système est également un mécanisme de multiplexage avec de bonnes performances. Son avantage par rapport à select.poll()
    est qu'il peut prendre en charge plus de connexions.

En plus des trois fonctions ci-dessus, il existe également les fonctions select.kevent() et select.kqueue(), qui conviennent aux systèmes FreeBSD.

Lorsque vous utilisez le module select, vous devez faire attention aux points suivants :

  • Il est préférable d'en utiliser des non bloquants socketpour éviter que le programme socketne soit bloqué en attendant des données afin que d'autres socketdonnées puissent être traitées.
  • selectLa fonction peut avoir des problèmes de performances. Lorsqu'un grand nombre de descripteurs de fichiers doivent être surveillés en même temps, cela peut entraîner une utilisation élevée du processeur, vous devez donc faire attention au réglage lors de son utilisation.

3. Exemples de codes

Un exemple simple est donné ci-dessous pour montrer comment utiliser le module select pour écouter plusieurs sockets en même temps :

Prise de serveur

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)

Dans cet exemple, nous utilisons le module select pour écouter server_socketet tout en même temps connected_clients. Lorsqu'il y a une nouvelle connexion client_socket, elle sera ajoutée à connected_clientsla liste ; lorsqu'il y a un socket lisible, leurs opérations associées seront traitées selon que ce soit server_socketou non .client_socket

Il convient de noter que lorsqu'il s'agit d'un fichier déconnecté client_socket, vous devez le fermer et le supprimer du connected_clientsfichier , sinon il existera toujours connected_clientsdans le fichier , provoquant des erreurs inattendues dans le programme.

ClientSocket

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()

Nous selectsurveillons tous socketles objets à l'aide de fonctions. S'il y a des données à lire, nous les lisons et les imprimons. Si une exception se produit lors de la réception des données, nous socketssupprimons l'objet de la liste socketet le fermons.

4. Plusieurs serveurs et plusieurs clients utilisent des exemples de code sélectionnés

Serveur

Utilisation du multithreading pour démarrer plusieurs serveurs
Voici un exemple de code de serveur qui écoute sur 8001, 8002和8003les ports en même temps. Lorsque le client se connecte avec succès, un message de bienvenue sera envoyé au client. Lorsque le client envoie des informations, elles seront renvoyées au client inchangées. Lorsque le client ferme la connexion, le serveur fermera également le message correspondant 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()

Dans cet exemple de code, nous utilisons le multithreading en Python pour surveiller simultanément les connexions client sur les ports 8001, 8002 et 8003. Cette méthode est plus flexible et plus efficace. La fonction handle_client() est utilisée pour gérer chaque connexion client. Elle envoie un message de bienvenue au client et le renvoie au client tel qu'il est lorsque le client envoie des données. La fonction Listen() est utilisée pour écouter un port et démarrer un nouveau thread pour chaque connexion client. Enfin, dans le thread principal, nous avons démarré trois threads pour écouter différents ports, attendre la fin de tous les threads et fermer toutes les connexions.

client

Le programme client suivant peut se connecter à plusieurs serveurs et écouter les messages renvoyés par chaque serveur en même temps. Le code gère les exceptions qui peuvent survenir lors de la connexion, de l'envoi et de la réception de données.

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. Résumé

Dans les applications pratiques, selectsa fonction et son avantage sont qu'il permet au programme de surveiller plusieurs connexions, résolvant ainsi le problème selon lequel la méthode de connexion directe ne peut gérer qu'une seule connexion. Cette méthode permet au programme de surveiller d'autres connexions tout en traitant une connexion, ce qui améliore considérablement les performances de concurrence du programme et convient aux scénarios de communication réseau dans des environnements à forte concurrence.

En bref, selectla méthode convient aux scénarios multi-connexions et peut améliorer l’efficacité de fonctionnement du programme. La méthode de connexion directe convient aux scénarios simples de connexion unique.

Ce qui précède est une introduction pertinente à propos de [python socket select], j'espère que cela pourra vous être utile !

Je suppose que tu aimes

Origine blog.csdn.net/qq_43030934/article/details/132626374
conseillé
Classement