Thursday, July 5, 2012

Bite-sized Twisted:Telnet

Finally, in this post, we'll monitor keypresses instead of lines of text. This means that you won't have to press enter after each command.

Strictly speaking, the changes we'll make to the code are for Telnet, and not really Twisted-specific -- though Twisted's support for Telnet makes it easier.

Telnet's modes

On my computer (running Ubuntu), running telnet will default to line by line mode. I can see this by starting the obstinate server:

from twisted.internet.protocol import Protocol, Factory
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor

class ObstinateProtocol(Protocol):

def dataReceived(self, data):
self.transport.write('no\r\n')

factory = Factory()
factory.protocol = ObstinateProtocol
endpoint = TCP4ServerEndpoint(reactor, 8900)
endpoint.listen(factory)
reactor.run()
obstinate1.py

Then if I start telnet and send some stuff, I see that it reacts to the lines I send:

$ telnet 127.0.0.1 8900
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Wanna play?
no
Are you obstinate?
no
Are you a duck?
no

We can switch the telnet client to character mode by typing the escape character Control ], then typing mode char. From then on, the server will receive each byte as we type it:

^]mode char

telnet> mode char
no
no
no
no
no
no

Each of those no lines was printed after pressing single keys. Great! We don't have to press enter anymore to play boom!

You can force the client to character mode

by telling the Telnet client that you will echo, will supress go-ahead and won't use linemode. We'll use Twisted's Telnet protocol from the conch package (changed lines are highlighted):

from twisted.internet.protocol import Protocol, Factory
from twisted.conch.telnet import Telnet, SGA, LINEMODE, ECHO
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor

class ObstinateProtocol(Telnet):

def connectionMade(self):
self.will(ECHO)
self.will(SGA)
self.wont(LINEMODE)

def enableLocal(self, option):
return option in (ECHO, SGA)

def applicationDataReceived(self, data):
self.transport.write('no\r\n')


factory = Factory()
factory.protocol = ObstinateProtocol
endpoint = TCP4ServerEndpoint(reactor, 8900)
endpoint.listen(factory)
reactor.run()
lessobstinate.py

Telnet into this and you should see the same behavior as when we set the client to character mode manually.

Finally, a playable game

I've pushed changes to the tx-telnet-2 branch on GitHub. You can see everything that's changed since the last post here.

To play the game, get the tx-telnet-2 branch and run run.py as before. Then telnet to port 8900 multiple times from multiple computers. If you die, you'll have to telnet in again :)