7

我有一个棘手而有趣的问题要问你。

在处理 I/O 任务(例如通过 Twisted、Tornado 中的某些传输层实现协议)时,我发现了类似的场景或模式。该模式是通用的而不是抽象的。例如,当您使用类似 MODEM的设备时,您向他发送命令并接收结果。

但是,有时您需要对调制解调器对最后一条命令的响应做出反应,并使用新命令。例如,假设modem为M,->为通信操作符,取一个参数,message key,server为S。

    1. s ->(a) M
       1.1 M ->(b) S # modem reacts on `a` as `b`; so next we should send him command B
       1.2 M ->(c) S # modem responses on `a` as `c`; so next we should send him C
    2. s ->(b) M
       2.1 M ->(g) S
       2.2 M -> (f) S
       ...
       2.N M -> (x) S
    ...

所以,它看起来像 FSM 行为。在使用非阻塞 I/O(通过流对象)时,最好在 tornado 中实现这个场景。通过简单地提供跟踪场景作为输入并覆盖输入中描述的状态(事件)的处理程序,我们可以获得良好的有限状态机行为。

输入可能具有以下符号:

{
  a: (b, c, d),
  b: (c, 'exit|silence'),
  c: (a, 'exit|silence'),
  d: (b)
}

其中所有这些字母数字符号都是州名。每个键值对都是状态名称和可能的状态转换集。

使用 tornado coroutines 和 futures 中引入的 FSM 的可能实现是什么?请分享您的想法和代码。

4

1 回答 1

5

I think that Twisted is more suited for protocol implementations. Anyway, in Python functions and methods are objects of first class which means that you can store them inside of dictionaries. You can also use functools.partial to bind a function with arguments to a dictionary key. You can use it to implement transitions. Each state should be a function containing a dictionary in which keys are possible input states and values are output states. Then you can easily hoop from one state to another. To make use of Tornado loop next states, instead of be called directly, should be registered as a callback using ioloop.IOLoop.instance().add_callback.

An example implementation of automata accepting language a*b*c:

import errno
import functools
import socket
from tornado import ioloop, iostream

class Communicator(object):
    def connection_ready(self, sock, fd, events):
        while True:
            try:
                connection, address = sock.accept()
            except socket.error, e:
                if e[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                    raise
                return
            connection.setblocking(0)
            self.stream = iostream.IOStream(connection)
            self.stream.read_until(delimiter='\n', callback=self.initial_state) 

    def initial_state(self, msg):
        msg = msg.rstrip()
        print "entering initial state with message: %s" % msg
        transitions = {
            'a' : functools.partial(ioloop.IOLoop.instance().add_callback, self.state_a, msg),
            'b' : functools.partial(ioloop.IOLoop.instance().add_callback, self.state_b, msg),
            'c' : functools.partial(ioloop.IOLoop.instance().add_callback, self.final_state, msg)
        }
        try:
            transitions[msg[0]]()
        except:
            self.stream.write("Aborted (wrong input)\n", self.stream.close)

    def state_a(self, msg):
        print "entering state a with message: %s" % msg
        transitions = {
            'a' : functools.partial(ioloop.IOLoop.instance().add_callback, self.stream.write, "got a\n", functools.partial(self.state_a, msg[1:])),
            'b' : functools.partial(ioloop.IOLoop.instance().add_callback, self.state_b, msg),
            'c' : functools.partial(ioloop.IOLoop.instance().add_callback, self.final_state, msg[1:])
        }
        try:
            transitions[msg[0]]()
        except:
            self.stream.write("Aborted (wrong input)\n", self.stream.close)

    def state_b(self, msg):
        print "entering state b with message: %s" % msg
        transitions = {
            'a' : functools.partial(ioloop.IOLoop.instance().add_callback, self.state_a, msg),
            'b' : functools.partial(ioloop.IOLoop.instance().add_callback, self.stream.write, "got b\n", functools.partial(self.state_a, msg[1:])),
            'c' : functools.partial(ioloop.IOLoop.instance().add_callback, self.final_state, msg[1:])}
        try:
            transitions[msg[0]]()
        except:
            self.stream.write("Aborted (wrong input)\n" , self.stream.close)

    def final_state(self, msg):
        print "entering final state with message: %s" % msg
        self.stream.write("Finished properly with message %s\n" % msg, self.stream.close)

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setblocking(0)
    sock.bind(("", 8000))
    sock.listen(5000)

    communicator = Communicator()
    io_loop = ioloop.IOLoop.instance()
    callback = functools.partial(communicator.connection_ready, sock)
    io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
    try:
        io_loop.start()
    except KeyboardInterrupt:
        io_loop.stop()
        print "exited cleanly"

Session using Netcat:

$ nc localhost 8000
aaaaa
got a
got a
got a
got a
got a
Aborted (wrong input)
$ nc localhost 8000
abababab
got a
got b
got a
got b
got a
got b
got a
got b
Aborted (wrong input)
$ nc localhost 8000
aaabbbc
got a
got a
got a
got b
got b
got b
Finished properly with message 
$ nc localhost 8000
abcabc
got a
got b
Finished properly with message abc
于 2013-09-30T01:38:23.883 回答