Twisted / Python - 来自另一个线程的协议内的调用方法

时间:2012-03-30 18:54:51

标签: python multithreading twisted irc

对不起,如果我的标题错了,我是Twisted的新手并不能真正描述我的问题。

所以问题是,我有一个基于ircLogBot.py(http://twistedmatrix.com/documents/current/words/examples/ircLogBot.py)的IRC bot,它通过PHP页面在IRC和MySQL数据库之间中继消息。

它必须每1秒加载一个PHP页面,解析内容(JSON),循环遍历它,然后将每个项目发布到IRC。除了将其发布到IRC之外,我已将其全部排序。

它很难的原因是因为循环在另一个线程内运行(它必须工作)而且我不知道如何从该线程调用msg()。

这个描述可能真的令人困惑,所以看看我的代码。我评论了我要发送信息的位置:

from twisted.words.protocols import irc
from twisted.internet import reactor, protocol, threads
from twisted.python import log
# system imports
import time, sys, json, urllib, urllib2, threading
url = 'http://86.14.76.169/root/scripts/ircbot.php'
urltwo = 'http://86.14.76.169/root/scripts/returnchat.php'
class LogBot(irc.IRCClient):
    try:
        """A logging IRC bot."""
        nickname = "WorldConflictBot"
        def connectionMade(self):
            irc.IRCClient.connectionMade(self)


        def connectionLost(self, reason):
            irc.IRCClient.connectionLost(self, reason)


        # 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.action('JackBot', self.factory.channel, 'Joined')
        def privmsg(self, user, channel, msg):
            """This will get called when the bot receives a message."""
            user = user.split('!', 1)[0]
            values = {}
            values = {'type' : 'message',
                      'message' : msg,
                      'username' : user,
                     }
            data = urllib.urlencode(values)
            req = urllib2.Request(url, data)
            response = urllib2.urlopen(req)
            the_page = response.read()
            # 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: Hey :)" % user
                self.msg(channel, msg)


        def action(self, user, channel, msg):
            """This will get called when the bot sees someone do an action."""
            user = user.split('!', 1)[0]


        # 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]
            values = {}
            values = {'type' : 'nick',
                      'from' : old_nick,
                      'to' : new_nick,
                     }
            data = urllib.urlencode(values)
            req = urllib2.Request(url, data)
            response = urllib2.urlopen(req)
            the_page = response.read() 


        # 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 + '^'

    except KeyboardInterrupt:
        LogBotLooper.exit()
        sys.exit()

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):
        self.channel = 'worldconflict'

    def buildProtocol(self, addr):
        p = LogBot()
        p.factory = self
        return p
        l = LogBotLooper()
        l.factory = self
        return l
    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()

class LogBotLooper(irc.IRCClient):
    def __init__(self):
        i = 0
        lastid = 0
        while 1:       
            time.sleep(1)
            if(i == 0):
                values = {'justlastid': 'true'}
            else:
                values = {'lastid' : lastid}
            data = urllib.urlencode(values)
            req = urllib2.Request(urltwo, data)
            response = urllib2.urlopen(req)
            the_page = response.read()
            if(i == 0):
                lastid = the_page
                i += 1
            else:
                if(the_page != 'error'):
                    jsonpage = json.loads(the_page)
                    for message in jsonpage['messages']:
                        #Need to send the variable `message` to IRC.
                    lastid = jsonpage['highestid']

    def exit(self):
        sys.exit()

if __name__ == '__main__':
    try:
        # initialize logging
        log.startLogging(sys.stdout)

        # create factory protocol and application
        f = LogBotFactory()
        # connect factory to this host and port
        reactor.connectTCP("irc.skyirc.net", 6667, f)
        reactor.callInThread(LogBotLooper)
        # run bot
        reactor.run()
    except KeyboardInterrupt:
        LogBotLooper.exit()
        sys.exit()

2 个答案:

答案 0 :(得分:2)

您可能已经知道这一点,但您的协议类应该只是坚持事件处理和传输本身的其他抽象。这样你就可以保持关注点的分离,并且你有一个可维护的框架。在MVC范例中,您的协议类是控制器,甚至可能是视图,但绝对不是模型。进行PHP Web服务调用可能属于模型。

至于将工作转移到其他线程(对于任何阻塞I / O,例如Web服务调用,您肯定需要这样做),您需要:

from twisted.internet import threads, reactor

从主反应堆主题,调用threads.deferToThread(mycallable, *args, **kwargs)从下一个可用的工作线程调用mycallable

从任何工作线程,调用reactor.callFromThread(mycallable, *args, **kwargs)从主反应堆线程调用mycallable

要将工作从一个工作线程转移到另一个工作线程,请结合使用以下两种技术:reactor.callFromThread(threads.deferToThread, mycallable, *args, **kwargs)

我相信这两个调用都会返回一个Deferred对象(我知道deferToThread会这样做)。如果向延迟添加回调,那些回调将在与原始callable相同的线程中执行。要将回调执行委派给工作线程,请在回调中使用上述技术。 (他们并没有把它称为“扭曲”。)

答案 1 :(得分:1)

如果我的帖子没有收到错误信息,我会遇到与您相同的问题。

http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html

threads.blockingCallFromThread 是此问题的另一个答案。

只需替换

#Need to send the variable `message` to IRC.

threads.blockingCallFromThread(reactor, irc.send_message, message) 
#I assume you call irc.send_message here