2013-09-26 16 views
7

Ho una domanda complicata e interessante per te.Macchine a stati basati su Coroutine

Mentre lavoravo su operazioni di I/O come l'implementazione del protocollo tramite un livello di trasporto in Twisted, Tornado, ho trovato uno scenario o schema simile. Il modello è piuttosto generico che astratto. Ad esempio, quando si lavora con il dispositivo-MODEM-like, gli si inviano comandi e si ricevono i risultati.

Tuttavia, a volte è necessario reagire alla risposta di un modem all'ultimo comando con nuovi comandi. Ad esempio, supponiamo che il modem è M, -> è operatore di comunicazione che prende un parametro, tasto messaggi, e il 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 
    ... 

Così, sembra che il comportamento FSM. Sarebbe bello implementare questo scenario in tornado, mentre si lavora con I/O non bloccanti (tramite gli oggetti stream). Semplicemente fornendo lo scenario di tracciamento come input e sovrascrivendo i gestori agli stati (eventi) descritti in input, possiamo raggiungere un buon comportamento della macchina a stati finiti.

ingresso può avere la seguente notazione:

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

dove tutti questi segni alfanumerici sono nomi di stato. ogni coppia chiave-valore è il nome dello stato e il possibile insieme di transizioni di stato.

Qual è la possibile implementazione di FSM utilizzando introdotti nelle coroutine e nei future del tornado? Si prega di condividere le tue menti e il codice.

+0

C'è una domanda? – Veedrac

+1

@Veedrac È meglio ora? –

risposta

5

Penso che Twisted sia più adatto per implementazioni di protocollo. Ad ogni modo, in Python le funzioni e i metodi sono oggetti di prima classe, il che significa che è possibile memorizzarli all'interno di dizionari. È inoltre possibile utilizzare functools.partial per associare una funzione con argomenti a un tasto di dizionario. Puoi usarlo per implementare le transizioni. Ogni stato dovrebbe essere una funzione contenente un dizionario in cui le chiavi sono possibili stati di input e i valori sono stati di output. Quindi puoi facilmente passare da uno stato all'altro. Per utilizzare il ciclo Tornado, gli stati successivi, invece di essere chiamati direttamente, devono essere registrati come callback utilizzando ioloop.IOLoop.instance().add_callback.

un esempio di implementazione automi accettare la lingua 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" 

sessione utilizzando 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

Sembra buono, cercherò di perfezionarlo un po '. Grazie. –