SocketServer(socket、群聊、TCPserver、UDPserver)

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 socketserver
import threading


class 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 socketserver
import threading


class 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 socketserver
import threading
import datetime
import logging


FORMAT = "%(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 = True
threading.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模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编

程的难度。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值