API WebSocket v1 - desconexões inesperadas de feeds de dados de mercado

Última atualização: 1 de abr. de 2025

Nossa API WebSocket v1 tem como objetivo fornecer uma fonte persistente de nossos dados de mercado (tickers, livros de ofertas, tempo e vendas, etc.), mas às vezes as conexões WebSocket são encerradas inesperadamente pelo Cloudflare.

As desconexões iniciadas pelo Cloudflare geralmente resultam em erros de rede, como o seguinte erro 1006:

  • Erro ao ler do websocket: fechar 1006 (fechamento anormal): EOF inesperado

ou o seguinte, onde o Python lança uma exceção ao tentar ler uma nova mensagem de dados de mercado de uma conexão já fechada:

  • websocket._exceptions.WebSocketConnectionClosedException: o socket já está fechado

Embora não haja uma solução que evite esse tipo de desconexões inesperadas, existem algumas soluções alternativas muito eficazes. Caso você experimente desconexões frequentes do WebSocket, recomendamos implementar uma ou mais das seguintes:

Endereço IP alternativo

As precauções de segurança do Cloudflare são frequentemente aplicadas a endereços IP específicos, grupos de endereços IP semelhantes (como um bloco de classe C) ou certas localizações geográficas, portanto, alterar seu endereço IP é às vezes uma solução simples, mas eficaz.

Por exemplo, hospedar seu software de API em uma região AWS diferente ou usar uma VPN em uma localização geográfica diferente, alteraria significativamente o endereço IP local e potencialmente resolveria o problema de desconexão inesperada.

Lógica de reconexão

Usar uma única conexão WebSocket que é monitorada para quaisquer desconexões não intencionais e reconectada e reassinada conforme necessário é outra solução simples, mas eficaz. A seguir, um exemplo básico de código Python mostrando como reconectar e reassinar uma conexão WebSocket sempre que uma exceção WebSocketConnectionClosed for lançada:

#!/usr/bin/env python3 import sys from websocket import create_connection ws = create_connection('wss://ws.kraken.com/') print(ws.recv()) ws.send('{"event":"subscribe", "subscription":{"name":"ticker"}, "pair":["XBT/USD"]}') print(ws.recv()) for count in range(100): if count == 30 or count == 60: ws.close() try: print(ws.recv()) except: ws = create_connection('wss://ws.kraken.com/') print(ws.recv()) ws.send('{"event":"subscribe", "subscription":{"name":"ticker"}, "pair":["XBT/USD"]}') print(ws.recv()) ws.close() sys.exit(0)

Conforme mostrado na seguinte saída de exemplo, a conexão WebSocket e a assinatura são renovadas sempre que a conexão é encerrada inesperadamente (em 30 e 60 iterações do loop):

{"connectionID":8986307948074364287,"event":"systemStatus","status":"online","version":"1.0.1"} {"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}} [274,{"a":["9766.00000",16,"16.15314657"],"b":["9765.90000",16,"16.06793017"],"c":["9766.00000","0.15407543"],"v":["934.89117067","6431.35804307"],"p":["9793.82728","9736.03263"],"t":[3442,18738],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.80000"]},"ticker","XBT/USD"] {"event":"heartbeat"} ... [274,{"a":["9766.00000",15,"15.80052495"],"b":["9765.90000",15,"15.09399036"],"c":["9766.00000","0.18958527"],"v":["935.24379229","6430.73154248"],"p":["9793.81679","9736.04572"],"t":[3444,18738],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] [274,{"a":["9766.00000",15,"15.59644495"],"b":["9765.90000",15,"15.09399036"],"c":["9766.00000","0.20408000"],"v":["935.44787229","6430.93562248"],"p":["9793.81072","9736.04667"],"t":[3445,18739],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] {"event":"heartbeat"} [274,{"a":["9766.00000",11,"11.59644495"],"b":["9765.90000",20,"20.24943617"],"c":["9766.00000","4.00000000"],"v":["939.44787229","6434.93562248"],"p":["9793.69231","9736.06529"],"t":[3446,18740],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] {"connectionID":14767302623274336124,"event":"systemStatus","status":"online","version":"1.0.1"} {"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}} [274,{"a":["9766.00000",11,"11.59644495"],"b":["9765.90000",20,"20.24943617"],"c":["9766.00000","4.00000000"],"v":["939.44787229","6434.93562248"],"p":["9793.69231","9736.06529"],"t":[3446,18740],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] [274,{"a":["9766.00000",8,"8.49644495"],"b":["9765.90000",21,"21.50083276"],"c":["9766.00000","1.50355505"],"v":["942.54787229","6438.03562248"],"p":["9793.60123","9736.07971"],"t":[3448,18742],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] {"event":"heartbeat"} {"event":"heartbeat"} [274,{"a":["9766.00000",4,"4.19644495"],"b":["9765.90000",22,"22.67116661"],"c":["9766.00000","4.30000000"],"v":["946.84787229","6442.33562248"],"p":["9793.47588","9736.09968"],"t":[3449,18743],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] ... {"event":"heartbeat"} {"connectionID":11355575731659611126,"event":"systemStatus","status":"online","version":"1.0.1"} {"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}} [274,{"a":["9766.00000",4,"4.00000071"],"b":["9765.90000",21,"21.97845481"],"c":["9766.00000","0.19644424"],"v":["947.04431653","6442.53206672"],"p":["9793.47018","9736.10059"],"t":[3450,18744],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] [274,{"a":["9766.00000",3,"3.85443405"],"b":["9765.90000",19,"19.69673762"],"c":["9766.00000","0.14556666"],"v":["947.18988319","6442.67763338"],"p":["9793.46596","9736.10127"],"t":[3451,18745],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] {"event":"heartbeat"} {"event":"heartbeat"} [274,{"a":["9766.00000",3,"3.51109438"],"b":["9765.90000",19,"19.69673762"],"c":["9766.00000","0.16647855"],"v":["947.53322286","6443.02097305"],"p":["9793.45601","9736.10286"],"t":[3453,18747],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"] ...

Conexões redundantes

Usar múltiplas (duas ou mais) conexões WebSocket redundantes é geralmente a solução mais eficaz para desconexões inesperadas de WebSocket, porque permite que os feeds de dados de mercado continuem ininterruptos, independentemente da frequência com que as conexões subjacentes são encerradas.

O seguinte exemplo básico de código Python mostra como implementar múltiplas conexões WebSocket com failover automático:

#!/usr/bin/env python3 import sys from websocket import create_connection ws = [ None, None ] data = [ "", "" ] ws[0] = create_connection("wss://ws.kraken.com/") print("WebSocket (Primary): %s" % ws[0].recv()) ws[1] = create_connection("wss://ws.kraken.com/") print("WebSocket (Backup): %s" % ws[1].recv()) ws[0].send('{"event":"subscribe", "subscription":{"name":"spread"}, "pair":["XBT/USD"]}') print("WebSocket (Primary): %s" % ws[0].recv()) ws[1].send('{"event":"subscribe", "subscription":{"name":"spread"}, "pair":["XBT/USD"]}') print("WebSocket (Backup): %s" % ws[1].recv()) source = 0 for count in range(100): try: try: data[0] = ws[0].recv() except Exception: source = 1 try: data[1] = ws[1].recv() except Exception: source = 0 except KeyboardInterrupt: ws[0].close() print("WebSocket (%(source)s): %(data)s" % {"source":"Primary" if source == 0 else "Backup", "data":data[source]}) ws[source].close() sys.exit(1)

O código acima cria duas conexões WebSocket e assina ambas as conexões ao mesmo feed de dados de mercado. A conexão primária é usada para enviar os dados de mercado, até que uma interrupção de teclado seja gerada (via Ctrl+C, por exemplo), que fecha a conexão primária. Leituras subsequentes da conexão primária lançam uma exceção, que é capturada e usada para alterar a fonte de dados de mercado para a conexão de backup, que é então usada para enviar os dados de mercado.

A seguinte saída de exemplo mostra como a fonte de dados de mercado muda da conexão primária para a conexão de backup sem interromper o feed de dados de mercado:

WebSocket (Primary): {"connectionID":6894434610526943167,"event":"systemStatus","status":"online","version":"1.0.1"} WebSocket (Backup): {"connectionID":11520162761468018366,"event":"systemStatus","status":"online","version":"1.0.1"} WebSocket (Primary): {"channelID":275,"channelName":"spread","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"spread"}} WebSocket (Backup): {"channelID":275,"channelName":"spread","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"spread"}} WebSocket (Primary): [275,["9674.90000","9675.00000","1591440118.120752","10.88476562","3.47935600"],"spread","XBT/USD"] WebSocket (Primary): {"event":"heartbeat"} WebSocket (Primary): {"event":"heartbeat"} WebSocket (Primary): [275,["9674.90000","9675.00000","1591440127.108830","10.98812533","3.47935600"],"spread","XBT/USD"] WebSocket (Primary): {"event":"heartbeat"} WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.406073","1.87412533","7.47935600"],"spread","XBT/USD"] WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.505372","0.77412533","7.47935600"],"spread","XBT/USD"] WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.572658","0.10335971","7.47935600"],"spread","XBT/USD"] WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.572658","0.10335971","7.47935600"],"spread","XBT/USD"] ^C WebSocket (Backup): [275,["9674.90000","9675.00000","1591440130.156840","0.10335971","8.27935600"],"spread","XBT/USD"] WebSocket (Backup): [275,["9674.90000","9675.00000","1591440130.195899","0.10335971","8.47435600"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.80000","9675.00000","1591440130.459388","0.10335971","8.47435600"],"spread","XBT/USD"] WebSocket (Backup): [275,["9672.60000","9675.00000","1591440130.195899","0.00516924","8.47435600"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.80000","9675.00000","1591440130.764856","0.00400000","8.47435600"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.80000","9674.90000","1591440130.780514","0.00400000","0.25000000"],"spread","XBT/USD"] WebSocket (Backup): {"event":"heartbeat"} WebSocket (Backup): [275,["9673.80000","9674.90000","1591440132.433940","4.00400000","0.25000000"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.80000","9674.90000","1591440132.519509","4.00000000","0.25000000"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.90000","9674.90000","1591440132.527887","0.10336612","0.25000000"],"spread","XBT/USD"] WebSocket (Backup): [275,["9673.90000","9675.00000","1591440132.527887","0.10336612","4.27935600"],"spread","XBT/USD"]

Observe que as conexões WebSocket redundantes são a mais complexa das três soluções, mas é a solução ideal em termos de confiabilidade.

Precisa de mais ajuda?