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() och server_activate(). De andra parametrarna skickas till basklassen BaseServer.

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öder fork().

block_on_close

ForkingMixIn.server_close väntar tills alla underordnade processer har slutförts, förutom om attributet block_on_close är False.

ThreadingMixIn.server_close väntar tills alla icke-daemon-trådar har avslutats, förutom om attributet block_on_close är False.

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 in ThreadingMixIn.daemon_threads till True för att inte vänta tills trådarna är klara.

Ändrad i version 3.7: ForkingMixIn.server_close och ThreadingMixIn.server_close väntar nu tills alla underordnade processer och icke-daemoniska trådar har slutförts. Lägg till ett nytt klassattribut ForkingMixIn.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 och RequestHandlerClass.

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(), och process_request(). Om den användartillhandahållna metoden handle() i klassen handler ger upphov till ett undantag, anropas serverns metod handle_error(). Om ingen begäran tas emot inom timeout sekunder, anropas handle_timeout() och handle_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 attributet timeout. 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 klassen ForkingMixIn service_actions() för att rensa upp zombiebarnprocesser.

Ändrad i version 3.3: Lagt till anropet service_actions till metoden serve_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 medan serve_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 och socket.AF_UNIX. Underordna TCP- eller UDP-serverklasserna i den här modulen med klassattributet address_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 och socket.SOCK_DGRAM är två vanliga värden.

timeout

Timeout-tid, mätt i sekunder, eller None om ingen timeout önskas. Om handle_request() inte får några inkommande förfrågningar inom timeout-perioden anropas metoden handle_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 dess handle()-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 av RequestHandlerClass 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 än None 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 av RequestHandlerClass. Om så önskas kan denna funktion skapa en ny process eller tråd för att hantera begäran; klasserna ForkingMixIn och ThreadingMixIn 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 är False kommer begäran att nekas. Denna funktion kan åsidosättas för att implementera åtkomstkontroller för en server. Standardimplementeringen returnerar alltid True.

Ä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 som client_address; och serverinstansen som server, 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 är request ett socket-objekt; för datagramtjänster är request 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. Om setup() 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 metoderna setup() och finish(), och tillhandahåller attributen rfile och wfile.

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änssnittet io.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().