• SocketServer模块


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

    类的继承关系

    socketserver简化了网络服务器的编写。

    他有四个同步类:TCPServer,UDPServer,UNIXStreamServer,UNIXDatagramServer。

    2个mixin类,ForKingMIXIN.和ThreadingMixln类,用来支持异步。

    class ForkingUDPServer(ForkingMixln,UDPServer):pass

    class ForkingTCPServer(ForkingMixln,TCPServer):pass

    class ThreadingUDPServer(ThreadingMixln,UDPServer):pass

    class ThreadingTCPServer(ThreadingMixln,TCPServer):pass

    fork创建多进程,thread是创建多线程。

    编程接口

    socketserver.BaseServer(server_address,RequestHandlerClass)

    需要提供务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。

    RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下:

     BaseRequestHandler类

    它是和用户连接的用户请求处理类的基类,定义为BaseRequestHandler(request,client_address,server)

    服务端server实例接收用户晴请求后,最后会实例化这个类。

    它被初始化时,送入3个构造参数,request,client_address,server自身。

    以后就可以在BaseRequestHandler类的实例上使用以下属性:

    self.request是和客户端的链接的socket对象

    self.server是TCPServer本身

    self.client_address是客户端地址

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

    import socketserver
    
    class MyHandler(socketserver.BaseRequestHandler):
        def handle(self):
            super().handle()
            #todo
            print("come")
    
    addr = ("127.0.0.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()
    
    server.server_close()

    上面的代码运行起来,测试可以看到服务已经启动起来了。处于监听的状态。

    再继续修改代码。

    import socketserver
    import threading
    
    class MyHandler(socketserver.BaseRequestHandler):
        def setup(self):
            super().setup()
            self.event = threading.Event()
    
    
        def handle(self):
            super().handle()
            print(self.server,self.client_address,self.request)
            while not self.event.wait(1):
                print("come")
    
    addr = ("127.0.0.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()
    
    server.server_close()
    
    结果为:
    <socketserver.ThreadingTCPServer object at 0x00000000026BA8D0> ('127.0.0.1', 55533) <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 55533)>

    在继续修改代码、

    import socketserver
    import threading
    
    class MyHandler(socketserver.BaseRequestHandler):
        def setup(self):
            super().setup()
            self.event = threading.Event()
    
    
        def handle(self):
            super().handle()
            print(self.server,self.client_address,self.request)
            while not self.event.is_set():
                data = self.request.recv(1024)
                print(data)
    
    addr = ("127.0.0.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()
    
    server.server_close()

    上面的代码运行起来,由小工具发送数据就可以直接显示了。

    import socketserver
    import threading
    
    class MyHandler(socketserver.BaseRequestHandler):
        def setup(self):
            super().setup()
            self.event = threading.Event()
    
    
        def handle(self):
            super().handle()
            print(self.server,self.client_address,self.request)
            while not self.event.is_set():
                data = self.request.recv(1024)
                msg = "your msg = {}".format(data.decode()).encode()
                print(data)
                self.request.send(msg)
    
    addr = ("127.0.0.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()
    
    server.server_close()

    上面就可以回消息了。上面可以连接几个客户端。

     测试代码

    import threading 
    import socketserver
    
    class MyHandler(socketsever.BasequestHandler):
        def handler(self):
            #super().handle()可以不调用,父类handle什么都没有做
            print("_"*30)
            print(self.server)#服务
            print(self.request)#服务端复制客户端链接请求的socket对象
            print(self.client_address)#客户端地址
            print(self.__dict__)
            print(self.server.__dict__)#能看到负责accept的socket
            
            print(threading.enumerate())
            print(threading.current_thread())
            print("-"*30)
            
    addr = ("192.168.142.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()#永久

    测试结果说明,handle方法相当于socket的recv方法。

    每个不同的链接上的请求过来后,生成这个链接的socket对象即self.request,客户端渎职是self.client_address。

    问题

    测试过程中,上面代码,连接后立即断开了,为什么?

    怎么样才能客户端和服务器端长时间连接

    import threading 
    import socketserver
    import logging
    
    logging.bacicConfig(level = logging.INFO,format = "%(asctime)s %(threadName)s %(thread)d %(message)s")
    
    class MyHandler(socketsever.BaseRequestHandler):
        def handler(self):
            #super().handle()可以不调用,父类handle什么都没有做
            print("_"*30)
            print(self.server)#服务
            print(self.request)#服务端复制客户端链接请求的socket对象
            print(self.client_address)#客户端地址
            print(self.__dict__)
            print(self.server.__dict__)#能看到负责accept的socket
            
            print(threading.enumerate())
            print(threading.current_thread())
            print("-"*30)
            for i in range(3):
                data = self.request.recv(1024)
                logging.info(data)
            logging.info("====end====")
            
    addr = ("192.168.142.1",9999)
    server = socketserver.ThreadingTCPServer(addr,MyHandler)
    
    server.serve_forever()#永久

    将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。

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

    TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。

    总结

    创建服务器需要几个步骤

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

    实现EchoServer

    顾名思义,Echo,来什么消息回显什么消息。

    客户端发来什么消息,返回什么消息。

    import threading
    from socketserver import ThreadingTCPServer,BaseRequestHandler
    import sys
    
    class EchoHandler(BaseRequestHandler):
        def setup(self):
            super().setup()
            self.event = threading.Event()#初始化工作
    
        def finish(self):
            super().finish()
            self.event.set()
    
        def handle(self):
            super().handle()
    
            while not self.event.is_set():
                data = self.request.recv(1024).decode()
                msg = "{} {}".format(self.client_address,data).encode()
                self.request.send(msg)
            print("end")
    
    addr = ("127.0.0.1",9999)
    server = ThreadingTCPServer(addr,EchoHandler)
    
    server_thread = threading.Thread(target=server.serve_forever,name="echoserver",daemon=True)
    server_thread.start()
    
    try:
        while True:
            cmd = input(">>>>>")
            if cmd.strip() =="quit":
                break
            print(threading.enumerate())
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        pass
    finally:
        print("exit")
        sys.exit(0)

    上面的代码启动起来,就可以回显消息了,主程序不会有任何显示。

    #小工具结果
    
    10:38:59 发送数据:123[1次]
    10:38:59 收到数据:('127.0.0.1', 51338) 123
    10:39:11 发送数据:xpffsw
    [1次]
    10:39:11 收到数据:('127.0.0.1', 51338) xpffsw

    练习——改写ChatServer

    使用ThreadingTCPServer改写ChatServer

    import threading
    from socketserver import ThreadingTCPServer,BaseRequestHandler
    import sys
    import logging
    
    FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
    logging.basicConfig(format=FORMAT,level=logging.INFO)
    
    class ChatHandler(BaseRequestHandler):
        clients = {}
    
        def setup(self):
            super().setup()
            self.event = threading.Event()#初始工作
            self.clients[self.client_address] = self.request
    
        def finish(self):
            super().finish()#清理工作
            self.clients.pop(self.client_address)#能执行到吗?
            self.event.set()
    
        def handle(self):
            super().handle()
            while not self.event.is_set():
               data = self.request.recv(1024).decode()
               if data == "quit":
                   break
                msg = "{} {}".format(self.client_address,data).encode()
               logging.info(msg)
               for c  in self.clients.values():
                   self.request.send(msg)
            print("end")
        
    addr =("0.0.0.0",9999)
    server = ThreadingTCPServer(addr,ChatHandler)
    
    server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
    server_thread.start()
    
    try:
        while True:
            cmd = input(">>>")
            if cmd.strip()== "quit":
                break
            print(threading.enumerate())
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        pass
    finally:
        print("exit")
        sys.exit(0)

    问题,self.clients.pop(self.client_address)#能执行到吗?

    如果连接的线程中handle方法中抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clients的pop方法还能执行吗?

    当然能执行,基类源码保证了即使异常,也能执行finish方法。但不代表不应该捕获客户端各种异常。

    解决客户端主动连接断开问题

    import threading
    from socketserver import ThreadingTCPServer,BaseRequestHandler
    import sys
    import logging
    
    FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
    logging.basicConfig(format=FORMAT,level=logging.INFO)
    
    class ChatHandler(BaseRequestHandler):
        clients = {}
    
        def setup(self):
            super().setup()
            self.event = threading.Event()#初始工作
            self.clients[self.client_address] = self.request
    
        def finish(self):
            super().finish()#清理工作
            self.clients.pop(self.client_address)#能执行到吗? 
            self.event.set()
    
        def handle(self):
            super().handle()
            while not self.event.is_set():
               data = self.request.recv(1024).decode()
               print(data,"____________________")#增加
               if data == "quit":
                   break
                msg = "{} {}".format(self.client_address,data).encode()
               logging.info(msg)
               for c  in self.clients.values():
                   print("++++++++++++++")#增加
                   self.request.send(msg)
            print("end")
    
    addr =("0.0.0.0",9999)
    server = ThreadingTCPServer(addr,ChatHandler)
    
    server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
    server_thread.start()
    
    try:
        while True:
            cmd = input(">>>")
            if cmd.strip()== "quit":
                break
            print(threading.enumerate())
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        pass
    finally:
        print("exit")
        sys.exit(0)

    通过打印可以看到,客户端主动断开,会导致recv方法立即返回一个空bytes,并没有同时抛出异常,当循环回到recv这一句的时候就会抛出异常。所以,可以通过判断data数据是否为空来客户端是否断开。

    import threading
    from socketserver import ThreadingTCPServer,BaseRequestHandler
    import sys
    import logging
    
    FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
    logging.basicConfig(format=FORMAT,level=logging.INFO)
    
    class ChatHandler(BaseRequestHandler):
        clients = {}
    
        def setup(self):
            super().setup()
            self.event = threading.Event()#初始工作
            self.clients[self.client_address] = self.request
    
        def finish(self):
            super().finish()#清理工作
            self.clients.pop(self.client_address)#能 执行到吗?
            self.event.set()
    
        def handle(self):
            super().handle()
            while not self.event.is_set()
               data = self.request.recv(1024).decode()
               print(data,"____________________")#增加
               if  not data or data == "quit":
                   print("broken pipe")
                   break
                msg = "{} {}".format(self.client_address,data).encode()
               logging.info(msg)
               for c  in self.clients.values():
                   self.request.send(msg)
            print("end")
    
    addr =("0.0.0.0",9999)
    server = ThreadingTCPServer(addr,ChatHandler)
    
    server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
    server_thread.start()
    
    try:
        while True:
            cmd = input(">>>")
            if cmd.strip()== "quit":
                break
            print(threading.enumerate())
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        pass
    finally:
        print("exit")
        sys.exit(0)

    上面的代码可以增加一个代码,处理一个异常。

    总结:

    为每个连接提供RequestHandlerclass类实例,一次调用setup,handle、finish方法,且使用了try……finally结构保证finish方法一定能被调用,这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数中使用循环。

    socketserver模块提供的不同的类,但是编程接口都是一样的,即便是多进程,多线程的类也是一样,大大减少了编程的难度。

  • 相关阅读:
    JavasScript 实现二分法快排注意点
    JS的面向对象二(通过构造函数的方式)
    JS的面向对象一(通过构造函数的方式)
    leetcode.977_有序数组的平方
    leetcode_38.报数
    leetcode_20.c++有效的括号
    leetcode_21.c++合并两个有序列表
    leetcode_最长公共前缀
    T2_两数相加
    T1_两数之和
  • 原文地址:https://www.cnblogs.com/xpc51/p/11966469.html
Copyright © 2020-2023  润新知