SocketServer模块

SocketServer

简介

socket编程过于底层,想要编写健壮的代码比较困难,很多语句都对socket底层API进行了封装

  • Python的封装就是socketserver模块,它是网路服务编程框架,便于企业级快速开发

类的继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32SezvwN-1576046194657)(D:\1Face_To_Face\14week _fourteen\xmind\socketserver类继承关系.png)]

SocketServer简化了网路服务器的编写

4个同步类

  1. TCPServer(BaseServer);
  2. UDPServer(TCPServer);
  3. UnixStreamServer;
  4. UnixDatagramServer;

2个Mixin类,用来支持异步

  1. ThreadingMixIn,创建多线程
    • ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
    • ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
  2. ForkingMixIn,创建多进程
    • class ForkingTCPServer(ForkingMixIn, TCPServer): pass
    • class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    • 注意:fork需要操作系统支持,Windows不支持

编程接口


socketserver.BaseServer

__init__(self, server_address, RequestHandlerClass)

  • server_address,服务器绑定的地址信息;
  • RequestHandlerClass,处理请求的RequestHandlerClass类;
  • RequestHandlerClass类必须是BaseRequestHandler的子类;
class BaseServer:
    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""  # 构造器,可以扩展,不能重写
        self.server_address = server_address  # 为TCPServer实例添加server_address属性
        self.RequestHandlerClass = RequestHandlerClass # 为TCPServer实例添加RequestHandlerClass类对象,注意不是实例
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False
BaseServer.serve_forever(self, poll_interval=0.5)
  1. 循环调用_handle_request_noblock(self),查看跟进代码1;
  2. _handle_request_noblock(self);
    • Server端与Client端进行连接,并创建newsocket对象,等价socket.socket().accept()
    • 调用self.process_request(request, client_address),查看跟进代码2;
  3. self.process_request(request, client_address),查看跟进代码3;
  4. finish_request(self, request, client_address)
    • RequestHandlerClasss实例化,即BaseRequestHandler类实例化
# 1
    def serve_forever(self, poll_interval=0.5): 
        self.__is_shut_down.clear() # threading.Event()设置为False
        try:
            with _ServerSelector() as selector: # 看不懂,跳过
                selector.register(self, selectors.EVENT_READ) 

                while not self.__shutdown_request:    
                    ready = selector.select(poll_interval)
                    if ready:
                        self._handle_request_noblock() # 跟进代码1,循环调用self._handle_request_noblock()
                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

    ...   
# 2
    def _handle_request_noblock(self):
        try:
        # 注意,BaseServer没有get_request()方法,子类TCPServer实现了get_request()方法
        # self.get_request(),巧妙之处:此时的self为TCPServer的实例,TCPServer的实例调用自己类的方法,完美
        request, client_address = self.get_request() # 每个Client端,都会创建一个newsocket对象与其建立通信连接
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address) # 跟进代码2
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)
    ...
# 3
    def process_request(self, request, client_address):
    """Call finish_request. 
    Overridden by ForkingMixIn and ThreadingMixIn. 
    """ # 该方法在ForkingMixIn and ThreadingMixIn中覆盖
        self.finish_request(request, client_address) # 跟进代码3
        self.shutdown_request(request)

    ...
# 4
    def finish_request(self, request, client_address): 
        # RequestHandlerClasss实例化,即BaseRequestHandler类实例化
        self.RequestHandlerClass(request, client_address, self)

BaseRequestHandler

BaseServer实例调用serve_forrver() / handle_request()之后,最后会实例化这个类

注意:未调用serve_forrver() / handle_request()之前,Server端只是bing和listen,没有accept;

  • __init__(self, request, client_address, server);
    • request,和Client端通信的服务端new-socket对象,包含laddr和raddr,raddr即client_address;
    • client_address,Clinet端的IP地址和Port端口,二元组(‘IP地址’,Port);
    • server,TCPServer实例本身,其实例属性self.socket为socket监听对象;

BaseRequestHandler初始化过程

  • 先调用self.setup(),一般在self.handle()中循环发送消息,退出之后一定会调用self.finish归还资源;
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() # 保证资源socket资源归还

BaseRequestHandler实例方法

  • self.setup()方法,为 self / self.server增加属性等;
  • self.handle()方法,收发核心,相当与socket.socket.recv()、sockte.sockte.send()。Client端和newsocket对象的消息收发,都在此方法中实现;
  • self.finish()方法,requeset.close()归还socket资源;
    # TCP编程,Server端每连接一个Client端,都会生成一个新的socket对象
    def setup(self): # 每一个连接初始化
        pass 

    def handle(self): # 每一个请求处理
        pass # 

    def finish(self): # 每一个连接清理
        pass
  • 应用举例
class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        pass

    def setup(self):
        pass

    def finish(self):
        pass

serversocket.TCPServer(BaseServer)

  • __init__(self, server_address, RequestHandlerClass, bind_and_activate=True)

    • server_address,服务器绑定的地址信息;
    • RequestHandlerClass,处理请求的BaseRequestHandler的子类;
    • bind_and_activate=True,缺省True,服务端绑定Ip和Port,并开始监听
  • 注意事项

    • 同步阻塞类
    • 构造器函数中,调用了socket.bind()、socket.listen()方法
    • get_request(self)方法调用socket.accept()方法
  • 部分源码阅读

# TCPServer部分源码
class TCPServer(BaseServer):
    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        BaseServer.__init__(self, server_address, RequestHandlerClass) # 调用父类构造器方法
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)    # 对应TCP编程中的socket监听对象
        if bind_and_activate:
            try:
                self.server_bind() #  对应self.socket.bind(),绑定Ip和Port
                self.server_activate() # 对应self.socket.listen(),开始监听
            except:
                self.server_close() # self.socket.close(),异常关闭监听socket监听对象
                raise   # 将捕获的异常重新抛出
    ...

    def get_request(self):  # 注意,实例化时不会调用get_request方法
        return self.socket.accept()

ThreadingTCPServer(ThreadingMixIn, TCPServer)

ThreadingTCPServer是异步非阻塞的,可以同时处理多个连接

  • ThreadingMixIn,通过继承关系创建不同的线程运行BaseRequestHandler类实例对象;
  • TCPServer是同步的阻塞的,一个连接的handle方法执行完毕,参能处理另一个连接,且只有主线程;
class ThreadingMixIn:
    def process_request_thread(self, request, client_address):
        try:
            self.finish_request(request, client_address) # 生成BaseRequestHandler类实例
        except Exception:
            self.handle_error(request, client_address)
        finally:
            self.shutdown_request(request)  # 归还资源

    def process_request(self, request, client_address):
        t = threading.Thread(target = self.process_request_thread,
                             args = (request, client_address)) # 创建线程
        t.daemon = self.daemon_threads # non-daemon线程
        t.start()

测试代码

  • 初始化代码
import socketserver
import threading
import logging

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

class MyHandler(socketserver.BaseRequestHandler):
    def __init__(self,request,client_address,server):
    # 函数的传参过程,易混点1
        super().__init__(request=request, client_address=client_address, server=server)
        self.event = threading.Event()  
        # 打印不出来,因为super().__init__调用了handle,且当前handle死循环
        logging.info(self.request) 
  • super().__init__(request=request,client_address=client_address, server=server)
    • 等号右边的为MyHandler构造函数的形参,是MyHandler构造函数的本地变量
    • 等号左边为BaseRequestHandler构造函数的形参,是BaseRequestHandler构造函数的本地变量
  1. 两者是不同作用域且只是标识符相同的不同本地变量
  2. 将传入Myhandler构造函数的实参,再次指向BaseRequestHandler构造函数的形参即不同作用域的不同本地变量指向了同一个对象,实现函数的传参
  • 测试代码
class MyHandler(socketserver.BaseRequestHandler):
    def __init__(self,request,client_address,server):
        super().__init__(request=request, client_address=client_address, server=server)
        self.event = threading.Event() # 无法成功赋值
        logging.info(self.request) 

    def handle(self):
        super().handle()   # 可以不调用,父类handle什么都没有做
        logging.info('handle')
        while not self.event.is_set():
            data = self.request.recv(1024)
            logging.info(data)
            self.request.send(data)

    def finish(self):
        super().finish()
        self.request.close()

server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()

while True:
    cmd = input('>>>>').strip().lower()
    if cmd == 'quit':
        server.server_close()
        break

    print(threading.enumerate())

self.event = threading.Event()无法成功赋值

  • 父类BaseRequestHandler初始化的时候,调用了self.handle();
  • 而此处的self.handle()先于self.event = threading.Event(),故抛异常;
  • 在异常处停止运行,所以无法赋值成功,放在super().__init__()之前即可;

总结

创建服务器的步骤

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

TCP群聊

初步尝试

import socketserver
import threading
import logging

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

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        super().handle()   # 可以不调用,父类handle什么都没有做
        while not self.event.is_set():
            try:
                data = self.request.recv(1024)
            except Exception as e:
                logging.info(e)
                self.finish()
            else:
                if data == b'' or data == b'quit':
                    self.server.clients.discard(self.request)
                    self.finish()
                    break

                logging.info((self.client_address,data)) # 观察client端发送的信息
                for c in self.server.clients:
                    c.send(data)

    def setup(self):
        super().setup()
        self.event = threading.Event()

        if not hasattr(self.server,'clients'):  # 动态为self.server添加属性clients
            setattr(self.server,'clients',set())   # 无则创建

        self.server.clients.add(self.request)  # 有则追加

    def finish(self):
        super().finish()
        self.event.set()
        self.request.close()


server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()
server.handle_request()
while True:
    cmd = input('>>>>').strip().lower()
    if cmd == 'quit':
        server.server_close()
        break

    print(threading.enumerate())

TCP群聊实现

import socketserver
import threading
import logging

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

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        super().handle()   # 可以不调用,父类handle什么都没有做
        # print('-'*30)    # 均为测试    
        # print(self.request)
        # print(self.server)
        # print(self.client_address)
        # print(self.__dict__)
        # print(self.server.__dict__)
        #
        # print(threading.enumerate())
        # print(threading.current_thread())
        # print('-'*30)
        while not self.event.is_set():
            data = self.request.recv(1024)
            logging.info(data)
            for c in self.server.clients:
                c.send(data)

        self.finish()

    def setup(self):
        super().setup()
        self.event = threading.Event()

        # if not hasattr(self.server,'clients'):  # 动态为self.server添加属性clients
        #     setattr(self.server,'clients',set())   # 无clients,添加
        #     self.server.clients.add(self.request)  # 有则追加
        # else:
        #     self.server.clients.add(self.request)

        if not hasattr(self.server,'clients'):  # 动态为self.server添加属性clients
            setattr(self.server,'clients',set())   # 无则创建

        self.server.clients.add(self.request)  # 有则追加
        logging.info(self.server.clients)   # 观察添加情况

    def finish(self):
        super().finish()
        self.event.set()
        self.request.close()

addr = ('127.0.0.1',9999)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()

while True:
    cmd = input('>>>>').strip().lower()
    if cmd == 'quit':
        server.server_close()
        break

    print(threading.enumerate())

addr = ('127.0.0.1',9999)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()

while True:
    cmd = input('>>>>').strip().lower()
    if cmd == 'quit':
        server.server_close()
        break

    print(threading.enumerate())
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值