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实现Linux命令xxd -i功能
Mar 06 Python
再谈Python中的字符串与字符编码(推荐)
Dec 14 Python
Python文件读写常见用法总结
Feb 22 Python
Python中的random.uniform()函数教程与实例解析
Mar 02 Python
Python中的pathlib.Path为什么不继承str详解
Jun 23 Python
python实现操作文件(文件夹)
Oct 31 Python
用python拟合等角螺线的实现示例
Dec 27 Python
python turtle工具绘制四叶草的实例分享
Feb 14 Python
Python图像处理库PIL中图像格式转换的实现
Feb 26 Python
Python自定义聚合函数merge与transform区别详解
May 26 Python
python 中 .py文件 转 .pyd文件的操作
Mar 04 Python
Django rest framework如何自定义用户表
Jun 09 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
杏林同学录(三)
2006/10/09 PHP
php使用curl伪造来源ip和refer的方法示例
2018/05/08 PHP
Iframe thickbox2.0使用的方法
2009/03/05 Javascript
javascript RadioButtonList获取选中值
2009/04/09 Javascript
js控制表单操作的常用代码小结
2013/08/15 Javascript
文本框(input)获取焦点(onfocus)时样式改变的示例代码
2014/01/10 Javascript
javascript中scrollTop详解
2015/04/13 Javascript
jQuery Easyui加载表格出错时在表格中间显示自定义的提示内容
2016/12/08 Javascript
jQuery内存泄露解决办法
2016/12/13 Javascript
读Javascript高性能编程重点笔记
2016/12/21 Javascript
vue.js 图片上传并预览及图片更换功能的实现代码
2018/08/27 Javascript
了解javascript中的Dom操作
2019/05/27 Javascript
javascript实现简易聊天室
2019/07/12 Javascript
JavaScript回调函数callback用法解析
2020/01/14 Javascript
Python实现PS滤镜的旋转模糊功能示例
2018/01/20 Python
对numpy 数组和矩阵的乘法的进一步理解
2018/04/04 Python
python如何获取列表中每个元素的下标位置
2019/07/01 Python
anaconda如何查看并管理python环境
2019/07/05 Python
Python opencv相机标定实现原理及步骤详解
2020/04/09 Python
Django中ORM找出内容不为空的数据实例
2020/05/20 Python
Pandas中DataFrame基本函数整理(小结)
2020/07/20 Python
一个非常简单好用的Python图形界面库(PysimpleGUI)
2020/12/28 Python
使用phonegap克隆和删除联系人的实现方法
2017/03/31 HTML / CSS
猎人靴英国官网:Hunter Boots
2017/02/02 全球购物
巴西最大的在线约会网站:ParPerfeito
2018/07/11 全球购物
PHP中如何使用Cookie
2015/10/28 面试题
大学生优秀的自我评价分享
2013/10/22 职场文书
生物专业个人自荐信范文
2013/11/29 职场文书
教师岗位职责范本
2013/12/29 职场文书
教师通用专业自荐书范文
2014/02/11 职场文书
2014旅游局领导班子四风问题对照检查材料思想汇报
2014/09/19 职场文书
交通事故死亡赔偿协议书
2014/12/03 职场文书
单位委托函范文
2015/01/29 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
python识别围棋定位棋盘位置
2021/07/26 Python
golang生成vcf通讯录格式文件详情
2022/03/25 Golang