当前位置: 移动技术网 > IT编程>脚本编程>Python > Python使用socketServer包搭建简易服务器过程详解

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

2020年06月18日  | 移动技术网IT编程  | 我要评论

红灯中心下载,行尸走肉第六季第11集,英纷

官方提供了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方面的操作。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网