通过同一websocket连接发送和接收帧而不会阻塞

时间:2018-10-04 17:53:45

标签: python websocket python-3.6 python-asyncio

很抱歉,很长的帖子,但是我已经为此花了一个多星期,所以我尝试了很多不同的东西。我对Python非常了解,但是我对Python的异步或非阻塞函数没有任何经验。

我正在为需要Websocket连接的Web服务编写API库/模块/程序包/其他内容。有许多要处理的传入消息,并且有时需要发送一些与控制相关的(Web应用程序级别,而不是Websocket控制消息)。我可以轻松地通过连接接收消息并对其执行操作。我可以发送消息,但是只能响应收到的消息,因为接收循环始终阻止等待消息。我不想等待传入的消息处理传出的消息,因此该脚本不必在输入新消息之前就挂在输入上。在努力使双向通信正常工作的过程中,我发现我需要使用Twisted,Tornado或asyncio之类的东西,但到目前为止,我尝试过的每个实现都失败了。请注意,发送必须在同一连接上进行。在接收循环之外打开一个短暂的连接将不起作用。到目前为止,这是我所做的:


websocket代码的第一次迭代是使用websocket-client包。这与文档中的示例非常接近:

import websocket
try:
    import thread
except ImportError:
    import _thread as thread
import time

def on_message(ws, message):
    # Send message frames to respective functions
    # for sorting, objectification, and processing

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")

def on_open(ws):
    def run(*args):
        # Send initial frames required for server to send the desired frames
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp(buildWebsocketURL()),
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

这将阻止循环外的任何进一步执行。我尝试学习_thread模块,但找不到任何迹象表明我可以从外部与websocket线程“通信”。我尝试设置一个pub / sub侦听器函数,该函数会将数据从另一个发送器函数转发到ws.send(),但没有用。没有错误或任何东西,只是没有任何已发送消息的指示。


接下来,我尝试了Websockets模块。这似乎是从头开始构建的,以利用异步。再次,我得到了一个客户端构建,该客户端构建将发送初始消息并对接收到的消息进行操作,但是进度在那里停止:

async def wsconnection():
        async with websockets.connect(getWebsocketURL()) as websocket:
            while True:
                message = await websocket.recv()
                if message == '{"type":"broadcaster.ready"}':
                    subscriptions = getSubscriptions()  # Get subscriptions from ident data
                    logging.info('Sending bookmarks to server as subscription keys')
                    subscriptionupdate = '{{"type": "subscribe","subscription_keys": ["{0}"],"subscription_scope": "update"}}'.format(
                        '","'.join(subscriptions))
                    subscriptioncontent = '{{"subscription_keys": ["{0}"],"subscription_scope": "content","type": "subscribe"}}'.format(
                        '","'.join(subscriptions))
                    logging.debug(subscriptioncontent)
                    await websocket.send(subscriptionupdate)
                    await websocket.send(subscriptioncontent)
                    await websocket.send(
                        '{"type":"message_lobby.read","lobby_id":"1","message_id:"16256829"}')
                sortframe(message) 

asyncio.get_event_loop().run_until_complete(wsconnection())

我尝试了上述发布/订阅侦听器在这里的应用,但没有成功。在更彻底地阅读了该模块的文档之后,我尝试将websocket协议对象(包含send()和recv()方法)移出循环,然后创建两个协程(?),一个侦听传入的消息,另一个侦听并发送外发消息。到目前为止,如果在wsconnection()函数范围内不运行async with websockets.connect(getWebsocketURL()) as websocket:行,我将完全无法获取websocket协议对象。我尝试使用websocket = websockets.client.connect(),根据我认为的docs,它可以设置我需要的协议对象,但不需要。我能找到的所有示例似乎都没有揭示任何明显的方式来构造Websocket发送方和接收方,而无需广泛了解asyncio。


我也使用asyncio和Twisted并使用了与以上类似的代码结构的autobahn,但我遇到了与上面相同的所有问题。

到目前为止,我最接近的是上面的Websockets软件包。这些文档有一个用于发送/接收连接的example snippet,但是我真的看不懂发生了什么,因为它们都是非常特定于asyncio的。总的来说,我真的很难绕过asyncio,我认为一个大问题是,它最近似乎发展非常迅速,因此围绕该冲突有大量非常特定于版本的信息。不幸的是,这不利于学习。 ~~~~这是我在该示例中尝试过的方法,它可以连接,接收初始消息,然后连接丢失/关闭:

async def producer(message):
    print('Sending message')

async def consumer_handler(websocket, path):
    while True:
        message = await websocket.recv()
        await print(message)
        await pub.sendMessage('sender', message)

async def producer_handler(websocket, path):
    while True:
        message = await producer()
        await websocket.send(message)

async def wsconnect():
        async with websockets.connect(getWebsocketURL()) as websocket:
            path = "443"
            async def handler(websocket, path):
                consumer_task = asyncio.ensure_future(
                    consumer_handler(websocket, path))
                producer_task = asyncio.ensure_future(
                    producer_handler(websocket, path))
                done, pending = await asyncio.wait(
                    [consumer_task, producer_task],
                    return_when=asyncio.FIRST_COMPLETED,
                )
                for task in pending:
                    task.cancel()

pub.subscribe(producer, 'sender')

asyncio.get_event_loop().run_until_complete(wsconnect())

那么我该如何构造此代码以通过同一websocket连接进行发送和接收?在打开websocket连接的同时,我还需要在同一脚本中进行各种API调用,这使事情变得更加复杂。

我正在使用Python 3.6.6,并且该脚本打算作为模块导入其他脚本中,因此需要将websocket功能包装在用于外部调用的函数或类中。

1 个答案:

答案 0 :(得分:0)

我和你的处境完全相同。我知道这是一个非常微妙的解决方案 因为它仍然不是全双工的,但是我似乎在互联网或stackoverflow上找不到涉及asyncio和我使用的websockets模块的任何示例。

我认为我不完全理解您的websockets示例(是服务器端还是客户端代码?),但我将解释我的情况和“解决方案”,也许对您也有用。

因此,我有一个服务器主要功能,该功能具有一个websocket,可使用recv()循环监听消息。当我发送“开始”消息时,它将启动一个函数,该函数每秒将数据发送到浏览器中的javascript客户端。但是,当函数正在发送数据时,有时我想暂停或停止来自客户端的数据流,并发送停止消息。问题是当数据发送已经开始时我使用recv()时,服务器停止发送数据,仅等待消息。我尝试了线程,多处理和其他一些东西,但是最终我想到了一个临时的解决方案,希望在客户端收到一段数据后立即向服务器发送“ pong”消息,以便服务器在下一次循环迭代或例如,如果“ pong”消息为“ stop”,则停止发送数据,但是是的,这不是真正的双工,而是快速的半双工...

我的python“服务器”上的代码

 async def start_server(self,websocket,webserver_path):
    self.websocket = websocket
    self.webserver_path = webserver_path
    while True:
        command = await self.websocket.recv()
        print("received command")
        if command == "start":
            await self.analyze()
        asyncio.sleep(1)

在我的分析功能中:

        for i,row in enumerate(data)
            await self.websocket.send(json.dumps(row))
            msg = await self.websocket.recv()
            if msg == "stop":
                self.stopFlag = True
                return
            await asyncio.sleep(1)

主要

start_server = websockets.serve(t.start_server, "127.0.0.1", 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

JavaScript客户端上的代码

var ws = new WebSocket("ws://127.0.0.1:5678/");
        ws.onmessage = function (event) {
            var datapoint = JSON.parse(event.data);
            console.log(counter);
            counter++;
            data.push(datapoint);
            if (data.length > 40){
                var element = data.shift();
                render(data);
            }
            ws.send("pong");//sending dummy message to let server continue
        };

我知道这不是解决方案,我希望其他人可以提供更好的解决方案,但是由于我有相同或非常相似的问题,并且没有其他答案,因此我决定发布,希望对您有所帮助。