socketserver
— Ett ramverk för nätverksservrar¶
Källkod: Lib/socketserver.py
Modulen socketserver
förenklar arbetet med att skriva nätverksservrar.
Tillgänglighet: not WASI.
Den här modulen fungerar inte eller är inte tillgänglig på WebAssembly. Se WebAssembly-plattformar för mer information.
Det finns fyra grundläggande konkreta serverklasser:
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Detta använder TCP-protokollet för internet, vilket möjliggör kontinuerliga dataströmmar mellan klienten och servern. Om bind_and_activate är true försöker konstruktören automatiskt att anropa
server_bind()
ochserver_activate()
. De andra parametrarna skickas till basklassenBaseServer
.
- class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Detta använder datagram, som är diskreta paket med information som kan anlända i fel ordning eller förloras under transporten. Parametrarna är desamma som för
TCPServer
.
- class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
- class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Dessa mer sällan använda klasser liknar TCP- och UDP-klasserna, men använder Unix-domänuttag; de är inte tillgängliga på plattformar som inte är Unix. Parametrarna är desamma som för
TCPServer
.
Dessa fyra klasser behandlar förfrågningar synkront; varje förfrågan måste slutföras innan nästa förfrågan kan startas. Detta är inte lämpligt om varje begäran tar lång tid att slutföra, eftersom den kräver mycket beräkningar eller eftersom den returnerar mycket data som klienten är långsam att bearbeta. Lösningen är att skapa en separat process eller tråd för att hantera varje begäran; mix-in-klasserna ForkingMixIn
och ThreadingMixIn
kan användas för att stödja asynkront beteende.
Att skapa en server kräver flera steg. Först måste du skapa en klass för hantering av begäranden genom att subklassa klassen BaseRequestHandler
och åsidosätta dess metod handle()
; denna metod kommer att behandla inkommande begäranden. För det andra måste du instansiera en av serverklasserna och skicka serverns adress och request handler-klassen till den. Det rekommenderas att använda servern i en with
-sats. Anropa sedan metoden handle_request()
eller serve_forever()
i serverobjektet för att behandla en eller flera förfrågningar. Slutligen anropa server_close()
för att stänga socket (om du inte använde en with
-sats).
När du ärver från ThreadingMixIn
för trådat anslutningsbeteende bör du uttryckligen deklarera hur du vill att dina trådar ska bete sig vid en plötslig avstängning. Klassen ThreadingMixIn
definierar ett attribut daemon_threads, som anger om servern ska vänta på att tråden avslutas eller inte. Du bör sätta flaggan explicit om du vill att trådarna ska bete sig självständigt; standardvärdet är False
, vilket innebär att Python inte kommer att avslutas förrän alla trådar som skapats av ThreadingMixIn
har avslutats.
Serverklasser har samma externa metoder och attribut, oavsett vilket nätverksprotokoll de använder.
Anteckningar om skapande av server¶
Det finns fem klasser i ett arvsdiagram, varav fyra representerar synkrona servrar av fyra typer:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Observera att UnixDatagramServer
härstammar från UDPServer
, inte från UnixStreamServer
— den enda skillnaden mellan en IP- och en Unix-server är adressfamiljen.
- class socketserver.ForkingMixIn¶
- class socketserver.ThreadingMixIn¶
Forking- och threading-versioner av varje typ av server kan skapas med hjälp av dessa mix-in-klasser. Till exempel skapas
ThreadingUDPServer
på följande sätt:klass ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
Mix-in-klassen kommer först, eftersom den åsidosätter en metod som definieras i
UDPServer
. Genom att ställa in de olika attributen ändras också beteendet hos den underliggande servermekanismen.ForkingMixIn
och de Forking-klasser som nämns nedan är endast tillgängliga på POSIX-plattformar som stöderfork()
.- block_on_close¶
ForkingMixIn.server_close
väntar tills alla underordnade processer har slutförts, förutom om attributetblock_on_close
ärFalse
.ThreadingMixIn.server_close
väntar tills alla icke-daemon-trådar har avslutats, förutom om attributetblock_on_close
ärFalse
.
- max_children¶
Ange hur många underordnade processer som ska finnas för att hantera begäranden åt gången för
ForkingMixIn
. Om gränsen nås kommer nya förfrågningar att vänta tills en underordnad process har avslutats.
- daemon_threads¶
För
ThreadingMixIn
använd daemoniska trådar genom att ställa inThreadingMixIn.daemon_threads
tillTrue
för att inte vänta tills trådarna är klara.
Ändrad i version 3.7:
ForkingMixIn.server_close
ochThreadingMixIn.server_close
väntar nu tills alla underordnade processer och icke-daemoniska trådar har slutförts. Lägg till ett nytt klassattributForkingMixIn.block_on_close
för att välja beteendet från före 3.7.
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
Dessa klasser är fördefinierade med hjälp av mix-in-klasserna.
Tillagd i version 3.12: Klasserna ForkingUnixStreamServer
och ForkingUnixDatagramServer
lades till.
För att implementera en tjänst måste du härleda en klass från BaseRequestHandler
och omdefiniera dess handle()
-metod. Du kan sedan köra olika versioner av tjänsten genom att kombinera en av serverklasserna med din request handler-klass. Klassen för hantering av begäran måste vara olika för datagram- och strömtjänster. Detta kan döljas genom att använda hanterarsubklasserna StreamRequestHandler
eller DatagramRequestHandler
.
Naturligtvis måste du fortfarande använda ditt huvud! Det är till exempel inte meningsfullt att använda en forking-server om tjänsten innehåller tillstånd i minnet som kan ändras av olika förfrågningar, eftersom ändringarna i barnprocessen aldrig skulle nå det ursprungliga tillståndet som förvaras i moderprocessen och skickas till varje barn. I det här fallet kan du använda en threading-server, men du måste förmodligen använda lås för att skydda integriteten hos de delade data.
Om du å andra sidan bygger en HTTP-server där all data lagras externt (t.ex. i filsystemet), kommer en synkron klass i princip att göra tjänsten ”döv” medan en förfrågan hanteras - vilket kan ta mycket lång tid om en klient är långsam med att få all data som den har begärt. Här är en threading- eller forking-server lämplig.
I vissa fall kan det vara lämpligt att behandla en del av en begäran synkront, men att avsluta behandlingen i en förgrenad underordnad del beroende på begärandedata. Detta kan implementeras genom att använda en synkron server och göra en explicit förgrening i klassen för hantering av begäran handle()
method.
En annan metod för att hantera flera samtidiga förfrågningar i en miljö som varken stöder trådar eller fork()
(eller där dessa är för dyra eller olämpliga för tjänsten) är att upprätthålla en explicit tabell över delvis avslutade förfrågningar och att använda selectors
för att bestämma vilken förfrågan som ska bearbetas härnäst (eller om en ny inkommande förfrågan ska hanteras). Detta är särskilt viktigt för strömtjänster där varje klient potentiellt kan vara ansluten under lång tid (om trådar eller underprocesser inte kan användas).
Serverobjekt¶
- class socketserver.BaseServer(server_address, RequestHandlerClass)¶
Detta är superklassen för alla Server-objekt i modulen. Den definierar det gränssnitt som anges nedan, men implementerar inte de flesta metoderna, vilket görs i underklasser. De två parametrarna lagras i respektive attribut
server_address
ochRequestHandlerClass
.- fileno()¶
Returnerar en heltalsfilbeskrivning för det uttag som servern lyssnar på. Denna funktion skickas oftast till
selectors
, för att möjliggöra övervakning av flera servrar i samma process.
- handle_request()¶
Behandla en enskild begäran. Denna funktion anropar följande metoder i ordning:
get_request()
,verify_request()
, ochprocess_request()
. Om den användartillhandahållna metodenhandle()
i klassen handler ger upphov till ett undantag, anropas serverns metodhandle_error()
. Om ingen begäran tas emot inomtimeout
sekunder, anropashandle_timeout()
ochhandle_request()
returneras.
- serve_forever(poll_interval=0.5)¶
Hantera förfrågningar fram till en explicit
shutdown()
-förfrågan. Pollar för avstängning varje poll_interval sekund. Ignorerar attributettimeout
. Den anropar ocksåservice_actions()
, som kan användas av en subklass eller mixin för att tillhandahålla åtgärder som är specifika för en viss tjänst. Till exempel använder klassenForkingMixIn
service_actions()
för att rensa upp zombiebarnprocesser.Ändrad i version 3.3: Lagt till anropet
service_actions
till metodenserve_forever
.
- service_actions()¶
Detta anropas i
serve_forever()
-loopen. Denna metod kan åsidosättas av subklasser eller mixin-klasser för att utföra åtgärder som är specifika för en viss tjänst, t.ex. rensningsåtgärder.Tillagd i version 3.3.
- shutdown()¶
Be
serve_forever()
-loopen att sluta och vänta tills den gör det.shutdown()
måste anropas medanserve_forever()
körs i en annan tråd annars kommer den att låsa sig.
- server_close()¶
Städa upp på servern. Kan åsidosättas.
- address_family¶
Den familj av protokoll som serverns socket tillhör. Vanliga exempel är
socket.AF_INET
,socket.AF_INET6
ochsocket.AF_UNIX
. Underordna TCP- eller UDP-serverklasserna i den här modulen med klassattributetaddress_family = AF_INET6
inställt om du vill ha IPv6-serverklasser.
- RequestHandlerClass¶
Den användartillhandahållna klassen för hantering av begäran; en instans av denna klass skapas för varje begäran.
- server_address¶
Den adress som servern lyssnar på. Adressernas format varierar beroende på protokollfamilj; se dokumentationen för modulen
socket
för mer information. För internetprotokoll är detta en tupel som innehåller en sträng som anger adressen och ett heltals portnummer:('127.0.0.1', 80)
, till exempel.
- socket¶
Det socket-objekt som servern ska lyssna på för inkommande förfrågningar.
Serverklasserna har stöd för följande klassvariabler:
- allow_reuse_address¶
Om servern tillåter återanvändning av en adress. Standardvärdet är
False
, och kan ställas in i underklasser för att ändra policyn.
- request_queue_size¶
Storleken på förfrågningskön. Om det tar lång tid att behandla en enskild begäran placeras alla begäranden som kommer in medan servern är upptagen i en kö, upp till
request_queue_size
begäranden. När kön är full kommer ytterligare förfrågningar från klienter att få felmeddelandet ”Connection denied”. Standardvärdet är vanligtvis 5, men detta kan åsidosättas av underklasser.
- socket_type¶
Den typ av socket som används av servern;
socket.SOCK_STREAM
ochsocket.SOCK_DGRAM
är två vanliga värden.
- timeout¶
Timeout-tid, mätt i sekunder, eller
None
om ingen timeout önskas. Omhandle_request()
inte får några inkommande förfrågningar inom timeout-perioden anropas metodenhandle_timeout()
.
Det finns olika servermetoder som kan åsidosättas av underklasser av basserverklasser som
TCPServer
; dessa metoder är inte användbara för externa användare av serverobjektet.- finish_request(request, client_address)¶
Behandlar faktiskt begäran genom att instansiera
RequestHandlerClass
och anropa desshandle()
-metod.
- get_request()¶
Måste acceptera en begäran från sockeln och returnera en 2-tupel som innehåller det nya socketobjektet som ska användas för att kommunicera med klienten och klientens adress.
- handle_error(request, client_address)¶
Denna funktion anropas om metoden
handle()
i en instans avRequestHandlerClass
ger upphov till ett undantag. Standardåtgärden är att skriva ut spårningen till standardfelet och fortsätta att hantera ytterligare förfrågningar.Ändrad i version 3.6: Anropas nu endast för undantag som härrör från klassen
Exception
.
- handle_timeout()¶
Denna funktion anropas när attributet
timeout
har angetts till ett annat värde änNone
och timeout-perioden har löpt ut utan att några begäranden har tagits emot. Standardåtgärden för forking-servrar är att samla in status för alla underordnade processer som har avslutats, medan den här metoden inte gör någonting i threading-servrar.
- process_request(request, client_address)¶
Anropar
finish_request()
för att skapa en instans avRequestHandlerClass
. Om så önskas kan denna funktion skapa en ny process eller tråd för att hantera begäran; klassernaForkingMixIn
ochThreadingMixIn
gör detta.
- server_activate()¶
Anropas av serverkonstruktören för att aktivera servern. Standardbeteendet för en TCP-server anropar bara
listen()
på serverns socket. Kan åsidosättas.
- server_bind()¶
Anropas av serverkonstruktören för att binda sockeln till önskad adress. Kan åsidosättas.
- verify_request(request, client_address)¶
Måste returnera ett booleskt värde; om värdet är
True
kommer begäran att behandlas, och om det ärFalse
kommer begäran att nekas. Denna funktion kan åsidosättas för att implementera åtkomstkontroller för en server. Standardimplementeringen returnerar alltidTrue
.
Ändrad i version 3.6: Stöd för protokollet context manager har lagts till. Att avsluta kontexthanteraren är likvärdigt med att anropa
server_close()
.
Objekt för hantering av begäran¶
- class socketserver.BaseRequestHandler¶
Detta är superklassen för alla request handler-objekt. Den definierar det gränssnitt som anges nedan. En konkret subklass av request handler måste definiera en ny
handle()
-metod och kan åsidosätta alla andra metoder. En ny instans av subklassen skapas för varje begäran.- setup()¶
Anropas före
handle()
-metoden för att utföra eventuella initialiseringsåtgärder som krävs. Standardimplementationen gör ingenting.
- handle()¶
Denna funktion måste göra allt arbete som krävs för att hantera en begäran. Standardimplementationen gör ingenting. Flera instansattribut är tillgängliga för den; begäran är tillgänglig som
request
; klientadressen somclient_address
; och serverinstansen somserver
, om den behöver tillgång till information per server.Typen av
request
är olika för datagram- och strömtjänster. För strömtjänster ärrequest
ett socket-objekt; för datagramtjänster ärrequest
ett par av string och socket.
- finish()¶
Anropas efter
handle()
-metoden för att utföra eventuella rengöringsåtgärder som krävs. Standardimplementationen gör ingenting. Omsetup()
ger upphov till ett undantag, kommer denna funktion inte att anropas.
- request¶
Det nya
socket.socket
-objektet som ska användas för att kommunicera med klienten.
- client_address¶
Klientadress som returneras av
BaseServer.get_request()
.
- server¶
BaseServer
-objekt som används för att hantera begäran.
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
Dessa
BaseRequestHandler
subklasser åsidosätter metodernasetup()
ochfinish()
, och tillhandahåller attributenrfile
ochwfile
.- rfile¶
Ett filobjekt som tar emot begäran läses från. Stöder det läsbara gränssnittet
io.BufferedIOBase
.
- wfile¶
Ett filobjekt som svaret skrivs till. Stöder det skrivbara gränssnittet
io.BufferedIOBase
Ändrad i version 3.6:
wfile
stöder också det skrivbara gränssnittetio.BufferedIOBase
.
Exempel¶
socketserver.TCPServer
Exempel¶
Detta är serversidan:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
# after we return, the socket will be closed.
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
En alternativ klass för hantering av förfrågningar som använder sig av strömmar (filliknande objekt som förenklar kommunikation genom att tillhandahålla standardgränssnittet för filer):
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler.
# We can now use e.g. readline() instead of raw recv() calls.
# We limit ourselves to 10000 bytes to avoid abuse by the sender.
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
Skillnaden är att readline()
-anropet i den andra hanteraren kommer att anropa recv()
flera gånger tills det stöter på ett radbrytningstecken, vidare än den första hanteraren var tvungen att använda en recv()
-slinga för att ackumulera data tills ett radbrytningstecken uppstod. Om den bara hade använt ett enda recv()
utan slingan skulle den bara ha returnerat det som hittills hade mottagits från klienten. TCP är strömbaserat: data anländer i den ordning de skickades, men det finns inget samband mellan klientens send()
eller sendall()
-anrop och antalet recv()
-anrop på servern som krävs för att ta emot dem.
Detta är klientsidan:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data, "utf-8"))
sock.sendall(b"\n")
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Utdata från exemplet bör se ut ungefär så här:
Server:
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
Klient:
$ python TCPClient.py hello world with TCP
Sent: hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent: python is nice
Received: PYTHON IS NICE
socketserver.UDPServer
Exempel¶
Detta är serversidan:
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print(f"{self.client_address[0]} wrote:")
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
Detta är klientsidan:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Utdata från exemplet ska se ut precis som i exemplet med TCP-servern.
Asynkrona mixins¶
Om du vill bygga asynkrona hanterare använder du klasserna ThreadingMixIn
och ForkingMixIn
.
Ett exempel för klassen ThreadingMixIn
:
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
Utdata från exemplet bör se ut ungefär så här:
$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
Klassen ForkingMixIn
används på samma sätt, med skillnaden att servern skapar en ny process för varje förfrågan. Endast tillgänglig på POSIX-plattformar som stöder fork()
.