-
Notifications
You must be signed in to change notification settings - Fork 0
Adding re connection management #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,9 @@ namespace ReactiveXComponent.Connection | |
public interface IXCSession : IDisposable | ||
{ | ||
bool IsOpen { get; } | ||
event EventHandler SessionOpened; | ||
event EventHandler SessionClosed; | ||
event EventHandler<System.IO.ErrorEventArgs> ConnectionError; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the JS API we called it DisconnectionError because what we are handling here is the case of a disconnection after connection. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This event is raised when we encounter a problem while connecting. That's why it's called ConnectionError. You can refer to WebSocketClient.Init() method. |
||
IXCPublisher CreatePublisher(string component); | ||
IXCSubscriber CreateSubscriber(string component); | ||
List<string> GetXCApiList(string requestId = null, TimeSpan ? timeout = null); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,29 @@ | ||
using System; | ||
using System.Text.RegularExpressions; | ||
using System.Threading; | ||
using System.Timers; | ||
using ReactiveXComponent.Common; | ||
using SuperSocket.ClientEngine; | ||
using WebSocket4Net; | ||
|
||
namespace ReactiveXComponent.WebSocket | ||
{ | ||
public class WebSocketClient : IWebSocketClient | ||
internal class WebSocketClient : IWebSocketClient | ||
{ | ||
private WebSocket4Net.WebSocket _webSocket; | ||
private readonly object _webSocketLock = new object(); | ||
private AutoResetEvent _socketOpenEvent; | ||
private AutoResetEvent _socketCloseEvent; | ||
|
||
private WebSocketEndpoint _endpoint; | ||
private int _timeout; | ||
private TimeSpan _timeout; | ||
|
||
private System.Timers.Timer _reconnectionTimer; | ||
private int _maxRetries; | ||
private int _currentRetryCount; | ||
private TimeSpan _retryInterval; | ||
private bool _isClosing; | ||
private bool _isReconnecting; | ||
|
||
public event EventHandler<EventArgs> ConnectionOpened; | ||
public event EventHandler<EventArgs> ConnectionClosed; | ||
|
@@ -27,22 +35,8 @@ private void OpenConnection() | |
_socketOpenEvent = new AutoResetEvent(false); | ||
_socketCloseEvent = new AutoResetEvent(false); | ||
var serverUri = GetServerUri(); | ||
_webSocket = new WebSocket4Net.WebSocket(serverUri); | ||
|
||
_webSocket.Security.AllowUnstrustedCertificate = true; | ||
_webSocket.Security.AllowNameMismatchCertificate = true; | ||
|
||
_webSocket.Opened += WebSocketOnOpened; | ||
_webSocket.Closed += WebSocketOnClosed; | ||
_webSocket.Error += WebSocketOnError; | ||
InitWebSocket(serverUri); | ||
_webSocket.Open(); | ||
|
||
if (!_socketOpenEvent.WaitOne(_timeout)) | ||
{ | ||
throw new ReactiveXComponentException($"Could not connect to the web socket server {serverUri} after {_timeout} ms"); | ||
} | ||
|
||
_webSocket.MessageReceived += WebSocketOnMessageReceived; | ||
} | ||
|
||
private void CloseConnection() | ||
|
@@ -53,6 +47,7 @@ private void CloseConnection() | |
{ | ||
if (CanClose()) | ||
{ | ||
_isClosing = true; | ||
_webSocket.Close(); | ||
_socketCloseEvent.WaitOne(_timeout); | ||
} | ||
|
@@ -96,7 +91,15 @@ private void WebSocketOnClosed(object sender, EventArgs eventArgs) | |
{ | ||
_socketCloseEvent.Set(); | ||
|
||
ConnectionClosed?.Invoke(this, EventArgs.Empty); | ||
if (!_isClosing) | ||
{ | ||
TryReconnect(); | ||
} | ||
|
||
if (!_isReconnecting) | ||
{ | ||
ConnectionClosed?.Invoke(this, EventArgs.Empty); | ||
} | ||
} | ||
|
||
private void WebSocketOnError(object sender, ErrorEventArgs errorEventArgs) | ||
|
@@ -111,10 +114,50 @@ private void WebSocketOnMessageReceived(object sender, MessageReceivedEventArgs | |
|
||
public bool IsOpen { get { return _webSocket != null && _webSocket.State == WebSocketState.Open; } } | ||
|
||
public void Init(WebSocketEndpoint endpoint, int timeout) | ||
public void Init(WebSocketEndpoint endpoint, TimeSpan timeout, TimeSpan? retryInterval, int maxRetries) | ||
{ | ||
_endpoint = endpoint; | ||
_timeout = timeout; | ||
|
||
_maxRetries = maxRetries; | ||
_retryInterval = (retryInterval != null) ? retryInterval.Value : TimeSpan.FromSeconds(5); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be interesting to increment the _retryInterval depending on _currentRetryCount There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the JS API we decided to leave the reconnection strategy up to the user... what is the point of implementing that on the API side if it is probably application specific? |
||
_currentRetryCount = 0; | ||
|
||
_reconnectionTimer = new System.Timers.Timer(_retryInterval.TotalMilliseconds); | ||
|
||
_reconnectionTimer.Elapsed += (sender, args) => | ||
{ | ||
lock (_webSocketLock) | ||
{ | ||
if (!IsOpen && !_isClosing && _currentRetryCount < _maxRetries) | ||
{ | ||
_isReconnecting = true; | ||
DisposeWebSocket(); | ||
|
||
var serverUri = GetServerUri(); | ||
InitWebSocket(serverUri); | ||
|
||
_webSocket.Open(); | ||
|
||
if (_socketOpenEvent.WaitOne(_timeout)) | ||
{ | ||
_isReconnecting = false; | ||
_currentRetryCount = 0; | ||
_reconnectionTimer.Stop(); | ||
} | ||
else | ||
{ | ||
_currentRetryCount++; | ||
|
||
if (_currentRetryCount >= _maxRetries) | ||
{ | ||
var errorEvent = new System.IO.ErrorEventArgs(new ReactiveXComponentException($"Could not connect to the web socket server {serverUri} after {_timeout} ms")); | ||
ConnectionError?.Invoke(this, errorEvent); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
|
||
public void Open() | ||
|
@@ -132,6 +175,33 @@ public void Send(string data) | |
_webSocket?.Send(data); | ||
} | ||
|
||
private void InitWebSocket(string serverUri) | ||
{ | ||
_webSocket = new WebSocket4Net.WebSocket(serverUri); | ||
|
||
_webSocket.Security.AllowUnstrustedCertificate = true; | ||
_webSocket.Security.AllowNameMismatchCertificate = true; | ||
|
||
_webSocket.Opened += WebSocketOnOpened; | ||
_webSocket.Closed += WebSocketOnClosed; | ||
_webSocket.Error += WebSocketOnError; | ||
_webSocket.MessageReceived += WebSocketOnMessageReceived; | ||
} | ||
|
||
private void DisposeWebSocket() | ||
{ | ||
_webSocket.Opened -= WebSocketOnOpened; | ||
_webSocket.Closed -= WebSocketOnClosed; | ||
_webSocket.Error -= WebSocketOnError; | ||
_webSocket.MessageReceived -= WebSocketOnMessageReceived; | ||
_webSocket.Dispose(); | ||
} | ||
|
||
private void TryReconnect() | ||
{ | ||
_reconnectionTimer.Start(); | ||
} | ||
|
||
#region IDisposable implementation | ||
|
||
private bool _disposed; | ||
|
@@ -144,13 +214,13 @@ private void Dispose(bool disposing) | |
{ | ||
CloseConnection(); | ||
|
||
_webSocket.Opened -= WebSocketOnOpened; | ||
_webSocket.Closed -= WebSocketOnClosed; | ||
_webSocket.Error -= WebSocketOnError; | ||
_webSocket.MessageReceived -= WebSocketOnMessageReceived; | ||
DisposeWebSocket(); | ||
|
||
_socketOpenEvent.Dispose(); | ||
_socketCloseEvent.Dispose(); | ||
|
||
_isClosing = false; | ||
_reconnectionTimer.Dispose(); | ||
} | ||
|
||
// clear unmanaged resources | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not Action instead of EventHandler ? It seems that you don't use the sender and EventArgs are empty most of the time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll refactor this in the next version as I try to avoid any impact on the modules using this Api (like Client Api generated by the XCStudio).