Python twisted: 函数未正确调用?
我遇到了一个问题,想要设置一个客户端,这个客户端要连接到一个“分发器”服务器,用来发送一些数据。这个服务器的作用是接收客户端的数据,然后把这些数据发送给所有连接的客户端。服务器运行得很好。
主要的客户端还应该作为一个IRC机器人来工作。下面是一个它应该如何工作的文字示例:
(IRC) John: 你好啊!
1. IRC客户端收到了这个消息,现在我们需要把它发送给分发器。
2. 分发器应该接收到这个“John: 你好啊!”的字符串,并把它发送给所有连接的客户端。
3. 如果其他客户端向分发器发送数据,分发器会把这些数据广播给所有客户端,而IRC客户端则应该把接收到的数据输出到指定的频道。
下面的代码是IRC机器人客户端(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()
下面的代码是分发器服务器(distributor.py):
(这个运行得很好,但也许对进一步参考有用)
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()
我希望客户端能够把从IRC服务器接收到的所有聊天数据输出到“分发器”服务器,并且也能把从“分发器”接收到的数据输出。
但是,我遇到了这样的错误:
在ircbot.py的以下这一行,
EchoClient().sendData('IRC'+' '.join(map(str, [name, msg])))
我得到了以下错误:
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'
同样的情况也发生在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'
我哪里做错了?我该如何从IRCBot类中调用正确的函数,让它把数据发送到分发器服务器,并把从分发器服务器接收到的数据输出到IRC的指定频道?
欢迎任何建议和可能的解决方案。
如果我遗漏了其他细节,请告诉我。
谢谢你的时间!
2 个回答
TFM
在你的代码中从来没有定义过,所以我不知道这是什么情况。
另一个错误是你创建了一个客户端,但从来没有把它连接到任何地方,比如用 reactor.connectTCP(...)
或 endpoint.connect(...)
。在这之前,transport
属性会是 None
,直到有东西把它设置好。
如果你能简化一下这个代码,做一个完整的版本,不包含那些不必要的细节,比如所有的日志打印信息,那会对你有帮助。这样更容易看出真正的问题是什么。
你应该避免写这种会阻塞的代码:
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,)
想了解更多,可以查看 在服务器上使用 Tail -f 日志,处理数据,然后通过 twisted 服务给客户端。
除此之外,主要的问题是你在试图发送数据时并没有建立连接。当你写类似这样的代码:
EchoClient().sendData('IRC'+' '.join(map(str, [name, msg])))
你其实是在创建一个协议实例,这个实例负责处理连接,但你并没有真正建立一个 连接。所以发送数据的尝试会失败,因为这个协议没有和任何传输方式连接起来。
你的代码片段已经展示了正确建立连接的方法,实际上展示了两次:
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
错误在于你创建了一个新的 EchoClient
实例,而这个实例没有连接。reactor.connectTCP
调用会创建一个新的连接,并且将新的 EchoClient
实例和这个连接关联起来。
你应该使用通过工厂创建的 EchoClient
实例,而不是 EchoClient().sendData(...)
:
def buildProtocol(self, addr):
print 'Connected to the Distributor'
return EchoClient()
你的 buildProtocol
实现创建了这个实例,缺少的只是让它 保存 这个实例,以便你的 IRC 机器人可以使用。
可以考虑这样做:
def buildProtocol(self, addr):
print 'Connected to the Distributor'
self.connection = EchoClient()
return self.connection
然后你的 IRC 客户端可以使用保存的 EchoClient
实例:
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)
注意,我这里给出的具体代码是一个非常粗糙的方法。它使用全局变量 f
来找到 EchoFactory
实例。像大多数全局变量的使用一样,这会让代码有点难以理解。此外,我没有添加任何处理 connectionLost
事件的代码来清除 connection
属性。这意味着你可能会认为自己正在向分布式服务器发送数据,但实际上连接已经丢失了。同样,IRC 客户端在第一次尝试使用时,也不能保证连接已经建立,所以当它尝试使用 f.connection.sendData
时,可能会出现 AttributeError
。
不过,解决这些问题并不需要太大的努力。你可以像处理其他问题一样修复全局变量的使用——通过将参数传递给函数,或者将对象作为引用保存在其他对象上等等。通过处理可能出现的 AttributeError
,或者在创建分布式连接之后再创建 IRC 连接来解决这个问题。此外,处理丢失的连接时,可以将属性值重置为 None
或其他标记,并在尝试使用分布式客户端连接发送数据之前,注意在 IRC 代码中处理这种情况。