简体   繁体   English

仅在 Python asyncio 中处理完所有 websocket 消息后才返回

[英]Returning only once all websocket messages are processed in Python asyncio

I am working with a streaming API from Kucoin (crypto exchange) which sends order_book updates, and I am trying to process inbound order_book websocket messages using a message handler method and then only return once all messages are processed, but I think I am designing the pattern wrong or otherwise misunderstanding async & websockets.我正在使用来自 Kucoin(加密交换)的流 API,它发送order_book更新,我正在尝试使用消息处理程序方法处理入站order_book websocket 消息,然后仅在处理order_book所有消息后才返回,但我想我正在设计模式错误或以其他方式误解 async 和 websockets。

What I want is something like this - below code doesn't work, but for illustrative purposes it's what I'm trying to achieve, so please bear with my lizard brain on this.我想要的是这样的 - 下面的代码不起作用,但为了说明目的,这是我想要实现的,所以请忍受我的蜥蜴大脑。

async for message in websocket:
    message_handled = handler_method(message)  # this updates self.order_book
    if websocket.index(message) + 1 == len(websocket): # check if this is final message
        return self.order_book

What is the correct way to do this so all the message s are processed by handler_method() before returning?执行此操作的正确方法是什么,以便在返回之前通过handler_method()处理所有message

EDIT:编辑:

Please see example debug logs for the websocket.请参阅 websocket 的示例调试日志。

client - event = data_received(<1016 bytes>)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741271,"change":"2346.05,buy,120","timestamp":1627660760709},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741272,"change":"2345.1,buy,33","timestamp":1627660760710},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741273,"change":"2346.1,buy,3360","timestamp":1627660760710},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741274,"change":"2346.15,sell,0","timestamp":1627660760710},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741275,"change":"2346.85,sell,0","timestamp":1627660760710},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741276,"change":"2346.05,buy,540","timestamp":1627660760710},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client - event = data_received(<849 bytes>)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741277,"change":"2344.8,buy,2145","timestamp":1627660760724},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741278,"change":"2344.8,buy,1667","timestamp":1627660760724},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741279,"change":"2346.1,buy,3780","timestamp":1627660760724},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741280,"change":"2344.8,buy,1189","timestamp":1627660760724},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741281,"change":"2344.8,buy,711","timestamp":1627660760724},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client - event = data_received(<1690 bytes>)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741282,"change":"2346.05,buy,120","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741283,"change":"2338.1,buy,407","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741284,"change":"2360.25,sell,9681","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741285,"change":"2337.8,buy,20","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741286,"change":"2338.1,buy,6","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741287,"change":"2334.1,buy,0","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741288,"change":"2346.1,buy,3360","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741289,"change":"2346.2,buy,120","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741290,"change":"2346.1,buy,3780","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)
client < Frame(fin=True, opcode=1, data=b'{"data":{"sequence":1627402741291,"change":"2354.3,sell,0","timestamp":1627660760755},"subject":"level2","topic":"/contractMarket/level2:ETHUSDTM","type":"message"}', rsv1=False, rsv2=False, rsv3=False)```

It seems you are designing this thing, so let me give you some hints.看来你正在设计这个东西,所以让我给你一些提示。 First of all, it is typical in the networking to send one message in separate chunks.首先,在网络中通常以不同的块发送一条消息。 But in order for that to work correctly you need to do some additional stuff.但是为了让它正常工作,你需要做一些额外的事情。 The most importantly: you need boundaries, a way to communicate to your server the begining and end of this chunked message.最重要的是:您需要边界,这是一种向您的服务器传达此分块消息的开始和结束的方式。

For example you may assume that messages come in strict order: for a given data set X with chunks A_1,...,A_k you design your protocol to first send "new data set with k chunks" message and then each chunk A_i sequentially.例如,您可能假设消息以严格的顺序出现:对于具有块 A_1,...,A_k 的给定数据集 X,您将协议设计为首先发送“具有 k 个块的新数据集”消息,然后依次发送每个块 A_i。 That would be similar to HTTP and its "Content-Length:" header which is followed with content of concrete length.这类似于 HTTP 及其“Content-Length:”标头,后跟具体长度的内容。 A variant of that is that you send "new data set" message, then all A_i sequentially and "end of data set" message.一种变体是您发送“新数据集”消息,然后依次发送所有 A_i 和“数据集结束”消息。 This is how HTTP with "Transfer-Encoding: chunked" header works.这就是带有“Transfer-Encoding: chunked”标头的 HTTP 的工作方式。

An alternative is to send "new data set of Id T with k chunks" message and then add the id "T" to each chunk.另一种方法是发送“具有 k 个块的 Id T 的新数据集”消息,然后将 id“T”添加到每个块。 This allows you to process things asynchronously (for example interleave two data sets at the same time).这允许您异步处理事物(例如同时交错两个数据集)。 So it is obviously beneficial, but harder to implement correctly.所以这显然是有益的,但更难正确实施。

Once you design your protocol you have to implement your server side.一旦你设计了你的协议,你就必须实现你的服务器端。 Depending on the approach it will have a different implementation.根据方法的不同,它会有不同的实现。 For example for the sequential scenario this is quite easy.例如,对于顺序场景,这很容易。 Here's a pseudocode:这是一个伪代码:

while True:
    message = await websocket.receive()  # <-- assumed to be the initial "new data set" message

    # TODO: break if disconnected or invalid message

    total_chunks_length = get_chunks_length(message)
    total_processed_data = []
    while total_chunks_length > 0:
        message = await websocket.receive()
        processed_data = await handler_method(message)
        total_processed_data.append(processed_data)
        total_chunks_length -= 1

    await websocket.send(total_processed_data)

This becomes more difficult with the second, asynchronous design, because you have to keep track of ids.对于第二种异步设计,这变得更加困难,因为您必须跟踪 id。 I won't go into details here, hopefuly you can come up with a solution.此处不再赘述,希望你能提出解决方案。

Not sure if this will be helpful to anyone else in the future but I ended up solving this by calling websocket.recv() and then using a while loop to process the ws.messages queue of messages directly.不确定这是否会对将来的其他人有帮助,但我最终通过调用websocket.recv()解决了这个问题,然后使用 while 循环直接处理ws.messages消息队列。 See below example:见下面的例子:

message = json.loads(await ws.recv())
handler_method(message)

while len(ws.messages) > 0:
    message = json.loads(ws.messages.popleft())
    handler_method(message)

return something

What this does is: recv receives all the messages from the websocket connection, loads them up in the ws.messages queue (a collections.deque object), and calls ws.messages.popleft() , which yields an individual message.它的作用是: recv接收来自 websocket 连接的所有消息,将它们加载到ws.messages队列(一个collections.deque对象)中,并调用ws.messages.popleft() ,这会产生一条单独的消息。

Now that the ws.messages queue is filled (ie len > 0 if there was more than 1 message received), we want to simply call ws.messages.popleft() until there are no remaining messages, and finally return after the conclusion of the while loop.现在ws.messages队列已满(即 len > 0 如果收到的消息超过 1 条),我们只想调用ws.messages.popleft()直到没有剩余消息,最后在结束后return while 循环。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM