Skip to content

Commit c0472bb

Browse files
authored
v4.0.0rc1
* v4.0.0rc1 Release
1 parent d23c9b9 commit c0472bb

40 files changed

+555
-520
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 4.0.0rc1 - 2023-05-18
4+
5+
### Changed
6+
- Redesign of Websocket part. Please consult `README.md` for details on its new usage.
7+
38
## 3.3.1 - 2023-03-21
49

510
### Updated

README.md

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -176,36 +176,66 @@ There are 2 types of error returned from the library:
176176

177177
## Websocket
178178

179+
### Connector v4
180+
181+
WebSocket can be established through the following connections:
182+
- USD-M WebSocket Stream (`https://binance-docs.github.io/apidocs/futures/en/#websocket-market-streams`)
183+
- COIN-M WebSocket Stream (`https://binance-docs.github.io/apidocs/delivery/en/#websocket-market-streams`)
184+
179185
```python
186+
# WebSocket Stream Client
180187
import time
181-
from binance.websocket.cm_futures.websocket_client import CMFuturesWebsocketClient
188+
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
182189

183-
def message_handler(message):
184-
print(message)
190+
def message_handler(_, message):
191+
logging.info(message)
185192

186-
ws_client = CMFuturesWebsocketClient()
187-
ws_client.start()
193+
my_client = UMFuturesWebsocketClient(on_message=message_handler)
188194

189-
ws_client.mini_ticker(
190-
symbol='bnbusdt',
191-
id=1,
192-
callback=message_handler,
193-
)
195+
# Subscribe to a single symbol stream
196+
my_client.agg_trade(symbol="bnbusdt")
197+
time.sleep(5)
198+
logging.info("closing ws connection")
199+
my_client.stop()
200+
```
194201

195-
# Combine selected streams
196-
ws_client.instant_subscribe(
197-
stream=['bnbusdt@bookTicker', 'ethusdt@bookTicker'],
198-
callback=message_handler,
199-
)
202+
#### Request Id
200203

201-
time.sleep(10)
204+
Client can assign a request id to each request. The request id will be returned in the response message. Not mandatory in the library, it generates a uuid format string if not provided.
202205

203-
print("closing ws connection")
204-
ws_client.stop()
206+
```python
207+
# id provided by client
208+
my_client.agg_trade(symbol="bnbusdt", id="my_request_id")
205209

210+
# library will generate a random uuid string
211+
my_client.agg_trade(symbol="bnbusdt")
206212
```
213+
214+
#### Combined Streams
215+
- If you set `is_combined` to `True`, `"/stream/"` will be appended to the `baseURL` to allow for Combining streams.
216+
- `is_combined` defaults to `False` and `"/ws/"` (raw streams) will be appended to the `baseURL`.
217+
207218
More websocket examples are available in the `examples` folder
208219

220+
## Websocket < v4
221+
222+
```python
223+
import time
224+
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
225+
226+
def message_handler(message):
227+
print(message)
228+
229+
my_client = UMFuturesWebsocketClient(on_message=message_handler)
230+
231+
# Subscribe to a single symbol stream
232+
my_client.agg_trade(symbol="bnbusdt")
233+
time.sleep(5)
234+
print("closing ws connection")
235+
my_client.stop()
236+
237+
```
238+
209239
### Heartbeat
210240

211241
Once connected, the websocket server sends a ping frame every 3 minutes and requires a response pong frame back within

binance/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.3.1"
1+
__version__ = "4.0.0rc1"

binance/websocket/binance_client_factory.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

binance/websocket/binance_client_protocol.py

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,104 @@
1-
import json
21
import logging
32
import threading
4-
from urllib.parse import urlparse
5-
from twisted.internet import reactor, ssl
6-
from twisted.internet.error import ReactorAlreadyRunning
7-
from autobahn.twisted.websocket import WebSocketClientFactory, connectWS
8-
from binance.websocket.binance_client_protocol import BinanceClientProtocol
9-
from binance.websocket.binance_client_factory import BinanceClientFactory
3+
from websocket import (
4+
ABNF,
5+
create_connection,
6+
WebSocketException,
7+
WebSocketConnectionClosedException,
8+
)
109

1110

1211
class BinanceSocketManager(threading.Thread):
13-
def __init__(self, stream_url):
12+
def __init__(
13+
self,
14+
stream_url,
15+
on_message=None,
16+
on_open=None,
17+
on_close=None,
18+
on_error=None,
19+
on_ping=None,
20+
on_pong=None,
21+
logger=None,
22+
):
1423
threading.Thread.__init__(self)
15-
16-
self.factories = {}
17-
self._connected_event = threading.Event()
24+
if not logger:
25+
logger = logging.getLogger(__name__)
26+
self.logger = logger
1827
self.stream_url = stream_url
19-
self._conns = {}
20-
self._user_callback = None
21-
22-
def _start_socket(
23-
self, stream_name, payload, callback, is_combined=False, is_live=True
24-
):
25-
if stream_name in self._conns:
26-
return False
27-
28-
if is_combined:
29-
factory_url = self.stream_url + "/stream"
30-
else:
31-
factory_url = self.stream_url + "/ws"
32-
33-
if not is_live:
34-
payload_obj = json.loads(payload.decode("utf8"))
35-
36-
if is_combined:
37-
factory_url = factory_url + "?streams=" + payload_obj["params"]
38-
else:
39-
factory_url = factory_url + "/" + payload_obj["params"]
40-
payload = None
41-
42-
logging.info("Connection with URL: {}".format(factory_url))
28+
self.on_message = on_message
29+
self.on_open = on_open
30+
self.on_close = on_close
31+
self.on_ping = on_ping
32+
self.on_pong = on_pong
33+
self.on_error = on_error
34+
self.create_ws_connection()
4335

44-
factory = BinanceClientFactory(factory_url, payload=payload)
45-
factory.base_client = self
46-
factory.protocol = BinanceClientProtocol
47-
factory.setProtocolOptions(
48-
openHandshakeTimeout=5, autoPingInterval=300, autoPingTimeout=5
36+
def create_ws_connection(self):
37+
self.logger.debug(
38+
"Creating connection with WebSocket Server: %s", self.stream_url
4939
)
50-
factory.callback = callback
51-
self.factories[stream_name] = factory
52-
reactor.callFromThread(self.add_connection, stream_name, self.stream_url)
40+
self.ws = create_connection(self.stream_url)
41+
self.logger.debug(
42+
"WebSocket connection has been established: %s", self.stream_url
43+
)
44+
self._callback(self.on_open)
5345

54-
def add_connection(self, stream_name, url):
55-
if not url.startswith("wss://"):
56-
raise ValueError("expected wss:// URL prefix")
46+
def run(self):
47+
self.read_data()
5748

58-
factory = self.factories[stream_name]
59-
options = ssl.optionsForClientTLS(hostname=urlparse(url).hostname)
60-
self._conns[stream_name] = connectWS(factory, options)
49+
def send_message(self, message):
50+
self.logger.debug("Sending message to Binance WebSocket Server: %s", message)
51+
self.ws.send(message)
6152

62-
def stop_socket(self, conn_key):
63-
if conn_key not in self._conns:
64-
return
53+
def ping(self):
54+
self.ws.ping()
6555

66-
# disable reconnecting if we are closing
67-
self._conns[conn_key].factory = WebSocketClientFactory(self.stream_url)
68-
self._conns[conn_key].disconnect()
69-
del self._conns[conn_key]
56+
def read_data(self):
57+
data = ""
58+
while True:
59+
try:
60+
op_code, frame = self.ws.recv_data_frame(True)
61+
except WebSocketException as e:
62+
if isinstance(e, WebSocketConnectionClosedException):
63+
self.logger.error("Lost websocket connection")
64+
else:
65+
self.logger.error("Websocket exception: {}".format(e))
66+
raise e
67+
except Exception as e:
68+
self.logger.error("Exception in read_data: {}".format(e))
69+
raise e
7070

71-
def run(self):
72-
try:
73-
reactor.run(installSignalHandlers=False)
74-
except ReactorAlreadyRunning:
75-
# Ignore error about reactor already running
76-
pass
71+
if op_code == ABNF.OPCODE_CLOSE:
72+
self.logger.warning(
73+
"CLOSE frame received, closing websocket connection"
74+
)
75+
self._callback(self.on_close)
76+
break
77+
elif op_code == ABNF.OPCODE_PING:
78+
self._callback(self.on_ping, frame.data)
79+
self.ws.pong("")
80+
self.logger.debug("Received Ping; PONG frame sent back")
81+
elif op_code == ABNF.OPCODE_PONG:
82+
self.logger.debug("Received PONG frame")
83+
self._callback(self.on_pong)
84+
else:
85+
data = frame.data
86+
if op_code == ABNF.OPCODE_TEXT:
87+
data = data.decode("utf-8")
88+
self._callback(self.on_message, data)
7789

7890
def close(self):
79-
keys = set(self._conns.keys())
80-
for key in keys:
81-
self.stop_socket(key)
82-
self._conns = {}
91+
if not self.ws.connected:
92+
self.logger.warn("Websocket already closed")
93+
else:
94+
self.ws.send_close()
95+
return
96+
97+
def _callback(self, callback, *args):
98+
if callback:
99+
try:
100+
callback(self, *args)
101+
except Exception as e:
102+
self.logger.error("Error from callback {}: {}".format(callback, e))
103+
if self.on_error:
104+
self.on_error(self, e)

0 commit comments

Comments
 (0)