Writing Clients — Twisted 25.5.0 documentation
Twisted Core
Developer Guides
Writing Clients
View page source
Writing Clients
Overview
Twisted is a framework designed to be very flexible, and let you write
powerful clients. The cost of this flexibility is a few layers in the way
to writing your client. This document covers creating clients that can be
used for TCP, SSL and Unix sockets. UDP is covered
in a different document
At the base, the place where you actually implement the protocol parsing
and handling, is the
Protocol
class. This class will usually be
descended
from
twisted.internet.protocol.Protocol
. Most
protocol handlers inherit either from this class or from one of its
convenience children. An instance of the protocol class will be instantiated
when you connect to the server and will go away when the connection is
finished. This means that persistent configuration is not saved in the
Protocol
The persistent configuration is kept in a
Factory
class,
which usually inherits from
twisted.internet.protocol.Factory
(or
twisted.internet.protocol.ClientFactory
: see below).
The default factory class just instantiates the
Protocol
and then sets the protocol’s
factory
attribute to point to itself (the factory).
This lets the
Protocol
access, and possibly modify, the persistent configuration.
Protocol
As mentioned above, this and auxiliary classes and functions are where
most of the code is. A Twisted protocol handles data in an asynchronous
manner. This means that the protocol never waits for an event, but rather
responds to events as they arrive from the network.
Here is a simple example:
from
twisted.internet.protocol
import
Protocol
from
sys
import
stdout
class
Echo
Protocol
):
def
dataReceived
self
data
):
stdout
write
data
This is one of the simplest protocols. It just writes whatever it reads
from the connection to standard output. There are many events it does not
respond to. Here is an example of a
Protocol
responding to
another event:
from
twisted.internet.protocol
import
Protocol
class
WelcomeMessage
Protocol
):
def
connectionMade
self
):
self
transport
write
"Hello server, I am the client!
\r\n
self
transport
loseConnection
()
This protocol connects to the server, sends it a welcome message, and
then terminates the connection.
The
connectionMade
event is
usually where set up of the
Protocol
object happens, as well as
any initial greetings (as in the
WelcomeMessage
protocol above). Any tearing down of
Protocol
-specific objects is done in
connectionLost
Simple, single-use clients
In many cases, the protocol only needs to connect to the server once,
and the code just wants to get a connected instance of the protocol. In
those cases
twisted.internet.endpoints
provides
the appropriate API, and in particular
connectProtocol
which takes a
protocol instance rather than a factory.
from
twisted.internet
import
reactor
from
twisted.internet.protocol
import
Protocol
from
twisted.internet.endpoints
import
TCP4ClientEndpoint
connectProtocol
class
Greeter
Protocol
):
def
sendMessage
self
msg
):
self
transport
write
"MESSAGE
%s
\n
msg
def
gotProtocol
):
sendMessage
"Hello"
reactor
callLater
sendMessage
"This is sent in a second"
reactor
callLater
transport
loseConnection
point
TCP4ClientEndpoint
reactor
"localhost"
1234
connectProtocol
point
Greeter
())
addCallback
gotProtocol
reactor
run
()
Regardless of the type of client endpoint, the way to set up a new
connection is simply pass it to
connectProtocol
along with a
protocol instance. This means it’s easy to change the mechanism you’re
using to connect, without changing the rest of your program. For example,
to run the greeter example over SSL, the only change required is to
instantiate an
SSL4ClientEndpoint
instead of a
TCP4ClientEndpoint
. To take advantage of this, functions and
methods which initiates a new connection should generally accept an
endpoint as an argument and let the caller construct it, rather than taking
arguments like ‘host’ and ‘port’ and constructing its own.
For more information on different ways you can make outgoing connections
to different types of endpoints, as well as parsing strings into endpoints,
see
the documentation for the endpoints API
You may come across code using
ClientCreator
, an older API which is not as flexible as
the endpoint API. Rather than calling
connect
on an endpoint,
such code will look like this:
from
twisted.internet.protocol
import
ClientCreator
...
creator
ClientCreator
reactor
Greeter
creator
connectTCP
"localhost"
1234
addCallback
gotProtocol
reactor
run
()
In general, the endpoint API should be preferred in new code, as it lets
the caller select the method of connecting.
ClientFactory
Still, there’s plenty of code out there that uses lower-level APIs, and
a few features (such as automatic reconnection) have not been
re-implemented with endpoints yet, so in some cases they may be more
convenient to use.
To use the lower-level connection APIs, you will need to call one of the
reactor.connect*
methods directly.
For these cases, you need a
ClientFactory
The
ClientFactory
is in charge of creating the
Protocol
and also receives events relating to the connection state.
This allows it to do things like reconnect in the event of a connection error.
Here is an example of a simple
ClientFactory
that uses the
Echo
protocol (above) and also prints what state the connection is in.
from
twisted.internet.protocol
import
Protocol
ClientFactory
from
sys
import
stdout
class
Echo
Protocol
):
def
dataReceived
self
data
):
stdout
write
data
class
EchoClientFactory
ClientFactory
):
def
startedConnecting
self
connector
):
'Started to connect.'
def
buildProtocol
self
addr
):
'Connected.'
return
Echo
()
def
clientConnectionLost
self
connector
reason
):
'Lost connection. Reason:'
reason
def
clientConnectionFailed
self
connector
reason
):
'Connection failed. Reason:'
reason
To connect this
EchoClientFactory
to a server, you could use
this code:
from
twisted.internet
import
reactor
reactor
connectTCP
host
port
EchoClientFactory
())
reactor
run
()
Note that
clientConnectionFailed
is called when a connection could not be established,
and that
clientConnectionLost
is called when a connection was made and then disconnected.
Reactor Client APIs
connectTCP
IReactorTCP.connectTCP
provides support for IPv4 and IPv6 TCP clients.
The
host
argument it accepts can be either a hostname or an IP address literal.
In the case of a hostname, the reactor will automatically resolve the name to an IP address before attempting the connection.
This means that for a hostname with multiple address records, reconnection attempts may not always go to the same server (see below).
It also means that there is name resolution overhead for each connection attempt.
If you are creating many short-lived connections (typically around hundreds or thousands per second) then you may want to resolve the hostname to an address first and then pass the address to
connectTCP
instead.
Reconnection
Often, the connection of a client will be lost unintentionally due to
network problems. One way to reconnect after a disconnection would be to
call
connector.connect()
when the connection is lost:
from
twisted.internet.protocol
import
ClientFactory
class
EchoClientFactory
ClientFactory
):
def
clientConnectionLost
self
connector
reason
):
connector
connect
()
The connector passed as the first argument is the interface between a
connection and a protocol. When the connection fails and the factory
receives the
clientConnectionLost
event, the factory can
call
connector.connect()
to start the connection over again
from scratch.
However, most programs that want this functionality should
implement
ReconnectingClientFactory
instead,
which tries to reconnect if a connection is lost or fails and which
exponentially delays repeated reconnect attempts.
Here is the
Echo
protocol implemented with
ReconnectingClientFactory
from
twisted.internet.protocol
import
Protocol
ReconnectingClientFactory
from
sys
import
stdout
class
Echo
Protocol
):
def
dataReceived
self
data
):
stdout
write
data
class
EchoClientFactory
ReconnectingClientFactory
):
def
startedConnecting
self
connector
):
'Started to connect.'
def
buildProtocol
self
addr
):
'Connected.'
'Resetting reconnection delay'
self
resetDelay
()
return
Echo
()
def
clientConnectionLost
self
connector
reason
):
'Lost connection. Reason:'
reason
ReconnectingClientFactory
clientConnectionLost
self
connector
reason
def
clientConnectionFailed
self
connector
reason
):
'Connection failed. Reason:'
reason
ReconnectingClientFactory
clientConnectionFailed
self
connector
reason
A Higher-Level Example: ircLogBot
Overview of ircLogBot
The clients so far have been fairly simple.
A more complicated example comes with Twisted Words in the
doc/words/examples
directory.
ircLogBot.py
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An example IRC log bot - logs a channel's events to a file.
If someone says the bot's name in the channel followed by a ':',
e.g.
logbot: hello!
the bot will reply:
foo: I am a log bot
Run this script with two arguments, the channel name the bot should
connect to, and file to log to, e.g.:
$ python ircLogBot.py test test.log
will log channel #test to the file 'test.log'.
To run the script:
$ python ircLogBot.py
"""
import
sys
# system imports
import
time
from
twisted.internet
import
protocol
reactor
from
twisted.python
import
log
# twisted imports
from
twisted.words.protocols
import
irc
class
MessageLogger
"""
An independent logger class (because separation of application
and protocol logic is a good thing).
"""
def
__init__
self
file
):
self
file
file
def
log
self
message
):
"""Write a message to the file."""
timestamp
time
strftime
"[%H:%M:%S]"
time
localtime
time
time
()))
self
file
write
timestamp
message
\n
self
file
flush
()
def
close
self
):
self
file
close
()
class
LogBot
irc
IRCClient
):
"""A logging IRC bot."""
nickname
"twistedbot"
def
connectionMade
self
):
irc
IRCClient
connectionMade
self
self
logger
MessageLogger
open
self
factory
filename
"a"
))
self
logger
log
"[connected at
%s
]"
time
asctime
time
localtime
time
time
())))
def
connectionLost
self
reason
):
irc
IRCClient
connectionLost
self
reason
self
logger
log
"[disconnected at
%s
]"
time
asctime
time
localtime
time
time
()))
self
logger
close
()
# callbacks for events
def
signedOn
self
):
"""Called when bot has successfully signed on to server."""
self
join
self
factory
channel
def
joined
self
channel
):
"""This will get called when the bot joins the channel."""
self
logger
log
"[I have joined
%s
]"
channel
def
privmsg
self
user
channel
msg
):
"""This will get called when the bot receives a message."""
user
user
split
"!"
)[
self
logger
log
"<
user
msg
# Check to see if they're sending me a private message
if
channel
==
self
nickname
msg
"It isn't nice to whisper! Play nice with the group."
self
msg
user
msg
return
# Otherwise check to see if it is a message directed at me
if
msg
startswith
self
nickname
":"
):
msg
%s
: I am a log bot"
user
self
msg
channel
msg
self
logger
log
"<
self
nickname
msg
def
action
self
user
channel
msg
):
"""This will get called when the bot sees someone do an action."""
user
user
split
"!"
)[
self
logger
log
"*
user
msg
# irc callbacks
def
irc_NICK
self
prefix
params
):
"""Called when an IRC user changes their nickname."""
old_nick
prefix
split
"!"
)[
new_nick
params
self
logger
log
old_nick
is now known as
new_nick
# For fun, override the method that determines how a nickname is changed on
# collisions. The default method appends an underscore.
def
alterCollidedNick
self
nickname
):
"""
Generate an altered version of a nickname that caused a collision in an
effort to create an unused related name for subsequent registration.
"""
return
nickname
"^"
class
LogBotFactory
protocol
ClientFactory
):
"""A factory for LogBots.
A new protocol instance will be created each time we connect to the server.
"""
def
__init__
self
channel
filename
):
self
channel
channel
self
filename
filename
def
buildProtocol
self
addr
):
LogBot
()
factory
self
return
def
clientConnectionLost
self
connector
reason
):
"""If we get disconnected, reconnect to server."""
connector
connect
()
def
clientConnectionFailed
self
connector
reason
):
"connection failed:"
reason
reactor
stop
()
if
__name__
==
"__main__"
# initialize logging
log
startLogging
sys
stdout
# create factory protocol and application
LogBotFactory
sys
argv
],
sys
argv
])
# connect factory to this host and port
reactor
connectTCP
"irc.freenode.net"
6667
# run bot
reactor
run
()
ircLogBot.py
connects to an IRC server, joins a channel, and
logs all traffic on it to a file. It demonstrates some of the
connection-level logic of reconnecting on a lost connection, as well as
storing persistent data in the
Factory
Persistent Data in the Factory
Since the
Protocol
instance is recreated each time the
connection is made, the client needs some way to keep track of data that
should be persisted. In the case of the logging bot, it needs to know which
channel it is logging, and where to log it.
from
twisted.words.protocols
import
irc
from
twisted.internet
import
protocol
class
LogBot
irc
IRCClient
):
def
connectionMade
self
):
irc
IRCClient
connectionMade
self
self
logger
MessageLogger
open
self
factory
filename
"a"
))
self
logger
log
"[connected at
%s
]"
time
asctime
time
localtime
time
time
())))
def
signedOn
self
):
self
join
self
factory
channel
class
LogBotFactory
protocol
ClientFactory
):
def
__init__
self
channel
filename
):
self
channel
channel
self
filename
filename
def
buildProtocol
self
addr
):
LogBot
()
factory
self
return
When the protocol is created, it gets a reference to the factory as
self.factory
. It can then access attributes of the factory in
its logic. In the case of
LogBot
, it opens the file and
connects to the channel stored in the factory.
Factories have a default implementation of
buildProtocol
It does the same thing the example above does using the
protocol
attribute of the factory to create the protocol instance.
In the example above, the factory could be rewritten to look like this:
class
LogBotFactory
protocol
ClientFactory
):
protocol
LogBot
def
__init__
self
channel
filename
):
self
channel
channel
self
filename
filename
Further Reading
The
Protocol
class used throughout this document is a base implementation of
IProtocol
used in most Twisted applications for convenience.
To learn about the complete
IProtocol
interface, see the API documentation for
IProtocol
The
transport
attribute used in some examples in this
document provides the
ITCPTransport
interface. To learn
about the complete interface, see the API documentation
for
ITCPTransport
Interface classes are a way of specifying what methods and attributes an
object has and how they behave. See the
Components: Interfaces and Adapters
document for more information on
using interfaces in Twisted.