管理 Perspective 的客户端

作者:

Kevin Turner

概述

在我们之前展示的所有 IPerspective 使用中,我们忽略了 mind 参数,并为每个连接创建了一个新的 Avatar。这通常是一个简单的设计选择,并且在简单的情况下效果很好。

在更复杂的情况下,例如一个代表游戏宇宙中持久存在的玩家对象的 Avatar,我们希望来自同一玩家的连接使用相同的 Avatar

在更复杂的场景中,另一个必要的事情是异步通知玩家。当然,允许玩家调用 perspective_remoteListener(referencable) 是可能的,但这意味着代码重复和更高的登录延迟,两者都不好。

在前面的部分中,所有领域看起来都是相同的。在本部分中,我们将展示领域在实现这两个目标方面的有用性。

管理 Avatar

管理持久化 Avatar 的最简单方法是使用直接的缓存机制

from zope.interface import implementer

class SimpleAvatar(pb.Avatar):
    greetings = 0
    def __init__(self, name):
        self.name = name
    def perspective_greet(self):
        self.greetings += 1
        return "<%d>hello %s" % (self.greetings, self.name)

@implementer(portal.IRealm)
class CachingRealm:

    def __init__(self):
        self.avatars = {}

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces: raise NotImplementedError
        if avatarId in self.avatars:
            p = self.avatars[avatarId]
        else:
            p = self.avatars[avatarId] = SimpleAvatar(avatarId)
        return pb.IPerspective, p, lambda:None

这为我们提供了一个 Perspective,它计算了发送给客户端的问候数量。实现缓存策略,而不是生成包含正确 Avatar 的领域,通常更容易。这使得向门户添加新的检查器或向检查器数据库添加新用户变得透明。否则,需要在检查器和 Avatar 之间进行仔细的同步(就像 UNIX 的 /etc/shadow/etc/passwd 之间的同步一样)。

但是,有时 Avatar 会需要足够的每个连接状态,以至于生成新的 Avatar 并缓存其他内容会更容易。以下是一个示例

from zope.interface import implementer

class Greeter:
    greetings = 0
    def hello(self):
        self.greetings += 1
        return "<%d>hello" % (self.greetings, self.name)

class SimpleAvatar(pb.Avatar):
    def __init__(self, name, greeter):
        self.name = name
        self.greeter = greeter
    def perspective_greet(self):
        return self.greeter.hello()+' '+self.name

@implementer(portal.IRealm)
class CachingRealm:
    def __init__(self):
        self.greeters = {}

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces: raise NotImplementedError
        if avatarId in self.greeters:
            p = self.greeters[avatarId]
        else:
            p = self.greeters[avatarId] = Greeter()
        return pb.IPerspective, SimpleAvatar(avatarId, p), lambda:None

使用这种模式来拥有一个被通知新连接的 Avatar 似乎很诱人。但是,这里的问题有两个:它会导致一个需要转发所有方法的薄类,并且无法知道何时发生断开连接。幸运的是,有一个更好的模式

from zope.interface import implementer

class SimpleAvatar(pb.Avatar):
    greetings = 0
    connections = 0
    def __init__(self, name):
        self.name = name
    def connect(self):
        self.connections += 1
    def disconnect(self):
        self.connections -= 1
    def perspective_greet(self):
        self.greetings += 1
        return "<%d>hello %s" % (self.greetings, self.name)

@implementer(portal.IRealm)
class CachingRealm:
    def __init__(self):
        self.avatars = {}

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces: raise NotImplementedError
        if avatarId in self.avatars:
            p = self.avatars[avatarId]
        else:
            p = self.avatars[avatarId] = SimpleAvatar(avatarId)
        p.connect()
        return pb.IPerspective, p, p.disconnect

可以使用这种模式来定义并发连接数的任意限制

from zope.interface import implementer

class SimpleAvatar(pb.Avatar):
    greetings = 0
    connections = 0
    def __init__(self, name):
        self.name = name
    def connect(self):
        self.connections += 1
    def disconnect(self):
        self.connections -= 1
    def perspective_greet(self):
        self.greetings += 1
        return "<%d>hello %s" % (self.greetings, self.name)

@implementer(portal.IRealm)
class CachingRealm:
    def __init__(self, max=1):
        self.avatars = {}
        self.max = max

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces: raise NotImplementedError
        if avatarId in self.avatars:
            p = self.avatars[avatarId]
        else:
            p = self.avatars[avatarId] = SimpleAvatar(avatarId)
        if p.connections >= self.max:
            raise ValueError("too many connections")
        p.connect()
        return pb.IPerspective, p, p.disconnect

管理客户端

到目前为止,我们所有的领域都忽略了 mind 参数。在 PB 的情况下,mind 是由远程登录方法提供的对象 - 通常,当它通过网络传输时,它会变成一个 pb.RemoteReference。此对象允许在连接建立并经过身份验证后立即向客户端发送消息。

这是一个简单的远程时钟应用程序,它展示了 mind 参数的有用性

from zope.interface import implementer

class SimpleAvatar(pb.Avatar):
    def __init__(self, client):
        self.s = internet.TimerService(1, self.telltime)
        self.s.startService()
        self.client = client
    def telltime(self):
        self.client.callRemote("notifyTime", time.time())
    def perspective_setperiod(self, period):
        self.s.stopService()
        self.s = internet.TimerService(period, self.telltime)
        self.s.startService()
    def logout(self):
        self.s.stopService()

@implementer(portal.IRealm)
class Realm:
    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces: raise NotImplementedError
        p = SimpleAvatar(mind)
        return pb.IPerspective, p, p.logout

在更复杂的情况下,您可能希望缓存 Avatar 并为每个 Avatar 提供一组“当前客户端”或类似的东西。