python twisted transport.write never sends data - python

I have a server which is loosely based on the Twisted example chat server. It works with telnet. Now I want a client to connect to it.I copied the sample one-time client. The problem is the server never receives the "hello" string which is supposed to start the "conversation". I read in the FAQ that a common mistake is to block the reactor from running but I cannot see where I could be doing that in the code below. Can anyone tell me what is wrong?
from twisted.internet import reactor
from twisted.internet.protocol import Factory, Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint
class Greeter(Protocol):
def sendMessage(self, msg):
self.transport.write("%s\n" % msg)
class GreeterFactory(Factory):
def buildProtocol(self, addr):
return Greeter()
def gotProtocol(p):
p.sendMessage("hello")
reactor.callLater(1, p.sendMessage, "/p2")
reactor.callLater(2, p.transport.loseConnection)
point = TCP4ClientEndpoint(reactor, "localhost", 8123)
d = point.connect(GreeterFactory())
d.addCallback(gotProtocol)
reactor.run()

OK. I hope this helps someone else. My problem above was that my server (which used lineReceived):
def lineReceived(self, line):
print "line received: %s" % line
if self.state == "GETNAME":
self.handle_GETNAME(line)
else:
self.handle_CHAT(line)
expected a carriage-return AND linefeed. So by changing my sendMessage above to:
def sendMessage(self, msg):
self.transport.write("%s\r\n" % msg)
it now works magically! So frustratingly simple.

Related

Python TCP Sockets - Single Client to Multiple Servers

I have about 200 ip controlled power-bars that I need to connect to and control over TCP sockets. So far I can connect to a single power-bar and control it without issue. Where my issue lies is in how to connect to, play ping pong, as well as send and receive commands for all 200 in one client.
I have researched, hopefully exhaustively, and at most all I can find is a pointer towards select, or twisted - but only for multiple clients connecting to a single server (whereas I need the reverse). All I really need is a prod in the right direction. I can create the sockets for all 200, but I cannot for the life of me figure out how to connect to each device using the IP and Port (60000) and send and receive the proper messages in a non-blocking manner.
Any pointers in the general direction would be greatly appreciated. Hopefully this answer will help someone else with a similar issue to solve. Thanks.
You're learning about non-blocking patterns in Python at the right time ;) There's a plethora of ways to do it so I'm not really surprised you're confused. You've named twisted, which is the most mature framework, and there's also asyncio, which is built into Python 3+. Pick which ever one is easiest for you to learn. As you can see, they're very similar in style.
asyncio_client.py
import asyncio
from uuid import uuid4
class Echo(asyncio.Protocol):
def __init__(self):
self.identity = uuid4().hex
def connection_made(self, transport):
message = '{}: hello world'.format(self.identity)
transport.write(message.encode())
def data_received(self, data):
print(data.decode())
def echo_factory():
return Echo()
async def connect_to_server(loop):
await loop.create_connection(echo_factory, host='127.0.0.1', port=6000)
def main():
loop = asyncio.get_event_loop()
loop.create_task(connect_to_server(loop))
loop.create_task(connect_to_server(loop))
loop.create_task(connect_to_server(loop))
loop.run_forever()
main()
twisted_client.py
from uuid import uuid4
from twisted.internet import endpoints, protocol, reactor
class Echo(protocol.Protocol):
def __init__(self):
self.identity = uuid4().hex
def connectionMade(self):
message = '{}: hello world'.format(self.identity)
self.transport.write(message.encode())
def dataReceived(self, data):
print(data.decode())
def connect_to_server(factory):
return endpoints.clientFromString(reactor, 'tcp:6000:host=127.0.0.1').connect(factory)
def main():
factory = protocol.ClientFactory.forProtocol(Echo)
connect_to_server(factory)
connect_to_server(factory)
connect_to_server(factory)
reactor.run()
main()

How to handle TCP connection events in order to call methods within other class?

I am creating a robot which is going to be driven by the commands received over TCP connection. Therefore, I will have a robot class with methods (e.g. sense(), drive()...) and the class for TCP connection.
To establish TCP connection, I looked at examples from twisted. On the client side, I have written a client.py script for connection handling:
from twisted.internet import reactor, protocol
import random
from eventhook import EventHook
import common
#from Common.socketdataobjects import response
# a client protocol
class EchoClient(protocol.Protocol):
"""Once connected, send a message, then print the result."""
def connectionMade(self):
self.transport.write("hello, world!")
#the server should be notified that the connection to the robot has been established
#along with robot state (position)
#eventConnectionEstablishedHook.fire()
def dataReceived(self, data):
print "Server said:", data
self.transport.write("Hello %s" % str(random.randint(1,10)))
'''
serverMessage = common.deserializeJson(data)
command = serverMessage.command
arguments = serverMessage.arguments
#here we get for example command = "DRIVE"
#arguments = {motor1Speed: 50, motor2Speed: 40}
instead of above response, used for testing purposes,
the commands should be extracted from the data and according to the command,
the method in Robot instance should be called.
When the command execution finishes, the self.transport.write() method should be called
to notify the server that the command execution finished
'''
def connectionLost(self, reason):
print "connection lost"
class EchoFactory(protocol.ClientFactory):
protocol = EchoClient
def clientConnectionFailed(self, connector, reason):
print "Connection failed - goodbye!"
reactor.stop()
def clientConnectionLost(self, connector, reason):
print "Connection lost - goodbye!"
reactor.stop()
# this connects the protocol to a server runing on port 8000
def initializeEventHandlers(connectionEstablishedHook):
global connection
connection.established = 0
global eventConnectionEstablishedHook
eventConnectionEstablishedHook = connectionEstablishedHook
def main():
f = EchoFactory()
reactor.connectTCP("localhost", 8000, f)
reactor.run()
# this only runs if the module was *not* imported
if __name__ == '__main__':
main()
Beside this script, I have a robot class:
Class Robot(object():
def __init(self)__:
self.position = (0,0)
def drive(self, speedMotor1, speedMotor2, driveTime)
updateMotor1State(speedMotor1)
updateMotor2State(speedMotor2)
time.sleep(driveTime)
#when the execution finished, the finish status should be sent to client in order to inform the server
return "Finished"
def sense(self)
#logic to get the data from the environment
What I would like to do, is to receive the data(commands) from TCP connection and then call the according method in Robot instance. Some procedures might take longer (e.g. driving), so I tried to use events, but haven't figured out the appropriate way to communicate between TCP client and robot using events:
if __name__ == '__main__':
robotController = Robot()
eventController = Controller()
connectionEstablishedHook = EventHook()
client.initializeEventHandlers(connectionEstablishedHook)
eventController.connection = connectionEstablishedHook
client.main()
I tried to create ClientMainProgram script, where I wanted to create an instance of a robot, an instance of TCP client and implement the communication between them using events.
Previously I have managed to implement event handling using Michael Foord's events pattern on a simpler example. I would be very thankful if anyone could provide the solution to this question or any similar example which might be helpful to solve this problem.
Events are easily represented using regular Python function calls.
For example, if your protocol looks like this:
from twisted.internet.protocol import Protocol
class RobotController(Protocol):
def __init__(self, robot):
self.robot = robot
def dataReceived(self, data):
for byte in data:
self.commandReceived(byte)
def commandReceived(self, command):
if command == "\x00":
# drive:
self.robot.drive()
elif command == "\x01":
# sense:
self.robot.sense()
...
(The specifics of the protocol used in this example are somewhat incidental. I picked this protocol because it's very simple and has almost no parsing logic. For your real application I suggest you use twisted.protocols.amp.)
Then all you need to do is make sure the robot attribute is properly initialized. You can do this easily using the somewhat newer endpoint APIs that can often replace use of factories:
from sys import argv
from twisted.internet.endpoints import clientFromString, connectProtocol
from twisted.internet.task import react
def main(reactor, description):
robot = ...
endpoint = clientFromString(reactor, description)
connecting = connectProtocol(endpoint, RobotController(robot))
def connected(controller):
...
connecting.addCallback(connected)
return connecting
react(main, argv[1:])

Twisted Python - IRC Client

first question here.
So for a club at school we are working on making a IRC client in Python and Twisted.
So I take the example bot that twisted gives you. I've managed to get it connected to a irc channel, and it logs.
I know I've probably gotta use 2 threads to have both reading from the server and input simultaneous, which I can achieve, but only if it command line input. Mind you it is still logging the data from the channel at the same time.
So to do this I used: d = threads.deferToThread(aSillyBlockingMethod)
Which calls my raw_input() loop.
My problem lies in not being able to figure out how to change this from just typing into and printing form the commandline; to being able to actually send messages to the irc server for other people to read.
Any help would be greatly appreciated. I am a novice python programmer and don't know too much web stuff; like protocols, ports, and stuff like that but I am slowly picking them up. If anyone knows of an easier way to do this let me know please I am not committed to using Twisted.
Here is my code, or rather the modified bot I'm tinkering with:
# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log
from twisted.internet import threads
# system imports
import time, sys
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('%s %s\n' % (timestamp, message))
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 succesfully 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('!', 1)[0]
self.logger.log("<%s> %s" % (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("<%s> %s" % (self.nickname, msg))
def action(self, user, channel, msg):
"""This will get called when the bot sees someone do an action."""
user = user.split('!', 1)[0]
self.logger.log("* %s %s" % (user, msg))
# irc callbacks
def irc_NICK(self, prefix, params):
"""Called when an IRC user changes their nickname."""
old_nick = prefix.split('!')[0]
new_nick = params[0]
self.logger.log("%s is now known as %s" % (old_nick, 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 + '^'
def aSillyBlockingMethod(self):
import time
while True:
msg = raw_input()
print msg
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):
p = LogBot()
p.factory = self
return p
def clientConnectionLost(self, connector, reason):
"""If we get disconnected, reconnect to server."""
connector.connect()
def clientConnectionFailed(self, connector, reason):
print "connection failed:", reason
reactor.stop()
if __name__ == '__main__':
# initialize logging
log.startLogging(sys.stdout)
# create factory protocol and application
f = LogBotFactory("#goon.squad.dev", "test.txt")
# connect factory to this host and port
reactor.connectTCP("irc.freenode.net", 6667, f)
#Use this to keep user input open
d = threads.deferToThread(aSillyBlockingMethod)
# run bot
reactor.run()
--TyrZaraki
I know I've probably gotta use 2 threads to have both reading from the server and input simultaneous, which I can achieve, but only if it command line input. Mind you it is still logging the data from the channel at the same time.
Actually, it's not necessary to use two threads for this. A major strength of Twisted is doing I/O without using threads. The question and answer Michael linked to talk about Twisted's non-threaded support for interacting with standard input and standard output via twisted.internet.stdio.StandardIO.
My problem lies in not being able to figure out how to change this from just typing into and printing form the commandline; to being able to actually send messages to the irc server for other people to read.
IRCClient has a method for sending messages to the IRC server - the sample code you included in your question even uses this method already, IRCClient.msg. All you need to do is call it.
Your threaded code could change like this to do so (again, threads are unnecessary, but in the interest of just showing you how to send a message from your input handling code, I'll base this part of the answer on threads, to avoid other changes to the code which might make the answer harder to understand. You do not need threads to do this.):
def aSillyBlockingMethod(bot):
import time
while True:
msg = raw_input()
bot.threadSafeMsg("#bottest", msg)
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())))
# The bot is now connected. Start reading input here.
# Pass a reference to this protocol instance, so that
# messages can be sent to this protocol instance.
deferToThread(aSillyBlockingMethod, self)
# Define a helper function for aSillyBlockingMethod to use.
# Since aSillyBlockingMethod runs in a thread, it cannot just call
# IRCClient.msg, since that method - like almost all methods in Twisted -
# is not thread-safe. Instead it must call this thread-safe wrapper.
def threadSafeMsg(self, channel, message):
reactor.callFromThread(self.msg, channel, message)
Notice that all that's happening here is that aSillyBlockingMethod is calling a method on LogBot. The thread-safe wrapper would not be necessary when using StandardIO, since that eliminates the need for threads.

Python twisted: Functions are not called properly?

I've got a problem with setting up a client which connects to a "distributor" server to send certain data.
The server's purpose is to get data from the client and then send that data to it's all connected clients. The server works without any issues.
The main client is also supposed to work as an IRC bot.
Here's a text example of how it should work like:
(IRC) John: Hello there!
1. The IRC client got the message, we need to send it to the distributor now.
2. Distributor should get this "John: Hello there!" string and send it back to it's all connected clients.
3. If other clients send data to the distributor, which this will broadcast to all clients, the IRC client should output at it's turn the received data to a specified channel
The following code is the IRC bot client (ircbot.py):
import sys
import socket
import time
import traceback
from twisted.words.protocols import irc
from twisted.internet import reactor
from twisted.internet import protocol
VERBOSE = True
f = None
class IRCBot(irc.IRCClient):
def _get_nickname(self):
return self.factory.nickname
nickname = property(_get_nickname)
def signedOn(self):
self.msg("NickServ", "id <password_removed>") # Identify the bot
time.sleep(0.1) # Wait a little...
self.join(self.factory.channel) # Join channel #chantest
print "Signed on as %s." % (self.nickname,)
def joined(self, channel):
print "Joined %s." % (channel,)
def privmsg(self, user, channel, msg):
name = user.split('!', 1)[0]
prefix = "%s: %s" % (name, msg)
print prefix
if not user:
return
if self.nickname in msg:
msg = re.compile(self.nickname + "[:,]* ?", re.I).sub('', msg)
print msg
else:
prefix = ''
if msg.startswith("!"):
if name.lower() == "longdouble":
self.msg(channel, "Owner command") # etc just testing stuff
else:
self.msg(channel, "Command")
if channel == "#testchan" and name != "BotName":
EchoClient().sendData('IRC:'+' '.join(map(str, [name, msg])))
# This should make the bot send chat data to the distributor server (NOT IRC server)
def irc_NICK(self, prefix, params):
"""Called when an IRC user changes their nickname."""
old_nick = prefix.split('!')[0]
new_nick = params[0]
self.msg(, "%s is now known as %s" % (old_nick, new_nick))
def alterCollidedNick(self, nickname):
return nickname + '1'
class BotFactory(protocol.ClientFactory):
protocol = IRCBot
def __init__(self, channel, nickname='BotName'):
self.channel = channel
self.nickname = nickname
def clientConnectionLost(self, connector, reason):
print "Lost connection (%s), reconnecting." % (reason,)
connector.connect()
def clientConnectionFailed(self, connector, reason):
print "Could not connect: %s" % (reason,)
class EchoClient(protocol.Protocol):
def connectionMade(self):
pass
def sendData(self, data):
self.transport.write(data)
def dataReceived(self, data):
if VERBOSE:
print "RECV:", data
IRC.msg("#chantest", data)
#This one should send the received data from the distributor to the IRC channel
def connectionLost(self, reason):
print "Connection was lost."
class EchoFactory(protocol.ClientFactory):
def startedConnecting(self, connector):
print 'Started to connect.'
def buildProtocol(self, addr):
print 'Connected to the Distributor'
return EchoClient()
def clientConnectionFailed(self, connector, reason):
print "Cannot connect to distributor! Check all settings!"
reactor.stop()
def clientConnectionLost(self, connector, reason):
print "Distributor Lost connection!!"
reactor.stop()
if __name__ == "__main__":
IRC = BotFactory('#chantest')
reactor.connectTCP('irc.rizon.net', 6667, IRC) # Our IRC connection
f = EchoFactory()
reactor.connectTCP("localhost", 8000, f) # Connection to the Distributor server
reactor.run()
The following code is the distributor server (distributor.py):
(This one works fine, but maybe it could be useful for further reference)
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
class MultiEcho(Protocol):
def __init__(self, factory):
self.factory = factory
def connectionMade(self):
print "Client connected:",self
self.factory.echoers.append(self)
self.factory.clients = self.factory.clients+1
#self.transport.write("Welcome to the server! There are currently "+`self.factory.clients`+" clients connected.")
def dataReceived(self, data):
print "RECV:",data
for echoer in self.factory.echoers:
echoer.transport.write(data)
def connectionLost(self, reason):
print "Client disconnected:",self
self.factory.echoers.remove(self)
self.factory.clients = self.factory.clients-1
class MultiEchoFactory(Factory):
def __init__(self):
self.clients = 0
self.names = []
self.echoers = []
def buildProtocol(self, addr):
return MultiEcho(self)
if __name__ == '__main__':
print "Running..."
reactor.listenTCP(8000, MultiEchoFactory())
reactor.run()
I want the client to output all incoming chat data from the IRC server to the "distributor" server and also output incoming data from the "distributor".
However, I get errors like this:
For the following line in ircbot.py,
EchoClient().sendData('IRC'+' '.join(map(str, [name, msg])))
I get the following error:
Joined #chantest.
Longdouble: test
Traceback (most recent call last):
File "C:\Python\lib\site-packages\twisted\internet\tcp.py", line 460, in doRea
d
return self.protocol.dataReceived(data)
File "C:\Python\lib\site-packages\twisted\words\protocols\irc.py", line 2277,
in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "C:\Python\lib\site-packages\twisted\protocols\basic.py", line 564, in da
taReceived
why = self.lineReceived(line)
File "C:\Python\lib\site-packages\twisted\words\protocols\irc.py", line 2285,
in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "C:\Python\lib\site-packages\twisted\words\protocols\irc.py", line 2329,
in handleCommand
method(prefix, params)
File "C:\Python\lib\site-packages\twisted\words\protocols\irc.py", line 1813,
in irc_PRIVMSG
self.privmsg(user, channel, message)
File "C:\Python\Traance\kwlbot\ircbot.py", line 51, in privmsg
EchoClient().sendData('IRC'+' '.join(map(str, [name, msg])))
File "C:\Python\Traance\kwlbot\ircbot.py", line 90, in sendData
self.transport.write(data)
exceptions.AttributeError: 'NoneType' object has no attribute 'write'
And same goes to this line in the same ircbot.py
IRC.msg("#chantest", data)
->
RECV: Hello from Distributor Server
Traceback (most recent call last):
File "C:\Python\Traance\kwlbot\ircbot.py", line 96, in dataReceived
IRC.msg("#chantest", data)
AttributeError: BotFactory instance has no attribute 'msg'
What am I doing wrong? How can I call the right function from the IRCbot class to make it send the data to the distributor server and data received from the distributor server to output in the specified channel via IRC?
Any suggestions and possible solutions are welcome.
If I missed any other details, please let me know.
Thank you for your time!
You should avoid writing blocking code like this:
def signedOn(self):
self.msg("NickServ", "id <password_removed>") # Identify the bot
time.sleep(0.1) # Wait a little...
self.join(self.factory.channel) # Join channel #chantest
print "Signed on as %s." % (self.nickname,)
For details, see Tail -f log on server, process data, then serve to client via twisted.
Apart from that, the main problem here is that you are trying to send data without having a connection. When you write something like:
EchoClient().sendData('IRC'+' '.join(map(str, [name, msg])))
you're creating a protocol instance which is responsible for handling a connection and then trying to use it, but you're not creating a connection. The attempt to send data fails because the protocol hasn't been attached to any transport.
Your snippet already demonstrates the correct way to create a connection, twice in fact:
IRC = BotFactory('#chantest')
reactor.connectTCP('irc.rizon.net', 6667, IRC) # Our IRC connection
f = EchoFactory()
reactor.connectTCP("localhost", 8000, f) # Connection to the Distributor server
The mistake is creating a new EchoClient instance, one with no connection. The reactor.connectTCP call creates a new connection and a new EchoClient instance and associates them with each other.
Instead of EchoClient().sendData(...), you want to use the EchoClient instance created by your factory:
def buildProtocol(self, addr):
print 'Connected to the Distributor'
return EchoClient()
Your buildProtocol implementation creates the instance, all that's missing is for it to save the instance so it can be used by your IRC bot.
Consider something like this:
def buildProtocol(self, addr):
print 'Connected to the Distributor'
self.connection = EchoClient()
return self.connection
Your IRC client can then use the saved EchoClient instance:
if channel == "#testchan" and name != "BotName":
f.connection.sendData('IRC:'+' '.join(map(str, [name, msg])))
# This should make the bot send chat data to the distributor server (NOT IRC server)
Note that the specific code I give here is a very crude approach. It uses the global variable f to find the EchoFactory instance. As with most global variable usage this makes the code a little hard to follow. Further, I haven't added any code to handle connectionLost events to clear the connection attribute out. This means you might think you're sending data to the distributed server when the connection has already been lost. And similarly, there's no guarantee that the connection to the distributed server will have been created by the time the IRC client first tries to use it, so you may have an AttributeError when it tries to use f.connection.sendData.
However, fixing these doesn't require much of a leap. Fix the global variable usage as you would any other - by passing arguments to functions, saving objects as references on other objects, etc. Fix the possible AttributeError by handling it, or by not creating the IRC connection until after you've created the distributed connection, etc. And handle lost connections by resetting the attribute value to None or some other sentinel, and paying attention to such a case in the IRC code before trying to use the distributed client connection to send any data.
TFM is never defined in your code, so I don't know what the deal is there.
The other error is that you're instantiating a client, but never connecting it to anything, as with reactor.connectTCP(...) or endpoint.connect(...). The transport attribute will be None until it's set by something.
(It would be helpful for you to come up with a simpler version of this code which is complete and doesn't include unnecessary details like all the printed log messages. It makes it harder to see what the real issues are.)

Twisted ignoring data sent from MUD Clients?

I have the following code (almost an exact copy of the Chat server example listed here:
import twisted.scripts.twistd
from twisted.protocols import basic
from twisted.internet import protocol, reactor
from twisted.application import service, internet
class MyChat(basic.LineReceiver):
def connectionMade(self):
print "Got new client!"
self.factory.clients.append(self)
def connectionLost(self, reason):
print "Lost a client!"
self.factory.clients.remove(self)
def lineReceived(self, line):
print "received", repr(line)
for c in self.factory.clients:
c.message(line)
def message(self, message):
self.transport.write(message + '\n')
factory = protocol.ServerFactory()
factory.protocol = MyChat
factory.clients = []
if __name__ == "__main__":
print "Building reactor...."
reactor.listenTCP(50000, factory)
print "Running ractor...."
reactor.run()
else:
application = service.Application("chatserver")
internet.TCPServer(50000, factory).setServiceParent(application)
The server runs without error, and if I connect to it via Telnet, I can send data and the server prints to the console and relays it to all clients (as is expected). However, if I connect to it via a different tool (a MUD client), it never gets the data.
I have ensured that the client is sending the data (Traced the packets with Wireshark, and they're going across the wire), but the server either never receives it, or is choosing to ignore it for some reason.
I have tried this with two MUD clients, gmud, and JMC. If it is important, I am running Windows 7 x64.
Does anyone have any idea why this could be happening?
Thanks,
Mike
EDIT:
Thanks to the hints provided by Maiku Mori, I tried adding another method that was specified in the Twisted API Docs, dataReceived. Once this was added, the MUD clients worked perfectly, but Telnet is now sending every character as it's own set of data, instead of waiting for the user to press Enter.
Here's a snipped of the new code:
def dataReceived(self, data):
print "Dreceived", repr(data)
for c in self.factory.clients:
c.message(data)
# def lineReceived(self, line):
# print "received", repr(line)
# for c in self.factory.clients:
# c.message(line)
Has anyone experiences this before, and if so, how do you get around it? Ideally, I would like Telnet and MUD clients to work with this application.
Thanks again.
In case anyone stumbles across this question with similar problems, I'm leaving my findings as the accepted answer so that people don't have to hunt the way I did.
I fixed the issue by changing the delimiter value from in my Twisted protocol from "\r\n" (default), to just "\n" (which is what my MUD clients send. This means that in Telnet, when you enter the string:
Hello, World
Your application will receive it as:
Hello, World\r
You may need to do data sanitation on the server side to keep things in order. My final code was as follows:
import twisted.scripts.twistd
from twisted.protocols import basic
from twisted.internet import protocol, reactor
from twisted.application import service, internet
class MyChat(basic.LineReceiver):
def __init__(self):
self.delimiter = "\n"
def connectionMade(self):
print "Got new client!"
self.factory.clients.append(self)
def connectionLost(self, reason):
print "Lost a client!"
self.factory.clients.remove(self)
def lineReceived(self, line):
print "received", repr(line)
for c in self.factory.clients:
c.message(line)
def message(self, message):
self.transport.write(message + '\n')
factory = protocol.ServerFactory()
factory.protocol = MyChat
factory.clients = []
if __name__ == "__main__":
print "Building reactor...."
reactor.listenTCP(50000, factory)
print "Running ractor...."
reactor.run()
else:
application = service.Application("chatserver")
internet.TCPServer(50000, factory).setServiceParent(application)
Thanks for all the help.
Are you sure that the MUD clients send line ending chars after each line? The lineReceived will only be called after line ending char has been sent.
EDIT:
Here I found API docs for LineReceiver. You could play around with dataReceived method to see if you are actually getting any kind of data. If I recall you can use it just like lineReceived.

Categories

Resources