博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SocketServer(socket、群聊、TCPserver、UDPserver)
阅读量:4131 次
发布时间:2019-05-25

本文共 6427 字,大约阅读时间需要 21 分钟。

1. SocketServer

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

1.1 类的继承关系

socketserver简化了网络服务器的编写。它有四个同步类:

  • TCPServer
  • UDPServer
  • UnixStreamServer
  • UnixDatagramServer

2个Mixin类:ForkingMixin和ThreadingMixin类,用来支持异步。由此得到:

  • class ForkingUDPServer(ForkingMixin, UDPServer): pass
  • class ForkingTCPServer(ForkingMixin, TCPSserver): pass
  • class ThreadingUDPServer(ThreadingMixin, UDPServer): pass
  • class ThreadingTCPServer(ThreadingMixin, TCPServer): pass

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

1.2 编程接口

socketserver.BaseServer(server_address, RequestHandlerClass)

需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。RequestHandlerClass必须是BaseRequestHandler类的子类,在BaseSserver中的代码如下:

# BaseServer代码class BaseServer:    def __init__(self, server_address, RequestHandlerClass):        """Constructor. May be extended, do not override."""        self.server_address = server_address        self.RequestHandlerClass = RequestHandlerClass        self.__is_shut_down = threading.Event()        self.__shutdown_request = False            def finish_request(self, request, client_address):  # 处理请求的方法        """Finish one request by instantiating RequestHandlerClass."""        self.RequestHandlerClass(request, client_address, self)  # RequestHandlerClass构造

 

1.2.1 BaseRequestHandler类

它是和用户连接的用户请求处理类的基类,定义为:BaseRequestHandler(request, client_address, server),服务端Server实例接收用户请求后,最后会实例化这个类。它被初始化时,送入三个构造参数:request, client_address, server自身,以后就可以在BaseRequestHandler类的实例使用以下属性:

  • self.request是和客户端连接的socket对象
  • self.server是TCPServer实例本身
  • self.client_address是客户端地址

这个类在初始化的时候,它会依次调用3个方法,子类可以覆盖这些方法。

# 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

测试代码:

import socketserverimport threadingclass MyHandler(socketserver.BaseRequestHandler):    def handle(self):        super().handle()  # 可以不调用,父类的handle什么都没做        print(self.server)  # 服务        print(self.request)  # 服务端负责客户端请求的socket对象        print(self.client_address)  # 客户端地址        print(self.server)        print(self.__dict__)        print(self.server.__dict__)  # 能看到负责accept的socket        print('*' * 30)        for i in range(3):            data = self.request.recv(1024)            print(data)            msg = 'server recv msg= {}'.format(data.decode()).encode()            self.request.send(msg)        print(threading.enumerate())        print(threading.current_thread())        print()server = socketserver.TCPServer(('127.0.0.1', 9999), MyHandler)# server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyHandler)print(server)server.serve_forever()  # 永久的提供服务,默认阻塞行为

测试说明,handle方法相当于socket的recv方法,每个不同的连接上的请求过来后,生成这个连接的socket对象即self.request,客户端的地址是self.client_address。

问题:测试过程中,上面代码连接后,立即就断开了,为什么?怎么才能客户端和服务端长时间连接呢?

用ThreadingTCPServer替换TCPserver,ThreadingTCPServer是异步的,可以同时处理多个连接。TCPserver是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。

总结,创建服务器需要几个步骤:

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

实现EchoServer:

顾名思义,echo,来什么信息回显什么信息,客户端发来什么信息,回显什么信息。

import socketserverimport threadingclass MyHandler(socketserver.BaseRequestHandler):    def setup(self):  # 注意每一次连接都会创建一个MyHandler实例        super().setup()        self.event = threading.Event()  # 不用担心会被覆盖    def handle(self):        super().handle()  # 可以不调用,因为父类什么都没做        while True:            data = self.request.recv(1024)            print(data)            msg = "{}".format(data.decode())            self.request.send(msg.encode())    def finish(self):        super().finish()        self.event.set()server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyHandler)# server.serve_forever()  # 默认阻塞,可以放在线程中threading.Thread(target=server.serve_forever, name='server_forever').start()while True:    cmd = input(">>>").strip()    if cmd == 'quit':        server.server_close()        print('bye')        break    print(threading.enumerate())

利用socketserver实现群聊:

import socketserverimport threadingimport datetimeimport loggingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatHandler(socketserver.BaseRequestHandler):    clients = {}  # 类属性    def setup(self):        super().setup()        self.event = threading.Event()        self.lock = threading.Lock()        with self.lock:            self.clients[self.client_address] = self.request    def handle(self):        super().handle()  # 可以不调用,因为父类什么都没做        while not self.event.is_set():            try:                data = self.request.recv(1024)  # 接收信息也可能出现异常            except Exception as e:                logging.error(e)                data = b''            logging.info(data)            if data.strip() == b'quit' or data.strip() == b'':  # 客户端主动断开,移除self.client_address在finish方法中                self.request.close()                break            msg = "{} [{}:{}] {}".format(datetime.datetime.now(), *self.client_address, data.decode())            exc = set()            with self.lock:                for c, v in self.clients.items():                    try:                        v.send(msg.encode())  # 可能出现异常,发送失败,如突然断网了                    except Exception as e:                        logging.error(e)                        exc.add(c)                for c in exc:                    self.clients.pop(c)    def finish(self):        super().finish()        with self.lock:            self.clients.pop(self.client_address)        self.event.set()server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), ChatHandler)server.daemon_threads = Truethreading.Thread(target=server.serve_forever, name='serve_forever').start()while True:    cmd = input('>>>').strip()    if cmd == 'quit':        server.server_close()        print('bye')        break    logging.info(threading.enumerate())

总结:

为每一个连接提供RequestHandlerClass类实例,依次调用setuphandlefinish方法,且使用了try...finally结构

保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数

中使用循环。

socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编

程的难度。

 

转载地址:http://qpfvi.baihongyu.com/

你可能感兴趣的文章
jQuery Deferred对象
查看>>
javascript DOM选择器querySelector
查看>>
jQuery Mobile orientationchange 事件
查看>>
DOMContentLoaded事件
查看>>
animationend 事件
查看>>
CSS 动画的 steps()
查看>>
HTML <figure> 标签
查看>>
html5<canvas>画图
查看>>
CSS布局-负边距
查看>>
word-wrap和word-break的区别
查看>>
CSS outline属性以及和border属性的区别
查看>>
CSS重设(reset)方法总结
查看>>
Canvas 最佳实践(性能篇)
查看>>
JavaScript事件委托的技术原理
查看>>
jQuery 事件委托
查看>>
CSS预处理器
查看>>
JS原型与原型链终极详解
查看>>
CSS布局 -- 圣杯布局 & 双飞翼布局
查看>>
JavaScript—requestAnimationFrame
查看>>
jQuery 事件 - mousedown() 方法
查看>>