I. Einleitung
In Python select
handelt 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.
select
Die Funktion kehrt zurück , wenn eines dieser Ereignisse eintritt .rlist、wlist和xlist
Dabei 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überselect.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,
socket
um zu vermeiden, dass das Programmsocket
beim Warten auf Daten blockiert wird, sodass anderesocket
Daten verarbeitet werden können. select
Die 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_socket
In 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_clients
Liste hinzugefügt. Wenn ein lesbarer Socket vorhanden ist, werden die zugehörigen Vorgänge entsprechend verarbeitet ob server_socket
oder nicht .client_socket
Es ist zu beachten, dass client_socket
Sie beim Umgang mit einer nicht verbundenen Datei diese schließen und aus connected_clients
der , da sie sonst immer connected_clients
in 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 socket
Objekte 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, sockets
entfernen wir das Objekt aus der Liste socket
und 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和8003
Ports 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 select
die 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, select
die 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!