如何在同一解释器中将 python cmd 的输入发送到高速公路 websocket 客户端 运行?

How do I send input from python cmd to autobahn websocket client running in the same interpreter?

我正在尝试通过 python 的 cmd 库从交互式提示中获取输入并将输入传递给 autobahn websocket client to send up to a websocket server. The cmd loop and the autobahn websocket client loop are running in the same interpreter. I'm trying to use crochet 以使其工作。 websocket 客户端成功连接到服务器,但是当我在 cmd 提示符下输入一些内容来调用 sendMessage 时,我得到了此 post 底部显示的异常。任何关于我可能搞砸的地方的指导将不胜感激。如果有更好的方法来完成我想做的事情,我会洗耳恭听。

这些是相关的导入和设置:

from cmd import Cmd
from crochet import setup, run_in_reactor, wait_for, retrieve_result, TimeoutError

# Setup crochet before importing twisted
setup()

from twisted.internet import reactor, ssl
from twisted.python import log
from autobahn.twisted.websocket import WebSocketClientFactory, \
    WebSocketClientProtocol, \
    connectWS

这是websocket客户端协议class:

class MyClientProtocol(WebSocketClientProtocol):

    def __init__(self, *args, **kwargs):
        super(MyClientProtocol, self).__init__(*args, **kwargs)

    def onConnect(self, response):
        print("Connected")

    def onMessage(self, payload, isBinary):
        if not isBinary:
            print('Message received: {}'.format(payload.decode('utf8')))

    def sendTask(self, payload):
        payload = json.dumps(payload, ensure_ascii = False).encode('utf8')
        self.sendMessage(payload)

这是 websocket 客户端工厂 class:

class MyClientFactory(WebSocketClientFactory):

    def __init__(self, *args, **kwargs):
        super(MyClientFactory, self).__init__(*args, **kwargs)

    def buildFactory(self, uri, headers):
        factory = WebSocketClientFactory(uri, headers=headers)
        factory.protocol = MyClientProtocol
        return factory

这是向 websocket 客户端发送输入的 cmd class:

class mycmd(Cmd):
    def do_send(self, inp):
        payload = {'task': inp}
        m = MyClientProtocol()
        reactor.callFromThread(m.sendTask, payload)

这就是我调用 websocket 客户端和 cmd 循环的方式:

if __name__ == '__main__':

    @run_in_reactor
    def start_connectWS():
        headers = {'header1': 'value1'}
        f = MyClientFactory()
        connectStatement = f.buildFactory(uri, headers)
        if connectStatement.isSecure:
            contextFactory = ssl.ClientContextFactory()
        else:
            contextFactory = None
        connectWS(connectStatement, contextFactory)

    start_connectWS()
    mycmd().cmdloop()

这是例外情况:

Unhandled Error
Traceback (most recent call last):
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/tomd/project/lib/python3.7/site-packages/crochet/_eventloop.py", line 412, in <lambda>
    target=lambda: self._reactor.run(installSignalHandlers=False),
  File "/Users/tomd/project/lib/python3.7/site-packages/twisted/internet/base.py", line 1283, in run
    self.mainLoop()
  File "/Users/tomd/project/lib/python3.7/site-packages/twisted/internet/base.py", line 1292, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/Users/tomd/project/lib/python3.7/site-packages/twisted/internet/base.py", line 886, in runUntilCurrent
    f(*a, **kw)
  File "./client.py", line 62, in sendTask
    self.sendMessage(payload)
  File "/Users/tomd/project/lib/python3.7/site-packages/autobahn/websocket/protocol.py", line 2215, in sendMessage
    if self.state != WebSocketProtocol.STATE_OPEN:
builtins.AttributeError: 'MyClientProtocol' object has no attribute 'state'

您的 copmmand class 创建了一个新的、未连接的协议实例,然后尝试像已连接一样使用它:

class mycmd(Cmd):
    def do_send(self, inp):
        payload = {'task': inp}
        m = MyClientProtocol()
        reactor.callFromThread(m.sendTask, payload)

具体来说,这会创建一个新的协议实例 class:

        m = MyClientProtocol()

这会尝试像连接一样使用它:

        reactor.callFromThread(m.sendTask, payload)

稍后您将获得实际将协议连接到某物的代码:

        connectWS(connectStatement, contextFactory)

但是此代码未以任何有用的方式连接到您的命令 class。

您需要使用调用 connectWS.

产生的连接,而不是创建新的 MyClientProtocol 实例

许多 种方法可以解决这个问题,但需要权衡取舍。一种恰好易于解释的方法是使用 websocket 代码和命令解释器代码之间共享的可变状态。

例如,MyClientProtocol.onConnect 可以将自己设置为工厂实例的属性,您的命令行代码可以接受工厂实例作为参数,然后从属性中读取连接的协议实例。

class MyClientProtocol(...):
    def onConnect(self, response):
        self.factory.connectedProtocol = self
    ...

class mycmd(Cmd):
    # ... __init__ that accepts factory and sets it on self

    def do_send(self, inp):
        payload = {'task': inp}
        m = self.factory.connectedProtocol
        if m is None:
            print("No connection")
        else:
            reactor.callFromThread(m.sendTask, payload)