Twisted is an event-driven framework. This means that instead of having the program's functions called in a sequence specified by the program's logic, they are called in response to external actions, or events. For example, a GUI program might have code for responding to the "button pressed" event. The designer of the program can't be sure exactly when such an event will occur; but she writes a function to respond to this event whenever it does happen. Such a function is known as an event handler .
Every event-driven framework includes a special function called an event loop . Once started, an event loop runs indefinitely. While it's running, it waits for events. When an event occurs, the event loop triggers the appropriate event handler function.
Using an event loop requires a different mindset on the part of the programmer than traditional sequential programming. Once you start the event loop, you no longer have the ability to directly instruct your program what to do; it can perform actions only in response to events. Therefore, you need to think in terms of events and event handlers when you design your program. What are the events you want your program to respond to? How do you want it to react when a given event occurs?
In Twisted, there's a special object that implements the event loop. This object is called the reactor . You can think of the reactor as the central nervous system of a Twisted application. In addition to being responsible for the event loop, the reactor handles many other important tasks: scheduling, threading, establishing network connections, and listening for connections from other machines. To allow the reactor to do all these things, you must start its event loop, handing off control of your program.
2.1.1. How Do I Do That?
Starting the reactor is easy. Import the reactor object from the twisted.internet module. Then call reactor.run( ) to start the reactor's event loop. Example 2-1 shows all the code you need.
Example 2-1. runreactor.py
from twisted.internet import reactor print "Running the reactor..." reactor.run( ) print "Reactor stopped."
The reactor will continue to run until it's told to stop. Press Ctrl-C to exit the event loop and end the application:
$ python runreactor.py Running the reactor... ^CReactor stopped.
That's a pretty boring example. Although the reactor was running, it hadn't been given anything to do. Example 2-2 provides a more interesting bit of code, which introduces the reactor's callLater method. reactor.callLater is used to set up scheduled events. This method is useful when you want to schedule a function to run in the future. (Such a function is still an event handler, with the event in this case being the passage of time.) In Example 2-2, functions are called after a predetermined number of seconds have passed.
Example 2-2. calllater.py
from twisted.internet import reactor import time def printTime( ): print "Current time is", time.strftime("%H:%M:%S") def stopReactor( ): print "Stopping reactor" reactor.stop( ) reactor.callLater(1, printTime) reactor.callLater(2, printTime) reactor.callLater(3, printTime) reactor.callLater(4, printTime) reactor.callLater(5, stopReactor) print "Running the reactor..." reactor.run( ) print "Reactor stopped."
Run this program, and you'll see output like the following:
$ python calllater.py Running the reactor... Current time is 10:33:44 Current time is 10:33:45 Current time is 10:33:46 Current time is 10:33:47 Stopping reactor Reactor stopped.
2.1.2. How Does That Work?
Example 2-1 simply imports the reactor and calls reactor.run( ) to start it. The reactor will keep running (although it hadn't been giving anything to do) until you press Ctrl-C. At that point, the reactor stops, and the program proceeds to the final line of code, which prints out a message informing you that the reactor has been stopped.
The second example uses the reactor.callLater function to schedule some function calls. reactor.callLater has two required arguments: the number of seconds to wait, and the function to run. After setting up the scheduled function calls, control is handed off to the reactor's event loop using reactor.run( ).
The first four scheduled function calls were to printTime( ), which simply printed out the current time. However, the fifth scheduled function, stopReactor( ), did something interesting. It called reactor.stop( ), which caused the reactor to exit the event loop. That's why you didn't have to press Ctrl-C to stop the reactor this time. It stopped itself.
Notice the order in which things happened. First, the reactor was given instructions to run certain functions at specific times. Then the reactor was started, which handed control of the program to the reactor's event loop and allowed it to manage the scheduled function calls. The reactor continued to run until it was told to stopthis time through a call to reactor.stop( ). Once the reactor had stopped, the program could proceed with the final line of code, which printed a "Reactor stopped" message.
2.1.3. Establishing a TCP Connection
All networked programs have to start with one basic step: making a connection. Eventually, of course, you'll want to send email, transfer files, and stream live video, but before any of that can happen, a connection between two computers must be established. This section shows how to use the Twisted reactor to open a TCP connection.
2.1.4. How Do I Do That?
Call the reactor.connectTCP( ) method to start a TCP connection, passing a ClientFactory object as the third parameter. The ClientFactory object waits for the connection to be established, and then creates a Protocol object that manages the flow of data back and forth along that connection. Example 2-3 shows how to establish a connection between your computer and a server on the Internet (in this case, www.google.com).
Example 2-3. tcpconnection.py
from twisted.internet import reactor, protocol class QuickDisconnectProtocol(protocol.Protocol): def connectionMade(self): print "Connected to %s." % self.transport.getPeer( ).host self.transport.loseConnection( ) class BasicClientFactory(protocol.ClientFactory): protocol = QuickDisconnectProtocol def clientConnectionLost(self, connector, reason): print "Lost connection: %s" % reason.getErrorMessage( ) reactor.stop( ) def clientConnectionFailed(self, connector, reason): print "Connection failed: %s" % reason.getErrorMessage( ) reactor.stop( ) reactor.connectTCP('www.google.com', 80, BasicClientFactory( )) reactor.run( )
When you run this example, you should get the following output:
$ python connection.py Connected to www.google.com. Lost connection: Connection was closed cleanly.
unless, of course, your computer is offline. In that case, you'll see an error message, such as this:
$ python connection.py Connection failed: DNS lookup failed: address 'www.google.com' not found.
2.1.5. How Does That Work?
There are two main classes used for working with client connections, ClientFactory and Protocol. These classes are designed to handle all the potential events that arise when you start working with connections: when a connection is established, when a connection attempt fails, when an existing connection is broken, or when data is received from the other side.
ClientFactory and Protocol have specific and distinct roles. The ClientFactory's job is to manage connection-related events, and to create a new Protocol object for each successful connection. Once the connection is established, the Protocol object takes over, and is put in charge of sending and receiving data across the connection and deciding when (if ever) to close the connection.
Example 2-3 defines a custom Protocol called QuickDisconnectProtocol, which inherits from protocol.Protocol. It overrides a single method, connectionMade. This method is run as soon as the connection is up and running, just after the reactor has successfully established the connection and the ClientFactory has created this instance of QuickDisconnectProtocol. TRue to its name, the QuickDisconnectProtocol object quickly closes its connection (after printing the name of the host it's connected to).
BasicClientFactory is a class inheriting from protocol.ClientFactory. It first sets the class-level attribute protocol to QuickDisconnectProtocol. An instance of this class will be created to manage each successful connection. BasicClientFactory overrides two methods of ClientFactory, clientConnectionLost and clientConnectionFailed. These methods are event handlers. clientConnectionFailed will be called if the reactor is unable to establish the connection. clientConnectionLost will be called when an established connection is closed or broken.
To tell the reactor to establish a TCP connection, Example 2-3 calls reactor.connectTCP:
reactor.connectTCP('www.google.com', 80, BasicClientFactory( ))
This line tells the reactor to attempt a TCP connection to the server www.google.com, port 80, managed by an instance of BasicClientFactory.
Building Simple Clients and Servers
Web Services and RPC
NNTP Clients and Servers
Services, Processes, and Logging