17.7. asynchat异步套接字命令/响应处理器

Source code: Lib/asynchat.py


这个模块建立在 asyncore 基础结构上,简化了异步客户端和服务器,使得处理带有以任意字符串终止或者可变长度的元素的协议更加容易。asynchat 定义了你子类化的抽象类 async_chat ,提供了 collect_incoming_data()found_terminator() 方法的实现。它使用了和 asyncore一样的异步循环,和两种类型的通道, asyncore.dispatcherasynchat.async_chat,它们可以被自由地混合在通道映射中。当接收传入的连接请求时,一个 asyncore.dispatcher 服务器通道往往会产生新的 asynchat.async_chat 通道对象。

class asynchat.async_chat

这个类是一个 asyncore.dispatcher 的抽象类。为了实际使用代码,你必须要子类化 async_chat,提供有意义的 collect_incoming_data()found_terminator() 方法。asyncore.dispatcher 方法可以被使用,尽管不是所有的在消息/响应上下文都有意义。

asyncore.dispatcher一样, async_chat 定义了一套在调用 select() 后套接字状态的分析产生的事件。一旦轮询循环开始,async_chat 对象的方法被事件处理框架调用,with no action on the part of the programmer.

两个类属性可以被修改以提高性能,或者可能甚至节约内存。

ac_in_buffer_size

异步输入缓存大小 (默认为 4096)。

ac_out_buffer_size

异步输出缓存大小 (默认为 4096).

不像 asyncore.dispatcherasync_chat 允许你定义一个生产者(producers)的先进先出(fifo)队列。一个生产者需要有一个方法, more(), 这个方法返回将被传送给通道的数据。生产者指出竭尽 (也就是 不包含数据) 会使它的 more() 方法返回空字符串。这时, async_chat 对象会从先进先出队列中移除生产者,并且如果有的话,它会开始使用下一个生产者。当生产者先进先出队列为空时,handle_write()  方法什么也不做。你使用通道对象的 set_terminator() 方法来描述怎样识别来自远程端点的输入传输的末尾,或者重要节点。

为了创建有用的 async_chat 子类,你输入的方法 collect_incoming_data()found_terminator() 必须要异步地处理通道接受的数据。方法描述如下。

async_chat.close_when_done()

压入 None 到生产者先进先出队列中。当这个生产者被弹出先进先出队列时会使得通达被关闭。

async_chat.collect_incoming_data(data)

当任意数量的data到达后被调用。该方法为默认方法,必须被重写。会引发NotImplementedError 异常。

async_chat.discard_buffers()

在紧急情况时这个方法会丢弃任何保留在生产者先进先出队列的输入以及/或者输出缓存。

async_chat.found_terminator()

当输入数据流符合由 set_terminator() 设置的终止条件时被调用。默认方法必须被覆写,会抛出 NotImplementedError 异常。通过实例属性应该使用缓存的输入数据。

async_chat.get_terminator()

返回当前通道的终止符。

async_chat.push(data)

向通道的先进先出队列压入数据以确保其传输。这是你要通道向网络写数据所做的全部,不过你可以在更复杂的系统中使用自己的生产者来实现诸如加密和分块。

async_chat.push_with_producer(producer)

接受生产者对象做参数,并把它加到与通道相关的生产者先进先出队列中。当所有当前压入的生产者竭尽了通道时会调用more() 来消耗生产者数据并把数据发送到远程端点。

async_chat.set_terminator(term)

设置在通道上被识别的终止条件。相应与三种不同的处理传入协议数据的方式,term 会使三种不同类型的值。

term Description
string Will call found_terminator() when the string is found in the input stream
integer Will call found_terminator() when the indicated number of characters have been received
None The channel continues to collect data forever

注意,任何在found_terminator() 被调用之后,跟随在终止符后的数据可以被通道读取。 Note that any data following the terminator will be available for reading by the channel after  is called.

17.7.1. asynchat - 辅助类

class asynchat.fifo([list=None])

fifo 可以保存由程序压入的,但是还没有弹出来写入到通道中的数据。fifo 是一个列表,用来储存数据以及/或者生产者,直到它们被使用。如果提供了 list 参数,那么它应该包含写入通道中的生产者或者数据。

is_empty()

当且仅当先进先出队列是空的时候返回 True

first()

返回最近被从先进先出队列 push() 的项。

push(data)

添加数据(可能是一个字符串或者一个生产者对象)到生产者先进先出队列中。

pop()

如果先进先出队列非空,返回 True, first(),删除弹出的项。对一个空的先进先出队列,返回 False, None

17.7.2. asynchat 例子

下面这个不完整的例子展示了 async_chat如何读取HTTP请求。Web服务器可能会为每一个连入的客户端连线创建一个 http_request_handler 对象。注意,初始时通道终止符被设成与HTTP头部最后的空行相匹配,还有一个用来标识头部是否被读取的标记。

一旦头部被读取,如果请求的类型是POST (指明将来的数据在输入流中呈现) ,然后会使用 Content-Length: 头部来设置一个数字终止符以从通道中读取争取数量的数据。

一旦所有的相关的输入被整理后, handle_request() 方法被调用,设置通道终止符为 None 可以确保额外的由web客户端发送的数据被忽略。

class http_request_handler(asynchat.async_chat):

    def __init__(self, sock, addr, sessions, log):
        asynchat.async_chat.__init__(self, sock=sock)
        self.addr = addr
        self.sessions = sessions
        self.ibuffer = []
        self.obuffer = ""
        self.set_terminator("\r\n\r\n")
        self.reading_headers = True
        self.handling = False
        self.cgi_data = None
        self.log = log

    def collect_incoming_data(self, data):
        """Buffer the data"""
        self.ibuffer.append(data)

    def found_terminator(self):
        if self.reading_headers:
            self.reading_headers = False
            self.parse_headers("".join(self.ibuffer))
            self.ibuffer = []
            if self.op.upper() == "POST":
                clen = self.headers.getheader("content-length")
                self.set_terminator(int(clen))
            else:
                self.handling = True
                self.set_terminator(None)
                self.handle_request()
        elif not self.handling:
            self.set_terminator(None) # browsers sometimes over-send
            self.cgi_data = parse(self.headers, "".join(self.ibuffer))
            self.handling = True
            self.ibuffer = []
            self.handle_request()