2013-09-26 15 views
7

Mam trudne i interesujące pytanie do Ciebie.Maszyny stanu oparte na korupinach

Podczas pracy nad zadaniami I/O, takimi jak implementacja protokołu za pośrednictwem warstwy transportowej w Twisted, Tornado, znalazłem podobny scenariusz lub wzorzec. Wzór jest raczej ogólny niż abstrakcyjny. Na przykład, gdy pracujesz z urządzeniem podobnym do MODEM, wysyłasz mu polecenia i otrzymuje wyniki.

Czasami jednak trzeba reagować na odpowiedź modemu na ostatnie polecenie za pomocą nowych poleceń. Na przykład załóżmy, że modem jest M, -> komunikacja jest operator, który pobiera jeden parametr, klawisz wiadomości, a serwer jest 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 
    ... 

Tak, wygląda na to zachowanie FSM. Byłoby miło wdrożyć ten scenariusz w tornado, pracując z nie blokującymi we/wy (za pośrednictwem obiektów strumieniowych). Po prostu dostarczając scenariusz śledzenia jako dane wejściowe i przesyłając procedury obsługi do stanów (zdarzeń) opisanych w danych wejściowych, możemy osiągnąć niezłe zachowanie maszyny skończonej.

wejściowe mogą mieć następującą notację:

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

gdzie wszystkie te znaki alfanumeryczne są nazwy państwa. każda para klucz-wartość jest nazwą stanu i możliwym zbiorem przejść między stanami.

Jaka jest możliwa implementacja FSM za pomocą wprowadzonych w coroutines tornado i kontraktów futures? Proszę, podziel się swoimi myślami i kodem.

+0

tam jest pytanie? – Veedrac

+1

@Veedrac Czy teraz jest lepiej? –

Odpowiedz

5

Myślę, że Twisted jest bardziej odpowiedni do implementacji protokołów. W każdym razie w Pythonie funkcje i metody są obiektami pierwszej klasy, co oznacza, że ​​można je przechowywać wewnątrz słowników. Możesz także użyć functools.partial, aby powiązać funkcję z argumentami z kluczem słownika. Możesz go użyć do realizacji przejść. Każdy stan powinien być funkcją zawierającą słownik, w którym klawisze są możliwe, stany wejściowe i wartości są stanami wyjściowymi. Następnie można łatwo obrócić z jednego stanu do drugiego. Aby skorzystać z następnych stanów pętli Tornado, zamiast być wywołanym bezpośrednio, należy zarejestrować się jako wywołanie zwrotne za pomocą ioloop.IOLoop.instance().add_callback.

Przykładem realizacja automaty akceptując jezyk 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 używając 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 
+0

Wygląda dobrze, postaram się trochę go udoskonalić. Dziękuję Ci. –

Powiązane problemy