Day31 Socketserver

之前实现的Socket不能实现并发,只支持一对一的连接,socketserver内部使用IO多路复用以及“多进程”和”多线程“,从而实现并发处理多个客户端请求

一. Socketserver

模块中包含服务类(server class)请求处理类(request handle class)

 

服务类

包含有5个server类,只能实现同步操作,单线程只能按照顺序一次处理客户端请求

(继承关系)

+------------+

| BaseServer |

+------------+

+-----------+ +------------------+

| TCPServer |------- | UnixStreamServer |

+-----------+ +------------------+

+-----------+ +--------------------+

| UDPServer |------- | UnixDatagramServer |

+-----------+ +--------------------+

2个Mixin

+--------------+ +----------------+

| ForkingMixIn | | ThreadingMixIn |

+--------------+ +----------------+

各自实现了多进程和多线程功能。

ForkingMixIn只在支持fork()的POSIX平台上可用

将同步的server类和Mixin类组合就可以实现异步服务功能,所以socketserver下主要使用以下几个类

  • class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
  • class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
  • class ForkingUDPServer(ForkingMixIn, UDPServer): pass
  • class ForkingTCPServer(ForkingMixIn, TCPServer): pass

Windows系统没有提供os.fork()接口,所以无法使用多进程的ForkingDUPServer和ForkingTCPServer类,而Linux和Unix多进程和多线程都可以使用

一般过程

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

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

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(1024)  # 接受客户端的数据
      if msg == b"quit" or msg == "": # 退出
        break

      print(msg.decode('utf-8'))
      self.request.send(msg.upper()) # 将消息发送回客户端
  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机制,保证了这些代码一定会执行。

客户端

import socket

# 监听本机网卡,端口
ip_port = ('127.0.0.1', 8000)
# 收发消息长度
BUFSIZE = 1024

# 获取客户套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 尝试链接服务器
s.connect_ex(ip_port)

# 可以一直收发消息
while True:
     msg = input('>>: ').strip()
     # 发送消息给服务端
     if not msg:
          continue
     s.send(msg.encode('utf-8'))

     feedback = s.recv(BUFSIZE)
     # 接收服务端消息
     print(feedback.decode('utf-8'))

# 关闭客户端套接字
s.close()

 

 

总结

如何创建一个socketserver:

1. 创建一个请求处理的类,并且这个类要继承BaseRequestHandler class ,并且还要重写父类里handle()方法(handle方法默认结束后断开链接继续监听)

2. 必须实例化 TCPServer,并且传递server IP和上面创建的请求处理类,给这个TCPServer;

3. Server对象调用server.handle_requese()(只处理一个请求)或server.server_forever()(处理多个一个请求,永远执行)。如果不调用server_forever,在外面循环调用handle_request

server_forever:服务循环,监听端口,处理请求

4. 关闭连接server_close()

 

二. TCP\UDP版本源码分析

 

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)

ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
  2. 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
  3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
  4. 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找

真正的请求处理函数

BaseServer._handle_request_noblock()

  1. get_request: 接收请求 accept
  2. verify_request: 验证,做一些验证工作,比如 ip 过滤
  3. process_request: 处理请求,子类重写该方法后,需要 调用 SocketServer.BaseServer.process_request,
  4. BaseServer.process_request 中有 BaseRequestHandler 的回调动作,实例化用户定义的 handler, __init__ 中完成对 handle() 的调用
  5. shutdown_reques: 关闭连接
def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that select.select has returned that the socket is
        readable before this function was called, so there should be
        no risk of blocking in get_request().
        """
        try:
            # 接收请求
            # get_request 由子类实现,一般为接收请求,返回 socket
            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)
        else:
            self.shutdown_request(request)

 UDPServer get_request 返回的是一个 (data, socket) 的 tuple,而 TCPServer 返回的是 socket。 handle 中要区分处理,msg, sock = self.request(msg 已经获取,无需额外 recv)

对于数据的传送, 应该使用 socket 的 sendto() 和 recvfrom() 方法。 尽管传统的 send() 和 recv() 也可以达到同样的效果, 但是前面的两个方法对于 UDP 连接而言更普遍。

TCPServer

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

 

UDPServer

def get_request(self):
    data, client_addr = self.socket.recvfrom(self.max_packet_size)
    return (data, self.socket), client_addr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值