August'24: Kamaelia is in maintenance mode and will recieve periodic updates, about twice a year, primarily targeted around Python 3 and ecosystem compatibility. PRs are always welcome. Latest Release: 1.14.32 (2024/3/24)
There are a number of lower down cookbook recipes for TCP systems in
Kamaelia. This cookbook page walks through a small number of techniques
which are useful in creating TCPSystems.
Discussion Please discuss this on the
discussion page for this page
One of the earliest systems that Kamaelia was designed for was to
build network systems, specifically scaleable TCP based network
servers.
One of the more trivial examples of TCP server is the traditional
echo server. On the surface of things echo servers tend to look useless,
but are often extremely useful for simply testing the question "is this
thing on?". As a result that's why even Skype has something similar! An
echo server takes whatever message it recieves and sends it back to
you.
In Kamaelia building this protocol is relatively easy to do:
import Axon from Kamaelia.Chassis.ConnectedServer import SimpleServer class Echo(Axon.Component.component): def main(self): while 1: while self.dataReady("inbox"): data = self.recv("inbox") self.send(data, "outbox") yield 1 SimpleServer(protocol=Echo, port=1500).run()
Example 1: Basic echo server
We can then run this and connect back to our server: (locally typed
chars in italics)
# telnet 127.0.0.1 1500 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Hello Hello This This is is a a test test
As you can see this works pretty much you'd hope - you provide
something that can create protocol handlers to the simple server. When a
connection occurs, the simple server creates an instance, and that
instance recieves data from the socket on its inbox "inbox", and any
data it sends to its outbox "outbox" is sent to the socket.
OK, so that's a trivial server, how about something a little more
complex? How about a protocol that when a client connects it runs, sends
a message to the user and then breaks the connection? This is very
similar to a "message of the day service" or a finger service.
Well, to do this, we don't actually care about looping or waiting for
data, or anything similar and interesting, we can just send messages and
shutdown. Specifically to shutdown, we send a
Axon.Ipc.shutdownMicroprocess message out of our "signal" outbox, so a
simple "message of the day" server could look like this:
import Axon from Kamaelia.Chassis.ConnectedServer import SimpleServer message = """\ Hello, this is the message of the day Bye Bye! """ class MOTD(Axon.Component.component): def main(self): self.send(message, "outbox") self.send(Axon.Ipc.shutdownMicroprocess(), "signal") print "Shutting down" yield 1 SimpleServer(protocol=MOTD, port=1501).run()
Example 2: Basic server with a message of the day protocol that shuts down immediately
When a client breaks their connection, the protocol handler recieves
a Kamaelia.IPC.socketShutdown IPC mesage on it's control inbox,
which you can test for in order to determine whether to shutdown or not!
Taking the original echo protocol above and extending it to handle this,
looks like this:
import Axon import Kamaelia.IPC from Kamaelia.Chassis.ConnectedServer import SimpleServer class Echo(Axon.Component.component): def main(self): protocolRunning = True while protocolRunning: while self.dataReady("inbox"): data = self.recv("inbox") self.send(data, "outbox") while self.dataReady("control"): data = self.recv("control") if isinstance(data, Kamaelia.IPC.socketShutdown): protocolRunning = False yield 1 print "Protocol finished!" SimpleServer(protocol=Echo, port=1500).run()
Example 3: Server with a protocol that shuts down when it recieves notification the socket has shutdown
OK, so that's a relatively simple component - what about a simple
component that sits, waits for a message, and when it gets one, sends
one message in response and also shuts down? Well, this is kinda a
combination of examples 1& 2. In realworld terms, this is very
similar conceptually to the "finger" protocol (though not quite :), and
has basic similarities to HTTP as well.
import Axon import Kamaelia.IPC from Kamaelia.Chassis.ConnectedServer import SimpleServer class Echo(Axon.Component.component): def main(self): while not self.dataReady(): yield 1 message = self.recv("inbox") self.send("You sent the message:" + message, "outbox") self.send(Axon.Ipc.shutdownMicroprocess(), "signal") print "Protocol finished!" yield 1 SimpleServer(protocol=Echo, port=1500).run()
Example 4: Server with a protocol that sends a message and shuts down when it recieves any message from the connection
There's a couple of further examples worth looking at here. One is a
protocol that runs and when it recieves a special message - in this case
the word "shutdown" - it causes the connection to be shutdown, but also
one that also handles a client disconnection:
import Axon import Kamaelia.IPC from Kamaelia.Chassis.ConnectedServer import SimpleServer class Echo(Axon.Component.component): def main(self): protocolRunning = True while protocolRunning: while self.dataReady("inbox"): data = self.recv("inbox") if "shutdown" in data: protocolRunning = False self.send(Axon.Ipc.shutdownMicroprocess(), "signal") else: self.send("munch"+data, "outbox") while self.dataReady("control"): data = self.recv("control") if isinstance(data, Kamaelia.IPC.socketShutdown): protocolRunning = False yield 1 print "Protocol finished!" SimpleServer(protocol=Echo, port=1500).run()
Example 5: Server with a protocol where the client can forcibly break the connection or request disconnection
A final basic example is how to create a server that when a client
connects the server will send a message and shutdown. For this the
client needs to send a serverShutdown message to a serversignal outbox
as well as a shutdownMicroprocess to the signal.
import Axon from Kamaelia.Chassis.ConnectedServer import SimpleServer from Kamaelia.IPC import serverShutdown message = """Hello, this is the message of the day Bye Bye!""" class MOTD(Axon.Component.component): Outboxes = ["outbox", "signal", "serversignal"] def main(self): self.send(message, "outbox") self.send(serverShutdown(), "serversignal") print "Shutting down" yield 1 SimpleServer(protocol=MOTD, port=1501).run()
Example 6: Server where the server shuts down as soon as the first client connects.
For a more fun example, let's create a simple server that accepts
connections from users, and is expected to chat to the people who
connect. For extra bonus points, what it will do is take anything that's
typed to it, and use this to build up a simple markov chain. This markov
chain will be shared between all connections, and as a result its
dialogue will grow as the number of connections to it grows.
Whilst this sounds complex, the code is relatively simple, and focusses
almost entirely around the markov chain aspects rather than the network
system aspects.
I wrote this as an example of a relatively simple, but non-trivial
network protocol. It creates a server that sits there waiting for
connections. Anything that you type at it updates the markov chain for
anyone/everyone connected. The protocol handler itself could be bolted
into an IRC bot instead so you could have a deranged bot sitting on a
channel which talks vaguely on-topic (but relatively - not totally -
incoherently) most of the time. I thought I'd blog about it because it
makes quite a nice fun/simple introduction to Kamaelia in it's own
special way. The markov chain used is based on the one here. (courtesy
of a google search)
import Axon, random nlnl = '\n', '\n' key = nlnl def new_key(key, word): if word == '\n': return nlnl else: return (key[1], word) class Chatty(Axon.Component.component): data = {} def updateChain(self, message): key = nlnl for word in message.split(): self.__class__.data.setdefault(key, []).append(word) key = new_key(key, word) def response(self): key, result, word = nlnl, [], None while word != "\n": word = random.choice(self.__class__.data.get(key, nlnl)) key = new_key(key, word) result.append(word) return " ".join(result) def main(self): while 1: if self.dataReady("inbox"): message = self.recv("inbox") self.updateChain(message) self.send(self.response(), "outbox") yield 1 if __name__ == "__main__": from Kamaelia.Chassis.ConnectedServer import SimpleServer SimpleServer(protocol=Chatty, port=1500).run()
Example 7: Markov Chain Chat server.
And that's pretty much all there is to it. As you'd imagine (I hope), a
Chatty component is created to handle any accepted connection on port
1500, and anything the user types is received on the inbox "inbox", used
to update the class's markov chain DB, and then generates a response to
send to the outbox "outbox" (meaning it gets sent to the socket). The
upshot is the more people who connect, the more the database gets
updated.
The nice thing about this is that the bulk of the code here focusses on
the logic that's desired, not on any networking details. OK, this
example isn't ideal because it misses some important things like
shutdown and what happens if the connection disappears, but it also is
interesting because you can test the component in isolation as
well:
Pipeline( ConsoleReader(), Chatty(), ConsoleEchoer(), ).run()
Which is a nice thing to be able to do! If you wanted to train the
markov chain server you could also do that as follows:
Pipeline( ReadFileAdaptor("SomeTrainingMaterial"), TCPClient("127.0.0.1", 1500), # assuming localhost ConsoleEchoer(), # May as well see the deranged output :) ).run()
The fun thing about this trainer is that you can see the output from
the markov chain during testing as well :-)
-- Michael, January 2007