Python - [Socket-Auswahl] Einführung in die grundlegende Verwendung

I. Einleitung

In Python selecthandelt es sich um ein Modul für asynchrones E/A-Multiplexing. Es bietet eine einfache Möglichkeit, mehrere Dateideskriptoren zu überwachen, um festzustellen, welche davon zum Lesen oder Schreiben bereit sind oder eine Ausnahme haben. Mit dem Select-Modul können Sie eine effiziente Netzwerkprogrammierung implementieren, z. B. die gleichzeitige Überwachung mehrerer Clientverbindungen auf der Serverseite oder die gleichzeitige Verbindung mehrerer Server auf der Clientseite.

2. Gewöhnliche Direktverbindung und Nutzung einer ausgewählten Verbindung

Zum Herstellen einer Verbindung können sowohl ausgewählte als auch normale Direktverbindungsmethoden verwendet werden, die beiden weisen jedoch die folgenden Unterschiede auf:

1. Direktverbindungsmethode:

Bevor Sie Daten senden, müssen Sie zunächst eine Verbindung herstellen und dann die Daten über die Sendefunktion senden. Diese Methode kann nur eine Verbindung gleichzeitig verarbeiten und nicht mehrere Verbindungen gleichzeitig.

2. Wählen Sie die Verbindungsmethode:

Sie können Select verwenden, um mehrere Dateideskriptoren zu überwachen. Wenn bei einem der Dateideskriptoren ein Lese- oder Schreibereignis auftritt, kehrt die Select-Funktion zurück und benachrichtigt das entsprechende Programm zur Verarbeitung. Diese Methode kann mehrere Verbindungen gleichzeitig verarbeiten und verbessert so die Parallelitätsleistung des Systems.

2. Wählen Sie Einführung

Das Auswahlmodul stellt drei Hauptfunktionen bereit, select.select()、select.poll() 和 select.epoll()die verschiedene Multiplexmechanismen implementieren können.

Eine kurze Einführung in die drei Funktionen:

  • select.select(rlist, wlist, xlist, timeout): Wird zur Überwachung von Änderungen in Dateideskriptoren verwendet und kann Lese-, Schreib- und abnormale Ereignisse überwachen. selectDie Funktion kehrt zurück , wenn eines dieser Ereignisse eintritt . rlist、wlist和xlistDabei handelt es sich um die Liste der zu überwachenden lesbaren, beschreibbaren und abnormalen Dateideskriptoren. Timeout ist der Timeout-Parameter in Sekunden.
  • select.poll() : Das Verwendungsszenario ist select.select()das gleiche, jedoch mit besserer Leistung und geeignet für die Überwachung einer größeren Anzahl von Dateideskriptoren.
  • select.epoll(): Linux Der E/A-Multiplexmechanismus unter dem System ist ebenfalls ein Multiplexmechanismus mit guter Leistung. Sein Vorteil gegenüber select.poll()
    besteht darin, dass es mehr Verbindungen unterstützen kann.

Zusätzlich zu den oben genannten drei Funktionen gibt es die Funktionen select.kevent() und select.kqueue(), die für FreeBSD-Systeme geeignet sind.

Bei der Verwendung des Select-Moduls müssen Sie folgende Punkte beachten:

  • Es ist am besten, die Nichtblockierung zu verwenden, socketum zu vermeiden, dass das Programm socketbeim Warten auf Daten blockiert wird, sodass andere socketDaten verarbeitet werden können.
  • selectDie Funktion weist möglicherweise einige Leistungsprobleme auf. Wenn eine große Anzahl von Dateideskriptoren gleichzeitig überwacht werden muss, kann dies zu einer hohen CPU-Auslastung führen. Daher müssen Sie bei der Verwendung auf die Optimierung achten.

3. Codebeispiel

Nachfolgend finden Sie ein einfaches Beispiel, um zu demonstrieren, wie Sie mit dem Select-Modul mehrere Sockets gleichzeitig abhören können:

Server-Socket

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)

server_socketIn diesem Beispiel verwenden wir das Auswahlmodul, um alle gleichzeitig abzuhören connected_clients. Wenn eine neue client_socket-Verbindung vorhanden ist, wird diese zur connected_clientsListe hinzugefügt. Wenn ein lesbarer Socket vorhanden ist, werden die zugehörigen Vorgänge entsprechend verarbeitet ob server_socketoder nicht .client_socket

Es ist zu beachten, dass client_socketSie beim Umgang mit einer nicht verbundenen Datei diese schließen und aus connected_clientsder , da sie sonst immer connected_clientsin der Datei vorhanden ist und unerwartete Fehler im Programm verursacht.

Client-Socket

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

Wir selectüberwachen alle socketObjekte mithilfe von Funktionen. Wenn Daten zum Lesen vorhanden sind, lesen wir die Daten und drucken sie aus. Wenn beim Empfangen von Daten eine Ausnahme auftritt, socketsentfernen wir das Objekt aus der Liste socketund schließen es.

4. Mehrere Server und mehrere Clients verwenden ausgewählte Codebeispiele

Server

Starten mehrerer Server mithilfe von Multithreading
Nachfolgend finden Sie ein Beispiel für Servercode, der 8001, 8002和8003Ports gleichzeitig überwacht. Wenn der Client erfolgreich eine Verbindung herstellt, wird eine Willkommensnachricht an den Client gesendet. Wenn der Client Informationen sendet, werden diese unverändert an den Client zurückgegeben. Wenn der Client die Verbindung schließt, schließt der Server auch die entsprechende Nachricht 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()

In diesem Beispielcode verwenden wir Multithreading in Python, um gleichzeitig Clientverbindungen an den Ports 8001, 8002 und 8003 zu überwachen. Diese Methode ist flexibler und effizienter. Die Funktion handle_client() wird verwendet, um jede Clientverbindung zu verwalten. Sie sendet eine Willkommensnachricht an den Client und gibt sie so an den Client zurück, wie sie ist, wenn der Client Daten sendet. Die Funktion listen() wird verwendet, um einen Port abzuhören und für jede Client-Verbindung einen neuen Thread zu starten. Schließlich haben wir im Hauptthread drei Threads gestartet, um verschiedene Ports abzuhören, auf das Ende aller Threads gewartet und alle Verbindungen geschlossen.

Klient

Das folgende Clientprogramm kann eine Verbindung zu mehreren Servern herstellen und gleichzeitig die von jedem Server zurückgegebenen Nachrichten abhören. Die Ausnahmen, die beim Verbinden, Senden und Empfangen von Daten auftreten können, werden im Code behandelt.

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)

V. Zusammenfassung

In praktischen Anwendungen besteht selectdie Funktion und der Vorteil darin, dass das Programm mehrere Verbindungen überwachen kann, wodurch das Problem gelöst wird, dass die direkte Verbindungsmethode nur eine einzige Verbindung verarbeiten kann. Mit dieser Methode kann das Programm andere Verbindungen überwachen, während eine Verbindung verarbeitet wird, was die Parallelitätsleistung des Programms erheblich verbessert und sich für Netzwerkkommunikationsszenarien in Umgebungen mit hoher Parallelität eignet.

Kurz gesagt, selectdie Methode eignet sich für Szenarien mit mehreren Verbindungen und kann die Betriebseffizienz des Programms verbessern. Die Direktverbindungsmethode eignet sich für einfache Einzelverbindungsszenarien.

Das Obige ist die relevante Einführung zu [Python-Socket-Auswahl], ich hoffe, es kann Ihnen hilfreich sein!

Ich denke du magst

Origin blog.csdn.net/qq_43030934/article/details/132626374
Empfohlen
Rangfolge