python-cookbook事件驱动的i/o模型

来源:互联网 发布:淘宝hd版怎么不了图片 编辑:程序博客网 时间:2024/05/21 04:43

行行色色的事件驱动或异步I/O的包, 我们无法理解底层实现模型, 根据python的cookbook, 比较明了的体现了该模型, 便于今后理解及开发相关ioloop, eventloop。

基于select实现的单进程单线程的事件轮询模型。 测试用telnet 127.0.0.1 8888可以多开几个窗口


#!/usr/bin/env pythonclass EventHandler(object):    """An I/O event loop    """    def fileno(self):        raise NotImplemented    def wants_to_receive(self):        """Return True if receiving is allowed        """        return False    def handle_receive(self):        """Perform the receive operation        """        pass    def wants_to_send(self):        """Return True if sending is requested        """        return False    def handle_send(self):        """send data        """        passimport socketclass TCPServer(EventHandler):    """    """    def __init__(self, address, client_handler, handler_list):        """        """        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)        self.sock.bind(address)        self.sock.listen(1)        self.client_handler = client_handler        self.handler_list = handler_list    def fileno(self):        return self.sock.fileno()        def wants_to_receive(self):        return True        def handle_receive(self):        # import time        # time.sleep(3)        client, addr = self.sock.accept()        # Add the client to the event loop's handler list        self.handler_list.append(self.client_handler(client, self.handler_list))        print "accept:%s,%s" % (client, addr) class TCPClient(EventHandler):            def __init__(self, sock, handler_list):        self.sock = sock        self.handler_list = handler_list        self.outgoing = bytearray()     def fileno(self):        return self.sock.fileno()        def close(self):        self.sock.close()        self.handler_list.remove(self)    def wants_to_send(self):        return True if self.outgoing else False         def handle_send(self):        nsent = self.sock.send(self.outgoing)        self.outgoing = self.outgoing[nsent:]class TCPEchoClient(TCPClient):    def wants_to_receive(self):        return True    def handle_receive(self):        data = self.sock.recv(8192)        print "recv:%s" % data        if not data:            self.close()        else:            self.outgoing.extend(data)import selectdef event_loop(handlers):    while True:        wants_recv = [h for h in handlers if h.wants_to_receive()]        wants_send = [h for h in handlers if h.wants_to_send()]        can_recv, can_send, err = select.select(wants_recv, wants_send, [])        for h in can_recv:            h.handle_receive()        for h in can_send:            h.handle_send()if __name__ == "__main__":    handlers = []    handlers.append(TCPServer(('', 8888), TCPEchoClient, handlers))    event_loop(handlers)    

附加:

服务端:

sock.bind(addr)sock.listen(1)  # servclient,addr=sock.accept() client.recv/send
客服端:

sock.connect(addr)scok.recv/send


1. serv只有一个, 可以不停的accept

2. 一次主动connect, 建立一个连接. accept与connect对应

3. serv的端口与ip是bind不变的

4. 每次connect之后,会分配一个新端口与之通信(新的socket的fd)

5. event loop模型就是处理一个serv不停的accept, 多个client的读写问题。很多技术贴解释select, epool说可读可写意思是:

可读: accept到了一个client

可写: client读完了(if not data: client.send(self.outgoing)), 不考虑没recv完send情况,其中细节有很多

流程描述:

第一次遍历handlers:  

可读: accept一个client之后, 取名叫client0, client0加入handers(client0此时是可读不可写)。

可写队列空

第二次遍历handlers: 

可读:  发现client0可读, 执行handle_receive(读取数据到outgoing)。如果又新accpet一个client, 取名叫client1, 执行handler_receive加入handlers, client1可读不可写

可写: 空

第三次遍历handlers:

可读: client0可读, 执行handle_receive, 假如这里已经读取完了所有数据(这里有一个读完了的细节), 从handlers里面移除. client1与client0的第二次遍历一样

可写: client0可写, send完数据. 如果需多次send, 需多次select(每次select返回该client可写)


简单来说:

serv可读

client可读

client可写

可读, 可写多次

移除client

阻塞, 或继续serv可读

一次connect至少需要三次select返回


UDP的event loop实例,来自cookbook

很方便处理多个udp连接

#!/usr/bin/env pythonclass EventHandler(object):    """An I/O event loop    """    def fileno(self):        raise NotImplemented        def wants_to_receive(self):        """Return True if receiving is allowed        """        return False             def handle_receive(self):        """Perform the receive operation        """        pass    def wants_to_send(self):        """Return True if sending is requested        """        return False          def handle_send(self):        """send data        """        passimport socketimport timeclass UDPServer(EventHandler):    """An UDP io event loop handler    """    def __init__(self, address):        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)        self.sock.bind(address)        def fileno(self):        return self.sock.fileno()    def wants_to_receive(self):        # blocking        return True     class UDPTimeServer(UDPServer):    def handle_receive(self):        msg, addr = self.sock.recvfrom(1)        self.sock.sendto(str(time.time()).encode('ascii'), addr)class UDPEchoServer(UDPServer):    def handle_receive(self):        msg, addr = self.sock.recvfrom(1024 * 10)        self.sock.sendto(msg, addr)import select def event_loop(handlers):    while True:        wants_recv = [h for h in handlers if h.wants_to_receive()]        wants_send = [h for h in handlers if h.wants_to_send()]        can_recv, can_send, err = select.select(wants_recv, wants_send, [])        for h in can_recv:            h.handle_receive()        for h in can_send:            h.handle_send()if __name__:    handlers = [UDPTimeServer(('', 14000)), UDPEchoServer(('', 15000))]    event_loop(handlers)

测试

import socketimport timesock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock.sendto(b'',('localhost',14000))print(sock.recvfrom(1024))sock2.sendto(b'hi', ('',15000))print(sock.recvfrom(1024)) print(sock2.recvfrom(1024))~                                



0 0
原创粉丝点击