Python使用socketServer包搭建简易服务器过程详解


Posted in Python onJune 12, 2020

官方提供了socketserver包去方便我们快速的搭建一个服务器框架。

server类

socketserver包提供5个Server类,这些单独使用这些Server类都只能完成同步的操作,他是一个单线程的,不能同时处理各个客户端的请求,只能按照顺序依次处理。

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

两个Mixin类

+--------------+    +----------------+
| ForkingMixIn |    | ThreadingMixIn |
+--------------+    +----------------+

各自实现了多进程和多线程的功能(ForkingMixIn在Windows不支持)

于是将这些同步类和Mixin类组合就实现了异步服务类的效果。

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass

基本使用

由于server需要同时处理来自多个客户端的请求,需要提供异步的支持,所以通常使用上面的异步类创建服务器。在Windows系统中没有提供os.fork()接口,Windows无法使用多进程的ForkingUDPServer和ForkingTCPServer,只能使用ThreadingTCPServer或者ThreadingUDPServer;而Linux和Unix多线程和多进程版本都可以使用。

服务器主要负责接受客户端的连接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。

import socketserver
# 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler) 
server.serve_forever() # 启动服务器,

只需要上面两行代码就可以创建开启一个服务,运行上面代码后常看本机8000端口,发现有程序正在监听。

C:\Users\user>netstat -anp tcp | findstr 8000
TCP 127.0.0.1:8000 0.0.0.0:0 LISTENING

ThreadingTCPServer可以对我们的请求进行接受,但是并不会进行处理请求,处理请求的类是上面指定BaseRequestHandler类,该类可以定义handle方法来处理接受的请求。

BaseRequestHandler的源码

class BaseRequestHandler:

  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

在server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)中,BaseRequestHandler将作为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(连接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象便可以与客户端交互了。

简单示例

import socketserver
import threading 

class MyRequestHandler(socketserver.BaseRequestHandler):
  """ BaseRequestHandler的实例化方法中,获得了三个属性
  self.request = request  # 该线程中与客户端交互的 socket 对象。
  self.client_address   # 该线程处理的客户端地址
  self.server = server   # 服务器对象
  """

  def handle(self):
    while True:
      msg = self.request.recv()  # 接受客户端的数据
      if msg == b"quit" or msg == "": # 退出
        break

      print(msg.decode())
      self.request.send(msg) # 将消息发送回客户端
  def finish(self):
    self.request.close()    # 关闭套接字
if __name__ == "__main__":
  # 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
  server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), MyRequestHandler)

  server.serve_forever()  # 启动服务器

我们创建了一个ThreadingTCPServer服务器,然后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息返回客户端。最后我们在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码一定会执行。

上一篇使用socket实现了一个群聊服务器,这个里使用socketServer将更加方便的实现

class MyRequestHandle(BaseRequestHandler):
  clients = {} # 在类属性中记录所有与客户端连接socket。
  lock = threading.Lock() # 互斥锁,各个线程共用

  def setup(self): # 新的用户连接时,预处理,将这个新的连接加入到clients中,考虑线程安全,需要加锁
    with self.lock:
      self.clients[self.client_address] = self.request

  def handle(self): # 处理客户端的请求主逻辑
    while True:
      data = self.request.recv(1024).strip()  # 接受数据

      if data == b"quit" or data == b"": # 客户端退出
        with self.lock:
          self.server.clients.pop(self.client_address)
          self.request.close()
          break

      print("{}-{}: {}".format(*self.client_address, data.decode()))

      with self.lock:
        for _, c in self.server.clients.items(): # 群发
          c.send(data)
  def finish(self):
    with server.lock:
      for _, c in server.clients.items():
        c.close()
    server.server_close()def main():
  server = ThreadingTCPServer(("127.0.0.1", 8000), MyRequestHandle)
  # 将创建的所有线程设置为daemon线程,这样控台主程序退出时,这个服务器的所有线程将会被结束
  server.daemon_threads = True 

if __name__ == "__main__":
  main()

上面requestHandlerclass中的handle方法和finish方式对应了上一篇中TCP服务器的recv方法和stop方法,他们处理请求的逻辑是相同的。只是上面使用了socketserver的代码变少了,处理的逻辑也变少了,TCPserver帮我们完成了大量的工作,这利于软件的快速开发。

内置的两个RequestHandlerClass

StreamHandlerRequest

StreamHandlerRequest顾名思义是一种流式的求情处理类,对应TCP协议的面向字节流的传输形式。我们从源代码分析。(去除了一些次要代码)

class StreamRequestHandler(BaseRequestHandler):
  rbufsize = -1 # 读缓存
  wbufsize = 0  # 写缓存
  timeout = None # 超时时间
  # IP/TCP拥塞控制的Nagle算法算法。
  disable_nagle_algorithm = False

  def setup(self): # 实现了setup,
    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)
    
    # 使用 makefile方法获得了一个只读文件对象 rfile
    self.rfile = self.connection.makefile('rb', self.rbufsize)
    
    # 获得一个只写的文件对象 wfile
    if self.wbufsize == 0:
      self.wfile = _SocketWriter(self.connection)
    else:
      self.wfile = self.connection.makefile('wb', self.wbufsize)

  def finish(self): # 负责将这个 wfile 和 rfile方法关闭。
    if not self.wfile.closed: 
      try:
        self.wfile.flush()
      except socket.error:
        pass
    self.wfile.close()
    self.rfile.close()

使用StreamRequestHandler方法可以将这个socket包装成一个类文件对象,方便我们使用一套文件对象的方法处理这个socket,它没有实现handle方法,我仍然需要我们实现。我们可以这样使用它

class MyHandle(StreamRequestHandler):
  # 如果需要使用setup和finish方法,需要调用父类方法,否则该方法将会被覆盖。
  def setup(self):
    super().setup()
    # 添加自己的需求
  def handle(self):
    # 这里我们可以使用wfile和rfile来处理socket消息了,例如之前使用self.request.recv()方法等同于self.rfile.read()
    # 而 self.wfile.write 等同于 self.request.send(),在handle方法中完成业务逻辑即可

  def finish(self):
    super().finish()

server = ThreadingTCPServer("127.0.0.1", MyHandle)
server.serve_forever()

StreamRequestHandler主要定义了两个新的 wfile对象和rfile对象,来分别对这个socket进行读写操作,当我们业务需要时,比如需要使用文件接口方法时,选择继承于StreamRequestHandler构建我们自己处理请求类来完成业务逻辑将会更加的方便。

DatagramRequestHandler

DatagramRequestHandler字面意思是数据报请求处理,也就是基于UDPServer的服务器才能使用该请求处理类

class DatagramRequestHandler(BaseRequestHandler):

  def setup(self):
    from io import BytesIO
    # udp的self.request包含两部分(data,socket)它来自于
    # data, client_addr = self.socket.recvfrom(self.max_packet_size)
    #   return (data, self.socket), client_addr
    # (data, self.socket)就是这个self.request,在这里将其解构,data为recvfrom接收的数据
    self.packet, self.socket = self.request
    
    # 该数据包封装为 BytesIO,同样为一个类文件对象。
    self.rfile = BytesIO(self.packet)
    self.wfile = BytesIO()

  def finish(self):
    self.socket.sendto(self.wfile.getvalue(), self.client_address)

从源码可以看出,DatagramRequestHandler将数据包封装为一个rfile,并实例化一个ByteIO对象用于写入数据,写入的数据可以通过self.socket这个套接字发送。这样可以使用rfile和wfile这两个类文件对象的read或者write接口来进行一些IO方面的操作。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python使用mailbox打印电子邮件的方法
Apr 30 Python
python数字图像处理之高级滤波代码详解
Nov 23 Python
Python实现基于二叉树存储结构的堆排序算法示例
Dec 08 Python
Python3使用turtle绘制超立方体图形示例
Jun 19 Python
python监控文件并且发送告警邮件
Jun 21 Python
python pandas 对时间序列文件处理的实例
Jun 22 Python
Python 查找list中的某个元素的所有的下标方法
Jun 27 Python
python实现批量nii文件转换为png图像
Jul 18 Python
Python lambda表达式filter、map、reduce函数用法解析
Sep 11 Python
Python 3.8正式发布,来尝鲜这些新特性吧
Oct 15 Python
Python日志打印里logging.getLogger源码分析详解
Jan 17 Python
Python中json.load()和json.loads()有哪些区别
Jun 07 Python
Django之腾讯云短信的实现
Jun 12 #Python
python相对企业语言优势在哪
Jun 12 #Python
keras实现基于孪生网络的图片相似度计算方式
Jun 11 #Python
为什么说python适合写爬虫
Jun 11 #Python
python新手学习使用库
Jun 11 #Python
keras实现多种分类网络的方式
Jun 11 #Python
python的help函数如何使用
Jun 11 #Python
You might like
DC《小丑》11项提名领跑奥斯卡 Netflix成第92届奥斯卡提名最大赢家
2020/04/09 欧美动漫
PHP 判断变量类型实现代码
2009/10/23 PHP
php+javascript的日历控件
2009/11/19 PHP
php常用ODBC函数集(详细)
2013/06/24 PHP
浅析PHP原理之变量分离/引用(Variables Separation)
2013/08/09 PHP
thinkPHP中钩子的使用方法实例分析
2017/11/16 PHP
Gambit vs ForZe BO3 第二场 2.13
2021/03/10 DOTA
jquery的Theme和Theme Switcher使用小结
2010/09/08 Javascript
jquery聚焦文本框与扩展文本框聚焦方法
2012/10/12 Javascript
JQuery 文本框回车跳到下一个文本框示例代码
2013/08/30 Javascript
jquery右下角弹出提示框示例代码
2013/10/08 Javascript
Javascript玩转继承(二)
2014/05/08 Javascript
jQuery实现遮罩层登录对话框
2016/12/29 Javascript
AngularJS前端页面操作之用户修改密码功能示例
2017/03/27 Javascript
vue cli构建的项目中请求代理与项目打包问题
2018/02/26 Javascript
vue.js学习笔记之v-bind和v-on解析
2018/05/03 Javascript
ES6中Set和Map数据结构,Map与其它数据结构互相转换操作实例详解
2019/02/28 Javascript
python中while循环语句用法简单实例
2015/05/07 Python
简单讲解Python中的闭包
2015/08/11 Python
Python的Flask开发框架简单上手笔记
2015/11/16 Python
Python实现HTTP协议下的文件下载方法总结
2016/04/20 Python
python单线程文件传输的实例(C/S)
2019/02/13 Python
Python实现从SQL型数据库读写dataframe型数据的方法【基于pandas】
2019/03/18 Python
python实现while循环打印星星的四种形状
2019/11/23 Python
python 最简单的实现适配器设计模式的示例
2020/06/30 Python
Python 的 f-string 可以连接字符串与数字的原因解析
2021/02/20 Python
计算机专业应届毕业生自荐信
2013/09/26 职场文书
本科生学习总结的自我评价
2013/10/02 职场文书
医学专业大学生求职的自我评价
2013/11/27 职场文书
巴西世界杯32强口号
2014/06/05 职场文书
党的群众路线教育实践活动批评与自我批评范文
2014/10/16 职场文书
机关作风建设整改方案
2014/10/27 职场文书
医德医风自我评价2015
2015/03/03 职场文书
忠诚与背叛观后感
2015/06/04 职场文书
Django与数据库交互的实现
2021/06/03 Python
详解redis在微服务领域的贡献
2021/10/16 Redis