本节主要是讲解python3网络编程之socketserver,在上一节中我们讲到了socket。由于socket无法支持多用户和多并发,于是就有了socket server。

socket server最主要的作用就是实现并发处理。

socketserver中包含了两种类:

  1. 服务类(server class):提供了许多方法:像绑定,监听,运行等等(也就是建立连接的过程)
  2. 请求处理类(request handle class):专注于如何处理用户所发送的数据(也就是事物逻辑)

PS:一般情况下,所有的服务,都是先建立连接,也就是建立一个服务类的实例,然后处理用户的请求,也就是建立一个请求处理类的实例。

接下来就看一下这两个类。

服务类

服务类有五种类型:

  1. BaseServer:不直接对外服务。
  2. TCPServer:针对TCP套接字流。
  3. UDPServer:针对UDP数据套接字。
  4. UnixStream:针对Unix套接字,不常用。
  5. UnixDatagramServer:针对Unix套接字,不常用。

他们之间的继承关系如下:

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

 服务类的方法:

class SocketServer.BaseServer:这是模块中的所有服务器对象的超类。它定义了接口,如下所述,但是大多数的方法不实现,在子类中进行细化。

    BaseServer.fileno():返回服务器监听套接字的整数文件描述符。通常用来传递给select.select(), 以允许一个进程监视多个服务器。

    BaseServer.handle_request():处理单个请求。处理顺序:get_request(), verify_request(), process_request()。如果用户提供handle()方法抛出异常,将调用服务器的handle_error()方法。如果self.timeout内没有请求收到, 将调用handle_timeout()并返回handle_request()。

    BaseServer.serve_forever(poll_interval=0.5): 处理请求,直到一个明确的shutdown()请求。每poll_interval秒轮询一次shutdown。忽略self.timeout。如果你需要做周期性的任务,建议放置在其他线程。

    BaseServer.shutdown():告诉serve_forever()循环停止并等待其停止。python2.6版本。

    BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。

    BaseServer.RequestHandlerClass:用户提供的请求处理类,这个类为每个请求创建实例。

    BaseServer.server_address:服务器侦听的地址。格式根据协议家族地址的各不相同,请参阅socket模块的文档。

    BaseServer.socketSocket:服务器上侦听传入的请求socket对象的服务器。

服务器类支持下面的类变量:

    BaseServer.allow_reuse_address:服务器是否允许地址的重用。默认为false ,并且可在子类中更改。

    BaseServer.request_queue_size

请求队列的大小。如果单个请求需要很长的时间来处理,服务器忙时请求被放置到队列中,最多可以放request_queue_size个。一旦队列已满,来自客户端的请求将得到 “Connection denied”错误。默认值通常为5 ,但可以被子类覆盖。

    BaseServer.socket_type:服务器使用的套接字类型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。

    BaseServer.timeout:超时时间,以秒为单位,或 None表示没有超时。如果handle_request()在timeout内没有收到请求,将调用handle_timeout()。

下面方法可以被子类重载,它们对服务器对象的外部用户没有影响。

    BaseServer.finish_request():实际处理RequestHandlerClass发起的请求并调用其handle()方法。 常用。

    BaseServer.get_request():接受socket请求,并返回二元组包含要用于与客户端通信的新socket对象,以及客户端的地址。

    BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法抛出异常时调用。默认操作是打印traceback到标准输出,并继续处理其他请求。

    BaseServer.handle_timeout():超时处理。默认对于forking服务器是收集退出的子进程状态,threading服务器则什么都不做。

    BaseServer.process_request(request, client_address) :调用finish_request()创建RequestHandlerClass的实例。如果需要,此功能可以创建新的进程或线程来处理请求,ForkingMixIn和ThreadingMixIn类做到这点。常用。

    BaseServer.server_activate():通过服务器的构造函数来激活服务器。默认的行为只是监听服务器套接字。可重载。

    BaseServer.server_bind():通过服务器的构造函数中调用绑定socket到所需的地址。可重载。

    BaseServer.verify_request(request, client_address):返回一个布尔值,如果该值为True ,则该请求将被处理,反之请求将被拒绝。此功能可以重写来实现对服务器的访问控制。默认的实现始终返回True。client_address可以限定客户端,比如只处理指定ip区间的请求。 常用。

这个几个服务类都是同步处理请求的:一个请求没处理完不能处理下一个请求。要想支持异步模型,可以利用多继承让server类继承ForkingMixIn 或 ThreadingMixIn mix-in classes。

ForkingMixIn利用多进程(分叉)实现异步。

ThreadingMixIn利用多线程实现异步。

请求处理类

要实现一项服务,还必须派生一个handler class请求处理类,并重写父类的handle()方法。handle方法就是用来专门是处理请求的。该模块是通过服务类和请求处理类组合来处理请求的。

socketserver模块提供的请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler。从名字看出可以一个处理流式套接字,一个处理数据报套接字。

请求处理类有三种方法:

  1. setup()
  2. handle()
  3. finish()

setup()  

  Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

  也就是在handle()之前被调用,主要的作用就是执行处理请求之前的初始化相关的各种工作。默认不会做任何事。(如果想要让其做一些事的话,就要程序员在自己的请求处理器中覆盖这个方法(因为一般自定义的请求处理器都要继承python中提供的BaseRequestHandler,ps:下文会提到的),然后往里面添加东西即可)

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.

  handle()的工作就是做那些所有与处理请求相关的工作。默认也不会做任何事。他有数个实例参数: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.

  在handle()方法之后会被调用,他的作用就是执行当处理完请求后的清理工作,默认不会做任何事

然后我们来看一下Handler的源码:

class 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)

Handler源码

从源码中可以看出,BaseRequestHandler中的setup()/handle()/finish()什么内容都没有定义,而他的两个派生类StreamRequestHandler和DatagramRequestHandler则都重写了setup()/finish()。

因此当我们需要自己编写socketserver程序时,只需要合理选择StreamRequestHandler和DatagramRequestHandler之中的一个作为父类,然后自定义一个请求处理类,并在其中重写handle()方法即可。

创建一个socketserver的步骤:

  • 自己创建一个请求处理类,并且这个类要继承BaseRequestHandler,并且还要重写父类里面的handle()方法。

这个子类用来处理客户端的请求

与客户端所有的交互都是在handle()方法中重写

  • 实例化一个server(如TCPServer)类,并且讲Server_IP和上一步创建的子类传给这个实例化的类(此处是TCPServer)作为参数。
  • 调用第二步实例化出来的对象的方法,这里假定这个实例化出来的对象为server。

server.handle_request() # 只处理一个请求,处理完后退出

server.serve_forever() # 处理多个请求,永远执行

  • 调用close()方法关闭server。

代码示例:

服务器端:

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
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()

服务器端

客户端:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")
finally:
    sock.close()

print("Sent:     {}".format(data))
print("Received: {}".format(received))

客户端

但你发现,上面的代码,依然不能同时处理多个连接。

让你的socketserver并发起来, 必须选择使用以下一个多并发的类

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer

class socketserver.ThreadingUDPServer

所以,只需要把下面这一句

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

换成下面这个,就可以多并发了,这样,客户端每连进一个来,服务器端就会分配一个新的线程来处理这个客户端的请求

server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

python3网络编程之socketserver的更多相关文章

  1. 网络编程之socketserver

    网络编程之socketserver """ socketserver.py 中的5个基础类 +------------+ | BaseServer | +-------- ...

  2. 网络编程之socketserver初识

    网络编程之socketserver初识 Server #!/usr/bin/env python # @Author : "Wjl" # @Date : 2017/12/22 # ...

  3. python3网络编程之socket

    文章内容: socket介绍 socket参数介绍 流程描述 socket对象内建方法 基本socket实例 通过socket实现简单ssh并实现接收大数据 socket介绍 socket又称&quo ...

  4. 网络编程之socketserver以及socket更多方法

    关于socketserver 关于socket的更多方法 服务端套接字函数: s.bind() 绑定(主机,端口号)到套接字 s.listen() 开始tcp监听 s.accept () 被动接受tc ...

  5. 网络编程之socket

    网络编程之socket socket:在网络编程中的一个基本组件,也称套接字. 一个套接字就是socket模块中的socket类的一个实例. 套接字包括两个: 服务器套接字和客户机套接字 套接字的实例 ...

  6. [深入浅出WP8.1(Runtime)]网络编程之HttpClient类

    12.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,我们还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类 ...

  7. java网络编程之TCP通讯

    java中的网络编程之TCP协议的详细介绍,以及如何使用,同时我在下面举2例说明如何搭配IO流进行操作, /* *TCP *建立连接,形成传输数据的通道: *在连接中进行大数据量传输: *通过三次握手 ...

  8. 网络编程之UDP编程

    网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了Data ...

  9. 网络编程之TCP编程

    网络编程之TCP编程 前面已经介绍过关于TCP协议的东西,这里不做赘述.Java对于基于TCP协议的网络通信提供了良好的封装,Java使用socket对象来代表两端的通信窗口,并通过Socket产生I ...

随机推荐

  1. javascript痛点之四this的指向问题

    先看以下例子 1.我们直接调用this看看指向的是谁 alert(this);//指向window 2.在函数中直接调用看看指向的是谁 function fn(){ alert(this); } fn ...

  2. springmvc 之 helloworld

    构建SPRINGMVC主要分为几个部分(大体方式为创建并配置2个XML文件.一个JAVA文件及一个JSP文件). 一.创建动态JAVA WEB项目  //创建项目并导入JAR包. 二.创建并配置ser ...

  3. Linux find运行机制详解

    本文目录: 1.1 find基本用法示例 1.2 find理论部分 1.2.1 expression-operators 1.2.2 expression-options 1.2.3 expressi ...

  4. LoadRunner11_录制Oracle数据库脚本

    [oracle环境] ①oracle:无需在本地安装oracle,但是oracle的odbc驱动一定要装:(我的安装路径为 D:\oracle ).安装好后在环境变量 " Path &quo ...

  5. CSS Why

    前面的话 在CSS学习目录中,已经详细地介绍了CSS如何使用.知其然,还要知其所以然.本文将介绍CSS各部分出现的原因,仅限个人理解,如有不妥,欢迎交流 Why CSS 早期的大多数网站标记几乎完全由 ...

  6. 怎么用VBS脚本自动注册yy娱乐的账号

    set WshShell=WScript.CreateObject("WScript.Shell") Const user = "hugetech2" Cons ...

  7. Educational Codeforces Round 20.C

    C. Maximal GCD time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  8. Hibernate使用注解进行ORM映射实例

    在上一篇博客中,我们通过xml配置文件进行实体类和表的映射,但是近两年来有更多的项目对一些比较稳定的实体类使用了注解进行ORM映射,这样使得编程更加简洁.简单.其实使用注解进行ORM映射和使用xml进 ...

  9. vsftpd配置虚拟用户

    #安装vsftpd yum -y install vsftpd #创建本地ftp账户 groupadd ftpuser useradd -g ftpuser -s /sbin/nologin ftpu ...

  10. 2238"回文字串"报告

    题目描述: 回文串,就是从前往后和从后往前看都是一样的字符串.那么现在给你一个字符串,请你找出该字符串中,长度最大的一个回文子串. 输入描述: 有且仅有一个仅包含小写字母的字符串,保证其长度不超过50 ...