【问题标题】:How to implement a two way jsonrpc + twisted server/client如何实现两种方式 jsonrpc + 扭曲的服务器/客户端
【发布时间】:2010-12-08 12:36:24
【问题描述】:

您好,我正在开发一个基于 twisted 的 rpc 服务器,以服务于多个微控制器,这些微控制器使 rpc 调用 twisted jsonrpc 服务器。但是应用程序还要求服务器随时向每个微发送信息,所以问题是如何防止来自微的远程 jsonrpc 调用的响应与为一个用户。

我现在得到的结果是 micros 收到了错误的信息,因为他们不知道来自套接字的 netstring/json 字符串是他们对先前要求的响应还是来自服务器的新请求。

这是我的代码:

from twisted.internet import reactor
from txjsonrpc.netstring import jsonrpc
import weakref

creds  = {'user1':'pass1','user2':'pass2','user3':'pass3'}

class arduinoRPC(jsonrpc.JSONRPC):
    def connectionMade(self):
        pass

    def jsonrpc_identify(self,username,password,mac):
        """ Each client must be authenticated just after to be connected calling this rpc """
        if creds.has_key(username):
            if creds[username] == password:
                authenticated = True
            else:
                authenticated = False
        else:
            authenticated = False

        if authenticated:
            self.factory.clients.append(self)
            self.factory.references[mac] = weakref.ref(self)
            return {'results':'Authenticated as %s'%username,'error':None}
        else:
            self.transport.loseConnection()

    def jsonrpc_sync_acq(self,data,f):
        """Save into django table data acquired from sensors and send ack to gateway"""
        if not (self in self.factory.clients):
            self.transport.loseConnection()
        print f
        return {'results':'synced %s records'%len(data),'error':'null'}

    def connectionLost(self, reason):
        """ mac address is searched and all reference to self.factory.clientes are erased """  
        for mac in self.factory.references.keys():
            if self.factory.references[mac]() == self:
                print 'Connection closed - Mac address: %s'%mac
                del self.factory.references[mac]
                self.factory.clients.remove(self)


class rpcfactory(jsonrpc.RPCFactory):
    protocol = arduinoRPC
    def __init__(self, maxLength=1024):
        self.maxLength = maxLength
        self.subHandlers = {}
        self.clients    =   []
        self.references =   {}

""" Asynchronous remote calling to micros, simulating random calling from server """
import threading,time,random,netstring,json
class asyncGatewayCalls(threading.Thread):
    def __init__(self,rpcfactory):
        threading.Thread.__init__(self)
        self.rpcfactory =   rpcfactory
        """identifiers of each micro/client connected"""
        self.remoteMacList    =   ['12:23:23:23:23:23:23','167:67:67:67:67:67:67','90:90:90:90:90:90:90']
    def run(self):
        while True:
            time.sleep(10)
            while True:
                """ call to any of three potential micros connected """ 
                mac = self.remoteMacList[random.randrange(0,len(self.remoteMacList))]
                if self.rpcfactory.references.has_key(mac):
                    print 'Calling %s'%mac
                    proto   =   self.rpcfactory.references[mac]()
                    """ requesting echo from selected micro"""
                    dataToSend  = netstring.encode(json.dumps({'method':'echo_from_micro','params':['plop']}))
                    proto.transport.write(dataToSend)
                    break

factory = rpcfactory(arduinoRPC)

"""start thread caller""" 
r=asyncGatewayCalls(factory)
r.start()

reactor.listenTCP(7080, factory)
print "Micros remote RPC server started"
reactor.run()

【问题讨论】:

    标签: python twisted microcontroller json-rpc


    【解决方案1】:

    您需要为每条消息添加足够的信息,以便收件人可以确定如何解释它。您的要求听起来与AMP 的要求非常相似,因此您可以改用 AMP 或使用与 AMP 相同的结构来识别您的消息。具体来说:

    • 在请求中,放置一个特定的键 - 例如,AMP 使用“_ask”来识别请求。它还为这些提供了一个唯一值,进一步标识了连接生命周期内的请求。
    • 在响应中输入不同的键 - 例如,AMP 为此使用“_answer”。该值与响应所针对的请求中“_ask”键中的值相匹配。

    使用这样的方法,您只需查看是否有“_ask”键或“_answer”键即可确定您是否收到了新请求或对先前请求的响应。

    在一个单独的主题上,您的 asyncGatewayCalls 类不应该是基于线程的。它没有明显的理由使用线程,并且这样做也会以一种会导致未定义行为的方式滥用 Twisted API。大多数 Twisted API 只能在您调用 reactor.run 的线程中使用。唯一的例外是reactor.callFromThread,您可以使用它从任何其他线程向反应器线程发送消息。但是,asyncGatewayCalls 尝试写入传输,这将导致缓冲区损坏或正在发送的数据的任意延迟,或者可能是更糟的事情。相反,您可以这样写asyncGatewayCalls

    from twisted.internet.task import LoopingCall
    
    class asyncGatewayCalls(object):
        def __init__(self, rpcfactory):
            self.rpcfactory = rpcfactory
            self.remoteMacList = [...]
    
        def run():
            self._call = LoopingCall(self._pokeMicro)
            return self._call.start(10)
    
        def _pokeMicro(self):
            while True:
                mac = self.remoteMacList[...]
                if mac in self.rpcfactory.references:
                    proto = ...
                    dataToSend = ...
                    proto.transport.write(dataToSend)
                    break
    
    factory = ...
    r = asyncGatewayCalls(factory)
    r.run()
    
    reactor.listenTCP(7080, factory)
    reactor.run()
    

    这为您提供了一个单线程解决方案,该解决方案的行为应该与您对原始 asyncGatewayCalls 类的预期行为相同。但是,它不是在线程中的循环中休眠以调度调用,而是使用反应器的调度 API(通过更高级别的 LoopingCall 类,它调度要重复调用的事物)来确保每次调用 _pokeMicro十秒。

    【讨论】:

    • 是的,你是对的,几小时前,我在阅读线程 API 文档 (task.LoopingCall) 后得出了同样的结论。我测试过,效果很好。感谢您的帮助
    • jsonrpc 'id' 字段不适合确定哪个响应是针对哪个请求的吗?
    • JSON-RPC 2.0 消息可以清楚地区分。通知 - 没有 id 字段的请求,响应也不相同(结果和错误)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    • 2012-06-04
    • 2023-03-23
    相关资源
    最近更新 更多