温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

46网络编程_socketserver

发布时间:2020-07-22 17:39:49 来源:网络 阅读:190 作者:chaijowin 栏目:编程语言

 

目录

socketserver模块:... 1

编程接口:... 2

总结,创建服务器步骤:... 4

例,实现EchoServer... 4

例,改写ChatServer... 5

 

 

 

socketserver模块:

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

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

 

        +------------+

        | BaseServer |

        +------------+

              |

              v

        +-----------+        +------------------+

        | TCPServer |------->| UnixStreamServer |

        +-----------+        +------------------+

              |

              v

        +-----------+        +--------------------+

        | UDPServer |------->| UnixDatagramServer |

        +-----------+        +--------------------+

 

4sync同步类:

TCPServerUDPServerUnixStreamServerUnixDatagramServer

很少用;

 

2mixin类:

ForkingMixInThreadingMixIn

 

4async异步类,生产中常用

ForkingTCPServer(ForkingMixIn,TCPServer)ForkingUDPServer(ForkingMixIn,UDPServer)   #创建多进程

ThreadingTCPServer(ThreadingMixIn,TCPServer)ThreadingUDPServer(ThreadingMixIn,UDPServer)   #创建多线程

 

注:

一般ThreadingTCPServer够用;

如果并发很高可考虑用ForkingTCPServer

ThreadingUDPServer甚至也很少用,尽管在LAN中,如果忙起来时接收到的包的顺序是乱的;

 

 

编程接口:

class BaseServer:

    def __init__(self, server_address, RequestHandlerClass):   #服务器绑定的地址信息;用于处理请求,该类必须是BaseRequestHandler类的子类

 

    def finish_request(self, request, client_address):   #处理请求的方法

        """Finish one request by instantiating RequestHandlerClass."""

        self.RequestHandlerClass(request, client_address, self)   #实例化,RequesthandlerClass的构造

 

查看源码,写框架的思想:

class BaseRequestHandler:   #和用户连接的用户请求处理类,server实例接收用户请求后,最后会实例化这个类;它会一次调用三个函数setup()(每一个连接初始化)、handler()(每一次请求处理,必须覆盖)、finish()(每一个连接清理),子类可覆盖

    def __init__(self, request, client_address, server):   #初始化时送入3个构造参数,requestclient_addressserverTCPServer),以后可在BaseRequestHandler类的实例上使用self.request(和client连接的socket对象)、self.cleint_address(是客户端地址)、self.server(是TCPServer本身)

        self.request = request

        self.client_address = client_address

        self.server = server

        self.setup()

        try:

            self.handle()

        finally:

            self.finish()

 

    def setup(self):   #每一个连接初始化,初始化工作,如ChatServer中维护的数据结构放到此段;实现了这三个方法,只不过是空操作,而raise NotImplementedError称为抽象,不实现

        pass

 

    def handle(self):   #每一次请求处理,必须覆盖;handle()sock.accept()对应,用户连接请求过来后,建立连接并生成一个socket对象(保存在self.request中)和客户端地址(保存在self.client_address中),之后的操作就和socket编程一样了

        pass

 

    def finish(self):   #每一个连接清理,清理工作

        pass

注:

setup()finish()只执行一次;

handler()在不加锁情况下,也是执行一次;

 

例:

class MyHandler(socketserver.BaseRequestHandler):   #右键MyHandlerGenerate-->Overwrite Methods,可快速生成要覆盖的方法

    def handle(self):

        super().handle()   #此句可不写,因为父类中的handler()为空操作;但如果是StreamRequestHandler则必须要写,该类中实现了handler()方法

        print(self.request, self.client_address, self.server)

        print('{} handler'.format(self.__class__))

        print(self.__dict__)

        print(type(self).__dict__)

        print(self.__class__.__bases__[0].__dict__)

        print(threading.enumerate(), threading.current_thread())

        # pass   #TODO   #提醒自己还没写完

        print('come')

        for i in range(3):   #clientserver端长时间连接,在handler里循环;分布式服务之间需传递心跳包(传递事务、节点信息等),服务之间要长连接,不能断;数据库连接池不应用长连接,传完数据就可断开,有很多连接等着连DB

            data = self.request.recv(1024)

            print(data)

 

addr = ('127.0.0.1', 9998)

server = socketserver.ThreadingTCPServer(addr, MyHandler)   #用多client连接测

# server = socketserver.TCPServer(addr, MyHandler)   #同步,等前一个连接断开后,才能接收并处理下一个连接的请求

server.serve_forever()   #启动大循环,类似while

 

server.shutdown()

server.server_close()   #建议关闭连接前先server.shutdown()

输出:

<socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 7576)> ('127.0.0.1', 7576) <socketserver.ThreadingTCPServer object at 0x0000000000B656A0>

<class '__main__.MyHandler'> handler

{'request': <socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 7576)>, 'client_address': ('127.0.0.1', 7576), 'server': <socketserver.ThreadingTCPServer object at 0x0000000000B656A0>}

{'__doc__': None, '__module__': '__main__', 'handle': <function MyHandler.handle at 0x0000000001231C80>}

(<class 'socketserver.BaseRequestHandler'>,)

{'setup': <function BaseRequestHandler.setup at 0x0000000001477A60>, '__init__': <function BaseRequestHandler.__init__ at 0x00000000014779D8>, '__dict__': <attribute '__dict__' of 'BaseRequestHandler' objects>, '__module__': 'socketserver', '__doc__': 'Base class for request handler classes.\n\n    This class is instantiated for each request to be handled.  The\n    constructor sets the instance variables request, client_address\n    and server, and then calls the handle() method.  To implement a\n    specific service, all you need to do is to derive a class which\n    defines a handle() method.\n\n    The handle() method can find the request as self.request, the\n    client address as self.client_address, and the server (in case it\n    needs access to per-server information) as self.server.  Since a\n    separate instance is created for each request, the handle() method\n    can define other arbitrary instance variables.\n\n    ', 'handle': <function BaseRequestHandler.handle at 0x0000000001477AE8>, '__weakref__': <attribute '__weakref__' of 'BaseRequestHandler' objects>, 'finish': <function BaseRequestHandler.finish at 0x0000000001477B70>}

[<_MainThread(MainThread, started 4136)>, <Thread(Thread-1, started 4372)>] <Thread(Thread-1, started 4372)>

come

 

总结,创建服务器步骤:

1class MyHandler(socketserver.BaseRequestHandler):,通过对BaseRequestHandler类进行子类化并覆盖其handle()方法,来创建请求处理程序类,此方法处理传入请求;

2server=socketserver.ThreadingTCPServer(addr,MyHandler),必须实例化一个服务器类,并向其传入服务器的地址和请求处理程序类;

3server.serve_forever()server.handle_request(),调用服务器对象的serve_forever()(一直启动)或server.handle_request()(一次性的)方法;

4server.shutdown()server.close(),调用server.close()(关闭套接字)前先server.shutdown()等待停止server.serve_forever()

 

为每一个连接提供RequestHandlerClass类实例,一次调用setup()handler()finish()方法,且使用了try...finally结构(查看BaseRequestHandler源码)保证finish()方法一定能被调用,这些方法一次执行完成;

如果想维持这个连接与客户端通信,需要在handler()中使用循环;

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

 

 

例,实现EchoServer

client发来什么,就返回什么消息;

class EchoHandler(socketserver.BaseRequestHandler):

    def setup(self):

        super().setup()

        self.event = threading.Event()

 

    def handle(self):

        super().handle()

        while not self.event.is_set():

            data = self.request.recv(1024)

            data = data.decode()

            msg = 'ack: {} {}'.format(self.client_address, data)

            msg = msg.encode()

            self.request.send(msg)

        print('end')

 

    def finish(self):

        super().finish()

        self.event.set()

 

addr = ('127.0.0.1', 9998)

server = socketserver.ThreadingTCPServer(addr, EchoHandler)

# server.serve_forever()

server_thread = threading.Thread(target=server.serve_forever, daemon=True)

server_thread.start()

 

# server.shutdown()

# server.server_close()

try:

    while True:

        cmd = input('>>> ')

        if cmd.strip() == 'quit':   #只有在client都断开,与server端没有连接时才正常退出

            break

except Exception as e:

    print(e)

except KeyboardInterrupt:

    print('exit')

finally:

    server.shutdown()

    server.server_close()

 

 

例,改写ChatServer

如果使用文件处理,使用StreamRequestHandler

可用心跳机制;

 

class ChatHandler(socketserver.BaseRequestHandler):

    clients = {}

    def setup(self):

        super().setup()

        self.event = threading.Event()

        print(self.client_address, threading.current_thread(), self.clients)

 

    def handle(self):

        super().handle()

        while not self.event.is_set():

            try:   #缓冲区异常、连接异常最好自己捕获到,虽然父类中有try,但最好自己捕获

                data = self.request.recv(1024).decode().strip()

                                     if len(data) == 0:   #if not data,解决client主动断开后产生的异常,20180901追加尚未测试

                                               raise BrokenPipeError('client broken')

            except Exception as e:

                logging.info(e)

                data = 'quit'   #技巧,某个连接一旦有问题,会有各种异常,此处直接断开

            logging.info(data)

 

            if data == 'quit':

                break

 

            self.clients[self.client_address] = self.request

            msg = 'ack: {}'.format(data)

            for c in self.clients.values():

                c.send(msg.encode())

 

    def finish(self):

        super().finish()

        self.clients.pop(self.client_address)

        self.event.set()

 

addr = ('127.0.0.1', 9998)

server = socketserver.ThreadingTCPServer(addr, ChatHandler)

server_thread = threading.Thread(target=server.serve_forever, daemon=True)

server_thread.start()

 

myutils.show_threads()   #在主线程中就可,没必要放到工作线程中

try:

    while True:

        cmd = input('>>> ').strip()

        if cmd == 'quit':

            break

except Exception as e:

    print(e)

except KeyboardInterrupt:

    print('exit')

finally:

    server.shutdown()

    server.server_close()

输出:

>>> [<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <_MainThread(MainThread, started 9820)>]

('127.0.0.1', 8000) <Thread(Thread-2, started 4008)> {}

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>]

('127.0.0.1', 8003) <Thread(Thread-3, started 9456)> {}

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

2018-08-24-09:33:36       Thread info: 9456 Thread-3 test

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

2018-08-24-09:33:41       Thread info: 4008 Thread-2 test

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

2018-08-24-09:33:48       Thread info: 9456 Thread-3 test2

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

2018-08-24-09:33:51       Thread info: 4008 Thread-2 test1

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]

2018-08-24-09:33:53       Thread info: 4008 Thread-2

2018-08-24-09:33:53       Thread info: 4008 Thread-2 [WinError 10053] 您的主机中的软件中止了一个已建立的连接。

2018-08-24-09:33:53       Thread info: 4008 Thread-2 quit

2018-08-24-09:33:55       Thread info: 9456 Thread-3

2018-08-24-09:33:55       Thread info: 9456 Thread-3 [WinError 10053] 您的主机中的软件中止了一个已建立的连接。

2018-08-24-09:33:55       Thread info: 9456 Thread-3 quit

[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <_MainThread(MainThread, started 9820)>]

quit

 

 


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI