socketserver Module simplifies the task of writing a web server
socketserver There are four basic server classes in the module :
The above four classes are Sync Processing network requests , in other words , After processing a request , To continue processing the next request . In some cases , For example, processing a request requires a lot of computation , Or the server needs to return a large amount of data and the client is slow to process , It takes a long time to process a request , that , Simply using the above four classes is not appropriate . The solution is to create separate processes or threads to handle time-consuming tasks ,ForkingMixIn and ThreadingMixIn These two mixed classes (mix-in class) Can be used to support asynchronous Behavior .
Creating a web server requires several steps :
When mixed ThreadingMinIn Class to add multi-threaded connection behavior , You need to make it clear that when the server encounters an unexpected shutdown , How your thread should handle .ThreadingMixIn Class defines a deamon_threads attribute , This attribute determines whether the server waits for the thread to terminate . If you want your server to automatically handle waiting threads , You need to explicitly set this property , The default value for this property is False, This means that the thread is not a daemon thread ,True Indicates that the thread is a daemon thread .( When all Non-daemon thread When they all quit ,python I'll quit )
For the four basic server classes mentioned earlier , No matter what network protocol they use , Their exposed methods and properties are the same .
There are five classes in the following inheritance diagram , Four of these classes are four types of synchronization server classes
Be careful UnixDataframServer Class inherits from UDPServer, Not inherited from UnixStreamServer, The only difference between them is the address family (adress family) The difference between
class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn
Multi process and multi-threaded versions of each type of server class , You can do this by mixing the above two classes , For example, a multithreaded UDP The server class can be created in the following way :
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
pass
Mixed type (ThreadingMixIn) Must be at the top of the inheritance list , So that it can cover UDPServer Class method . By setting the properties provided by the mixed type , Can change the behavior of the underlying server mechanism .
As mentioned below ForkingMixIn Class and others Forking Classes can only support fork() Of POSIX Use under platform .
Python 3.7 New changes in version :
socketserver.ForkingMixIn.server_close()
socketserver.ThreadingMixIn.server_close()
These two methods will now wait for all child processes and non daemon threads to complete before exiting
And added a
socketserver.ForkingMixIn.block_on_close
attribute , To selectively add 3.7 Behavior before version
class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
These predefined classes are defined using the above hybrid classes
Implement a service , You need to subclass BaseRequestHandler And rewrite it handle() Method . By combining certain types of server classes , You can run many types of services . The request processor class must be different from the server class . Of course, these details can be ignored , Instead, use existing classes directly StreamRequestHandler and DatagramRequestHandler.
Of course , You still need to use your head . such as , Multiprocess server The processes of will keep their own status information copy , Changes to the status information do not affect other processes , Other processes will not be aware of the state information changes , In this case ( Need to share data ), Consider using Multithreaded server class , however , Since shared data is involved , You have to consider using Thread lock To solve Data synchronization The problem of .
On the other hand , If you're building one HTTP The server , All data of the server is stored externally ( For example, it is stored in the file system ), In this case , When the server is busy processing a request , This server will actually become a …“ Deaf person ”( It means it can't hear any other requests ), Especially when the request being processed takes a long time , Or when the client receives data very slowly , In situations like this , Using a multi-threaded or multi process server is better .
In some cases , Synchronous processing part of the request , The rest is put into a new process , It might be more appropriate . This can be done in a Synchronization server Of Request processor class Of handle() Method to explicitly create a new process to implement .
If an environment supports neither multithreading nor multiprocessing ( It may be too expensive to support these functions , Or it is not suitable for this server ), There is another way to handle highly concurrent requests : Maintain a clear record of outstanding requests , Use selectors To decide which request in the table should be processed next , Or whether to process a new request . This is especially important for servers that provide streaming services , Because its client may be connected for a long time ( If multithreading or multiprocessing is not supported ),asyncore Module provides another way to deal with such problems .
This is the parent of all other server classes in this module . It defines a common interface , Most of the underlying but not implemented methods , These methods will be implemented in subclasses . The two parameters are stored in server_address and RequestHandlerClass Properties of the .
fileno()
Returns the of the socket that the server is listening on int File descriptor of type . Usually , This function is passed to the selector (selectors), To allow listening to multiple servers in the same process .
handle_request()
Process a single request . This function calls the following methods in sequence :
get_request()
verify_request()
process_request()
If the request provided by the user handles handle() Method throws an exception , that handle_error() Method will be called .
If in timeout No requests received within seconds , be handle_timeout() Will be called ,handle_request() Returns the
serve_forever(poll_interval=0.5)
If you do not receive a definite shutdown() request , Just keep processing requests .
every other poll_interval Poll once per second .
Ignore timeout attribute .
It will also call service_actions(), This method is used by subclasses or hybrid classes to add operations specific to a particular service . such as ,ForkingMixIn Class with service_actions() To clean up zombie child processes
Python 3.3 The change of :
by serve_forever Method add service_actions Method
service_actions()
This method is serve_forever() Method is called .
This method can be overridden by subclasses or mixed classes , To provide exclusive operations for specific services , Such as cleaning operation
Python 3.3 Version newly added
shutdown()
Give Way serve_forever() Loop exit , And wait for it to exit
shutdown() Must be between and serve_forever() Call in different threads , Otherwise, it will cause Deadlock .
server_close()
Clean up the server , Can be rewritten .
address_family
This attribute indicates the protocol family to which the socket of the server belongs , Common are :
socket.AF_INET
socket.AF_UNIX
RequestHandlerClass
User provided request processor class , For each request , Will create an instance of this class .
server_address
The address the server listens to .
Different protocol families have different address formats , see socket Module documentation for more detailed information
about IP agreement , An address is a tuple with two elements : A string type IP Address and an integer type port number , for example (‘127.0.0.1’,80)
socket
Socket on which the server listens for incoming requests
The above server classes support the following class variables :
There are also server methods , Can be BaseServer Subclasses of ( such as TCPServer) Rewritten . These methods are for external users of the server object , No eggs .
This is the superclass of all request processor classes . It defines the underlying interface .
A concrete request handler subclass must implement the new handle() Method , And rewrite some other methods .
For each request , Will create a new subclass instance
these BaseRequestHandler The subclass of overridden setup() and finish() Method , And to provide self.rfile and self.wfile attribute .
self.rfile and self.wfile Property can read data separately or return data to the client .
Of these two classes rfile Property support io.BufferedIOBase Readable interface .
Of these two classes wfile Property support io.BufferedIOBase Writable interface .
Python 3.6 The change of :
StreamRequestHandler.wfile Also support io.BufferedIOBase Writable interface .
This is the server side :
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
""" Our server's request processor . For each connection , The request processor will be instantiated once , And it must be rewritten handle() Method to communicate with the client . """
def handle(self):
# self.request Is connected to the client TCP Type socket
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Only the same content is returned here , Just converted to uppercase
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create a server , Bound to the localhost Of 9999 port
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Start the server , This time keep the server running , Instruct you to use Ctrl + C To terminate the program
server.serve_forever()
An alternative to the request processor class that utilizes streams ( This is a class file object , Simple communication with the client through the same interface as the standard file object )
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile Is a class file object created by the request processor ;
# We can use readline() To replace the original recv() call
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Allied , self.wfile Is a class file object used to write data back to the client
self.wfile.write(self.data.upper())
The difference between them is : Of the second processor readline() Method will call recv() Method , Until you encounter a newline character ; However , Of the first processor recv() Method will return the sendall() Method .
This is the client :
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create socket (SOCK_STREAM representative TCP Socket )
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to the server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server , Then close it
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
The output of this example should look like this :
The server :
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
client :
$ 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
This is the server side :
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
""" This class works with TCP The processor is similar , It's just self.request Is a containing data and Binary of client socket Because there is no connection , So I'm passing through sendto() When sending data , You need to specify the address of the target """
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
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()
This is the client :
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM It represents the use of UDP Type socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see , Nothing here connect() call ; UDP No connection .
# As a substitute , Data goes directly through sendto() Send to the recipient
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
The output of this example is the same as before TCP The same version of
To build an asynchronous processor , have access to ThreadingMixIn and ForkingMixIn class .
One ThreadingMixIn Example of a class :
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 It means to select any unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread for the server , This thread will open another thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# When the main thread terminates , Exit server
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()
The output of the example should look like this :
$ 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
ForkingMixIn Class is used in a similar way , The only difference is that the server will spawn a process for each request . This only supports fork() Of POSIX Available under the platform .
The original intention of translating this document is , The translator is an English scum , However, they are learning Python This module of , It's too slow to read English documents , It's not convenient to go back and review , So just write down the Chinese in the process of learning , It's convenient to come back later
This document is based on Python 3.9.5 Translation of official documents
Original address : socketserver
I just said , The translator is an English scum , Therefore, there will inevitably be errors in the translation process , The understanding of module functions may not be comprehensive , Please correct me , Thank you first , Worship !!