Python twisted: 函数未正确调用?

3 投票
2 回答
2072 浏览
提问于 2025-04-17 07:01

我遇到了一个问题,想要设置一个客户端,这个客户端要连接到一个“分发器”服务器,用来发送一些数据。这个服务器的作用是接收客户端的数据,然后把这些数据发送给所有连接的客户端。服务器运行得很好。

主要的客户端还应该作为一个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 个回答

0

TFM 在你的代码中从来没有定义过,所以我不知道这是什么情况。

另一个错误是你创建了一个客户端,但从来没有把它连接到任何地方,比如用 reactor.connectTCP(...)endpoint.connect(...)。在这之前,transport 属性会是 None,直到有东西把它设置好。

如果你能简化一下这个代码,做一个完整的版本,不包含那些不必要的细节,比如所有的日志打印信息,那会对你有帮助。这样更容易看出真正的问题是什么。

4

你应该避免写这种会阻塞的代码:

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 代码中处理这种情况。

撰写回答