微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python的socket模块源码中的一些实现要点分析

BaseServer 和 BaseRequestHandler
Python为网络编程提高了更高级的封装。SocketServer.py 提供了不少网络服务的类。它们的设计很优雅。Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。在构建网络服务的时候,Server 和 RequestHandler 并不是分开的,RequestHandler的实例对象在Server 内配合 Server工作。

改模块的主要几个Server关系如下:

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

BaseServer 分析
BaseServer 通过__init__初始化,对外提供serve_forever和 handler_request方法

init 初始化:

  def __init__(self,server_address,RequestHandlerClass):
    """Constructor. May be extended,do not override."""
    self.server_address = server_address
    self.RequestHandlerClass = RequestHandlerClass
    self.__is_shut_down = threading.Event()
    self.__shutdown_request = False

__init__源码很简单。主要作用是创建server对象,并初始化server地址和处理请求的class。熟悉socket编程应该很清楚,server_address是一个包含主机和端口的元组

serve_forever
创建了server对象之后,就需要使用server对象开启一个无限循环,下面来分析serve_forever的源码。

  def serve_forever(self,poll_interval=0.5):
    self.__is_shut_down.clear()
    try:
      while not self.__shutdown_request:
        r,w,e = _eintr_retry(select.select,[self],[],poll_interval)
        if self in r:
          self._handle_request_noblock()
    finally:
      self.__shutdown_request = False
      self.__is_shut_down.set()

serve_forever接受一个参数poll_interval,用于表示select轮询的时间。然后进入一个无限循环,调用select方式进行网络IO的监听。

如果select函数返回,表示有IO连接或数据,那么将会调用_handle_request_noblock方法

_handle_request_noblock
  def _handle_request_noblock(self):
    try:
      request,client_address = self.get_request()
    except socket.error:
      return
    if self.verify_request(request,client_address):
      try:
        self.process_request(request,client_address)
      except:
        self.handle_error(request,client_address)
        self.shutdown_request(request)

_handle_request_noblock方法即开始处理一个请求,并且是非阻塞。该方法通过get_request方法获取连接,具体的实现在其子类。一旦得到了连接,调用verify_request方法验证请求。验证通过,即调用process_request处理请求。如果中途出现错误,则调用handle_error处理错误,以及shutdown_request结束连接。

verify_request
  def verify_request(self,request,client_address):
    return True

方法对request进行验证,通常会被子类重写。简单的返回True即可,然后进入process_request方法处理请求。

process_request
  def process_request(self,client_address):
    self.finish_request(request,client_address)
    self.shutdown_request(request)

process_request方法是mixin的入口,MixIn子类通过重写该方法,进行多线程或多进程的配置。调用finish_request完成请求的处理,同时调用shutdown_request结束请求。

finish_request
  def finish_request(self,client_address):
    self.RequestHandlerClass(request,client_address,self)

finish_request方法将会处理完毕请求。创建requestHandler对象,并通过requestHandler做具体的处理。

BaseRequestHandler 分析
所有requestHandler都继承BaseRequestHandler基类。

  def __init__(self,server):
    self.request = request
    self.client_address = client_address
    self.server = server
    self.setup()
    try:
      self.handle()
    finally:
      self.finish()

该类会处理每一个请求。初始化对象的时候,设置请求request对象。然后调用setup方法,子类会重写该方法,用于处理socket连接。接下来的将是handler和finish方法。所有对请求的处理,都可以重写handler方法

至此,整个Python提供的Server方式即介绍完毕。总结一下,构建一个网络服务,需要一个BaseServer用于处理网络IO,同时在内部创建requestHandler对象,对所有具体的请求做处理。

BaseServer - BaseRequestHandler

__init__(server_address,RequestHandlerClass): 
  BaseServer.server_address
  BaseServer.RequestHandlerClass

serve_forever(): 

  select() 

  BaseServer._handle_request_noblock()

    BaseServer.get_request() -> request,client_addres

    BaseServer.verify_request()

      BaseServer.process_request()

        BaseServer.process_request()

          BaseServer.finish_request()

            BaseServer.RequestHandlerClass()

              BaseRequestHandler.__init__(request)

                BaseRequestHandler.request
                BaseRequestHandler.client_address = client_address

                BaseRequestHandler.setup()

                BaseRequestHandler.handle()

          BaseServer.shutdown_request()

            BaseServer.close_request()

      BaseServer.shutdown_request()

        BaseServer.close_request()

BaseServer 和 BaseRequestHandler是网络处理的两个基类。实际应用中,网络操作更多是使用 TCP 或 HTTP 协议。SocketServer.py 也提供了更高级的TCP、UDP封装。下面就来看下关于TCP方面的网络模块(UDP和TCP的在代码组织上差别不是特别大,暂且忽略)。

Tcpserver
Tcpserver 继承了BaseServer,初始化的时候,进行了socket套接字的创建。

def __init__(self,RequestHandlerClass,bind_and_activate=True):
  BaseServer.__init__(self,RequestHandlerClass)
  self.socket = socket.socket(self.address_family,self.socket_type)
  if bind_and_activate:
    self.server_bind()
    self.server_activate()

__init__ 方法通过 socket模块创建了socket对象,然后进行调用server_bind和server_activate。

server_bind
def server_bind(self):
  if self.allow_reuse_address:
    self.socket.setsockopt(socket.soL_SOCKET,socket.so_REUSEADDR,1)
  self.socket.bind(self.server_address)
  self.server_address = self.socket.getsockname()

server_bind 方法进行socket对象的bind操作,以及设置socket相关属性,如网络地址的复用。

server_activate
def server_activate(self):
  self.socket.listen(self.request_queue_size)

server_activate 方法也比较简单,添加socket对象的listen。

get_request
该类最重要的方法就是 get_request。该方法进行返回socket对象的请求连接。

  def get_request(self):
  """Get the request and client address from the socket.
  """
  return self.socket.accept()

get_request方法是在BaseServer基类中的_handle_request_noblock中调用,从那里里传入套接字对象获取的连接信息。如果是UDPServer,这里获取的就是UDP连接。

此外,Tcpserver还提供了一个 fileno 方法,提供给基类的select调用返回文件描述符。

StreamRequestHandler
Tcpserver实现了使用tcp套接字的网络服务,Handler方面则是对应的StreamRequestHandler。它继承了BaseRequestHandler。基类的setup方法和finish方法被它重写,用于通过连接实现缓存文件的读写操作。

setup方法

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)

setup判断了是否使用nagle算法。然后设置对应的连接属性。最重要的就是创建了一个可读(rfile)和一个可写(wfile)的“文件”对象,他们实际上并不是创建了文件,而是封装了读取数据和发送数据的操作,抽象成为对文件的操作。可以理解为 self.rfile 就是读取客户端数据的对象,它有一些方法可以读取数据。self.wfile则是用来发送数据给客户端的对象。后面的操作,客户端数据到来会被写入缓冲区可读,需要向客户端发送数据的时候,只需要向可写的文件中write数据即可。

实现TCP服务需要使用Tcpserver和StreamRequestHandler共同协作。大致函数调用流程如下,函数调用用括号表示,赋值不带括号,没有类前缀的表示系统调用

Tcpserver - StreamRequestHandler

__init__(server_address,RequestHandlerClass): 
  BaseServer.server_address
  BaseServer.RequestHandlerClass

  Tcpserver.socket = socket.socket(self.address_family,self.socket_type)
  Tcpserver.server_bind()
  Tcpserver.server_activate()

serve_forever(): 

  select() 

  BaseServer._handle_request_noblock()

    Tcpserver.get_request() -> request,client_addres
      socket.accept()

    BaseServer.verify_request()

      BaseServer.process_request()

        BaseServer.process_request()

          BaseServer.finish_request(request,client_address)

            BaseServer.RequestHandlerClass()

              BaseRequestHandler.__init__(request)

                BaseRequestHandler.request
                BaseRequestHandler.client_address = client_address

                StreamRequestHandler.setup()

                  StreamRequestHandler.connection = StreamRequestHandler.request
                  StreamRequestHandler.rfile
                  StreamRequestHandler.wfile

                BaseRequestHandler.handle()

                StreamRequestHandler.finsih()
                  StreamRequestHandler.wfile.close()
                  StreamRequestHandler.rfile.close()

          BaseServer.shutdown_request(request)
            Tcpserver.shutdown()
              request.shutdown()
            Tcpserver.close_request(request)
              request.close()

      Tcpserver.shutdown_request(request)
        Tcpserver.shutdown(request)
          request.shutdown()
        Tcpserver.close_request(request)
          request.close()

最早关于介绍BaseServer的时候,我们知道python对BaseServer设计的时候,预留了可用于Mixin扩展多线程或多进程的接口。mixin通过复写父类的parse_request方法实现。

ThreadingMixIn
ThreadingMixIn 类实现了多线程的方式,它只有两个方法,分别是process_request和 process_request_thread方法。多进程的方式是ForkingMixIn,暂且略过。

process_request
def process_request(self,client_address):
  t = threading.Thread(target = self.process_request_thread,args = (request,client_address))
  t.daemon = self.daemon_threads
  t.start()

process_request方法复写了父类的此方法。以此为接口入口,对每一个请求,调用Thread开启一个新的线程。每一个线程都绑定process_request_thread方法

process_request_thread
  def process_request_thread(self,client_address):
    try:
      self.finish_request(request,client_address)
      self.shutdown_request(request)
    except:
      self.handle_error(request,client_address)
      self.shutdown_request(request)

process_request_thread方法和BaseServer里的parse_request几乎一样。只不过是多线程的方式调用

使用的时候,通过多继承调用接口,例如:

class ThreadingTcpserver(ThreadingMixIn,Tcpserver): 
  pass

具体的调用过程如下:

ThreadingMixIn -- Tcpserver - StreamRequestHandler

__init__(server_address,client_addres
      socket.accept()

    BaseServer.verify_request()

      BaseServer.process_request()

        ThreadingMixIn.process_request()
          t = threading.Thread(target = ThreadingMixIn.process_request_thread)

          ThreadingMixIn.process_request_thread

            BaseServer.finish_request(request,client_address)

              BaseServer.RequestHandlerClass()

                BaseRequestHandler.__init__(request)

                  BaseRequestHandler.request
                  BaseRequestHandler.client_address = client_address

                  StreamRequestHandler.setup()

                    StreamRequestHandler.connection = StreamRequestHandler.request
                    StreamRequestHandler.rfile
                    StreamRequestHandler.wfile

                  BaseRequestHandler.handle()

                  StreamRequestHandler.finsih()
                    StreamRequestHandler.wfile.close()
                    StreamRequestHandler.rfile.close()

            BaseServer.shutdown_request(request)
              Tcpserver.shutdown()
                request.shutdown()
              Tcpserver.close_request(request)
                request.close()

      Tcpserver.shutdown_request(request)
        Tcpserver.shutdown(request)
          request.shutdown()
        Tcpserver.close_request(request)
          request.close()

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用爬虫利器 Playwright,轻松爬取抖查查数据 我们先分析登录的接口,其中 url 有一些非业务参数:ts、he、sign、secret。 然后根据这些参数作为关键词,定位到相关的 js 代码。 最后,逐步进行代码的跟踪,发现大部分的代码被混淆加密了。 花费了大半天,来还原这些混淆加密的代码
轻松爬取灰豚数据的抖音商品数据 调用两次登录接口实现模拟登录 我们分析登录接口,发现调用了两次不同的接口;而且,需要先调用 https://login.huitun.com/weChat/userLogin,然后再调用 https://dyapi.huitun.com/userLogin 接口。 登
成功绕过阿里无痕验证码,一键爬取飞瓜数据 飞瓜数据的登录接口,接入了阿里云的无痕验证码;通过接口方式模拟登录,难度比较高。所以,我们使用自动化的方式来实现模拟登录,并且获取到 cookie 数据。 [阿里无痕验证码] https://help.aliyun.com/document_detail/1
一文教你从零开始入门蝉妈妈数据爬取,成功逆向破解数据加密算法 通过接口进行模拟登录 我们先通过正常登录的方式,分析对应的登录接口。通过 F12 打开谷歌浏览器的调试面板,可以看到登录需要传递的一些参数;其中看到密码是被加密了。 不过我们通过经验可以大概猜测一下,应该是通过 md5 算法加密了。 接下
抽丝剥茧成功破解红人点集的签名加密算法 抽丝剥茧破解登录签名算法,成功实现模拟登录 headers = {} phone_num = "xxxx" password = "xxxx" md5_hash = hashlib.md5() md5_hash.upda
轻松绕过 Graphql 接口爬取有米有数的商品数据 有米有数数据的 API 接口,使用的是一种 API 查询语言 graphql。所有的 API 只有一个入口,具体的操作隐藏在请求数据体里面传输。 模拟登录,获取 sessionId 调用登录接口,进行模拟登录。 cookies = {} head
我最近重新拾起了计算机视觉,借助Python的opencv还有face_recognition库写了个简单的图像识别demo,额外定制了一些内容,原本想打包成exe然后发给朋友,不过在这当中遇到了许多小问题,都解决了,记录一下踩过的坑。 1、Pyinstaller打包过程当中出现warning,跟d
说到Pooling,相信学习过CNN的朋友们都不会感到陌生。Pooling在中文当中的意思是“池化”,在神经网络当中非常常见,通常用的比较多的一种是Max Pooling,具体操作如下图: 结合图像理解,相信你也会大概明白其中的本意。不过Pooling并不是只可以选取2x2的窗口大小,即便是3x3,
记得大一学Python的时候,有一个题目是判断一个数是否是复数。当时觉得比较复杂不好写,就琢磨了一个偷懒的好办法,用异常处理的手段便可以大大程度帮助你简短代码(偷懒)。以下是判断整数和复数的两段小代码: 相信看到这里,你也有所顿悟,能拓展出更多有意思的方法~
文章目录 3 直方图Histogramplot1. 基本直方图的绘制 Basic histogram2. 数据分布与密度信息显示 Control rug and density on seaborn histogram3. 带箱形图的直方图 Histogram with a boxplot on t