1 Event-driven Programming paradigm
1.1 Single thread
Single threaded or sequential programming is in which requests are processed
by a single thread. If the thread is doing some blocking task like IO then the
program can’t handle any other requests until the request is processed.
Example of single threaded server
import time
from http import server
from socketserver import ThreadingMixIn
class RequestHandler([Link]):
def do_GET(self):
start_time = [Link]()
[Link](5)
end_time = [Link]()
self.send_response(200, "OK")
self.end_headers()
response = (
"Started at {}, Ended at {} difference {}".format(
start_time, end_time, end_time - start_time
)
)
[Link]([Link](’utf-8’))
return
with [Link](
(’localhost’, 8000), RequestHandler
) as http_server:
http_server.serve_forever()
1.2 Multi-Threaded
Converting above program to a multithreaded version will help. A multi-
threaded program will handle each HTTP request in a new thread and thus
it can handle mutiple requests at the same time.
import time
from http import server
from socketserver import ThreadingMixIn
1
class RequestHandler([Link]):
def do_GET(self):
[Link](40)
end_time = [Link]()
self.send_response(200, "OK")
self.end_headers()
response = "Ended at {}".format(end_time)
[Link]([Link](’utf-8’))
return
class ThreadedHTTPServer(ThreadingMixIn, [Link]):
daemon_threads = True
with ThreadedHTTPServer(
(’localhost’, 8000), RequestHandler
) as http_server:
http_server.serve_forever()
Compared to the single threaded approach, the program with multithreaded
will be able to achieve more performance because the server will spawn a new
thread for each new request. And thus, when the thread is blocking, it can
handle another request during that time.
But here is a problem. When bunch of new requests are coming (usually
higher than the core of processor), Blocking thread is actually blocking the
CPU core.
Compare the threaded example using Apache Benchmark tool.
ab -n 100 -c 100 -s 120 [Link]
In the above, command -n is a short-hand for Number of requests to be
fired. -c is a concurrent requests the tool will fire at the same time. -s is a short
hand for total number of seconds after which the tool will timeout a request.
The threaded server, a maximum number of concurrent requests handled by
the server will be somewhere between (total number of cores * 1.5 ) + 1. The
server can’t handle other concurrent requests at the same time. The server will
process those requests in batches.
2
1.3 Event-driven
Now, if we convert above example into event-driven asynchronous code then it
will look as below.
import time
from [Link] import reactor
from [Link] import deferLater
from [Link] import Resource
from [Link] import Site, NOT_DONE_YET
class BusyPage(Resource):
isLeaf = True
def _delayedResponse(self, request):
end_time = [Link]()
response = "Ended at {}".format(end_time)
[Link]([Link]("utf-8"))
[Link]()
def render_GET(self, request):
d = deferLater(reactor, 40, lambda: request)
[Link](self._delayedResponse)
return NOT_DONE_YET
factory = Site(BusyPage())
[Link](8000, factory)
[Link]()
Compare the performance of the above code by Apache benchmarking tool.
Below is the same command we had used earlier to compare the multi-
threaded approach.
ab -n 100 -c 100 -s 120 [Link]
The output statistics of both the command will be different. The asyn-
chronous approach will be able to handle all the requests at the same time.
Reason behind this is when the request is coming, the IO thread is trying to
process the request. During that time, if it is blocked the code is handled by
event-driven approach and the thread is again available for entertaining another
request. The processing of request is again resumed when the duration of IO is
completed and returned.
3
2 Introduction to Twisted programming
2.1 History
Twisted was invented by Glyph Lefkowitz in the year October 22, 2002 which
is almost 16 years before. It is an old giant.
4
2.2 Current situation
At present, the Twisted framework is developed and managed by its community.
Latest stable version is 18.9.x. The Twisted framework is licensed under MIT
license. Twisted is based on event-driven programming paradigm.
2.3 Sockets
Twisted programming supports all the TCP, UDP, SSL/TSL based sockets. It
also supports the Unix sockets.
2.4 Protocol
Twisted supports many application layer protocols. Such as HTTP, XMPP,
IMAP, SSH, IRC, FTP and many more.
2.5 Applications/Frameworks based on Twisted
• Buildbot [?]
• Omgle video chat service [?]
• Twilio a cloud telephony provider [?]
• [Link] a popular Game livestream video sharing [?]
• Scrapy a framework for scrapping webpages [?]
And many more are based on Twisted!
3 Echo server and client example
3.1 Echo Server
\\Example echo_server.py
from [Link] import protocol, reactor
class EchoServer([Link]):
def dataReceived(self, data):
print(data)
[Link](data)
class EchoFactory([Link]):
def buildProtocol(self, addr):
5
return EchoServer()
[Link](8000, EchoFactory())
[Link]()
3.2 Echo client
\\ Example echo_client.py
from [Link] import reactor, protocol
class EchoClient([Link]):
def connectionMade(self):
request = "Hello, world!"
[Link]([Link]("utf-8"))
def dataReceived(self, data):
print(data)
[Link]()
class EchoFactory([Link]):
def buildProtocol(self, addr):
protocol = EchoClient()
[Link] = self
return protocol
def clientConnectionFailed(self, connector, reason):
[Link]()
def clientConnectionLost(self, connector, reason):
[Link]()
[Link]("localhost", 8000, EchoFactory())
[Link]()
6
4 Reactor
Reactor is the core of the event loop. It is responsible for waiting for the event
and notify the program when the event is occurred. Reactor is dependent on
platform specific libraries. Twisted will automatically choose default platform
specific library.
For under the hood details, In GNU/Linux platform it tries to fetch for epoll
[?]. If epoll [?] is not available, it will use poll [?]. All the POSIX compliant
platforms it will use poll. All other platforms such as Windows and Macintosh
it will use select reactor.
You have to register your callbacks to the reactor and have to start it. Once
the reactor is start, it will listen to the events and notifies to the program forever
or until [Link] is called.
In our echo server and echo client example, below code is representing the
reactor. The [Link]() will start the event loop until the SIGINT or
[Link]() is called.
[Link](8000, MyFactory())
[Link]()
5 Transport
Transport is responsible to provide the behaviour for transferring data to the
other end of the connection. The transport behaves differently according to
the connection for example, TCP or UDP will have different implementation of
transport. In echo client and echo server example below code is representing
Transport
[Link]()
All the transport implementations should be dependent on ITransport in-
terface. Below are the common methods of Transport
5.1 write
Should write data to the connection.
5.2 writeSequence
Should write list of strings to the connection.
5.3 loseConnection
Should close the connection after writing ending data.
7
5.4 getPeer
Should return remote address of the connection.
6 Protocol
Protocol will define the behaviour to process the network events in async man-
ner. Twisted does have in-built definition for many protocols such as HTTP,
Telnet, DNS etc. Base class is [Link].
In echo server and echo client example, below code is of Protocol.
\\ Client
class EchoClient([Link]):
def connectionMade(self):
request = "Hello, world!"
[Link]([Link]("utf-8"))
def dataReceived(self, data):
print(data)
[Link]()
\\ Server
class EchoServer([Link]):
def dataReceived(self, data):
print(data)
[Link](data)
All the protocol classes should implement IProtocol interface. Common
methods are below
6.1 makeConnection
Should create a connection.
6.2 connectionMade
Should be called when a new connection is made.
6.3 dataReceived
Should be called when any data is received from the other end of the circuit.
8
6.4 connectionLost
Should be called when the connection is terminated.
7 Protocol Factory
The factory will produce the instance of Protocol when the new connection is
made. Protocol will be garbage collected when the collection is called. The
Factory will store configuration details for Protocol instances.
According to our echo server and echo client example, below is the code of
portable factory.
\\ Server
class EchoFactory([Link]):
def buildProtocol(self, addr):
return EchoServer()
\\ Client
class EchoFactory([Link]):
def buildProtocol(self, addr):
protocol = EchoClient()
[Link] = self
return protocol
def clientConnectionFailed(self, connector, reason):
[Link]()
def clientConnectionLost(self, connector, reason):
[Link]()
The factory is following IProtocolFactory interface. There client imple-
mentation of common factory methods is ClientFactory. Common methods
are as per below
7.1 buildProtocol
should create the instance of Protocol class.
From the above example, purpose of protocol factories might not be clear in
your mind. For that reason, we will conver another example.
\\chat_server.py
from [Link] import Factory
9
from [Link] import LineReceiver
from [Link] import reactor
class ChatProtocol(LineReceiver):
def __init__(self, factory):
[Link] = factory
def connectionMade(self):
[Link](self)
def lineReceived(self, line):
for user in [Link]:
if user != self:
[Link](line+b"\r\n")
class ChatFactory(Factory):
def __init__(self):
[Link] = []
def buildProtocol(self, addr):
return ChatProtocol(self)
[Link](8000, ChatFactory())
[Link]()
Connect with chat server using multiple connections made by telnet pro-
gram. This will allow to communicate multiple clients as in chatting in group. In
that code, concurrent client instances are stored at Protocol Factory. Whenever
you have to store persistent information beyond the lifecycle of your socket, you
should store that in the Protocol Factory. Protocol will be created and garbage
collected with connection.
8 Deferreds
Deferreds are the core API provided by the Twisted. This API is provided for
writing a callbacks. A callback is a type of function which can be paused by an
event-loop and resumed when specific event is happended.
If you are assuming that just writing code in Twisted will make things asyn-
10
chronous then you are wrong. You have to wrap your blocking code into appro-
priate Deferred to make it event-driven.
We have already encountered with deferred API in our Twisted webserver
example.
\\Example from Twisted based HTTP server
d = deferLater(reactor, 40, lambda: request)
[Link](self._delayedResponse)
In above example, [Link] is adding delayedResponse as an call-
back function. deferLater will fire the deferred function.
8.1 addCallback
Below is the example of addCallback and callback API
\\Example of addCallback and callback
from [Link] import Deferred
def callback_func(result):
print(result)
d = Deferred()
[Link](callback_func)
[Link]("Trigerred the callback!")
In the above example, the callback will fire the callback with the arguemtn
of string. The callbackfunction is added as a callback by the addCallback
8.2 addErrback
\\Example Errback
from [Link] import Deferred
def errorback_func(failure):
print(failure)
d = Deferred()
[Link](errorback_func)
[Link]("Triggered! Error!".encode("utf-8"))
11
In above example, the errback is responsible for triggering the error. The
addErrback is responsible for registering the callback.
8.3 addCallbacks
from [Link] import Deferred
def callback_func(result):
print(result)
def errback_func(failure):
print(failure)
d = Deferred()
[Link](callback_func, errback_func)
[Link](b"Hello")
#[Link](b"Hello")
The function addCallbacks will add a callback and errorback function in
single call.
8.4 Comparision
The addCallback or addErrrback will add callbacks at different level whereas
the addCallbacks will add callback at the same level.
9 Testing
Testing in Twisted is considered as tricky. Twisted has dedicated utility called
trial for running and managing the tests. The trial is based on pytest. You
can use trial for various purposes.
9.1 Testing echo server
from [Link] import proto_helpers
from [Link] import unittest
from echo_server import EchoFactory
class TestEchoServer([Link]):
12
def test_response(self):
factory = EchoFactory()
protocol = [Link](("localhost", 0))
transport = proto_helpers.StringTransport()
[Link](transport)
data = b"test\r\n"
[Link](data)
[Link]([Link](), data)
Things to observe is [Link]. This is the special class
provided by Twisted for testing.
Run below command to run the test
PYTHONPATH=${PWD} trial test_server
10 Deployments
10.1 twistd
twistd is an utility for running the Twisted code. twistd will manage facilities
like logging and running the service in daemonize mode.
10.2 Twisted Application Configuration (TAC)
This file is responsible for managing the twisted applicaiton and its state. This
file will be given as input to twistd application.
10.3 Example
We will consider the echo server example we observed earlier and try to wrap it
into tac file.
10.3.1 Echo server
\\Example echo_server.py
from [Link] import protocol, reactor
class EchoProtocol([Link]):
def dataReceived(self, data):
[Link](data)
class EchoFactory([Link]):
13
def buildProtocol(self, addr):
return EchoProtocol()
10.3.2 tac file
\\ echo_server.tac
import os
import sys
[Link]([Link]([Link](__file__)))
from [Link] import internet, service
from echo_app import EchoFactory
application = [Link]("echo")
echoService = [Link](8000, EchoFactory())
[Link](application)
10.3.3 Run
You can run the twisted application using below command
twistd -n -y echo_server.tac
References
[1] [Link]
[2] [Link]
[3] [Link]
[4] [Link]
[5] [Link]
[6] [Link]
[7] [Link]
14