编写一个客户端

概论

Twisted是一个设计十分灵活的框架,而且允许编写出非常强大的客户端。编写灵活的客户端的只需要你写几个层就可以了。这个文档涵盖了如何创建一个客户端,不包括UDP UDP is covered in a different document .

首先,Protocol类是你通常用来执行和解析协议的地方。这个类每次都twisted.internet.protocol.Protocol 

许多协议的执行继承这个类或者它的子类。一个protocol类会被实例化每当你链接服务器的时候,而且在离开的时候会断开连接。这就是说,持久性的配置不会保存在Protool里。

持久化的类被保存在Factory里边。它继承自twisted.internet.protocol.Factory (or twisted.internet.protocol.ClientFactory 

默认的工厂累只会实例化Protocol只后设置协议的factory属性来指向这个协议。

这是例子:

\"\"

这个简单的例子只会进行回显。

再来个例子:

\"\"

看过我翻译的第一章的读者应该很熟悉这两个例子了,就是连接之后就回复,之后关闭。

好吧,开始进入正题,编写一个客户端

在许多例子里,protocol仅仅需要连接服务器一次就可以了,而且代码只想要获得链接协议的第一个实例对象,在这些例子里边,twisted.internet.endpoints提供了合适的API接口,connectProtocol 用的是protocol的实例,而不是工厂的实例!

\"\"

不管客户端的类型是什么,建立新的连接的方法只是把它通过 connectProtocol和协议实例。这也就是说非常容易改变你链接的状态,不用修改其他部分。举个例子,为了运行C例子(用SSL),只需要实例化 SSL4ClientEndpoint 代替 TCP4ClientEndpoint,为了使用这个优点,发起新的连接的函数和方法通常应该把endpoint作为参数,之后让调用者创建它,而不是使用‘host’和‘port’之类的参数来构建自己的端点。

你可能会看到用以下的代码创建一个客户端,这比较古老了:

1 from twisted.internet.protocol import ClientCreator
2 
3 ...
4 
5 creator = ClientCreator(reactor, Greeter)
6 d = creator.connectTCP(\"localhost\", 1234)
7 d.addCallback(gotProtocol)
8 reactor.run()

客户端工厂

现在依然有许多底层的API在外边,一些特性(比如自动重新连接)还没有用endpoint实现,所以在某些情况下使用它们可能会比较方便。

如果要使用底层api,需要调用reactor.connect。为了实现这些例子,你需要 ClientFactory。这个工厂是负责创建Protocol而且会接受和连接状态有关的事件。它允许你做一些事情,比如在一个连接错误之后再次连接。

给个例子:

\"\"

 

把它连接到一个服务器,你先需要写一些code:

\"\"\"\"

服务器启动一下,会显示如下:

\"\"

客户端reactor的api

connectTCP

IReactorTCP.connectTCP提供了IPV4,IPV6的tcp客户端方法。

参数有:host接受地址或者主机名,如果是主机名的话,reactor会自定解析为IP地址(多余开销)

    port接受端口

Reconnection

我们通常都会遇到连接失败的请跨国。一个重新连接的方法:

\"\"

第一个作为参数被传递的reactor是连接和协议之间的接口。当连接失败的时候,工厂会调用lost方法,之后调用connect()重新连接。

但是,大多数想要实现这个方法的工厂都应该重写ReconnectingClientFactory,而不是像上边那样,这个被重写的方法会尝试连接如果连接lost挥着faild,而且会自动实现梯度延迟。

给你个例子:
\"\"

高级例子:

这个客户端到目前为止还十分简单。一个更加复杂的例子来了:

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


\"\"\"
An example IRC log bot - logs a channel\'s events to a file.

If someone says the bot\'s name in the channel followed by a \':\',
e.g.

    <foo> logbot: hello!

the bot will reply:

    <logbot> foo: I am a log bot

Run this   with two arguments, the channel name the bot should
connect to, and file to log to, e.g.:

    $ python ircLogBot.py test test.log

will log channel #test to the file \'test.log\'.

To run the  :

    $ python ircLogBot.py <channel> <file>
\"\"\"


from __future__ import print_function

# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

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



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(sys.argv[1], sys.argv[2])

    # connect factory to this host and port
    reactor.connectTCP(\"irc.freenode.net\", 6667, f)

    # run bot
    reactor.run()

 

工厂中的持久数据

因为每次protocol连接都会创建一个实例,客户端需要以某种方式来跟踪持久化的数据。在日志的记录下,需要知道他正在记录哪一个通道,,以及在哪里对他进行日志记录。

 1 from twisted.words.protocols import irc
 2 from twisted.internet import protocol
 3 
 4 class LogBot(irc.IRCClient):
 5 
 6     def connectionMade(self):
 7         irc.IRCClient.connectionMade(self)
 8         self.logger = MessageLogger(open(self.factory.filename, \"a\"))
 9         self.logger.log(\"[connected at %s]\" %
10                         time.asctime(time.localtime(time.time())))
11 
12     def signedOn(self):
13         self.join(self.factory.channel)
14 
15 
16 class LogBotFactory(protocol.ClientFactory):
17 
18     def __init__(self, channel, filename):
19         self.channel = channel
20         self.filename = filename

创建协议的时候,它会获得一个工厂的引用,self.factory,它可以在之后访问工厂的属性,比如Logbot,

它打开文件并且连接到工厂中的channel

Factories有一个默认的方法BuildProtocol,它使用protoccol实例来创建。

1 class LogBotFactory(protocol.ClientFactory):
2     protocol = LogBot
3 
4     def __init__(self, channel, filename):
5         self.channel = channel
6         self.filename = filename

 

收藏 打印