38 - 网络编程-socketserver

目录

  • 1 socket编程弊端
  • 2 SocketServer模块
    • 2.1 服务器类
    • 2.2 Mixin类
    • 2.3 RequestHandlerClass是啥
    • 2.4 编程接口
  • 3 实现EchoServer
  • 4 聊天室

1 socket编程弊端

socket编程过于底层,编程虽然有套路,但是要写出健壮的代码还是比较困难的,所以很多语言都会socket底层API进行封装,Python的封装就是SocketServer模块。它是网络服务编程框架,便于企业级快速开发。

2 SocketServer模块

SocketServer,网络通信服务器,是Python标准库中的一个高级模块,其作用是创建网络服务器。该模块简化了编写网络服务器的任务。

2.1 服务器类

SocketServer模块中定义了五种服务器类。

  • BaseServer(server_address, RequestHandlerClass):服务器的基类,定义了API。
  • TCPServer(server_address, RequestHandlerClass, bind_and_activate=True):使用TCP/IP套接字。
  • UDPServer:使用数据报套接字
  • UnixStreamServer:使用UNIX套接字,只适用UNIX平台
  • UnixDatagramServer:使用UNIX套接字,只适用UNIX平台

它们的继承关系:

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

除了基类为抽象基类意外,其他四个类都是同步阻塞的。

2.2 Mixin类

SocketServer模块中还提供了一些Mixin类,用于和同步类组成多线程或者多进程的异步方式。

  • class ForkingUDPServer(ForkingMixIn, UDPServer)
  • class ForkingTCPServer(ForkingMixIn, TCPServer)
  • class ThreadingUDPServer(ThreadingMixIn, UDPServer)
  • class ThreadingTCPServer(ThreadingMixIn, TCPServer)
  • class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer)
  • class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer)

fork是创建多进程,thread是创建多线程。fork需要操作系统支持,Windows不支持。

2.3 RequestHandlerClass是啥

要想使用服务器类,需要传入一个RequestHandlerClass类,为什么要传入这个RequestHandlerClass类,它是干什么的?下面一起来看看源码:

  1. TCPServer入口
# 446 行
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
    """Constructor.  May be extended, do not override."""
    BaseServer.__init__(self, server_address, RequestHandlerClass)    # 把RequestHandlerClass交给了BaseServer
    self.socket = socket.socket(self.address_family,
                                self.socket_type)
  1. BaseServer
# 204 行
def __init__(self, server_address, RequestHandlerClass):
    """Constructor.  May be extended, do not override."""
    self.server_address = server_address
    self.RequestHandlerClass = RequestHandlerClass  # 将RequestHandlerClass当作类属性付给了实例本身
    self.__is_shut_down = threading.Event()
    self.__shutdown_request = False
  1. 开启的服务serve_forever方法
# 219 行
def serve_forever(self, poll_interval=0.5):
    self.__is_shut_down.clear()
    try:
        ... ...
                if ready:
                    self._handle_request_noblock()   # 这里执行了_handle_request_noblock()方法

                self.service_actions()
    finally:
        self.__shutdown_request = False
        self.__is_shut_down.set()
  1. _handle_request_noblock()方法
# 304行
    def _handle_request_noblock(self):
        ... ...
            try:
                self.process_request(request, client_address)    # 这里又调用了process_request方法
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)
  1. process_request方法
# 342 行
def process_request(self, request, client_address):
    self.finish_request(request, client_address)   # 这里又调用了finish_request方法
    self.shutdown_request(request)
  1. finish_request方法
# 359 行
def finish_request(self, request, client_address):
    """Finish one request by instantiating RequestHandlerClass."""
    self.RequestHandlerClass(request, client_address, self)   # 实例化对象

通过观察源码,我们知道RequestHandlerClass接受了两个参数,根据上下文代码我们可知:

  1. request:客户端的socket连接
  2. client_address: 客户端的地址二元组信息

那么这个RequestHandlerClass到底是啥?

2.4 编程接口

????????通过源码,我们可以知道,最后传入RequestHandlerClass的是socket和client的ip地址,是不是和我们前面写的TCP多线程版本的recv函数很想?没错,RequestHandlerClass在这里被称为编程接口,由于处理业务逻辑。
????????但是RequestHandlerClass我们没有参照,该如何写呢? socketserver提供了抽象基类BaseRequestHandler,共我们继承来实现。

BaseRequestHandler 是和用户连接的用户请求处理类的基类,所以 RequestHandlerClass 必须是 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

观察源码我们知道,setup、handler、finish在类初始化时就会被执行:

  1. setup():执行处理请求之前的初始化相关的各种工作。默认不会做任何事。
  2. handler():执行那些所有与处理请求相关的工作。默认也不会做任何事。
  3. finish():执行当处理完请求后的清理工作,默认不会做任何事。

这些参数到底是什么?

import socketserver

class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.server)  # <socketserver.TCPServer object at 0x00000153270DA320>
        print(self.client_address)  # ('127.0.0.1', 62124)
        print(self.request)  # <socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 999), raddr=('127.0.0.1', 62124)>

s = socketserver.TCPServer(('127.0.0.1', 999), MyRequestHandler)
s.serve_forever()

根据上面的输出信息我们知道:

  • self.server:指代当前server本身
  • self.client_address:客户端地址
  • self.request: 和客户端连接的socket对象

我们总结一下,使用socketserver创建一个服务器需要以下几个步骤:

  1. 从BaseRequestHandler类派生出子类,并覆盖其handler()方法来创建请求处理程序类,此方法将处理传入请求。
  2. 实例化一个服务器类,传参服务器的地址和请求处理类
  3. 调用服务器实例的handler_request()或serve_forever()方法(启动服务)
  4. 调用server_close()方法(关闭服务)

3 实现EchoServer

顾名思义:Echo,即来什么消息就回显什么消息,即客户端发来什么消息,就返回什么消息。

import socketserver
import logging
import threading

FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT)

class MyRequestHandler(socketserver.BaseRequestHandler):

    def setup(self):

        # 优先执行父类同名方法是一个很好的习惯,即便是父类不作任何处理
        super(MyRequestHandler, self).setup()
        self.event = threading.Event()

    def handle(self):
        super(MyRequestHandler, self).handle()
        while not self.event.is_set():
            data = self.request.recv(1024)
            if data == b'quit' or data == b'':
                self.event.set()
                break

            msg = '{}:{} {}'.format(*self.client_address, data.decode())
            logging.info(msg)
            self.request.send(msg.encode())

    def finish(self):
        super(MyRequestHandler, self).finish()
        self.event.set()

if __name__ == '__main__':
    with socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyRequestHandler) as server:

        # 让所有启动的线程都为daemon进程
        server.daemon_threads = True

        # serve_foreve会阻塞,所以交给子线程运行
        threading.Thread(target=server.serve_forever, daemon=True).start()
        while True:
            cmd = input('>>>').strip()
            if not cmd: continue
            if cmd == 'quit':
                server.server_close()
                break
            print(threading.enumerate())

ThreadingTCPServer是TCPServer的子类,它混合了ThreadingMixIn类使用多线程来接受客户端的连接。

4 聊天室

代码如下:

import socketserver
import logging
import threading

FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT)

class ChatRequestHandler(socketserver.BaseRequestHandler):
    clients = set()

    # 每个连接进来时的操作
    def setup(self):
        super(ChatRequestHandler, self).setup()
        self.event = threading.Event()
        self.lock = threading.Lock()
        self.clients.add(self.request)

    # 每个连接的业务处理
    def handle(self):
        super(ChatRequestHandler, self).handle()
        while not self.event.is_set():

            # use Client code except
            try:
                data = self.request.recv(1024)
            except ConnectionResetError:
                self.clients.remove(self.request)
                self.event.set()
                break

            # use TCP tools except
            if data.rstrip() == b'quit' or data == b'':
                with self.lock:
                    self.clients.remove(self.request)
                logging.info("{}:{} is down ~~~~~~".format(*self.client_address))
                self.event.set()
                break

            msg = "{}:{} {}".format(*self.client_address, data.decode()).encode()
            logging.info('{}:{} {}'.format(*self.client_address, msg))
            with self.lock:
                for client in self.clients:
                    client.send(msg)

    # 每个连接关闭的时候,会执行
    def finish(self):

        super(ChatRequestHandler, self).finish()
        self.event.set()
        self.request.close()

class ChatTCPServer:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.sock = socketserver.ThreadingTCPServer((self.ip, self.port), ChatRequestHandler)

    def start(self):
        self.sock.daemon_threads = True
        threading.Thread(target=self.sock.serve_forever, name='server', daemon=True).start()

    def stop(self):
        self.sock.server_close()

if __name__ == '__main__':
    cts = ChatTCPServer('127.0.0.1', 9999)
    cts.start()

    while True:
        cmd = input('>>>>:').strip()
        if cmd == 'quit':
            cts.stop()
        if not cmd: continue
        print(threading.enumerate())

使用TCP工具强制退出时会发送空串,而使用我们自己写的tcp client,则不会发送,所以这里所了两种异常的处理。socketserver只是用在服务端,客户端使用上一篇TCP client即可。

原文地址:https://www.cnblogs.com/dachenzi/p/10505611.html

时间: 2024-10-07 17:26:33

38 - 网络编程-socketserver的相关文章

py2/py3区别, 列表生成式, 网络编程, socketserver, 线程, uuid模块

一. py2/py3区别    - 编码&字符串        字符串:            py2:                unicode         v = u"root"    本质上用unicode存储(万国码)                (str/bytes)     v = "root"     本质用字节存储            py3:                str                v = "

Python2与Python3的区别/网络编程/socketserver模块中的多线程

一.知识点补充: 1.解释型语言和编译型语言 编译型(目前有两种解释): 先把代码编译成机器码 -> 机器寄存器去运行 :C语言 先把代码编译成XXX -> 计算机找虚拟机执行代码 -> 机器码交给计算机去执行 :C语言,JAVA,C# 解释型: 边解释边执行 2.Python中的作用域 Python中的函数是一个作用域 3.这里规定一下以后写元组的格式: v = (1,2,3,) 1.Python中函数是一个作用域 i=0 for i in range(10): pass print(

【python】网络编程-SocketServer 实现客户端与服务器间非阻塞通信

利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信.首先,先了解下SocketServer模块中可供使用的类:BaseServer:包含服务器的核心功能与混合(mix-in)类挂钩:这个类只用于派生,所以不会生成这个类的实例:可以考虑使用TCPServer和UDPServer.TCPServer/UDPServer:基本的网络同步TCP/UDP服务器.UnixStreamServer/ UnixDatagramServer:基本的基于文件同步TCP/UDP服务器.Fork

python网络编程-----&gt; socketserver实现并发

1.socketserver的作用: 基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环 socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题) 2.代码实现: 服务端: import socketserver class MyTcphandler(socketserver.BaseRequestHandler): def handle(self): while True: #通信循环 data=self.request.recv(

Pyhon网络编程-socketserver

一: 用法 import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): # 业务逻辑 server = socketserver.ThreadingTCPServer(('127.0.0.1',8088),MyServer) server.serve_forever() 原文地址:https://www.cnblogs.com/wc89/p/11030140.html

八. 网络编程( socketserver 模块 初级使用)

一 .socketserver 模块初级使用 ThreadingTCPServer ocketserver是标准库中的一个高级模块 socketserver可以简化创建客户端跟创建服务端的代码 socketserver 可以用于 TCP 协议 应用场景 应为tcp是一个长连接 只能保持一个人通话 但是socketserver就解决了同时多个客户端来 通话 初始化控制器类Handler[Handler是一个继承BaseRequestHandler的类Handler中的handle方法决定了每一个连

网络编程TCP/IP实现客户端与客户端聊天

一.TCP/IP协议 既然是网络编程,涉及几个系统之间的交互,那么首先要考虑的是如何准确的定位到网络上的一台或几台主机,另一个是如何进行可靠高效的数据传输.这里就要使用到TCP/IP协议. TCP/IP协议(传输控制协议)由网络层的IP协议和传输层的TCP协议组成.IP层负责网络主机的定位,数据传输的路由,由IP地址可以唯一的确定Internet上的一台主机.TCP层负责面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象. 二.TCP与UDP TCP是一种面向连接的保证可靠传输的协议

第八篇:python基础_8 面向对象与网络编程

本篇内容 接口与归一化设计 多态与多态性 封装 面向对象高级 异常处理 网络编程 一. 接口与归一化设计 1.定义 (1)归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度. (2)归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合. 2.模拟接口 #!/usr/bin/env pyhon #encoding: utf-8 #auth: yanglei class Interface: #定义接口Interface类来模

第三章 网络编程

终于学到网络编程了! 先上图和程序: 这是今天写的TCP的实现 服务器和客户端分别在两台电脑 这是服务器图: 这是服务器程序: 1 #-*- coding:utf-8 -*- 2 from socket import * #导入socket所有属性 3 from time import ctime #导入ctime() 4 5 6 host = '' #HOST 变量为空,表示bind()函数可以绑定在所有有效的地址上. 7 port = 21000 #设置端口 8 bufsize = 1024