public async Task SubscribeAsync(bool reConnectOnDisconnect) { var uri = ExchangeClientBase.IsSandbox ? WSS_SANDBOX_ENDPOINT_URL : WSS_ENDPOINT_URL; if (_authContainer != null) // authenticated feed { uri = new Uri(uri, "/users/self/verify"); } cancellationTokenSource = new CancellationTokenSource(); while (!cancellationTokenSource.IsCancellationRequested) { string disconnectReason = ""; try { webSocketClient = new ClientWebSocket(); await webSocketClient.ConnectAsync(uri, cancellationTokenSource.Token); if (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { await rateGateRealtime.WaitToProceedAsync(); // don't subscribe at too high of a rate await sendSubscriptionMsgAsync(Products : Products, gdax_Channel : gdax_Channel); // key is product name, value is whether connection was just opened if (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { // checking again bc maybe the server disconnected after the subscribe msg was sent // + move to processing subscriptions section below later foreach (var product in Products) { ConnectionOpened?.Invoke(product, gdax_Channel); } } while (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { using (var timeoutCTS = new CancellationTokenSource(6500)) // heartbeat every 1000 ms, so give it 5 hearbeat chances using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutCTS.Token, cancellationTokenSource.Token)) using (var stream = new MemoryStream(1024)) { var receiveBuffer = new ArraySegment <byte>(new byte[1024 * 8]); bool timedOut = false; WebSocketReceiveResult webSocketReceiveResult; do { try { webSocketReceiveResult = await webSocketClient.ReceiveAsync(receiveBuffer, linkedTokenSource.Token); } catch (OperationCanceledException) { timedOut = true; disconnectReason = " - stream timed out"; break; } await stream.WriteAsync(receiveBuffer.Array, receiveBuffer.Offset, webSocketReceiveResult.Count, cancellationTokenSource.Token); } while (!webSocketReceiveResult.EndOfMessage && !cancellationTokenSource.IsCancellationRequested); if (!timedOut && !cancellationTokenSource.IsCancellationRequested) { var message = stream.ToArray().Where(b => b != 0).ToArray(); var messageString = Encoding.ASCII.GetString(message, 0, message.Length); if (!String.IsNullOrEmpty(messageString)) { try { var jToken = JToken.Parse(messageString); var typeToken = jToken["type"]; if (typeToken == null) { RealtimeDataError?.Invoke(this, new RealtimeError("null typeToken: + " + Encoding.ASCII.GetString(message, 0, message.Length))); return; // go to next msg } var type = typeToken.Value <string>(); switch (type) { case "subscriptions": // + process initial subscription confirmation // + also for unsubscribe confirmation break; case "received": var rr = new RealtimeReceived(jToken); if (rr.Message != null) { RealtimeDataError?.Invoke(this, rr); } RealtimeReceived?.Invoke(this, rr); break; case "open": var ro = new RealtimeOpen(jToken); if (ro.Message != null) { RealtimeDataError?.Invoke(this, ro); } RealtimeOpen?.Invoke(this, ro); break; case "done": var rd = new RealtimeDone(jToken); if (rd.Message != null) { RealtimeDataError?.Invoke(this, rd); } RealtimeDone?.Invoke(this, rd); break; case "match": var rm = new RealtimeMatch(jToken); if (rm.Message != null) { RealtimeDataError?.Invoke(this, rm); } RealtimeMatch?.Invoke(this, rm); break; case "last_match": var rlm = new RealtimeMatch(jToken); if (rlm.Message != null) { RealtimeDataError?.Invoke(this, rlm); } RealtimeLastMatch?.Invoke(this, rlm); break; case "change": var rc = new RealtimeChange(jToken); if (rc.Message != null) { RealtimeDataError?.Invoke(this, rc); } RealtimeChange?.Invoke(this, rc); break; case "heartbeat": // + should implement this (checking LastTraderId) var hb = new Heartbeat(jToken); Heartbeat?.Invoke(this, hb); break; case "error": RealtimeDataError?.Invoke(this, new RealtimeError(jToken)); break; default: RealtimeDataError?.Invoke(this, new RealtimeError("Unexpected type: " + jToken)); break; } } catch (JsonReaderException e) { RealtimeDataError?.Invoke(this, new RealtimeError( "JsonReaderException: " + e.Message + ":" + messageString)); } } else { RealtimeDataError?.Invoke(this, new RealtimeError("empty message received. Connection state: " + webSocketClient.State + ", linkedToken: " + linkedTokenSource.Token.IsCancellationRequested)); } } } } } } catch (Exception e) { if (e.Message == "The remote party closed the WebSocket connection without completing the close handshake.") // System.Net.WebSockets.WebSocketException { disconnectReason = " - remote closed the WebSocket w/o completing the close handshake"; } else if (e.Message == "Unable to connect to the remote server") // System.Net.WebSockets.WebSocketException { disconnectReason = " - unable to connect to server"; // shorten it a bit await Task.Delay(10000); // if unable to connect, then wait 10 seconds before trying to connect again } else { RealtimeStreamError?.Invoke(this, new RealtimeError("other exception caught: " + e.GetType() + " : " + e.Message)); } } if (!reConnectOnDisconnect) { UnSubscribe(); } foreach (var product in Products) { RealtimeStreamError?.Invoke(this, new RealtimeError("disconnected" + disconnectReason)); ConnectionClosed?.Invoke(product, gdax_Channel); } if (!reConnectOnDisconnect) { break; } } }
private async void OnHeartbeat(object sender, Heartbeat heartbeat) { await checkSequenceNumber(heartbeat.Sequence); }