LECTURE 16 Twisted A SIMPLE TCP SERVER To begin our introduction to Twisted, we’ll build a simple TCP server. REACTOR BASICS At the core of any Twisted application is the reactor loop. The reactor loop works by waiting for events to occur and then calls the relevant event handler (“dispatches the event”). The simplest Twisted program is the following, which accesses the reactor object and starts it. from twisted.internet import reactor reactor.run() REACTOR BASICS At the core of any Twisted application is the reactor loop. The reactor loop works by waiting events to occur and then calls the relevant event handler (“dispatches the event”). The simplest Twisted program is the following, which accesses the reactor object and starts it. from twisted.internet import reactor reactor.run() # Not created explicitly! Just import it. The reactor loop runs in the main and only thread. Once we run the reactor, it assumes control of the program. If there is nothing to do (as is the case here), the reactor loop just sits idle, not consuming CPU resources. REACTOR BASICS The reactor provides basic interfaces to a number of services, including network communications, threading, and event dispatching. There are several reactor options, each specialized for a particular application. The default reactor on most systems is a Select-based reactor. To use a different reactor, you must “install” it. from twisted.internet import pollreactor pollreactor.install() # When reactor is imported, it will be a pollreactor from twisted.internet import reactor # Always import reactor right before you run it reactor.run() REACTOR BASICS The core required functionality of a reactor is defined in twisted.internet.interfaces.IReactorCore. These include: • run() – move the reactor to a “running” state. Starts the main loop. • stop() – shutdown the reactor. • callWhenRunning() – call a function when the reactor is running. • addSystemEventTrigger() -- add a function to be called when a system event occurs. • fireSystemEvent() -- fire a system-wide event. There are additional methods defined in IReactorProcess, IReactorTCP, IReactorSocket, IReactorThreads, IReactorSSL, etc… The choice of reactor determine which/how these methods are implemented. REACTOR BASICS Here’s an example Twisted program which registers a function call to be made when the reactor starts running. Twisted has a basic logging mechanism defined in twisted.python.log. import sys from twisted.python import log log.startLogging(sys.stdout) def func(x): log.msg("In func as event handler") log.msg(str(x)) log.msg(“Shutting down now!") reactor.stop() from twisted.internet import reactor reactor.callWhenRunning(func, “Hello!") reactor.run() REACTOR BASICS $ python twist.py 2015-02-17 11:46:16-0500 [-] Log opened. 2015-02-17 11:46:16-0500 [-] In func as event handler 2015-02-17 11:46:16-0500 [-] Hello! 2015-02-17 11:46:16-0500 [-] Shutting down now! 2015-02-17 11:46:16-0500 [-] Main loop terminated import sys from twisted.python import log log.startLogging(sys.stdout) def func(x): log.msg("In func as event handler") log.msg(str(x)) log.msg(“Shutting down now!") reactor.stop() from twisted.internet import reactor reactor.callWhenRunning(func, “Hello!") reactor.run() REACTOR BASICS Another example using the callLater scheduling method defined in IReactorTime. import sys, time from twisted.python import log log.startLogging(sys.stdout) def func(x): log.msg(str(x)) now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S", now))) log.msg(“Shutting down now!") reactor.stop() now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S", now))) from twisted.internet import reactor reactor.callLater(5, func, “Hello after 5 seconds!”) reactor.run() REACTOR BASICS import sys, time from twisted.python import log log.startLogging(sys.stdout) $ python twist.py 2015-02-17 11:49:10-0500 2015-02-17 11:49:10-0500 2015-02-17 11:49:15-0500 2015-02-17 11:49:15-0500 2015-02-17 11:49:15-0500 2015-02-17 11:49:15-0500 [-] Log opened. [-] 15/02/17 11:49:10 [-] Hello after 5 seconds! [-] 15/02/17 11:49:15 [-] Shutting down now! [-] Main loop terminated. def func(x): log.msg(str(x)) now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S", now))) log.msg(“Shutting down now!") reactor.stop() now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S", now))) from twisted.internet import reactor reactor.callLater(5, func, “Hello after 5 seconds!”) reactor.run() REACTOR BASICS import sys, time from twisted.python import log log.startLogging(sys.stdout) Let’s create a custom self-esteem boosting event. def func(x): log.msg(str(x)) now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg("Fire FireworkEvent after 3 seconds…") reactor.callLater(3, reactor.fireSystemEvent, ‘FireworkEvent') def Firework_CustomEventHandler(): now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg(“BABY YOU’RE A FIIIIIIIIREWOOOOOORK") reactor.stop() now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) from twisted.internet import reactor reactor.callLater(5, func, "func called after 5 sec") reactor.addSystemEventTrigger('during', ‘FireworkEvent', Firework_CustomEventHandler) reactor.run() REACTOR BASICS import sys, time from twisted.python import log log.startLogging(sys.stdout) Let’s create a custom self-esteem boosting event. addSystemEventTrigger can be done ‘before’, ‘during’, or ‘after’ event. def func(x): log.msg(str(x)) now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg("Fire FireworkEvent after 3 seconds…") reactor.callLater(3, reactor.fireSystemEvent, ‘FireworkEvent') def Firework_CustomEventHandler(): now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg(“BABY YOU’RE A FIIIIIIIIREWOOOOOORK") reactor.stop() now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) from twisted.internet import reactor reactor.callLater(5, func, "func called after 5 sec") reactor.addSystemEventTrigger('during', ‘FireworkEvent', Firework_CustomEventHandler) reactor.run() REACTOR BASICS import sys, time from twisted.python import log log.startLogging(sys.stdout) $ python twist.py 2015-02-17 11:53:43-0500 [-] Log opened. 2015-02-17 11:53:43-0500 [-] 15/02/17 11:53:43 2015-02-17 11:53:48-0500 [-] func called after 5 sec 2015-02-17 11:53:48-0500 [-] 15/02/17 11:53:48 2015-02-17 11:53:48-0500 [-] Fire FireworkEvent after 3 seconds... 2015-02-17 11:53:51-0500 [-] 15/02/17 11:53:51 2015-02-17 11:53:51-0500 [-] BABY YOU'RE A FIIIIIIIIREWOOOOOORK 2015-02-17 11:53:51-0500 [-] Main loop terminated. def func(x): log.msg(str(x)) now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg("Fire FireworkEvent after 3 seconds…") reactor.callLater(3, reactor.fireSystemEvent, ‘FireworkEvent') def Firework_CustomEventHandler(): now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) log.msg(“BABY YOU’RE A FIIIIIIIIREWOOOOOORK") reactor.stop() now = time.localtime(time.time()) log.msg(str(time.strftime("%y/%m/%d %H:%M:%S",now))) from twisted.internet import reactor reactor.callLater(5, func, "func called after 5 sec") reactor.addSystemEventTrigger('during', ‘FireworkEvent', Firework_CustomEventHandler) reactor.run() REACTOR BASICS Let’s back up for a second and make sure we understand what’s going on. • Our callback code runs in the same thread as the Twisted loop. • When our callbacks are running, the Twisted loop is not running. And vice versa. • The reactor loop resumes when our callback returns. Our code blocks the reactor loop so it should return asap. Before we continue writing our TCP server, we need to learn about a couple of abstractions. TRANSPORT BASICS The Transport abstraction is defined in twisted.internet.interfaces.ITransport. A Twisted Transport represents a single connection that can send/receive data. The Transport abstraction represents any such connection and handles the details of asynchronous I/O for whatever sort of connection it represents. The methods defined in Itransport are: • write(data) – send some data. • writeSequence(list_of_data) – send a sequence of data. • loseConnection() – close connection. • getPeer() – get remote address of other side of connection. • getHost() – get address of this side of connection. TRANSPORT BASICS You may have noticed that there are no methods for reading data. The Transport object will make a callback when it receives data – we don’t have to explicitly make it happen. Also, note that these methods are really just suggestions. Remember, Twisted is in control, not us. So when we tell the Transport to write some data, we’re really asking it to write some data whenever it is able to. PROTOCOLS The Protocol abstraction is defined in twisted.internet.interfaces.IProtocol. Protocols implement protocols. This could be one of the built-in protocols in twisted.protocols.basic or one of your own design. Strictly speaking, a Protocol instance implements the protocol for a single connection. This connection is represented by a Transport object. So every connection requires its own Protocol instance (and therefore, Protocol is a good candidate for storing stateful information about the connection). PROTOCOLS Methods of the IProtocol class include: • dataReceived(data) – called whenever data is received (transport’s callback!) • connectionLost(reason) – called when connection is shut down. • makeConnection(transport) – associates transport with protocol instance to make connection. • connectionMade() – called when a connection is made. PROTOCOL FACTORIES The Protocol Factory object is defined in twisted.internet.interfaces.IProtocolFactory. Protocol Factories simply create Protocol instances for each individual connection. Of interest is just one method: • buildProtocol(addr): The buildProtocol method is supposed to return a new Protocol instance for the connection to addr. Twisted will call this method to establish Protocols for connections. SIMPLE TCP SERVER from twisted.internet.protocol import Protocol, Factory class Echo(Protocol): def dataReceived(self, data): self.transport.write(data) f = Factory() f.protocol = Echo from twisted.internet import reactor reactor.listenTCP(9000, f) reactor.run() # reactor will call makeConnection with current transport instance. # when transport executes callback with data, just echo # f.buildProtocol() will create an instance of f.protocol # listenTCP defined in IReactorTCP, which the default reactor inherits SIMPLE TCP SERVER from twisted.internet.protocol import Protocol, Factory class Echo(Protocol): def dataReceived(self, data): self.transport.write(data) f = Factory() f.protocol = Echo from twisted.internet import reactor reactor.listenTCP(9000, f) reactor.run() Try “telnet localhost 9000”. Anything you send will be echoed back to you. The only thing we’re doing explicitly is running the server. Everything that is executed from that point forward is handled and scheduled by Twisted.