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中列表、字典、元组数据结构的简单学习笔记
Mar 20 Python
Python操作Redis之设置key的过期时间实例代码
Jan 25 Python
基于Python socket的端口扫描程序实例代码
Feb 09 Python
python实现聊天小程序
Mar 13 Python
python素数筛选法浅析
Mar 19 Python
浅谈python 导入模块和解决文件句柄找不到问题
Dec 15 Python
Python3.7 dataclass使用指南小结
Feb 22 Python
Python PyQt5 Pycharm 环境搭建及配置详解(图文教程)
Jul 16 Python
解决Python3 抓取微信账单信息问题
Jul 19 Python
Pycharm激活方法及详细教程(详细且实用)
May 12 Python
Python pip 常用命令汇总
Oct 19 Python
python缺失值的解决方法总结
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
php读取二进制流(C语言结构体struct数据文件)的深入解析
2013/06/13 PHP
关于JavaScript的gzip静态压缩方法
2007/01/05 Javascript
比较详细的javascript对象的property和prototype是什么一种关系
2007/08/06 Javascript
js模拟实现Array的sort方法
2007/12/11 Javascript
JQuery 学习笔记 选择器之三
2009/07/23 Javascript
extjs grid设置某列背景颜色和字体颜色的实现方法
2010/09/06 Javascript
关于scrollLeft,scrollTop的浏览器兼容性测试
2013/03/19 Javascript
Mac/Windows下如何安装Node.js
2013/11/22 Javascript
jQuery实现的简单提示信息插件
2015/12/08 Javascript
JS提示:Uncaught SyntaxError:Unexpected token ) 错误的解决方法
2016/08/19 Javascript
移动端Ionic App 资讯上下循环滚动的实现代码(跑马灯效果)
2017/08/29 Javascript
JS中的回调函数实例浅析
2018/03/21 Javascript
使用 Angular RouteReuseStrategy 缓存(路由)组件的实例代码
2019/11/01 Javascript
基于jsbarcode 生成条形码并将生成的条码保存至本地+源码
2020/04/27 Javascript
python插入排序算法的实现代码
2013/11/21 Python
利用Python实现Windows下的鼠标键盘模拟的实例代码
2017/07/13 Python
对python创建及引用动态变量名的示例讲解
2018/11/10 Python
解决新版Pycharm中Matplotlib图像不在弹出独立的显示窗口问题
2019/01/15 Python
python向字符串中添加元素的实例方法
2019/06/28 Python
Jacobi迭代算法的Python实现详解
2019/06/29 Python
python求平均数、方差、中位数的例子
2019/08/22 Python
python 协程中的迭代器,生成器原理及应用实例详解
2019/10/28 Python
python3 dict ndarray 存成json,并保留原数据精度的实例
2019/12/06 Python
Python正则表达式急速入门(小结)
2019/12/16 Python
Python扫描端口的实现
2021/01/25 Python
荷兰浴室和卫浴网上商店:Badkamerxxl.nl
2020/10/06 全球购物
俄罗斯天然和有机产品、健康生活网上商店:Fitomarket.ru
2020/10/09 全球购物
办公室主任竞聘演讲稿
2014/05/15 职场文书
2015年转正工作总结范文
2015/04/02 职场文书
证券区域经理岗位职责
2015/04/10 职场文书
中国合伙人观后感
2015/06/02 职场文书
2019广播稿怎么写
2019/04/17 职场文书
情况说明书格式及范文
2019/06/24 职场文书
创业方案:赚钱的烧烤店该怎样做?
2019/07/05 职场文书
Python的flask接收前台的ajax的post数据和get数据的方法
2021/04/12 Python
Python图片检索之以图搜图
2021/05/31 Python