This section is mainly about the socket server of Python 3 network programming. In the last section, we talked about socket. Because socket can not support multi-user and multi-concurrency, there is socket server.
The main function of socket server is to implement concurrent processing.
There are two kinds of socket servers:
- server class: Provides many methods: binding, listening, running, etc. (that is, the process of establishing connections)
- request handle class: Focus on how to process data sent by users (that is, logic of things)
PS: In general, all services are connected first, that is to say, an instance of a service class is established, and then the user's request is processed, that is, an instance of a request processing class is established.
Next, let's look at these two classes.
Service class
There are five types of services:
- BaseServer: No direct external service.
- TCPServer: For TCP socket streams.
- UDP Server: For UDP data sockets.
- UnixStream: Not commonly used for Unix sockets.
- Unix Data Gram Server: Not commonly used for Unix sockets.
The inheritance relationship between them is as follows:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
Method of service class:
ClassSocketServer. BaseServer: This is a superclass of all server objects in the module. It defines interfaces, as described below, but most methods are not implemented and refined in subclasses. BaseServer.fileno(): Returns the integer file descriptor of the server listening socket. It is usually passed to select.select() to allow a process to monitor multiple servers. BaseServer.handle_request(): Processing a single request. Processing order: get_request(), verify_request(), process_request(). If the user provides handle() method to throw an exception, the handle_error() method of the server is called. If no request is received within self.timeout, handle_timeout() is called and handle_request() is returned. BaseServer.serve_forever(poll_interval=0.5): Processes the request until a clear shutdown() request is made. Poll shutdown once per poll_interval second. Ignore self.timeout. If you need to do periodic tasks, it is recommended to place them on other threads. BaseServer.shutdown(): Tell serve_forever() to stop the loop and wait for it to stop. Version 2.6 of python. BaseServer.address_family: Address families, such as socket.AF_INET and socket.AF_UNIX. BaseServer. RequestHandler Class: User-provided request processing class that creates instances for each request. BaseServer.server_address: The address the server is listening on. The format varies according to the address of the protocol family. See the documentation of the socket module. BaseServer.socketSocket: The server on the server that listens for incoming request socket objects. The server class supports the following class variables: BaseServer.allow_reuse_address: Does the server allow address reuse? The default is false and can be changed in subclasses. BaseServer.request_queue_size The size of the request queue. If a single request takes a long time to process, when the server is busy, requests can be placed in a queue, up to request_queue_size. Once the queue is full, the request from the client will get a Connection denied error. The default value is usually 5, but can be overridden by subclasses. BaseServer.socket_type: The socket type used by the server; socket.SOCK_STREAM and socket.SOCK_DGRAM, etc. BaseServer.timeout: Timeout, in seconds, or None means no timeout. If handle_request() does not receive a request within timeout, handle_timeout() is called. The following methods can be overloaded by subclasses, which have no effect on external users of server objects. BaseServer.finish_request(): Actually process requests from RequestHandlerClass and call its handle() method. Commonly used. BaseServer.get_request(): Accepts the socket request and returns the binary containing the new socket object to communicate with the client and the address of the client. BaseServer.handle_error(request, client_address): Called if the handle() method of RequestHandlerClass throws an exception. The default operation is to print traceback to standard output and continue processing other requests. BaseServer.handle_timeout(): timeout processing. By default, forking servers collect the exit status of the child processes, while threading servers do nothing. BaseServer.process_request(request, client_address): Call finish_request() to create an instance of RequestHandlerClass. If necessary, this function can create new processes or threads to process requests, as the ForkingMixIn and ThreadingMixIn classes do. Commonly used. BaseServer.server_activate(): Activate the server through its constructor. The default behavior is simply to listen for server sockets. It can be overloaded. BaseServer.server_bind(): Binding socket s to the desired address is called from the server's constructor. It can be overloaded. BaseServer.verify_request(request, client_address): Returns a Boolean value, if it is True, the request will be processed, and vice versa, the request will be rejected. This function can be rewritten to achieve access control to the server. The default implementation always returns True. Client_address can restrict clients, such as only handling requests for specified ip intervals. Commonly used.
These service classes process requests synchronously: one request is not processed and the next one cannot be processed. To support the asynchronous model, you can use multiple inheritance to let the server class inherit ForkingMixIn or ThreadingMixIn mix-in classes.
Forking MixIn uses multi-process (bifurcation) to achieve asynchrony.
Threading MixIn uses multithreading to achieve asynchronization.
Request Processing Class
To implement a service, you must also derive a handler class request processing class and override the handle() method of the parent class. The handle method is used specifically for processing requests. This module handles requests through a combination of service classes and request processing classes.
The request processing classes provided by the socketserver module are BaseRequestHandler, as well as its derived classes StreamRequestHandler and DatagramRequestHandler. It can be seen from the name that a processing stream socket and a processing datagram socket can be used.
The request processing class has three methods:
- setup()
- handle()
- finish()
setup()
Called before the handle() method to perform any initialization actions required. The default implementation does nothing.
Called before handle(), the main function is to perform various tasks related to initialization before processing requests. By default, nothing will be done. (If you want it to do something, you need the programmer to override this method in his request processor (because a custom request processor inherits the BaseRequestHandler provided in python, ps: mentioned below), and then add something to it.)
handle()
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.
The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.
Hand () does all the work related to processing requests. Default won't do anything. He has several instance parameters: self.request self.client_address self.server
finish()
Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.
The handle() method is called after the handle() method, and its function is to perform the cleanup after processing the request. By default, nothing will be done.
Then let's look at the source code of Handler:
Handler source codeclass BaseRequestHandler: """Base class for request handler classes. This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method. The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define arbitrary other instance variariables. """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass # The following two classes make it possible to use the same service # class for stream or datagram servers. # Each class sets up these instance variables: # - rfile: a file object from which receives the request is read # - wfile: a file object to which the reply is written # When the handle() method returns, wfile is flushed properly class StreamRequestHandler(BaseRequestHandler): """Define self.rfile and self.wfile for stream sockets.""" # Default buffer sizes for rfile, wfile. # We default rfile to buffered because otherwise it could be # really slow for large data (a getc() call per byte); we make # wfile unbuffered because (a) often after a write() we want to # read and we need to flush the line; (b) big writes to unbuffered # files are typically optimized by stdio even when big reads # aren't. rbufsize = -1 wbufsize = 0 # A timeout to apply to the request socket, if not None. timeout = None # Disable nagle algorithm for this socket, if True. # Use only when wbufsize != 0, to avoid small packets. disable_nagle_algorithm = False def setup(self): self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # An final socket error may have occurred here, such as # the local error ECONNABORTED. pass self.wfile.close() self.rfile.close() class DatagramRequestHandler(BaseRequestHandler): # XXX Regrettably, I cannot get this working on Linux; # s.recvfrom() doesn't return a meaningful client address. """Define self.rfile and self.wfile for datagram sockets.""" def setup(self): from io import BytesIO self.packet, self.socket = self.request self.rfile = BytesIO(self.packet) self.wfile = BytesIO() def finish(self): self.socket.sendto(self.wfile.getvalue(), self.client_address)