diff options
Diffstat (limited to 'src')
78 files changed, 5931 insertions, 0 deletions
diff --git a/src/Scs.sln b/src/Scs.sln new file mode 100644 index 0000000..dcbc4c2 --- /dev/null +++ b/src/Scs.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scs", "Scs\Scs.csproj", "{0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Debug|x86.ActiveCfg = Debug|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Release|Any CPU.Build.0 = Release|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Scs/Changes.txt b/src/Scs/Changes.txt new file mode 100644 index 0000000..574ae60 --- /dev/null +++ b/src/Scs/Changes.txt @@ -0,0 +1,37 @@ +# Version 1.1.0.1 - 13.06.2011 - Halil ibrahim Kalkan + # Bugfix: + # Ping message must not be raised by messaging layer. + +# Version 1.1.0.0 - 28.05.2011 - Halil ibrahim Kalkan + # Additions: + # ClientDisconnected event added to IScsServiceApplication and IScsServer classes. + # MessageSent event is added to IMessenger interface. + # SynchronizedMessenger class is added to receive messages as synchronized. + # Changes/Improvements: + # Changed background thread mechanism to provide more scalable framework. + Used TPL Tasks and Async sockets instead of directly use of threads and blocking sockets (Added SequentialItemProcessor class). + # Added IScsWireProtocolFactory interface and changed IScsServer.WireProtocol to IScsServer.WireProtocolFactory. + Also, IScsWireProtocol is completely changed. + (This change is not backward compatible) + # BinarySerializationProtocol class is made public to allow user to override serialization methods. + # Codes completely revised, some parts refactored and commented. + # BugFix: + # Fixed a potential minor bug in Timer. + +# Version 1.0.2.0 - 11.05.2011 - Halil ibrahim Kalkan + Feature: + # Added RemoteEndPoint property to get address of client application in server side. + (Added to ICommunicationChannel, IScsServerClient and IScsServiceClient) + +# Version 1.0.1.0 - 10.04.2011 - Halil ibrahim Kalkan + Feature: + # Added ConnectTimeout property to IConnectableClient to provide a way of setting + timeout value while connecting to a server. + +# Version 1.0.0.1 - 10.04.2011 - Halil ibrahim Kalkan + BugFix: + # RequestReplyMessenger starts when a client is created. It must start when connected to server. + Otherwise, if user does not connect to client, a thread remains running. + +# Version 1.0.0.0 - 01.02.2011 - Halil ibrahim Kalkan + First stable release.
\ No newline at end of file diff --git a/src/Scs/Collections/ThreadSafeSortedList.cs b/src/Scs/Collections/ThreadSafeSortedList.cs new file mode 100644 index 0000000..10a980d --- /dev/null +++ b/src/Scs/Collections/ThreadSafeSortedList.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Hik.Collections +{ + /// <summary> + /// This class is used to store key-value based items in a thread safe manner. + /// It uses System.Collections.Generic.SortedList internally. + /// </summary> + /// <typeparam name="TK">Key type</typeparam> + /// <typeparam name="TV">Value type</typeparam> + public class ThreadSafeSortedList<TK, TV> + { + /// <summary> + /// Gets/adds/replaces an item by key. + /// </summary> + /// <param name="key">Key to get/set value</param> + /// <returns>Item associated with this key</returns> + public TV this[TK key] + { + get + { + _lock.EnterReadLock(); + try + { + return _items.ContainsKey(key) ? _items[key] : default(TV); + } + finally + { + _lock.ExitReadLock(); + } + } + + set + { + _lock.EnterWriteLock(); + try + { + _items[key] = value; + } + finally + { + _lock.ExitWriteLock(); + } + } + } + + /// <summary> + /// Gets count of items in the collection. + /// </summary> + public int Count + { + get + { + _lock.EnterReadLock(); + try + { + return _items.Count; + } + finally + { + _lock.ExitReadLock(); + } + } + } + + /// <summary> + /// Internal collection to store items. + /// </summary> + protected readonly SortedList<TK, TV> _items; + + /// <summary> + /// Used to synchronize access to _items list. + /// </summary> + protected readonly ReaderWriterLockSlim _lock; + + /// <summary> + /// Creates a new ThreadSafeSortedList object. + /// </summary> + public ThreadSafeSortedList() + { + _items = new SortedList<TK, TV>(); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } + + /// <summary> + /// Checks if collection contains spesified key. + /// </summary> + /// <param name="key">Key to check</param> + /// <returns>True; if collection contains given key</returns> + public bool ContainsKey(TK key) + { + _lock.EnterReadLock(); + try + { + return _items.ContainsKey(key); + } + finally + { + _lock.ExitReadLock(); + } + } + + /// <summary> + /// Checks if collection contains spesified item. + /// </summary> + /// <param name="item">Item to check</param> + /// <returns>True; if collection contains given item</returns> + public bool ContainsValue(TV item) + { + _lock.EnterReadLock(); + try + { + return _items.ContainsValue(item); + } + finally + { + _lock.ExitReadLock(); + } + } + + /// <summary> + /// Removes an item from collection. + /// </summary> + /// <param name="key">Key of item to remove</param> + public bool Remove(TK key) + { + _lock.EnterWriteLock(); + try + { + if (!_items.ContainsKey(key)) + { + return false; + } + + _items.Remove(key); + return true; + } + finally + { + _lock.ExitWriteLock(); + } + } + + /// <summary> + /// Gets all items in collection. + /// </summary> + /// <returns>Item list</returns> + public List<TV> GetAllItems() + { + _lock.EnterReadLock(); + try + { + return new List<TV>(_items.Values); + } + finally + { + _lock.ExitReadLock(); + } + } + + /// <summary> + /// Removes all items from list. + /// </summary> + public void ClearAll() + { + _lock.EnterWriteLock(); + try + { + _items.Clear(); + } + finally + { + _lock.ExitWriteLock(); + } + } + + /// <summary> + /// Gets then removes all items in collection. + /// </summary> + /// <returns>Item list</returns> + public List<TV> GetAndClearAllItems() + { + _lock.EnterWriteLock(); + try + { + var list = new List<TV>(_items.Values); + _items.Clear(); + return list; + } + finally + { + _lock.ExitWriteLock(); + } + } + } +} diff --git a/src/Scs/Communication/Scs/Client/ClientReConnecter.cs b/src/Scs/Communication/Scs/Client/ClientReConnecter.cs new file mode 100644 index 0000000..88f0159 --- /dev/null +++ b/src/Scs/Communication/Scs/Client/ClientReConnecter.cs @@ -0,0 +1,109 @@ +using System; +using Hik.Communication.Scs.Communication; +using Hik.Threading; + +namespace Hik.Communication.Scs.Client +{ + /// <summary> + /// This class is used to automatically re-connect to server if disconnected. + /// It attempts to reconnect to server periodically until connection established. + /// </summary> + public class ClientReConnecter : IDisposable + { + /// <summary> + /// Reconnect check period. + /// Default: 20 seconds. + /// </summary> + public int ReConnectCheckPeriod + { + get { return _reconnectTimer.Period; } + set { _reconnectTimer.Period = value; } + } + + /// <summary> + /// Reference to client object. + /// </summary> + private readonly IConnectableClient _client; + + /// <summary> + /// Timer to attempt ro reconnect periodically. + /// </summary> + private readonly Timer _reconnectTimer; + + /// <summary> + /// Indicates the dispose state of this object. + /// </summary> + private volatile bool _disposed; + + /// <summary> + /// Creates a new ClientReConnecter object. + /// It is not needed to start ClientReConnecter since it automatically + /// starts when the client disconnected. + /// </summary> + /// <param name="client">Reference to client object</param> + /// <exception cref="ArgumentNullException">Throws ArgumentNullException if client is null.</exception> + public ClientReConnecter(IConnectableClient client) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + + _client = client; + _client.Disconnected += Client_Disconnected; + _reconnectTimer = new Timer(20000); + _reconnectTimer.Elapsed += ReconnectTimer_Elapsed; + _reconnectTimer.Start(); + } + + /// <summary> + /// Disposes this object. + /// Does nothing if already disposed. + /// </summary> + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _client.Disconnected -= Client_Disconnected; + _reconnectTimer.Stop(); + } + + /// <summary> + /// Handles Disconnected event of _client object. + /// </summary> + /// <param name="sender">Source of the event</param> + /// <param name="e">Event arguments</param> + private void Client_Disconnected(object sender, EventArgs e) + { + _reconnectTimer.Start(); + } + + /// <summary> + /// Hadles Elapsed event of _reconnectTimer. + /// </summary> + /// <param name="sender">Source of the event</param> + /// <param name="e">Event arguments</param> + private void ReconnectTimer_Elapsed(object sender, EventArgs e) + { + if (_disposed || _client.CommunicationState == CommunicationStates.Connected) + { + _reconnectTimer.Stop(); + return; + } + + try + { + _client.Connect(); + _reconnectTimer.Stop(); + } + catch + { + //No need to catch since it will try to re-connect again + } + } + } +} diff --git a/src/Scs/Communication/Scs/Client/IConnectableClient.cs b/src/Scs/Communication/Scs/Client/IConnectableClient.cs new file mode 100644 index 0000000..9bbf7c0 --- /dev/null +++ b/src/Scs/Communication/Scs/Client/IConnectableClient.cs @@ -0,0 +1,43 @@ +using System; +using Hik.Communication.Scs.Communication; + +namespace Hik.Communication.Scs.Client +{ + /// <summary> + /// Represents a client for SCS servers. + /// </summary> + public interface IConnectableClient : IDisposable + { + /// <summary> + /// This event is raised when client connected to server. + /// </summary> + event EventHandler Connected; + + /// <summary> + /// This event is raised when client disconnected from server. + /// </summary> + event EventHandler Disconnected; + + /// <summary> + /// Timeout for connecting to a server (as milliseconds). + /// Default value: 15 seconds (15000 ms). + /// </summary> + int ConnectTimeout { get; set; } + + /// <summary> + /// Gets the current communication state. + /// </summary> + CommunicationStates CommunicationState { get; } + + /// <summary> + /// Connects to server. + /// </summary> + void Connect(); + + /// <summary> + /// Disconnects from server. + /// Does nothing if already disconnected. + /// </summary> + void Disconnect(); + } +} diff --git a/src/Scs/Communication/Scs/Client/IScsClient.cs b/src/Scs/Communication/Scs/Client/IScsClient.cs new file mode 100644 index 0000000..27cafe2 --- /dev/null +++ b/src/Scs/Communication/Scs/Client/IScsClient.cs @@ -0,0 +1,13 @@ +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messengers; + +namespace Hik.Communication.Scs.Client +{ + /// <summary> + /// Represents a client to connect to server. + /// </summary> + public interface IScsClient : IMessenger, IConnectableClient + { + //Does not define any additional member + } +} diff --git a/src/Scs/Communication/Scs/Client/ScsClientBase.cs b/src/Scs/Communication/Scs/Client/ScsClientBase.cs new file mode 100644 index 0000000..11cf673 --- /dev/null +++ b/src/Scs/Communication/Scs/Client/ScsClientBase.cs @@ -0,0 +1,330 @@ +using System; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Channels; +using Hik.Communication.Scs.Communication.Protocols; +using Hik.Threading; + +namespace Hik.Communication.Scs.Client +{ + /// <summary> + /// This class provides base functionality for client classes. + /// </summary> + internal abstract class ScsClientBase : IScsClient + { + #region Public events + + /// <summary> + /// This event is raised when a new message is received. + /// </summary> + public event EventHandler<MessageEventArgs> MessageReceived; + + /// <summary> + /// This event is raised when a new message is sent without any error. + /// It does not guaranties that message is properly handled and processed by remote application. + /// </summary> + public event EventHandler<MessageEventArgs> MessageSent; + + /// <summary> + /// This event is raised when communication channel closed. + /// </summary> + public event EventHandler Connected; + + /// <summary> + /// This event is raised when client disconnected from server. + /// </summary> + public event EventHandler Disconnected; + + #endregion + + #region Public properties + + /// <summary> + /// Timeout for connecting to a server (as milliseconds). + /// Default value: 15 seconds (15000 ms). + /// </summary> + public int ConnectTimeout { get; set; } + + /// <summary> + /// Gets/sets wire protocol that is used while reading and writing messages. + /// </summary> + public IScsWireProtocol WireProtocol + { + get { return _wireProtocol; } + set + { + if (CommunicationState == CommunicationStates.Connected) + { + throw new ApplicationException("Wire protocol can not be changed while connected to server."); + } + + _wireProtocol = value; + } + } + private IScsWireProtocol _wireProtocol; + + /// <summary> + /// Gets the communication state of the Client. + /// </summary> + public CommunicationStates CommunicationState + { + get + { + return _communicationChannel != null + ? _communicationChannel.CommunicationState + : CommunicationStates.Disconnected; + } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastReceivedMessageTime + { + get + { + return _communicationChannel != null + ? _communicationChannel.LastReceivedMessageTime + : DateTime.MinValue; + } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastSentMessageTime + { + get + { + return _communicationChannel != null + ? _communicationChannel.LastSentMessageTime + : DateTime.MinValue; + } + } + + #endregion + + #region Private fields + + /// <summary> + /// Default timeout value for connecting a server. + /// </summary> + private const int DefaultConnectionAttemptTimeout = 15000; //15 seconds. + + /// <summary> + /// The communication channel that is used by client to send and receive messages. + /// </summary> + private ICommunicationChannel _communicationChannel; + + /// <summary> + /// This timer is used to send PingMessage messages to server periodically. + /// </summary> + private readonly Timer _pingTimer; + + #endregion + + #region Constructor + + /// <summary> + /// Constructor. + /// </summary> + protected ScsClientBase() + { + _pingTimer = new Timer(30000); + _pingTimer.Elapsed += PingTimer_Elapsed; + ConnectTimeout = DefaultConnectionAttemptTimeout; + WireProtocol = WireProtocolManager.GetDefaultWireProtocol(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Connects to server. + /// </summary> + public void Connect() + { + WireProtocol.Reset(); + _communicationChannel = CreateCommunicationChannel(); + _communicationChannel.WireProtocol = WireProtocol; + _communicationChannel.Disconnected += CommunicationChannel_Disconnected; + _communicationChannel.MessageReceived += CommunicationChannel_MessageReceived; + _communicationChannel.MessageSent += CommunicationChannel_MessageSent; + _communicationChannel.Start(); + _pingTimer.Start(); + OnConnected(); + } + + /// <summary> + /// Disconnects from server. + /// Does nothing if already disconnected. + /// </summary> + public void Disconnect() + { + if (CommunicationState != CommunicationStates.Connected) + { + return; + } + + _communicationChannel.Disconnect(); + } + + /// <summary> + /// Disposes this object and closes underlying connection. + /// </summary> + public void Dispose() + { + Disconnect(); + } + + /// <summary> + /// Sends a message to the server. + /// </summary> + /// <param name="message">Message to be sent</param> + /// <exception cref="CommunicationStateException">Throws a CommunicationStateException if client is not connected to the server.</exception> + public void SendMessage(IScsMessage message) + { + if (CommunicationState != CommunicationStates.Connected) + { + throw new CommunicationStateException("Client is not connected to the server."); + } + + _communicationChannel.SendMessage(message); + } + + #endregion + + #region Abstract methods + + /// <summary> + /// This method is implemented by derived classes to create appropriate communication channel. + /// </summary> + /// <returns>Ready communication channel to communicate</returns> + protected abstract ICommunicationChannel CreateCommunicationChannel(); + + #endregion + + #region Private methods + + /// <summary> + /// Handles MessageReceived event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_MessageReceived(object sender, MessageEventArgs e) + { + if (e.Message is ScsPingMessage) + { + return; + } + + OnMessageReceived(e.Message); + } + + /// <summary> + /// Handles MessageSent event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_MessageSent(object sender, MessageEventArgs e) + { + OnMessageSent(e.Message); + } + + /// <summary> + /// Handles Disconnected event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_Disconnected(object sender, EventArgs e) + { + _pingTimer.Stop(); + OnDisconnected(); + } + + /// <summary> + /// Handles Elapsed event of _pingTimer to send PingMessage messages to server. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void PingTimer_Elapsed(object sender, EventArgs e) + { + if (CommunicationState != CommunicationStates.Connected) + { + return; + } + + try + { + var lastMinute = DateTime.Now.AddMinutes(-1); + if (_communicationChannel.LastReceivedMessageTime > lastMinute || _communicationChannel.LastSentMessageTime > lastMinute) + { + return; + } + + _communicationChannel.SendMessage(new ScsPingMessage()); + } + catch + { + + } + } + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises Connected event. + /// </summary> + protected virtual void OnConnected() + { + var handler = Connected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + /// <summary> + /// Raises Disconnected event. + /// </summary> + protected virtual void OnDisconnected() + { + var handler = Disconnected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + /// <summary> + /// Raises MessageReceived event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageReceived(IScsMessage message) + { + var handler = MessageReceived; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + /// <summary> + /// Raises MessageSent event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageSent(IScsMessage message) + { + var handler = MessageSent; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Client/ScsClientFactory.cs b/src/Scs/Communication/Scs/Client/ScsClientFactory.cs new file mode 100644 index 0000000..877e8dd --- /dev/null +++ b/src/Scs/Communication/Scs/Client/ScsClientFactory.cs @@ -0,0 +1,30 @@ +using Hik.Communication.Scs.Communication.EndPoints; + +namespace Hik.Communication.Scs.Client +{ + /// <summary> + /// This class is used to create SCS Clients to connect to a SCS server. + /// </summary> + public static class ScsClientFactory + { + /// <summary> + /// Creates a new client to connect to a server using an end point. + /// </summary> + /// <param name="endpoint">End point of the server to connect it</param> + /// <returns>Created TCP client</returns> + public static IScsClient CreateClient(ScsEndPoint endpoint) + { + return endpoint.CreateClient(); + } + + /// <summary> + /// Creates a new client to connect to a server using an end point. + /// </summary> + /// <param name="endpointAddress">End point address of the server to connect it</param> + /// <returns>Created TCP client</returns> + public static IScsClient CreateClient(string endpointAddress) + { + return CreateClient(ScsEndPoint.CreateEndPoint(endpointAddress)); + } + } +} diff --git a/src/Scs/Communication/Scs/Client/Tcp/ScsTcpClient.cs b/src/Scs/Communication/Scs/Client/Tcp/ScsTcpClient.cs new file mode 100644 index 0000000..20ee206 --- /dev/null +++ b/src/Scs/Communication/Scs/Client/Tcp/ScsTcpClient.cs @@ -0,0 +1,40 @@ +using Hik.Communication.Scs.Communication.Channels; +using Hik.Communication.Scs.Communication.Channels.Tcp; +using Hik.Communication.Scs.Communication.EndPoints.Tcp; +using System.Net; + +namespace Hik.Communication.Scs.Client.Tcp +{ + /// <summary> + /// This class is used to communicate with server over TCP/IP protocol. + /// </summary> + internal class ScsTcpClient : ScsClientBase + { + /// <summary> + /// The endpoint address of the server. + /// </summary> + private readonly ScsTcpEndPoint _serverEndPoint; + + /// <summary> + /// Creates a new ScsTcpClient object. + /// </summary> + /// <param name="serverEndPoint">The endpoint address to connect to the server</param> + public ScsTcpClient(ScsTcpEndPoint serverEndPoint) + { + _serverEndPoint = serverEndPoint; + } + + /// <summary> + /// Creates a communication channel using ServerIpAddress and ServerPort. + /// </summary> + /// <returns>Ready communication channel to communicate</returns> + protected override ICommunicationChannel CreateCommunicationChannel() + { + return new TcpCommunicationChannel( + TcpHelper.ConnectToServer( + new IPEndPoint(IPAddress.Parse(_serverEndPoint.IpAddress), _serverEndPoint.TcpPort), + ConnectTimeout + )); + } + } +} diff --git a/src/Scs/Communication/Scs/Client/Tcp/TcpHelper.cs b/src/Scs/Communication/Scs/Client/Tcp/TcpHelper.cs new file mode 100644 index 0000000..f0cd38b --- /dev/null +++ b/src/Scs/Communication/Scs/Client/Tcp/TcpHelper.cs @@ -0,0 +1,49 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Hik.Communication.Scs.Client.Tcp +{ + /// <summary> + /// This class is used to simplify TCP socket operations. + /// </summary> + internal static class TcpHelper + { + /// <summary> + /// This code is used to connect to a TCP socket with timeout option. + /// </summary> + /// <param name="endPoint">IP endpoint of remote server</param> + /// <param name="timeoutMs">Timeout to wait until connect</param> + /// <returns>Socket object connected to server</returns> + /// <exception cref="SocketException">Throws SocketException if can not connect.</exception> + /// <exception cref="TimeoutException">Throws TimeoutException if can not connect within specified timeoutMs</exception> + public static Socket ConnectToServer(EndPoint endPoint, int timeoutMs) + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + try + { + socket.Blocking = false; + socket.Connect(endPoint); + socket.Blocking = true; + return socket; + } + catch (SocketException socketException) + { + if (socketException.ErrorCode != 10035) + { + socket.Close(); + throw; + } + + if (!socket.Poll(timeoutMs * 1000, SelectMode.SelectWrite)) + { + socket.Close(); + throw new TimeoutException("The host failed to connect. Timeout occured."); + } + + socket.Blocking = true; + return socket; + } + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelBase.cs b/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelBase.cs new file mode 100644 index 0000000..506dce5 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelBase.cs @@ -0,0 +1,176 @@ +using System; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Protocols; + +namespace Hik.Communication.Scs.Communication.Channels +{ + /// <summary> + /// This class provides base functionality for all communication channel classes. + /// </summary> + internal abstract class CommunicationChannelBase : ICommunicationChannel + { + #region Public events + + /// <summary> + /// This event is raised when a new message is received. + /// </summary> + public event EventHandler<MessageEventArgs> MessageReceived; + + /// <summary> + /// This event is raised when a new message is sent without any error. + /// It does not guaranties that message is properly handled and processed by remote application. + /// </summary> + public event EventHandler<MessageEventArgs> MessageSent; + + /// <summary> + /// This event is raised when communication channel closed. + /// </summary> + public event EventHandler Disconnected; + + #endregion + + #region Public abstract properties + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + public abstract ScsEndPoint RemoteEndPoint { get; } + + #endregion + + #region Public properties + + /// <summary> + /// Gets the current communication state. + /// </summary> + public CommunicationStates CommunicationState { get; protected set; } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastReceivedMessageTime { get; protected set; } + + /// <summary> + /// Gets the time of the last succesfully sent message. + /// </summary> + public DateTime LastSentMessageTime { get; protected set; } + + /// <summary> + /// Gets/sets wire protocol that the channel uses. + /// This property must set before first communication. + /// </summary> + public IScsWireProtocol WireProtocol { get; set; } + + #endregion + + #region Constructor + + /// <summary> + /// Constructor. + /// </summary> + protected CommunicationChannelBase() + { + CommunicationState = CommunicationStates.Disconnected; + LastReceivedMessageTime = DateTime.MinValue; + LastSentMessageTime = DateTime.MinValue; + } + + #endregion + + #region Public abstract methods + + /// <summary> + /// Disconnects from remote application and closes this channel. + /// </summary> + public abstract void Disconnect(); + + #endregion + + #region Public methods + + /// <summary> + /// Starts the communication with remote application. + /// </summary> + public void Start() + { + StartInternal(); + CommunicationState = CommunicationStates.Connected; + } + + /// <summary> + /// Sends a message to the remote application. + /// </summary> + /// <param name="message">Message to be sent</param> + /// <exception cref="ArgumentNullException">Throws ArgumentNullException if message is null</exception> + public void SendMessage(IScsMessage message) + { + if (message == null) + { + throw new ArgumentNullException("message"); + } + + SendMessageInternal(message); + } + + #endregion + + #region Protected abstract methods + + /// <summary> + /// Starts the communication with remote application really. + /// </summary> + protected abstract void StartInternal(); + + /// <summary> + /// Sends a message to the remote application. + /// This method is overrided by derived classes to really send to message. + /// </summary> + /// <param name="message">Message to be sent</param> + protected abstract void SendMessageInternal(IScsMessage message); + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises Disconnected event. + /// </summary> + protected virtual void OnDisconnected() + { + var handler = Disconnected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + /// <summary> + /// Raises MessageReceived event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageReceived(IScsMessage message) + { + var handler = MessageReceived; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + /// <summary> + /// Raises MessageSent event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageSent(IScsMessage message) + { + var handler = MessageSent; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelEventArgs.cs b/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelEventArgs.cs new file mode 100644 index 0000000..e40849c --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Channels +{ + /// <summary> + /// Stores communication channel information to be used by an event. + /// </summary> + internal class CommunicationChannelEventArgs : EventArgs + { + /// <summary> + /// Communication channel that is associated with this event. + /// </summary> + public ICommunicationChannel Channel { get; private set; } + + /// <summary> + /// Creates a new CommunicationChannelEventArgs object. + /// </summary> + /// <param name="channel">Communication channel that is associated with this event</param> + public CommunicationChannelEventArgs(ICommunicationChannel channel) + { + Channel = channel; + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/ConnectionListenerBase.cs b/src/Scs/Communication/Scs/Communication/Channels/ConnectionListenerBase.cs new file mode 100644 index 0000000..f630ce4 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/ConnectionListenerBase.cs @@ -0,0 +1,38 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Channels +{ + /// <summary> + /// This class provides base functionality for communication listener classes. + /// </summary> + internal abstract class ConnectionListenerBase : IConnectionListener + { + /// <summary> + /// This event is raised when a new communication channel is connected. + /// </summary> + public event EventHandler<CommunicationChannelEventArgs> CommunicationChannelConnected; + + /// <summary> + /// Starts listening incoming connections. + /// </summary> + public abstract void Start(); + + /// <summary> + /// Stops listening incoming connections. + /// </summary> + public abstract void Stop(); + + /// <summary> + /// Raises CommunicationChannelConnected event. + /// </summary> + /// <param name="client"></param> + protected virtual void OnCommunicationChannelConnected(ICommunicationChannel client) + { + var handler = CommunicationChannelConnected; + if (handler != null) + { + handler(this, new CommunicationChannelEventArgs(client)); + } + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/ICommunicationChannel.cs b/src/Scs/Communication/Scs/Communication/Channels/ICommunicationChannel.cs new file mode 100644 index 0000000..cf9e89c --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/ICommunicationChannel.cs @@ -0,0 +1,38 @@ +using System; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.Messengers; + +namespace Hik.Communication.Scs.Communication.Channels +{ + /// <summary> + /// Represents a communication channel. + /// A communication channel is used to communicate (send/receive messages) with a remote application. + /// </summary> + internal interface ICommunicationChannel : IMessenger + { + /// <summary> + /// This event is raised when client disconnected from server. + /// </summary> + event EventHandler Disconnected; + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + ScsEndPoint RemoteEndPoint { get; } + + /// <summary> + /// Gets the current communication state. + /// </summary> + CommunicationStates CommunicationState { get; } + + /// <summary> + /// Starts the communication with remote application. + /// </summary> + void Start(); + + /// <summary> + /// Closes messenger. + /// </summary> + void Disconnect(); + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/IConnectionListener.cs b/src/Scs/Communication/Scs/Communication/Channels/IConnectionListener.cs new file mode 100644 index 0000000..2245941 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/IConnectionListener.cs @@ -0,0 +1,26 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Channels +{ + /// <summary> + /// Represents a communication listener. + /// A connection listener is used to accept incoming client connection requests. + /// </summary> + internal interface IConnectionListener + { + /// <summary> + /// This event is raised when a new communication channel connected. + /// </summary> + event EventHandler<CommunicationChannelEventArgs> CommunicationChannelConnected; + + /// <summary> + /// Starts listening incoming connections. + /// </summary> + void Start(); + + /// <summary> + /// Stops listening incoming connections. + /// </summary> + void Stop(); + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpCommunicationChannel.cs b/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpCommunicationChannel.cs new file mode 100644 index 0000000..0b3e310 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpCommunicationChannel.cs @@ -0,0 +1,210 @@ +using System; +using System.Net; +using System.Net.Sockets; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.EndPoints.Tcp; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.Scs.Communication.Channels.Tcp +{ + /// <summary> + /// This class is used to communicate with a remote application over TCP/IP protocol. + /// </summary> + internal class TcpCommunicationChannel : CommunicationChannelBase + { + #region Public properties + + ///<summary> + /// Gets the endpoint of remote application. + ///</summary> + public override ScsEndPoint RemoteEndPoint + { + get + { + return _remoteEndPoint; + } + } + private readonly ScsTcpEndPoint _remoteEndPoint; + + #endregion + + #region Private fields + + /// <summary> + /// Size of the buffer that is used to receive bytes from TCP socket. + /// </summary> + private const int ReceiveBufferSize = 4 * 1024; //4KB + + /// <summary> + /// This buffer is used to receive bytes + /// </summary> + private readonly byte[] _buffer; + + /// <summary> + /// Socket object to send/reveice messages. + /// </summary> + private readonly Socket _clientSocket; + + /// <summary> + /// A flag to control thread's running + /// </summary> + private volatile bool _running; + + /// <summary> + /// This object is just used for thread synchronizing (locking). + /// </summary> + private readonly object _syncLock; + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new TcpCommunicationChannel object. + /// </summary> + /// <param name="clientSocket">A connected Socket object that is + /// used to communicate over network</param> + public TcpCommunicationChannel(Socket clientSocket) + { + _clientSocket = clientSocket; + _clientSocket.NoDelay = true; + + var ipEndPoint = (IPEndPoint)_clientSocket.RemoteEndPoint; + _remoteEndPoint = new ScsTcpEndPoint(ipEndPoint.Address.ToString(), ipEndPoint.Port); + + _buffer = new byte[ReceiveBufferSize]; + _syncLock = new object(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Disconnects from remote application and closes channel. + /// </summary> + public override void Disconnect() + { + if (CommunicationState != CommunicationStates.Connected) + { + return; + } + + _running = false; + try + { + if (_clientSocket.Connected) + { + _clientSocket.Close(); + } + + _clientSocket.Dispose(); + } + catch + { + + } + + CommunicationState = CommunicationStates.Disconnected; + OnDisconnected(); + } + + #endregion + + #region Protected methods + + /// <summary> + /// Starts the thread to receive messages from socket. + /// </summary> + protected override void StartInternal() + { + _running = true; + _clientSocket.BeginReceive(_buffer, 0, _buffer.Length, 0, new AsyncCallback(ReceiveCallback), null); + } + + /// <summary> + /// Sends a message to the remote application. + /// </summary> + /// <param name="message">Message to be sent</param> + protected override void SendMessageInternal(IScsMessage message) + { + //Send message + var totalSent = 0; + lock (_syncLock) + { + //Create a byte array from message according to current protocol + var messageBytes = WireProtocol.GetBytes(message); + //Send all bytes to the remote application + while (totalSent < messageBytes.Length) + { + var sent = _clientSocket.Send(messageBytes, totalSent, messageBytes.Length - totalSent, SocketFlags.None); + if (sent <= 0) + { + throw new CommunicationException("Message could not be sent via TCP socket. Only " + totalSent + " bytes of " + messageBytes.Length + " bytes are sent."); + } + + totalSent += sent; + } + + LastSentMessageTime = DateTime.Now; + OnMessageSent(message); + } + } + + #endregion + + #region Private methods + + /// <summary> + /// This method is used as callback method in _clientSocket's BeginReceive method. + /// It reveives bytes from socker. + /// </summary> + /// <param name="ar">Asyncronous call result</param> + private void ReceiveCallback(IAsyncResult ar) + { + if(!_running) + { + return; + } + + try + { + //Get received bytes count + var bytesRead = _clientSocket.EndReceive(ar); + if (bytesRead > 0) + { + LastReceivedMessageTime = DateTime.Now; + + //Copy received bytes to a new byte array + var receivedBytes = new byte[bytesRead]; + Array.Copy(_buffer, 0, receivedBytes, 0, bytesRead); + + //Read messages according to current wire protocol + var messages = WireProtocol.CreateMessages(receivedBytes); + + //Raise MessageReceived event for all received messages + foreach (var message in messages) + { + OnMessageReceived(message); + } + } + else + { + throw new CommunicationException("Tcp socket is closed"); + } + + //Read more bytes if still running + if (_running) + { + _clientSocket.BeginReceive(_buffer, 0, _buffer.Length, 0, new AsyncCallback(ReceiveCallback), null); + } + } + catch + { + Disconnect(); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpConnectionListener.cs b/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpConnectionListener.cs new file mode 100644 index 0000000..773ff80 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpConnectionListener.cs @@ -0,0 +1,124 @@ +using System.Net.Sockets; +using System.Threading; +using Hik.Communication.Scs.Communication.EndPoints.Tcp; + +namespace Hik.Communication.Scs.Communication.Channels.Tcp +{ + /// <summary> + /// This class is used to listen and accept incoming TCP + /// connection requests on a TCP port. + /// </summary> + internal class TcpConnectionListener : ConnectionListenerBase + { + /// <summary> + /// The endpoint address of the server to listen incoming connections. + /// </summary> + private readonly ScsTcpEndPoint _endPoint; + + /// <summary> + /// Server socket to listen incoming connection requests. + /// </summary> + private TcpListener _listenerSocket; + + /// <summary> + /// The thread to listen socket + /// </summary> + private Thread _thread; + + /// <summary> + /// A flag to control thread's running + /// </summary> + private volatile bool _running; + + /// <summary> + /// Creates a new TcpConnectionListener for given endpoint. + /// </summary> + /// <param name="endPoint">The endpoint address of the server to listen incoming connections</param> + public TcpConnectionListener(ScsTcpEndPoint endPoint) + { + _endPoint = endPoint; + } + + /// <summary> + /// Starts listening incoming connections. + /// </summary> + public override void Start() + { + StartSocket(); + _running = true; + _thread = new Thread(DoListenAsThread); + _thread.Start(); + } + + /// <summary> + /// Stops listening incoming connections. + /// </summary> + public override void Stop() + { + _running = false; + StopSocket(); + } + + /// <summary> + /// Starts listening socket. + /// </summary> + private void StartSocket() + { + _listenerSocket = new TcpListener(System.Net.IPAddress.Any, _endPoint.TcpPort); + _listenerSocket.Start(); + } + + /// <summary> + /// Stops listening socket. + /// </summary> + private void StopSocket() + { + try + { + _listenerSocket.Stop(); + } + catch + { + + } + } + + /// <summary> + /// Entrance point of the thread. + /// This method is used by the thread to listen incoming requests. + /// </summary> + private void DoListenAsThread() + { + while (_running) + { + try + { + var clientSocket = _listenerSocket.AcceptSocket(); + if (clientSocket.Connected) + { + OnCommunicationChannelConnected(new TcpCommunicationChannel(clientSocket)); + } + } + catch + { + //Disconnect, wait for a while and connect again. + StopSocket(); + Thread.Sleep(1000); + if (!_running) + { + return; + } + + try + { + StartSocket(); + } + catch + { + + } + } + } + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/CommunicationException.cs b/src/Scs/Communication/Scs/Communication/CommunicationException.cs new file mode 100644 index 0000000..4a8c9c3 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/CommunicationException.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.Serialization; + +namespace Hik.Communication.Scs.Communication +{ + /// <summary> + /// This application is thrown in a communication error. + /// </summary> + [Serializable] + public class CommunicationException : Exception + { + /// <summary> + /// Contstructor. + /// </summary> + public CommunicationException() + { + + } + + /// <summary> + /// Contstructor for serializing. + /// </summary> + public CommunicationException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + public CommunicationException(string message) + : base(message) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + /// <param name="innerException">Inner exception</param> + public CommunicationException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/CommunicationStateException.cs b/src/Scs/Communication/Scs/Communication/CommunicationStateException.cs new file mode 100644 index 0000000..6501e6b --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/CommunicationStateException.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.Serialization; + +namespace Hik.Communication.Scs.Communication +{ + /// <summary> + /// This application is thrown if communication is not expected state. + /// </summary> + [Serializable] + public class CommunicationStateException : CommunicationException + { + /// <summary> + /// Contstructor. + /// </summary> + public CommunicationStateException() + { + + } + + /// <summary> + /// Contstructor for serializing. + /// </summary> + public CommunicationStateException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + public CommunicationStateException(string message) + : base(message) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + /// <param name="innerException">Inner exception</param> + public CommunicationStateException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/CommunicationStates.cs b/src/Scs/Communication/Scs/Communication/CommunicationStates.cs new file mode 100644 index 0000000..0e2da97 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/CommunicationStates.cs @@ -0,0 +1,18 @@ +namespace Hik.Communication.Scs.Communication +{ + /// <summary> + /// Communication states. + /// </summary> + public enum CommunicationStates + { + /// <summary> + /// Connected. + /// </summary> + Connected, + + /// <summary> + /// Disconnected. + /// </summary> + Disconnected + } +} diff --git a/src/Scs/Communication/Scs/Communication/EndPoints/ScsEndPoint.cs b/src/Scs/Communication/Scs/Communication/EndPoints/ScsEndPoint.cs new file mode 100644 index 0000000..f731a93 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/EndPoints/ScsEndPoint.cs @@ -0,0 +1,67 @@ +using System; +using Hik.Communication.Scs.Client; +using Hik.Communication.Scs.Communication.EndPoints.Tcp; +using Hik.Communication.Scs.Server; + +namespace Hik.Communication.Scs.Communication.EndPoints +{ + ///<summary> + /// Represents a server side end point in SCS. + ///</summary> + public abstract class ScsEndPoint + { + /// <summary> + /// Create a Scs End Point from a string. + /// Address must be formatted as: protocol://address + /// For example: tcp://89.43.104.179:10048 for a TCP endpoint with + /// IP 89.43.104.179 and port 10048. + /// </summary> + /// <param name="endPointAddress">Address to create endpoint</param> + /// <returns>Created end point</returns> + public static ScsEndPoint CreateEndPoint(string endPointAddress) + { + //Check if end point address is null + if (string.IsNullOrEmpty(endPointAddress)) + { + throw new ArgumentNullException("endPointAddress"); + } + + //If not protocol specified, assume TCP. + var endPointAddr = endPointAddress; + if (!endPointAddr.Contains("://")) + { + endPointAddr = "tcp://" + endPointAddr; + } + + //Split protocol and address parts + var splittedEndPoint = endPointAddr.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries); + if (splittedEndPoint.Length != 2) + { + throw new ApplicationException(endPointAddress + " is not a valid endpoint address."); + } + + //Split end point, find protocol and address + var protocol = splittedEndPoint[0].Trim().ToLower(); + var address = splittedEndPoint[1].Trim(); + switch (protocol) + { + case "tcp": + return new ScsTcpEndPoint(address); + default: + throw new ApplicationException("Unsupported protocol " + protocol + " in end point " + endPointAddress); + } + } + + /// <summary> + /// Creates a Scs Server that uses this end point to listen incoming connections. + /// </summary> + /// <returns>Scs Server</returns> + internal abstract IScsServer CreateServer(); + + /// <summary> + /// Creates a Scs Server that uses this end point to connect to server. + /// </summary> + /// <returns>Scs Client</returns> + internal abstract IScsClient CreateClient(); + } +}
\ No newline at end of file diff --git a/src/Scs/Communication/Scs/Communication/EndPoints/Tcp/ScsTcpEndPoint.cs b/src/Scs/Communication/Scs/Communication/EndPoints/Tcp/ScsTcpEndPoint.cs new file mode 100644 index 0000000..df1bca8 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/EndPoints/Tcp/ScsTcpEndPoint.cs @@ -0,0 +1,84 @@ +using System; +using Hik.Communication.Scs.Client; +using Hik.Communication.Scs.Client.Tcp; +using Hik.Communication.Scs.Server; +using Hik.Communication.Scs.Server.Tcp; + +namespace Hik.Communication.Scs.Communication.EndPoints.Tcp +{ + /// <summary> + /// Represens a TCP end point in SCS. + /// </summary> + public sealed class ScsTcpEndPoint : ScsEndPoint + { + ///<summary> + /// IP address of the server. + ///</summary> + public string IpAddress { get; set; } + + ///<summary> + /// Listening TCP Port for incoming connection requests on server. + ///</summary> + public int TcpPort { get; private set; } + + /// <summary> + /// Creates a new ScsTcpEndPoint object with specified port number. + /// </summary> + /// <param name="tcpPort">Listening TCP Port for incoming connection requests on server</param> + public ScsTcpEndPoint(int tcpPort) + { + TcpPort = tcpPort; + } + + /// <summary> + /// Creates a new ScsTcpEndPoint object with specified IP address and port number. + /// </summary> + /// <param name="ipAddress">IP address of the server</param> + /// <param name="port">Listening TCP Port for incoming connection requests on server</param> + public ScsTcpEndPoint(string ipAddress, int port) + { + IpAddress = ipAddress; + TcpPort = port; + } + + /// <summary> + /// Creates a new ScsTcpEndPoint from a string address. + /// Address format must be like IPAddress:Port (For example: 127.0.0.1:10085). + /// </summary> + /// <param name="address">TCP end point Address</param> + /// <returns>Created ScsTcpEndpoint object</returns> + public ScsTcpEndPoint(string address) + { + var splittedAddress = address.Trim().Split(':'); + IpAddress = splittedAddress[0].Trim(); + TcpPort = Convert.ToInt32(splittedAddress[1].Trim()); + } + + /// <summary> + /// Creates a Scs Server that uses this end point to listen incoming connections. + /// </summary> + /// <returns>Scs Server</returns> + internal override IScsServer CreateServer() + { + return new ScsTcpServer(this); + } + + /// <summary> + /// Creates a Scs Client that uses this end point to connect to server. + /// </summary> + /// <returns>Scs Client</returns> + internal override IScsClient CreateClient() + { + return new ScsTcpClient(this); + } + + /// <summary> + /// Generates a string representation of this end point object. + /// </summary> + /// <returns>String representation of this end point object</returns> + public override string ToString() + { + return string.IsNullOrEmpty(IpAddress) ? ("tcp://" + TcpPort) : ("tcp://" + IpAddress + ":" + TcpPort); + } + } +}
\ No newline at end of file diff --git a/src/Scs/Communication/Scs/Communication/Messages/IScsMessage.cs b/src/Scs/Communication/Scs/Communication/Messages/IScsMessage.cs new file mode 100644 index 0000000..5fd5ac3 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/IScsMessage.cs @@ -0,0 +1,18 @@ +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// Represents a message that is sent and received by server and client. + /// </summary> + public interface IScsMessage + { + /// <summary> + /// Unique identified for this message. + /// </summary> + string MessageId { get; } + + /// <summary> + /// Unique identified for this message. + /// </summary> + string RepliedMessageId { get; set; } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messages/MessageEventArgs.cs b/src/Scs/Communication/Scs/Communication/Messages/MessageEventArgs.cs new file mode 100644 index 0000000..dab8639 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/MessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// Stores message to be used by an event. + /// </summary> + public class MessageEventArgs : EventArgs + { + /// <summary> + /// Message object that is associated with this event. + /// </summary> + public IScsMessage Message { get; private set; } + + /// <summary> + /// Creates a new MessageEventArgs object. + /// </summary> + /// <param name="message">Message object that is associated with this event</param> + public MessageEventArgs(IScsMessage message) + { + Message = message; + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messages/PingMessage.cs b/src/Scs/Communication/Scs/Communication/Messages/PingMessage.cs new file mode 100644 index 0000000..6f153ba --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/PingMessage.cs @@ -0,0 +1,44 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// This message is used to send/receive ping messages. + /// Ping messages is used to keep connection alive between server and client. + /// </summary> + [Serializable] + public sealed class ScsPingMessage : ScsMessage + { + ///<summary> + /// Creates a new PingMessage object. + ///</summary> + public ScsPingMessage() + { + + } + + /// <summary> + /// Creates a new reply PingMessage object. + /// </summary> + /// <param name="repliedMessageId"> + /// Replied message id if this is a reply for + /// a message. + /// </param> + public ScsPingMessage(string repliedMessageId) + : this() + { + RepliedMessageId = repliedMessageId; + } + + /// <summary> + /// Creates a string to represents this object. + /// </summary> + /// <returns>A string to represents this object</returns> + public override string ToString() + { + return string.IsNullOrEmpty(RepliedMessageId) + ? string.Format("ScsPingMessage [{0}]", MessageId) + : string.Format("ScsPingMessage [{0}] Replied To [{1}]", MessageId, RepliedMessageId); + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messages/ScsMessage.cs b/src/Scs/Communication/Scs/Communication/Messages/ScsMessage.cs new file mode 100644 index 0000000..240f149 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/ScsMessage.cs @@ -0,0 +1,59 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// Represents a message that is sent and received by server and client. + /// This is the base class for all messages. + /// </summary> + [Serializable] + public class ScsMessage : IScsMessage + { + /// <summary> + /// Unique identified for this message. + /// Default value: New GUID. + /// Do not change if you do not want to do low level changes + /// such as custom wire protocols. + /// </summary> + public string MessageId { get; set; } + + /// <summary> + /// This property is used to indicate that this is + /// a Reply message to a message. + /// It may be null if this is not a reply message. + /// </summary> + public string RepliedMessageId { get; set; } + + /// <summary> + /// Creates a new ScsMessage. + /// </summary> + public ScsMessage() + { + MessageId = Guid.NewGuid().ToString(); + } + + /// <summary> + /// Creates a new reply ScsMessage. + /// </summary> + /// <param name="repliedMessageId"> + /// Replied message id if this is a reply for + /// a message. + /// </param> + public ScsMessage(string repliedMessageId) + : this() + { + RepliedMessageId = repliedMessageId; + } + + /// <summary> + /// Creates a string to represents this object. + /// </summary> + /// <returns>A string to represents this object</returns> + public override string ToString() + { + return string.IsNullOrEmpty(RepliedMessageId) + ? string.Format("ScsMessage [{0}]", MessageId) + : string.Format("ScsMessage [{0}] Replied To [{1}]", MessageId, RepliedMessageId); + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messages/ScsRawDataMessage.cs b/src/Scs/Communication/Scs/Communication/Messages/ScsRawDataMessage.cs new file mode 100644 index 0000000..0382c55 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/ScsRawDataMessage.cs @@ -0,0 +1,59 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// This message is used to send/receive a raw byte array as message data. + /// </summary> + [Serializable] + public class ScsRawDataMessage : ScsMessage + { + /// <summary> + /// Message data that is being transmitted. + /// </summary> + public byte[] MessageData { get; set; } + + /// <summary> + /// Default empty constructor. + /// </summary> + public ScsRawDataMessage() + { + + } + + /// <summary> + /// Creates a new ScsRawDataMessage object with MessageData property. + /// </summary> + /// <param name="messageData">Message data that is being transmitted</param> + public ScsRawDataMessage(byte[] messageData) + { + MessageData = messageData; + } + + /// <summary> + /// Creates a new reply ScsRawDataMessage object with MessageData property. + /// </summary> + /// <param name="messageData">Message data that is being transmitted</param> + /// <param name="repliedMessageId"> + /// Replied message id if this is a reply for + /// a message. + /// </param> + public ScsRawDataMessage(byte[] messageData, string repliedMessageId) + : this(messageData) + { + RepliedMessageId = repliedMessageId; + } + + /// <summary> + /// Creates a string to represents this object. + /// </summary> + /// <returns>A string to represents this object</returns> + public override string ToString() + { + var messageLength = MessageData == null ? 0 : MessageData.Length; + return string.IsNullOrEmpty(RepliedMessageId) + ? string.Format("ScsRawDataMessage [{0}]: {1} bytes", MessageId, messageLength) + : string.Format("ScsRawDataMessage [{0}] Replied To [{1}]: {2} bytes", MessageId, RepliedMessageId, messageLength); + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messages/ScsTextMessage.cs b/src/Scs/Communication/Scs/Communication/Messages/ScsTextMessage.cs new file mode 100644 index 0000000..b5665fa --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messages/ScsTextMessage.cs @@ -0,0 +1,58 @@ +using System; + +namespace Hik.Communication.Scs.Communication.Messages +{ + /// <summary> + /// This message is used to send/receive a text as message data. + /// </summary> + [Serializable] + public class ScsTextMessage : ScsMessage + { + /// <summary> + /// Message text that is being transmitted. + /// </summary> + public string Text { get; set; } + + /// <summary> + /// Creates a new ScsTextMessage object. + /// </summary> + public ScsTextMessage() + { + + } + + /// <summary> + /// Creates a new ScsTextMessage object with Text property. + /// </summary> + /// <param name="text">Message text that is being transmitted</param> + public ScsTextMessage(string text) + { + Text = text; + } + + /// <summary> + /// Creates a new reply ScsTextMessage object with Text property. + /// </summary> + /// <param name="text">Message text that is being transmitted</param> + /// <param name="repliedMessageId"> + /// Replied message id if this is a reply for + /// a message. + /// </param> + public ScsTextMessage(string text, string repliedMessageId) + : this(text) + { + RepliedMessageId = repliedMessageId; + } + + /// <summary> + /// Creates a string to represents this object. + /// </summary> + /// <returns>A string to represents this object</returns> + public override string ToString() + { + return string.IsNullOrEmpty(RepliedMessageId) + ? string.Format("ScsTextMessage [{0}]: {1}", MessageId, Text) + : string.Format("ScsTextMessage [{0}] Replied To [{1}]: {2}", MessageId, RepliedMessageId, Text); + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messengers/IMessenger.cs b/src/Scs/Communication/Scs/Communication/Messengers/IMessenger.cs new file mode 100644 index 0000000..176b479 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messengers/IMessenger.cs @@ -0,0 +1,44 @@ +using System; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Protocols; + +namespace Hik.Communication.Scs.Communication.Messengers +{ + /// <summary> + /// Represents an object that can send and receive messages. + /// </summary> + public interface IMessenger + { + /// <summary> + /// This event is raised when a new message is received. + /// </summary> + event EventHandler<MessageEventArgs> MessageReceived; + + /// <summary> + /// This event is raised when a new message is sent without any error. + /// It does not guaranties that message is properly handled and processed by remote application. + /// </summary> + event EventHandler<MessageEventArgs> MessageSent; + + /// <summary> + /// Gets/sets wire protocol that is used while reading and writing messages. + /// </summary> + IScsWireProtocol WireProtocol { get; set; } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + DateTime LastReceivedMessageTime { get; } + + /// <summary> + /// Gets the time of the last succesfully sent message. + /// </summary> + DateTime LastSentMessageTime { get; } + + /// <summary> + /// Sends a message to the remote application. + /// </summary> + /// <param name="message">Message to be sent</param> + void SendMessage(IScsMessage message); + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messengers/RequestReplyMessenger.cs b/src/Scs/Communication/Scs/Communication/Messengers/RequestReplyMessenger.cs new file mode 100644 index 0000000..f346c9e --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messengers/RequestReplyMessenger.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Protocols; +using Hik.Threading; + +namespace Hik.Communication.Scs.Communication.Messengers +{ + /// <summary> + /// This class adds SendMessageAndWaitForResponse(...) and SendAndReceiveMessage methods + /// to a IMessenger for synchronous request/response style messaging. + /// It also adds queued processing of incoming messages. + /// </summary> + /// <typeparam name="T">Type of IMessenger object to use as underlying communication</typeparam> + public class RequestReplyMessenger<T> : IMessenger, IDisposable where T : IMessenger + { + #region Public events + + /// <summary> + /// This event is raised when a new message is received from underlying messenger. + /// </summary> + public event EventHandler<MessageEventArgs> MessageReceived; + + /// <summary> + /// This event is raised when a new message is sent without any error. + /// It does not guaranties that message is properly handled and processed by remote application. + /// </summary> + public event EventHandler<MessageEventArgs> MessageSent; + + #endregion + + #region Public properties + + /// <summary> + /// Gets/sets wire protocol that is used while reading and writing messages. + /// </summary> + public IScsWireProtocol WireProtocol + { + get { return Messenger.WireProtocol; } + set { Messenger.WireProtocol = value; } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastReceivedMessageTime + { + get + { + return Messenger.LastReceivedMessageTime; + } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastSentMessageTime + { + get + { + return Messenger.LastSentMessageTime; + } + } + + /// <summary> + /// Gets the underlying IMessenger object. + /// </summary> + public T Messenger { get; private set; } + + /// <summary> + /// Timeout value as milliseconds to wait for a receiving message on + /// SendMessageAndWaitForResponse and SendAndReceiveMessage methods. + /// Default value: 60000 (1 minute). + /// </summary> + public int Timeout { get; set; } + + #endregion + + #region Private fields + + /// <summary> + /// Default Timeout value. + /// </summary> + private const int DefaultTimeout = 60000; + + /// <summary> + /// This messages are waiting for a response those are used when + /// SendMessageAndWaitForResponse is called. + /// Key: MessageID of waiting request message. + /// Value: A WaitingMessage instance. + /// </summary> + private readonly SortedList<string, WaitingMessage> _waitingMessages; + + /// <summary> + /// This object is used to process incoming messages sequentially. + /// </summary> + private readonly SequentialItemProcessor<IScsMessage> _incomingMessageProcessor; + + /// <summary> + /// This object is used for thread synchronization. + /// </summary> + private readonly object _syncObj = new object(); + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new RequestReplyMessenger. + /// </summary> + /// <param name="messenger">IMessenger object to use as underlying communication</param> + public RequestReplyMessenger(T messenger) + { + Messenger = messenger; + messenger.MessageReceived += Messenger_MessageReceived; + messenger.MessageSent += Messenger_MessageSent; + _incomingMessageProcessor = new SequentialItemProcessor<IScsMessage>(OnMessageReceived); + _waitingMessages = new SortedList<string, WaitingMessage>(); + Timeout = DefaultTimeout; + } + + #endregion + + #region Public methods + + /// <summary> + /// Starts the messenger. + /// </summary> + public virtual void Start() + { + _incomingMessageProcessor.Start(); + } + + /// <summary> + /// Stops the messenger. + /// Cancels all waiting threads in SendMessageAndWaitForResponse method and stops message queue. + /// SendMessageAndWaitForResponse method throws exception if there is a thread that is waiting for response message. + /// Also stops incoming message processing and deletes all messages in incoming message queue. + /// </summary> + public virtual void Stop() + { + _incomingMessageProcessor.Stop(); + + //Pulse waiting threads for incoming messages, since underlying messenger is disconnected + //and can not receive messages anymore. + lock (_syncObj) + { + foreach (var waitingMessage in _waitingMessages.Values) + { + waitingMessage.State = WaitingMessageStates.Cancelled; + waitingMessage.WaitEvent.Set(); + } + + _waitingMessages.Clear(); + } + } + + /// <summary> + /// Calls Stop method of this object. + /// </summary> + public void Dispose() + { + Stop(); + } + + /// <summary> + /// Sends a message. + /// </summary> + /// <param name="message">Message to be sent</param> + public void SendMessage(IScsMessage message) + { + Messenger.SendMessage(message); + } + + /// <summary> + /// Sends a message and waits a response for that message. + /// </summary> + /// <remarks> + /// Response message is matched with RepliedMessageId property, so if + /// any other message (that is not reply for sent message) is received + /// from remote application, it is not considered as a reply and is not + /// returned as return value of this method. + /// + /// MessageReceived event is not raised for response messages. + /// </remarks> + /// <param name="message">message to send</param> + /// <returns>Response message</returns> + public IScsMessage SendMessageAndWaitForResponse(IScsMessage message) + { + return SendMessageAndWaitForResponse(message, Timeout); + } + + /// <summary> + /// Sends a message and waits a response for that message. + /// </summary> + /// <remarks> + /// Response message is matched with RepliedMessageId property, so if + /// any other message (that is not reply for sent message) is received + /// from remote application, it is not considered as a reply and is not + /// returned as return value of this method. + /// + /// MessageReceived event is not raised for response messages. + /// </remarks> + /// <param name="message">message to send</param> + /// <param name="timeoutMilliseconds">Timeout duration as milliseconds.</param> + /// <returns>Response message</returns> + /// <exception cref="TimeoutException">Throws TimeoutException if can not receive reply message in timeout value</exception> + /// <exception cref="CommunicationException">Throws CommunicationException if communication fails before reply message.</exception> + public IScsMessage SendMessageAndWaitForResponse(IScsMessage message, int timeoutMilliseconds) + { + //Create a waiting message record and add to list + var waitingMessage = new WaitingMessage(); + lock (_syncObj) + { + _waitingMessages[message.MessageId] = waitingMessage; + } + + try + { + //Send message + Messenger.SendMessage(message); + + //Wait for response + waitingMessage.WaitEvent.Wait(timeoutMilliseconds); + + //Check for exceptions + switch (waitingMessage.State) + { + case WaitingMessageStates.WaitingForResponse: + throw new TimeoutException("Timeout occured. Can not received response."); + case WaitingMessageStates.Cancelled: + throw new CommunicationException("Disconnected before response received."); + } + + //return response message + return waitingMessage.ResponseMessage; + } + finally + { + //Remove message from waiting messages + lock (_syncObj) + { + if (_waitingMessages.ContainsKey(message.MessageId)) + { + _waitingMessages.Remove(message.MessageId); + } + } + } + } + + #endregion + + #region Private methods + + /// <summary> + /// Handles MessageReceived event of Messenger object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void Messenger_MessageReceived(object sender, MessageEventArgs e) + { + //Check if there is a waiting thread for this message in SendMessageAndWaitForResponse method + if (!string.IsNullOrEmpty(e.Message.RepliedMessageId)) + { + WaitingMessage waitingMessage = null; + lock (_syncObj) + { + if (_waitingMessages.ContainsKey(e.Message.RepliedMessageId)) + { + waitingMessage = _waitingMessages[e.Message.RepliedMessageId]; + } + } + + //If there is a thread waiting for this response message, pulse it + if (waitingMessage != null) + { + waitingMessage.ResponseMessage = e.Message; + waitingMessage.State = WaitingMessageStates.ResponseReceived; + waitingMessage.WaitEvent.Set(); + return; + } + } + + _incomingMessageProcessor.EnqueueMessage(e.Message); + } + + /// <summary> + /// Handles MessageSent event of Messenger object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void Messenger_MessageSent(object sender, MessageEventArgs e) + { + OnMessageSent(e.Message); + } + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises MessageReceived event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageReceived(IScsMessage message) + { + var handler = MessageReceived; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + /// <summary> + /// Raises MessageSent event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageSent(IScsMessage message) + { + var handler = MessageSent; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + #endregion + + #region WaitingMessage class + + /// <summary> + /// This class is used to store messaging context for a request message + /// until response is received. + /// </summary> + private sealed class WaitingMessage + { + /// <summary> + /// Response message for request message + /// (null if response is not received yet). + /// </summary> + public IScsMessage ResponseMessage { get; set; } + + /// <summary> + /// ManualResetEvent to block thread until response is received. + /// </summary> + public ManualResetEventSlim WaitEvent { get; private set; } + + /// <summary> + /// State of the request message. + /// </summary> + public WaitingMessageStates State { get; set; } + + /// <summary> + /// Creates a new WaitingMessage object. + /// </summary> + public WaitingMessage() + { + WaitEvent = new ManualResetEventSlim(false); + State = WaitingMessageStates.WaitingForResponse; + } + } + + /// <summary> + /// This enum is used to store the state of a waiting message. + /// </summary> + private enum WaitingMessageStates + { + /// <summary> + /// Still waiting for response. + /// </summary> + WaitingForResponse, + + /// <summary> + /// Message sending is cancelled. + /// </summary> + Cancelled, + + /// <summary> + /// Response is properly received. + /// </summary> + ResponseReceived + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Communication/Messengers/SynchronizedMessenger.cs b/src/Scs/Communication/Scs/Communication/Messengers/SynchronizedMessenger.cs new file mode 100644 index 0000000..065cfd4 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Messengers/SynchronizedMessenger.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.Scs.Communication.Messengers +{ + /// <summary> + /// This class is a wrapper for IMessenger and is used + /// to synchronize message receiving operation. + /// It extends RequestReplyMessenger. + /// It is suitable to use in applications those want to receive + /// messages by synchronized method calls instead of asynchronous + /// MessageReceived event. + /// </summary> + public class SynchronizedMessenger<T> : RequestReplyMessenger<T> where T : IMessenger + { + #region Public properties + + ///<summary> + /// Gets/sets capacity of the incoming message queue. + /// No message is received from remote application if + /// number of messages in internal queue exceeds this value. + /// Default value: int.MaxValue (2147483647). + ///</summary> + public int IncomingMessageQueueCapacity { get; set; } + + #endregion + + #region Private fields + + /// <summary> + /// A queue that is used to store receiving messages until Receive(...) + /// method is called to get them. + /// </summary> + private readonly Queue<IScsMessage> _receivingMessageQueue; + + /// <summary> + /// This object is used to synchronize/wait threads. + /// </summary> + private readonly ManualResetEventSlim _receiveWaiter; + + /// <summary> + /// This boolean value indicates the running state of this class. + /// </summary> + private volatile bool _running; + + #endregion + + #region Constructors + + ///<summary> + /// Creates a new SynchronizedMessenger object. + ///</summary> + ///<param name="messenger">A IMessenger object to be used to send/receive messages</param> + public SynchronizedMessenger(T messenger) + : this(messenger, int.MaxValue) + { + + } + + ///<summary> + /// Creates a new SynchronizedMessenger object. + ///</summary> + ///<param name="messenger">A IMessenger object to be used to send/receive messages</param> + ///<param name="incomingMessageQueueCapacity">capacity of the incoming message queue</param> + public SynchronizedMessenger(T messenger, int incomingMessageQueueCapacity) + : base(messenger) + { + _receiveWaiter = new ManualResetEventSlim(); + _receivingMessageQueue = new Queue<IScsMessage>(); + IncomingMessageQueueCapacity = incomingMessageQueueCapacity; + } + + #endregion + + #region Public methods + + /// <summary> + /// Starts the messenger. + /// </summary> + public override void Start() + { + lock (_receivingMessageQueue) + { + _running = true; + } + + base.Start(); + } + + /// <summary> + /// Stops the messenger. + /// </summary> + public override void Stop() + { + base.Stop(); + + lock (_receivingMessageQueue) + { + _running = false; + _receiveWaiter.Set(); + } + } + + /// <summary> + /// This method is used to receive a message from remote application. + /// It waits until a message is received. + /// </summary> + /// <returns>Received message</returns> + public IScsMessage ReceiveMessage() + { + return ReceiveMessage(System.Threading.Timeout.Infinite); + } + + /// <summary> + /// This method is used to receive a message from remote application. + /// It waits until a message is received or timeout occurs. + /// </summary> + /// <param name="timeout"> + /// Timeout value to wait if no message is received. + /// Use -1 to wait indefinitely. + /// </param> + /// <returns>Received message</returns> + /// <exception cref="TimeoutException">Throws TimeoutException if timeout occurs</exception> + /// <exception cref="Exception">Throws Exception if SynchronizedMessenger stops before a message is received</exception> + public IScsMessage ReceiveMessage(int timeout) + { + while (_running) + { + lock (_receivingMessageQueue) + { + //Check if SynchronizedMessenger is running + if (!_running) + { + throw new Exception("SynchronizedMessenger is stopped. Can not receive message."); + } + + //Get a message immediately if any message does exists + if (_receivingMessageQueue.Count > 0) + { + return _receivingMessageQueue.Dequeue(); + } + + _receiveWaiter.Reset(); + } + + //Wait for a message + var signalled = _receiveWaiter.Wait(timeout); + + //If not signalled, throw exception + if (!signalled) + { + throw new TimeoutException("Timeout occured. Can not received any message"); + } + } + + throw new Exception("SynchronizedMessenger is stopped. Can not receive message."); + } + + /// <summary> + /// This method is used to receive a specific type of message from remote application. + /// It waits until a message is received. + /// </summary> + /// <returns>Received message</returns> + public TMessage ReceiveMessage<TMessage>() where TMessage : IScsMessage + { + return ReceiveMessage<TMessage>(System.Threading.Timeout.Infinite); + } + + /// <summary> + /// This method is used to receive a specific type of message from remote application. + /// It waits until a message is received or timeout occurs. + /// </summary> + /// <param name="timeout"> + /// Timeout value to wait if no message is received. + /// Use -1 to wait indefinitely. + /// </param> + /// <returns>Received message</returns> + public TMessage ReceiveMessage<TMessage>(int timeout) where TMessage : IScsMessage + { + var receivedMessage = ReceiveMessage(timeout); + if (!(receivedMessage is TMessage)) + { + throw new Exception("Unexpected message received." + + " Expected type: " + typeof(TMessage).Name + + ". Received message type: " + receivedMessage.GetType().Name); + } + + return (TMessage)receivedMessage; + } + + #endregion + + #region Protected methods + + /// <summary> + /// Overrides + /// </summary> + /// <param name="message"></param> + protected override void OnMessageReceived(IScsMessage message) + { + lock (_receivingMessageQueue) + { + if (_receivingMessageQueue.Count < IncomingMessageQueueCapacity) + { + _receivingMessageQueue.Enqueue(message); + } + + _receiveWaiter.Set(); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocol.cs b/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocol.cs new file mode 100644 index 0000000..a84784b --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocol.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.Scs.Communication.Protocols.BinarySerialization +{ + /// <summary> + /// Default communication protocol between server and clients to send and receive a message. + /// It uses .NET binary serialization to write and read messages. + /// + /// A Message format: + /// [Message Length (4 bytes)][Serialized Message Content] + /// + /// If a message is serialized to byte array as N bytes, this protocol + /// adds 4 bytes size information to head of the message bytes, so total length is (4 + N) bytes. + /// + /// This class can be derived to change serializer (default: BinaryFormatter). To do this, + /// SerializeMessage and DeserializeMessage methods must be overrided. + /// </summary> + public class BinarySerializationProtocol : IScsWireProtocol + { + #region Private fields + + /// <summary> + /// Maximum length of a message. + /// </summary> + private const int MaxMessageLength = 128 * 1024 * 1024; //128 Megabytes. + + /// <summary> + /// This MemoryStream object is used to collect receiving bytes to build messages. + /// </summary> + private MemoryStream _receiveMemoryStream; + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new instance of BinarySerializationProtocol. + /// </summary> + public BinarySerializationProtocol() + { + _receiveMemoryStream = new MemoryStream(); + } + + #endregion + + #region IScsWireProtocol implementation + + /// <summary> + /// Serializes a message to a byte array to send to remote application. + /// This method is synchronized. So, only one thread can call it concurrently. + /// </summary> + /// <param name="message">Message to be serialized</param> + /// <exception cref="CommunicationException">Throws CommunicationException if message is bigger than maximum allowed message length.</exception> + public byte[] GetBytes(IScsMessage message) + { + //Serialize the message to a byte array + var serializedMessage = SerializeMessage(message); + + //Check for message length + var messageLength = serializedMessage.Length; + if (messageLength > MaxMessageLength) + { + throw new CommunicationException("Message is too big (" + messageLength + " bytes). Max allowed length is " + MaxMessageLength + " bytes."); + } + + //Create a byte array including the length of the message (4 bytes) and serialized message content + var bytes = new byte[messageLength + 4]; + WriteInt32(bytes, 0, messageLength); + Array.Copy(serializedMessage, 0, bytes, 4, messageLength); + + //Return serialized message by this protocol + return bytes; + } + + /// <summary> + /// Builds messages from a byte array that is received from remote application. + /// The Byte array may contain just a part of a message, the protocol must + /// cumulate bytes to build messages. + /// This method is synchronized. So, only one thread can call it concurrently. + /// </summary> + /// <param name="receivedBytes">Received bytes from remote application</param> + /// <returns> + /// List of messages. + /// Protocol can generate more than one message from a byte array. + /// Also, if received bytes are not sufficient to build a message, the protocol + /// may return an empty list (and save bytes to combine with next method call). + /// </returns> + public IEnumerable<IScsMessage> CreateMessages(byte[] receivedBytes) + { + //Write all received bytes to the _receiveMemoryStream + _receiveMemoryStream.Write(receivedBytes, 0, receivedBytes.Length); + //Create a list to collect messages + var messages = new List<IScsMessage>(); + //Read all available messages and add to messages collection + while (ReadSingleMessage(messages)) { } + //Return message list + return messages; + } + + /// <summary> + /// This method is called when connection with remote application is reset (connection is renewing or first connecting). + /// So, wire protocol must reset itself. + /// </summary> + public void Reset() + { + if (_receiveMemoryStream.Length > 0) + { + _receiveMemoryStream = new MemoryStream(); + } + } + + #endregion + + #region Proptected virtual methods + + /// <summary> + /// This method is used to serialize a IScsMessage to a byte array. + /// This method can be overrided by derived classes to change serialization strategy. + /// It is a couple with DeserializeMessage method and must be overrided together. + /// </summary> + /// <param name="message">Message to be serialized</param> + /// <returns> + /// Serialized message bytes. + /// Does not include length of the message. + /// </returns> + protected virtual byte[] SerializeMessage(IScsMessage message) + { + using (var memoryStream = new MemoryStream()) + { + new BinaryFormatter().Serialize(memoryStream, message); + return memoryStream.ToArray(); + } + } + + /// <summary> + /// This method is used to deserialize a IScsMessage from it's bytes. + /// This method can be overrided by derived classes to change deserialization strategy. + /// It is a couple with SerializeMessage method and must be overrided together. + /// </summary> + /// <param name="bytes"> + /// Bytes of message to be deserialized (does not include message length. It consist + /// of a single whole message) + /// </param> + /// <returns>Deserialized message</returns> + protected virtual IScsMessage DeserializeMessage(byte[] bytes) + { + //Create a MemoryStream to convert bytes to a stream + using (var deserializeMemoryStream = new MemoryStream(bytes)) + { + //Go to head of the stream + deserializeMemoryStream.Position = 0; + + //Deserialize the message + var binaryFormatter = new BinaryFormatter + { + AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple, + Binder = new DeserializationAppDomainBinder() + }; + + //Return the deserialized message + return (IScsMessage) binaryFormatter.Deserialize(deserializeMemoryStream); + } + } + + #endregion + + #region Private methods + + /// <summary> + /// This method tries to read a single message and add to the messages collection. + /// </summary> + /// <param name="messages">Messages collection to collect messages</param> + /// <returns> + /// Returns a boolean value indicates that if there is a need to re-call this method. + /// </returns> + /// <exception cref="CommunicationException">Throws CommunicationException if message is bigger than maximum allowed message length.</exception> + private bool ReadSingleMessage(ICollection<IScsMessage> messages) + { + //Go to the begining of the stream + _receiveMemoryStream.Position = 0; + + //If stream has less than 4 bytes, that means we can not even read length of the message + //So, return false to wait more bytes from remore application. + if (_receiveMemoryStream.Length < 4) + { + return false; + } + + //Read length of the message + var messageLength = ReadInt32(_receiveMemoryStream); + if (messageLength > MaxMessageLength) + { + throw new Exception("Message is too big (" + messageLength + " bytes). Max allowed length is " + MaxMessageLength + " bytes."); + } + + //If message is zero-length (It must not be but good approach to check it) + if (messageLength == 0) + { + //if no more bytes, return immediately + if (_receiveMemoryStream.Length == 4) + { + _receiveMemoryStream = new MemoryStream(); //Clear the stream + return false; + } + + //Create a new memory stream from current except first 4-bytes. + var bytes = _receiveMemoryStream.ToArray(); + _receiveMemoryStream = new MemoryStream(); + _receiveMemoryStream.Write(bytes, 4, bytes.Length - 4); + return true; + } + + //If all bytes of the message is not received yet, return to wait more bytes + if (_receiveMemoryStream.Length < (4 + messageLength)) + { + _receiveMemoryStream.Position = _receiveMemoryStream.Length; + return false; + } + + //Read bytes of serialized message and deserialize it + var serializedMessageBytes = ReadByteArray(_receiveMemoryStream, messageLength); + messages.Add(DeserializeMessage(serializedMessageBytes)); + + //Read remaining bytes to an array + var remainingBytes = ReadByteArray(_receiveMemoryStream, (int)(_receiveMemoryStream.Length - (4 + messageLength))); + + //Re-create the receive memory stream and write remaining bytes + _receiveMemoryStream = new MemoryStream(); + _receiveMemoryStream.Write(remainingBytes, 0, remainingBytes.Length); + + //Return true to re-call this method to try to read next message + return (remainingBytes.Length > 4); + } + + /// <summary> + /// Writes a int value to a byte array from a starting index. + /// </summary> + /// <param name="buffer">Byte array to write int value</param> + /// <param name="startIndex">Start index of byte array to write</param> + /// <param name="number">An integer value to write</param> + private static void WriteInt32(byte[] buffer, int startIndex, int number) + { + buffer[startIndex] = (byte)((number >> 24) & 0xFF); + buffer[startIndex + 1] = (byte)((number >> 16) & 0xFF); + buffer[startIndex + 2] = (byte)((number >> 8) & 0xFF); + buffer[startIndex + 3] = (byte)((number) & 0xFF); + } + + /// <summary> + /// Deserializes and returns a serialized integer. + /// </summary> + /// <returns>Deserialized integer</returns> + private static int ReadInt32(Stream stream) + { + var buffer = ReadByteArray(stream, 4); + return ((buffer[0] << 24) | + (buffer[1] << 16) | + (buffer[2] << 8) | + (buffer[3]) + ); + } + + /// <summary> + /// Reads a byte array with specified length. + /// </summary> + /// <param name="stream">Stream to read from</param> + /// <param name="length">Length of the byte array to read</param> + /// <returns>Read byte array</returns> + /// <exception cref="EndOfStreamException">Throws EndOfStreamException if can not read from stream.</exception> + private static byte[] ReadByteArray(Stream stream, int length) + { + var buffer = new byte[length]; + var totalRead = 0; + while (totalRead < length) + { + var read = stream.Read(buffer, totalRead, length - totalRead); + if (read <= 0) + { + throw new EndOfStreamException("Can not read from stream! Input stream is closed."); + } + + totalRead += read; + } + + return buffer; + } + + #endregion + + #region Nested classes + + /// <summary> + /// This class is used in deserializing to allow deserializing objects that are defined + /// in assemlies that are load in runtime (like PlugIns). + /// </summary> + protected sealed class DeserializationAppDomainBinder : SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + var toAssemblyName = assemblyName.Split(',')[0]; + return (from assembly in AppDomain.CurrentDomain.GetAssemblies() + where assembly.FullName.Split(',')[0] == toAssemblyName + select assembly.GetType(typeName)).FirstOrDefault(); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocolFactory.cs b/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocolFactory.cs new file mode 100644 index 0000000..ded08ae --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocolFactory.cs @@ -0,0 +1,17 @@ +namespace Hik.Communication.Scs.Communication.Protocols.BinarySerialization +{ + /// <summary> + /// This class is used to create Binary Serialization Protocol objects. + /// </summary> + public class BinarySerializationProtocolFactory : IScsWireProtocolFactory + { + /// <summary> + /// Creates a new Wire Protocol object. + /// </summary> + /// <returns>Newly created wire protocol object</returns> + public IScsWireProtocol CreateWireProtocol() + { + return new BinarySerializationProtocol(); + } + } +} diff --git a/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocol.cs b/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocol.cs new file mode 100644 index 0000000..0ea3fb6 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocol.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.Scs.Communication.Protocols +{ + /// <summary> + /// Represents a byte-level communication protocol between applications. + /// </summary> + public interface IScsWireProtocol + { + /// <summary> + /// Serializes a message to a byte array to send to remote application. + /// This method is synchronized. So, only one thread can call it concurrently. + /// </summary> + /// <param name="message">Message to be serialized</param> + byte[] GetBytes(IScsMessage message); + + /// <summary> + /// Builds messages from a byte array that is received from remote application. + /// The Byte array may contain just a part of a message, the protocol must + /// cumulate bytes to build messages. + /// This method is synchronized. So, only one thread can call it concurrently. + /// </summary> + /// <param name="receivedBytes">Received bytes from remote application</param> + /// <returns> + /// List of messages. + /// Protocol can generate more than one message from a byte array. + /// Also, if received bytes are not sufficient to build a message, the protocol + /// may return an empty list (and save bytes to combine with next method call). + /// </returns> + IEnumerable<IScsMessage> CreateMessages(byte[] receivedBytes); + + /// <summary> + /// This method is called when connection with remote application is reset (connection is renewing or first connecting). + /// So, wire protocol must reset itself. + /// </summary> + void Reset(); + } +} diff --git a/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocolFactory.cs b/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocolFactory.cs new file mode 100644 index 0000000..3cc2489 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocolFactory.cs @@ -0,0 +1,14 @@ +namespace Hik.Communication.Scs.Communication.Protocols +{ + ///<summary> + /// Defines a Wire Protocol Factory class that is used to create Wire Protocol objects. + ///</summary> + public interface IScsWireProtocolFactory + { + /// <summary> + /// Creates a new Wire Protocol object. + /// </summary> + /// <returns>Newly created wire protocol object</returns> + IScsWireProtocol CreateWireProtocol(); + } +} diff --git a/src/Scs/Communication/Scs/Communication/Protocols/WireProtocolManager.cs b/src/Scs/Communication/Scs/Communication/Protocols/WireProtocolManager.cs new file mode 100644 index 0000000..0685046 --- /dev/null +++ b/src/Scs/Communication/Scs/Communication/Protocols/WireProtocolManager.cs @@ -0,0 +1,28 @@ +using Hik.Communication.Scs.Communication.Protocols.BinarySerialization; + +namespace Hik.Communication.Scs.Communication.Protocols +{ + /// <summary> + /// This class is used to get default protocols. + /// </summary> + internal static class WireProtocolManager + { + /// <summary> + /// Creates a default wire protocol factory object to be used on communicating of applications. + /// </summary> + /// <returns>A new instance of default wire protocol</returns> + public static IScsWireProtocolFactory GetDefaultWireProtocolFactory() + { + return new BinarySerializationProtocolFactory(); + } + + /// <summary> + /// Creates a default wire protocol object to be used on communicating of applications. + /// </summary> + /// <returns>A new instance of default wire protocol</returns> + public static IScsWireProtocol GetDefaultWireProtocol() + { + return new BinarySerializationProtocol(); + } + } +} diff --git a/src/Scs/Communication/Scs/Server/IScsServer.cs b/src/Scs/Communication/Scs/Server/IScsServer.cs new file mode 100644 index 0000000..a87e7a1 --- /dev/null +++ b/src/Scs/Communication/Scs/Server/IScsServer.cs @@ -0,0 +1,42 @@ +using System; +using Hik.Collections; +using Hik.Communication.Scs.Communication.Protocols; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// Represents a SCS server that is used to accept and manage client connections. + /// </summary> + public interface IScsServer + { + /// <summary> + /// This event is raised when a new client connected to the server. + /// </summary> + event EventHandler<ServerClientEventArgs> ClientConnected; + + /// <summary> + /// This event is raised when a client disconnected from the server. + /// </summary> + event EventHandler<ServerClientEventArgs> ClientDisconnected; + + /// <summary> + /// Gets/sets wire protocol factory to create IWireProtocol objects. + /// </summary> + IScsWireProtocolFactory WireProtocolFactory { get; set; } + + /// <summary> + /// A collection of clients that are connected to the server. + /// </summary> + ThreadSafeSortedList<long, IScsServerClient> Clients { get; } + + /// <summary> + /// Starts the server. + /// </summary> + void Start(); + + /// <summary> + /// Stops the server. + /// </summary> + void Stop(); + } +} diff --git a/src/Scs/Communication/Scs/Server/IScsServerClient.cs b/src/Scs/Communication/Scs/Server/IScsServerClient.cs new file mode 100644 index 0000000..3e60bef --- /dev/null +++ b/src/Scs/Communication/Scs/Server/IScsServerClient.cs @@ -0,0 +1,38 @@ +using System; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.Messengers; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// Represents a client from a perspective of a server. + /// </summary> + public interface IScsServerClient : IMessenger + { + /// <summary> + /// This event is raised when client disconnected from server. + /// </summary> + event EventHandler Disconnected; + + /// <summary> + /// Unique identifier for this client in server. + /// </summary> + long ClientId { get; } + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + ScsEndPoint RemoteEndPoint { get; } + + /// <summary> + /// Gets the current communication state. + /// </summary> + CommunicationStates CommunicationState { get; } + + /// <summary> + /// Disconnects from server. + /// </summary> + void Disconnect(); + } +} diff --git a/src/Scs/Communication/Scs/Server/ScsServerBase.cs b/src/Scs/Communication/Scs/Server/ScsServerBase.cs new file mode 100644 index 0000000..d5019ea --- /dev/null +++ b/src/Scs/Communication/Scs/Server/ScsServerBase.cs @@ -0,0 +1,168 @@ +using System; +using Hik.Collections; +using Hik.Communication.Scs.Communication.Channels; +using Hik.Communication.Scs.Communication.Protocols; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// This class provides base functionality for server classes. + /// </summary> + internal abstract class ScsServerBase : IScsServer + { + #region Public events + + /// <summary> + /// This event is raised when a new client is connected. + /// </summary> + public event EventHandler<ServerClientEventArgs> ClientConnected; + + /// <summary> + /// This event is raised when a client disconnected from the server. + /// </summary> + public event EventHandler<ServerClientEventArgs> ClientDisconnected; + + #endregion + + #region Public properties + + /// <summary> + /// Gets/sets wire protocol that is used while reading and writing messages. + /// </summary> + public IScsWireProtocolFactory WireProtocolFactory { get; set; } + + /// <summary> + /// A collection of clients that are connected to the server. + /// </summary> + public ThreadSafeSortedList<long, IScsServerClient> Clients { get; private set; } + + #endregion + + #region Private properties + + /// <summary> + /// This object is used to listen incoming connections. + /// </summary> + private IConnectionListener _connectionListener; + + #endregion + + #region Constructor + + /// <summary> + /// Constructor. + /// </summary> + protected ScsServerBase() + { + Clients = new ThreadSafeSortedList<long, IScsServerClient>(); + WireProtocolFactory = WireProtocolManager.GetDefaultWireProtocolFactory(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Starts the server. + /// </summary> + public virtual void Start() + { + _connectionListener = CreateConnectionListener(); + _connectionListener.CommunicationChannelConnected += ConnectionListener_CommunicationChannelConnected; + _connectionListener.Start(); + } + + /// <summary> + /// Stops the server. + /// </summary> + public virtual void Stop() + { + if (_connectionListener != null) + { + _connectionListener.Stop(); + } + + foreach (var client in Clients.GetAllItems()) + { + client.Disconnect(); + } + } + + #endregion + + #region Protected abstract methods + + /// <summary> + /// This method is implemented by derived classes to create appropriate connection listener to listen incoming connection requets. + /// </summary> + /// <returns></returns> + protected abstract IConnectionListener CreateConnectionListener(); + + #endregion + + #region Private methods + + /// <summary> + /// Handles CommunicationChannelConnected event of _connectionListener object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void ConnectionListener_CommunicationChannelConnected(object sender, CommunicationChannelEventArgs e) + { + var client = new ScsServerClient(e.Channel) + { + ClientId = ScsServerManager.GetClientId(), + WireProtocol = WireProtocolFactory.CreateWireProtocol() + }; + + client.Disconnected += Client_Disconnected; + Clients[client.ClientId] = client; + OnClientConnected(client); + e.Channel.Start(); + } + + /// <summary> + /// Handles Disconnected events of all connected clients. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void Client_Disconnected(object sender, EventArgs e) + { + var client = (IScsServerClient) sender; + Clients.Remove(client.ClientId); + OnClientDisconnected(client); + } + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises ClientConnected event. + /// </summary> + /// <param name="client">Connected client</param> + protected virtual void OnClientConnected(IScsServerClient client) + { + var handler = ClientConnected; + if (handler != null) + { + handler(this, new ServerClientEventArgs(client)); + } + } + + /// <summary> + /// Raises ClientDisconnected event. + /// </summary> + /// <param name="client">Disconnected client</param> + protected virtual void OnClientDisconnected(IScsServerClient client) + { + var handler = ClientDisconnected; + if (handler != null) + { + handler(this, new ServerClientEventArgs(client)); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/Scs/Server/ScsServerClient.cs b/src/Scs/Communication/Scs/Server/ScsServerClient.cs new file mode 100644 index 0000000..398ec79 --- /dev/null +++ b/src/Scs/Communication/Scs/Server/ScsServerClient.cs @@ -0,0 +1,223 @@ +using System; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Channels; +using Hik.Communication.Scs.Communication.Protocols; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// This class represents a client in server side. + /// </summary> + internal class ScsServerClient : IScsServerClient + { + #region Public events + + /// <summary> + /// This event is raised when a new message is received. + /// </summary> + public event EventHandler<MessageEventArgs> MessageReceived; + + /// <summary> + /// This event is raised when a new message is sent without any error. + /// It does not guaranties that message is properly handled and processed by remote application. + /// </summary> + public event EventHandler<MessageEventArgs> MessageSent; + + /// <summary> + /// This event is raised when client is disconnected from server. + /// </summary> + public event EventHandler Disconnected; + + #endregion + + #region Public properties + + /// <summary> + /// Unique identifier for this client in server. + /// </summary> + public long ClientId { get; set; } + + /// <summary> + /// Gets the communication state of the Client. + /// </summary> + public CommunicationStates CommunicationState + { + get + { + return _communicationChannel.CommunicationState; + } + } + + /// <summary> + /// Gets/sets wire protocol that is used while reading and writing messages. + /// </summary> + public IScsWireProtocol WireProtocol + { + get { return _communicationChannel.WireProtocol; } + set { _communicationChannel.WireProtocol = value; } + } + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + public ScsEndPoint RemoteEndPoint + { + get { return _communicationChannel.RemoteEndPoint; } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastReceivedMessageTime + { + get + { + return _communicationChannel.LastReceivedMessageTime; + } + } + + /// <summary> + /// Gets the time of the last succesfully received message. + /// </summary> + public DateTime LastSentMessageTime + { + get + { + return _communicationChannel.LastSentMessageTime; + } + } + + #endregion + + #region Private fields + + /// <summary> + /// The communication channel that is used by client to send and receive messages. + /// </summary> + private readonly ICommunicationChannel _communicationChannel; + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new ScsClient object. + /// </summary> + /// <param name="communicationChannel">The communication channel that is used by client to send and receive messages</param> + public ScsServerClient(ICommunicationChannel communicationChannel) + { + _communicationChannel = communicationChannel; + _communicationChannel.MessageReceived += CommunicationChannel_MessageReceived; + _communicationChannel.MessageSent += CommunicationChannel_MessageSent; + _communicationChannel.Disconnected += CommunicationChannel_Disconnected; + } + + #endregion + + #region Public methods + + /// <summary> + /// Disconnects from client and closes underlying communication channel. + /// </summary> + public void Disconnect() + { + _communicationChannel.Disconnect(); + } + + /// <summary> + /// Sends a message to the client. + /// </summary> + /// <param name="message">Message to be sent</param> + public void SendMessage(IScsMessage message) + { + _communicationChannel.SendMessage(message); + } + + #endregion + + #region Private methods + + /// <summary> + /// Handles Disconnected event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_Disconnected(object sender, EventArgs e) + { + OnDisconnected(); + } + + /// <summary> + /// Handles MessageReceived event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_MessageReceived(object sender, MessageEventArgs e) + { + var message = e.Message; + if (message is ScsPingMessage) + { + _communicationChannel.SendMessage(new ScsPingMessage { RepliedMessageId = message.MessageId }); + return; + } + + OnMessageReceived(message); + } + + /// <summary> + /// Handles MessageSent event of _communicationChannel object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void CommunicationChannel_MessageSent(object sender, MessageEventArgs e) + { + OnMessageSent(e.Message); + } + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises Disconnected event. + /// </summary> + private void OnDisconnected() + { + var handler = Disconnected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + /// <summary> + /// Raises MessageReceived event. + /// </summary> + /// <param name="message">Received message</param> + private void OnMessageReceived(IScsMessage message) + { + var handler = MessageReceived; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + /// <summary> + /// Raises MessageSent event. + /// </summary> + /// <param name="message">Received message</param> + protected virtual void OnMessageSent(IScsMessage message) + { + var handler = MessageSent; + if (handler != null) + { + handler(this, new MessageEventArgs(message)); + } + } + + #endregion + } +}
\ No newline at end of file diff --git a/src/Scs/Communication/Scs/Server/ScsServerFactory.cs b/src/Scs/Communication/Scs/Server/ScsServerFactory.cs new file mode 100644 index 0000000..d03b832 --- /dev/null +++ b/src/Scs/Communication/Scs/Server/ScsServerFactory.cs @@ -0,0 +1,20 @@ +using Hik.Communication.Scs.Communication.EndPoints; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// This class is used to create SCS servers. + /// </summary> + public static class ScsServerFactory + { + /// <summary> + /// Creates a new SCS Server using an EndPoint. + /// </summary> + /// <param name="endPoint">Endpoint that represents address of the server</param> + /// <returns>Created TCP server</returns> + public static IScsServer CreateServer(ScsEndPoint endPoint) + { + return endPoint.CreateServer(); + } + } +} diff --git a/src/Scs/Communication/Scs/Server/ScsServerManager.cs b/src/Scs/Communication/Scs/Server/ScsServerManager.cs new file mode 100644 index 0000000..dd2505a --- /dev/null +++ b/src/Scs/Communication/Scs/Server/ScsServerManager.cs @@ -0,0 +1,24 @@ +using System.Threading; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// Provides some functionality that are used by servers. + /// </summary> + internal static class ScsServerManager + { + /// <summary> + /// Used to set an auto incremential unique identifier to clients. + /// </summary> + private static long _lastClientId; + + /// <summary> + /// Gets an unique number to be used as idenfitier of a client. + /// </summary> + /// <returns></returns> + public static long GetClientId() + { + return Interlocked.Increment(ref _lastClientId); + } + } +} diff --git a/src/Scs/Communication/Scs/Server/ServerClientEventArgs.cs b/src/Scs/Communication/Scs/Server/ServerClientEventArgs.cs new file mode 100644 index 0000000..875b8e0 --- /dev/null +++ b/src/Scs/Communication/Scs/Server/ServerClientEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Hik.Communication.Scs.Server +{ + /// <summary> + /// Stores client information to be used by an event. + /// </summary> + public class ServerClientEventArgs : EventArgs + { + /// <summary> + /// Client that is associated with this event. + /// </summary> + public IScsServerClient Client { get; private set; } + + /// <summary> + /// Creates a new ServerClientEventArgs object. + /// </summary> + /// <param name="client">Client that is associated with this event</param> + public ServerClientEventArgs(IScsServerClient client) + { + Client = client; + } + } +} diff --git a/src/Scs/Communication/Scs/Server/Tcp/ScsTcpServer.cs b/src/Scs/Communication/Scs/Server/Tcp/ScsTcpServer.cs new file mode 100644 index 0000000..fe8a3f7 --- /dev/null +++ b/src/Scs/Communication/Scs/Server/Tcp/ScsTcpServer.cs @@ -0,0 +1,35 @@ +using Hik.Communication.Scs.Communication.Channels; +using Hik.Communication.Scs.Communication.Channels.Tcp; +using Hik.Communication.Scs.Communication.EndPoints.Tcp; + +namespace Hik.Communication.Scs.Server.Tcp +{ + /// <summary> + /// This class is used to create a TCP server. + /// </summary> + internal class ScsTcpServer : ScsServerBase + { + /// <summary> + /// The endpoint address of the server to listen incoming connections. + /// </summary> + private readonly ScsTcpEndPoint _endPoint; + + /// <summary> + /// Creates a new ScsTcpServer object. + /// </summary> + /// <param name="endPoint">The endpoint address of the server to listen incoming connections</param> + public ScsTcpServer(ScsTcpEndPoint endPoint) + { + _endPoint = endPoint; + } + + /// <summary> + /// Creates a TCP connection listener. + /// </summary> + /// <returns>Created listener object</returns> + protected override IConnectionListener CreateConnectionListener() + { + return new TcpConnectionListener(_endPoint); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Client/IScsServiceClient.cs b/src/Scs/Communication/ScsServices/Client/IScsServiceClient.cs new file mode 100644 index 0000000..414d0c9 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Client/IScsServiceClient.cs @@ -0,0 +1,24 @@ +using Hik.Communication.Scs.Client; + +namespace Hik.Communication.ScsServices.Client +{ + /// <summary> + /// Represents a service client that consumes a SCS service. + /// </summary> + /// <typeparam name="T">Type of service interface</typeparam> + public interface IScsServiceClient<out T> : IConnectableClient where T : class + { + /// <summary> + /// Reference to the service proxy to invoke remote service methods. + /// </summary> + T ServiceProxy { get; } + + /// <summary> + /// Timeout value when invoking a service method. + /// If timeout occurs before end of remote method call, an exception is thrown. + /// Use -1 for no timeout (wait indefinite). + /// Default value: 60000 (1 minute). + /// </summary> + int Timeout { get; set; } + } +} diff --git a/src/Scs/Communication/ScsServices/Client/ScsServiceClient.cs b/src/Scs/Communication/ScsServices/Client/ScsServiceClient.cs new file mode 100644 index 0000000..32ddee1 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Client/ScsServiceClient.cs @@ -0,0 +1,274 @@ +using System; +using System.Reflection; +using Hik.Communication.Scs.Client; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Messengers; +using Hik.Communication.ScsServices.Communication; +using Hik.Communication.ScsServices.Communication.Messages; + +namespace Hik.Communication.ScsServices.Client +{ + /// <summary> + /// Represents a service client that consumes a SCS service. + /// </summary> + /// <typeparam name="T">Type of service interface</typeparam> + internal class ScsServiceClient<T> : IScsServiceClient<T> where T : class + { + #region Public events + + /// <summary> + /// This event is raised when client connected to server. + /// </summary> + public event EventHandler Connected; + + /// <summary> + /// This event is raised when client disconnected from server. + /// </summary> + public event EventHandler Disconnected; + + #endregion + + #region Public properties + + /// <summary> + /// Timeout for connecting to a server (as milliseconds). + /// Default value: 15 seconds (15000 ms). + /// </summary> + public int ConnectTimeout + { + get { return _client.ConnectTimeout; } + set { _client.ConnectTimeout = value; } + } + + /// <summary> + /// Gets the current communication state. + /// </summary> + public CommunicationStates CommunicationState + { + get { return _client.CommunicationState; } + } + + /// <summary> + /// Reference to the service proxy to invoke remote service methods. + /// </summary> + public T ServiceProxy { get; private set; } + + /// <summary> + /// Timeout value when invoking a service method. + /// If timeout occurs before end of remote method call, an exception is thrown. + /// Use -1 for no timeout (wait indefinite). + /// Default value: 60000 (1 minute). + /// </summary> + public int Timeout + { + get { return _requestReplyMessenger.Timeout; } + set { _requestReplyMessenger.Timeout = value; } + } + + #endregion + + #region Private fields + + /// <summary> + /// Underlying IScsClient object to communicate with server. + /// </summary> + private readonly IScsClient _client; + + /// <summary> + /// Messenger object to send/receive messages over _client. + /// </summary> + private readonly RequestReplyMessenger<IScsClient> _requestReplyMessenger; + + /// <summary> + /// This object is used to create a transparent proxy to invoke remote methods on server. + /// </summary> + private readonly AutoConnectRemoteInvokeProxy<T, IScsClient> _realServiceProxy; + + /// <summary> + /// The client object that is used to call method invokes in client side. + /// May be null if client has no methods to be invoked by server. + /// </summary> + private readonly object _clientObject; + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new ScsServiceClient object. + /// </summary> + /// <param name="client">Underlying IScsClient object to communicate with server</param> + /// <param name="clientObject">The client object that is used to call method invokes in client side. + /// May be null if client has no methods to be invoked by server.</param> + public ScsServiceClient(IScsClient client, object clientObject) + { + _client = client; + _clientObject = clientObject; + + _client.Connected += Client_Connected; + _client.Disconnected += Client_Disconnected; + + _requestReplyMessenger = new RequestReplyMessenger<IScsClient>(client); + _requestReplyMessenger.MessageReceived += RequestReplyMessenger_MessageReceived; + + _realServiceProxy = new AutoConnectRemoteInvokeProxy<T, IScsClient>(_requestReplyMessenger, this); + ServiceProxy = (T)_realServiceProxy.GetTransparentProxy(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Connects to server. + /// </summary> + public void Connect() + { + _client.Connect(); + } + + /// <summary> + /// Disconnects from server. + /// Does nothing if already disconnected. + /// </summary> + public void Disconnect() + { + _client.Disconnect(); + } + + /// <summary> + /// Calls Disconnect method. + /// </summary> + public void Dispose() + { + Disconnect(); + } + + #endregion + + #region Private methods + + /// <summary> + /// Handles MessageReceived event of messenger. + /// It gets messages from server and invokes appropriate method. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void RequestReplyMessenger_MessageReceived(object sender, MessageEventArgs e) + { + //Cast message to ScsRemoteInvokeMessage and check it + var invokeMessage = e.Message as ScsRemoteInvokeMessage; + if (invokeMessage == null) + { + return; + } + + //Check client object. + if(_clientObject == null) + { + SendInvokeResponse(invokeMessage, null, new ScsRemoteException("Client does not wait for method invocations by server.")); + return; + } + + //Invoke method + object returnValue; + try + { + var type = _clientObject.GetType(); + var method = type.GetMethod(invokeMessage.MethodName); + returnValue = method.Invoke(_clientObject, invokeMessage.Parameters); + } + catch (TargetInvocationException ex) + { + var innerEx = ex.InnerException; + SendInvokeResponse(invokeMessage, null, new ScsRemoteException(innerEx.Message, innerEx)); + return; + } + catch (Exception ex) + { + SendInvokeResponse(invokeMessage, null, new ScsRemoteException(ex.Message, ex)); + return; + } + + //Send return value + SendInvokeResponse(invokeMessage, returnValue, null); + } + + /// <summary> + /// Sends response to the remote application that invoked a service method. + /// </summary> + /// <param name="requestMessage">Request message</param> + /// <param name="returnValue">Return value to send</param> + /// <param name="exception">Exception to send</param> + private void SendInvokeResponse(IScsMessage requestMessage, object returnValue, ScsRemoteException exception) + { + try + { + _requestReplyMessenger.SendMessage( + new ScsRemoteInvokeReturnMessage + { + RepliedMessageId = requestMessage.MessageId, + ReturnValue = returnValue, + RemoteException = exception + }); + } + catch + { + + } + } + + /// <summary> + /// Handles Connected event of _client object. + /// </summary> + /// <param name="sender">Source of object</param> + /// <param name="e">Event arguments</param> + private void Client_Connected(object sender, EventArgs e) + { + _requestReplyMessenger.Start(); + OnConnected(); + } + + /// <summary> + /// Handles Disconnected event of _client object. + /// </summary> + /// <param name="sender">Source of object</param> + /// <param name="e">Event arguments</param> + private void Client_Disconnected(object sender, EventArgs e) + { + _requestReplyMessenger.Stop(); + OnDisconnected(); + } + + #endregion + + #region Private methods + + /// <summary> + /// Raises Connected event. + /// </summary> + private void OnConnected() + { + var handler = Connected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + /// <summary> + /// Raises Disconnected event. + /// </summary> + private void OnDisconnected() + { + var handler = Disconnected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + #endregion + } +}
\ No newline at end of file diff --git a/src/Scs/Communication/ScsServices/Client/ScsServiceClientBuilder.cs b/src/Scs/Communication/ScsServices/Client/ScsServiceClientBuilder.cs new file mode 100644 index 0000000..ee9280e --- /dev/null +++ b/src/Scs/Communication/ScsServices/Client/ScsServiceClientBuilder.cs @@ -0,0 +1,36 @@ +using Hik.Communication.Scs.Communication.EndPoints; + +namespace Hik.Communication.ScsServices.Client +{ + /// <summary> + /// This class is used to build service clients to remotely invoke methods of a SCS service. + /// </summary> + public class ScsServiceClientBuilder + { + /// <summary> + /// Creates a client to connect to a SCS service. + /// </summary> + /// <typeparam name="T">Type of service interface for remote method call</typeparam> + /// <param name="endpoint">EndPoint of the server</param> + /// <param name="clientObject">Client-side object that handles remote method calls from server to client. + /// May be null if client has no methods to be invoked by server</param> + /// <returns>Created client object to connect to the server</returns> + public static IScsServiceClient<T> CreateClient<T>(ScsEndPoint endpoint, object clientObject = null) where T : class + { + return new ScsServiceClient<T>(endpoint.CreateClient(), clientObject); + } + + /// <summary> + /// Creates a client to connect to a SCS service. + /// </summary> + /// <typeparam name="T">Type of service interface for remote method call</typeparam> + /// <param name="endpointAddress">EndPoint address of the server</param> + /// <param name="clientObject">Client-side object that handles remote method calls from server to client. + /// May be null if client has no methods to be invoked by server</param> + /// <returns>Created client object to connect to the server</returns> + public static IScsServiceClient<T> CreateClient<T>(string endpointAddress, object clientObject = null) where T : class + { + return CreateClient<T>(ScsEndPoint.CreateEndPoint(endpointAddress), clientObject); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Communication/AutoConnectRemoteInvokeProxy.cs b/src/Scs/Communication/ScsServices/Communication/AutoConnectRemoteInvokeProxy.cs new file mode 100644 index 0000000..42b24c5 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Communication/AutoConnectRemoteInvokeProxy.cs @@ -0,0 +1,57 @@ +using System.Runtime.Remoting.Messaging; +using Hik.Communication.Scs.Client; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messengers; + +namespace Hik.Communication.ScsServices.Communication +{ + /// <summary> + /// This class extends RemoteInvokeProxy to provide auto connect/disconnect mechanism + /// if client is not connected to the server when a service method is called. + /// </summary> + /// <typeparam name="TProxy">Type of the proxy class/interface</typeparam> + /// <typeparam name="TMessenger">Type of the messenger object that is used to send/receive messages</typeparam> + internal class AutoConnectRemoteInvokeProxy<TProxy, TMessenger> : RemoteInvokeProxy<TProxy, TMessenger> where TMessenger : IMessenger + { + /// <summary> + /// Reference to the client object that is used to connect/disconnect. + /// </summary> + private readonly IConnectableClient _client; + + /// <summary> + /// Creates a new AutoConnectRemoteInvokeProxy object. + /// </summary> + /// <param name="clientMessenger">Messenger object that is used to send/receive messages</param> + /// <param name="client">Reference to the client object that is used to connect/disconnect</param> + public AutoConnectRemoteInvokeProxy(RequestReplyMessenger<TMessenger> clientMessenger, IConnectableClient client) + : base(clientMessenger) + { + _client = client; + } + + /// <summary> + /// Overrides message calls and translates them to messages to remote application. + /// </summary> + /// <param name="msg">Method invoke message (from RealProxy base class)</param> + /// <returns>Method invoke return message (to RealProxy base class)</returns> + public override IMessage Invoke(IMessage msg) + { + if (_client.CommunicationState == CommunicationStates.Connected) + { + //If already connected, behave as base class (RemoteInvokeProxy). + return base.Invoke(msg); + } + + //Connect, call method and finally disconnect + _client.Connect(); + try + { + return base.Invoke(msg); + } + finally + { + _client.Disconnect(); + } + } + } +} diff --git a/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteException.cs b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteException.cs new file mode 100644 index 0000000..3383368 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteException.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.Serialization; + +namespace Hik.Communication.ScsServices.Communication.Messages +{ + /// <summary> + /// Represents a SCS Remote Exception. + /// This exception is used to send an exception from an application to another application. + /// </summary> + [Serializable] + public class ScsRemoteException : Exception + { + /// <summary> + /// Contstructor. + /// </summary> + public ScsRemoteException() + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + public ScsRemoteException(SerializationInfo serializationInfo, StreamingContext context) + : base(serializationInfo, context) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + public ScsRemoteException(string message) + : base(message) + { + + } + + /// <summary> + /// Contstructor. + /// </summary> + /// <param name="message">Exception message</param> + /// <param name="innerException">Inner exception</param> + public ScsRemoteException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeMessage.cs b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeMessage.cs new file mode 100644 index 0000000..b482f3a --- /dev/null +++ b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeMessage.cs @@ -0,0 +1,36 @@ +using System; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.ScsServices.Communication.Messages +{ + /// <summary> + /// This message is sent to invoke a method of a remote application. + /// </summary> + [Serializable] + public class ScsRemoteInvokeMessage : ScsMessage + { + /// <summary> + /// Name of the remove service class. + /// </summary> + public string ServiceClassName { get; set; } + + /// <summary> + /// Method of remote application to invoke. + /// </summary> + public string MethodName { get; set; } + + /// <summary> + /// Parameters of method. + /// </summary> + public object[] Parameters { get; set; } + + /// <summary> + /// Represents this object as string. + /// </summary> + /// <returns>String representation of this object</returns> + public override string ToString() + { + return string.Format("ScsRemoteInvokeMessage: {0}.{1}(...)", ServiceClassName, MethodName); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeReturnMessage.cs b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeReturnMessage.cs new file mode 100644 index 0000000..7d4f14f --- /dev/null +++ b/src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeReturnMessage.cs @@ -0,0 +1,33 @@ +using System; +using Hik.Communication.Scs.Communication.Messages; + +namespace Hik.Communication.ScsServices.Communication.Messages +{ + /// <summary> + /// This message is sent as response message to a ScsRemoteInvokeMessage. + /// It is used to send return value of method invocation. + /// </summary> + [Serializable] + public class ScsRemoteInvokeReturnMessage : ScsMessage + { + /// <summary> + /// Return value of remote method invocation. + /// </summary> + public object ReturnValue { get; set; } + + /// <summary> + /// If any exception occured during method invocation, this field contains Exception object. + /// If no exception occured, this field is null. + /// </summary> + public ScsRemoteException RemoteException { get; set; } + + /// <summary> + /// Represents this object as string. + /// </summary> + /// <returns>String representation of this object</returns> + public override string ToString() + { + return string.Format("ScsRemoteInvokeReturnMessage: Returns {0}, Exception = {1}", ReturnValue, RemoteException); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Communication/RemoteInvokeProxy.cs b/src/Scs/Communication/ScsServices/Communication/RemoteInvokeProxy.cs new file mode 100644 index 0000000..b6f0caa --- /dev/null +++ b/src/Scs/Communication/ScsServices/Communication/RemoteInvokeProxy.cs @@ -0,0 +1,63 @@ +using System.Runtime.Remoting.Messaging; +using System.Runtime.Remoting.Proxies; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messengers; +using Hik.Communication.ScsServices.Communication.Messages; + +namespace Hik.Communication.ScsServices.Communication +{ + /// <summary> + /// This class is used to generate a dynamic proxy to invoke remote methods. + /// It translates method invocations to messaging. + /// </summary> + /// <typeparam name="TProxy">Type of the proxy class/interface</typeparam> + /// <typeparam name="TMessenger">Type of the messenger object that is used to send/receive messages</typeparam> + internal class RemoteInvokeProxy<TProxy, TMessenger> : RealProxy where TMessenger : IMessenger + { + /// <summary> + /// Messenger object that is used to send/receive messages. + /// </summary> + private readonly RequestReplyMessenger<TMessenger> _clientMessenger; + + /// <summary> + /// Creates a new RemoteInvokeProxy object. + /// </summary> + /// <param name="clientMessenger">Messenger object that is used to send/receive messages</param> + public RemoteInvokeProxy(RequestReplyMessenger<TMessenger> clientMessenger) + : base(typeof(TProxy)) + { + _clientMessenger = clientMessenger; + } + + /// <summary> + /// Overrides message calls and translates them to messages to remote application. + /// </summary> + /// <param name="msg">Method invoke message (from RealProxy base class)</param> + /// <returns>Method invoke return message (to RealProxy base class)</returns> + public override IMessage Invoke(IMessage msg) + { + var message = msg as IMethodCallMessage; + if (message == null) + { + return null; + } + + var requestMessage = new ScsRemoteInvokeMessage + { + ServiceClassName = typeof (TProxy).Name, + MethodName = message.MethodName, + Parameters = message.InArgs + }; + + var responseMessage = _clientMessenger.SendMessageAndWaitForResponse(requestMessage) as ScsRemoteInvokeReturnMessage; + if (responseMessage == null) + { + return null; + } + + return responseMessage.RemoteException != null + ? new ReturnMessage(responseMessage.RemoteException, message) + : new ReturnMessage(responseMessage.ReturnValue, null, 0, message.LogicalCallContext, message); + } + } +}
\ No newline at end of file diff --git a/src/Scs/Communication/ScsServices/Service/IScsServiceApplication.cs b/src/Scs/Communication/ScsServices/Service/IScsServiceApplication.cs new file mode 100644 index 0000000..8fdb2f0 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/IScsServiceApplication.cs @@ -0,0 +1,49 @@ +using System; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Represents a SCS Service Application that is used to construct and manage a SCS service. + /// </summary> + public interface IScsServiceApplication + { + /// <summary> + /// This event is raised when a new client connected to the service. + /// </summary> + event EventHandler<ServiceClientEventArgs> ClientConnected; + + /// <summary> + /// This event is raised when a client disconnected from the service. + /// </summary> + event EventHandler<ServiceClientEventArgs> ClientDisconnected; + + /// <summary> + /// Starts service application. + /// </summary> + void Start(); + + /// <summary> + /// Stops service application. + /// </summary> + void Stop(); + + /// <summary> + /// Adds a service object to this service application. + /// Only single service object can be added for a service interface type. + /// </summary> + /// <typeparam name="TServiceInterface">Service interface type</typeparam> + /// <typeparam name="TServiceClass">Service class type. Must be delivered from ScsService and must implement TServiceInterface.</typeparam> + /// <param name="service">An instance of TServiceClass.</param> + void AddService<TServiceInterface, TServiceClass>(TServiceClass service) + where TServiceClass : ScsService, TServiceInterface + where TServiceInterface : class; + + /// <summary> + /// Removes a previously added service object from this service application. + /// It removes object according to interface type. + /// </summary> + /// <typeparam name="TServiceInterface">Service interface type</typeparam> + /// <returns>True: removed. False: no service object with this interface</returns> + bool RemoveService<TServiceInterface>() where TServiceInterface : class; + } +} diff --git a/src/Scs/Communication/ScsServices/Service/IScsServiceClient.cs b/src/Scs/Communication/ScsServices/Service/IScsServiceClient.cs new file mode 100644 index 0000000..e60f91d --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/IScsServiceClient.cs @@ -0,0 +1,44 @@ +using System; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.EndPoints; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Represents a client that uses a SDS service. + /// </summary> + public interface IScsServiceClient + { + /// <summary> + /// This event is raised when client is disconnected from service. + /// </summary> + event EventHandler Disconnected; + + /// <summary> + /// Unique identifier for this client. + /// </summary> + long ClientId { get; } + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + ScsEndPoint RemoteEndPoint { get; } + + /// <summary> + /// Gets the communication state of the Client. + /// </summary> + CommunicationStates CommunicationState { get; } + + /// <summary> + /// Closes client connection. + /// </summary> + void Disconnect(); + + /// <summary> + /// Gets the client proxy interface that provides calling client methods remotely. + /// </summary> + /// <typeparam name="T">Type of client interface</typeparam> + /// <returns>Client interface</returns> + T GetClientProxy<T>() where T : class; + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsService.cs b/src/Scs/Communication/ScsServices/Service/ScsService.cs new file mode 100644 index 0000000..2321343 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsService.cs @@ -0,0 +1,43 @@ +using System; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Base class for all services that is serviced by IScsServiceApplication. + /// A class must be derived from ScsService to serve as a SCS service. + /// </summary> + public abstract class ScsService + { + /// <summary> + /// The current client for a thread that called service method. + /// </summary> + [ThreadStatic] + private static IScsServiceClient _currentClient; + + /// <summary> + /// Gets the current client which called this service method. + /// </summary> + /// <remarks> + /// This property is thread-safe, if returns correct client when + /// called in a service method if the method is called by SCS system, + /// else throws exception. + /// </remarks> + protected internal IScsServiceClient CurrentClient + { + get + { + if (_currentClient == null) + { + throw new Exception("Client channel can not be obtained. CurrentClient property must be called by the thread which runs the service method."); + } + + return _currentClient; + } + + internal set + { + _currentClient = value; + } + } + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsServiceApplication.cs b/src/Scs/Communication/ScsServices/Service/ScsServiceApplication.cs new file mode 100644 index 0000000..2faa20b --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsServiceApplication.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Hik.Collections; +using Hik.Communication.Scs.Communication.Messages; +using Hik.Communication.Scs.Communication.Messengers; +using Hik.Communication.Scs.Server; +using Hik.Communication.ScsServices.Communication.Messages; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Implements IScsServiceApplication and provides all functionallity. + /// </summary> + internal class ScsServiceApplication : IScsServiceApplication + { + #region Public events + + /// <summary> + /// This event is raised when a new client connected to the service. + /// </summary> + public event EventHandler<ServiceClientEventArgs> ClientConnected; + + /// <summary> + /// This event is raised when a client disconnected from the service. + /// </summary> + public event EventHandler<ServiceClientEventArgs> ClientDisconnected; + + #endregion + + #region Private fields + + /// <summary> + /// Underlying IScsServer object to accept and manage client connections. + /// </summary> + private readonly IScsServer _scsServer; + + /// <summary> + /// User service objects that is used to invoke incoming method invocation requests. + /// Key: Service interface type's name. + /// Value: Service object. + /// </summary> + private readonly ThreadSafeSortedList<string, ServiceObject> _serviceObjects; + + /// <summary> + /// All connected clients to service. + /// Key: Client's unique Id. + /// Value: Reference to the client. + /// </summary> + private readonly ThreadSafeSortedList<long, IScsServiceClient> _serviceClients; + + #endregion + + #region Constructors + + /// <summary> + /// Creates a new ScsServiceApplication object. + /// </summary> + /// <param name="scsServer">Underlying IScsServer object to accept and manage client connections</param> + /// <exception cref="ArgumentNullException">Throws ArgumentNullException if scsServer argument is null</exception> + public ScsServiceApplication(IScsServer scsServer) + { + if (scsServer == null) + { + throw new ArgumentNullException("scsServer"); + } + + _scsServer = scsServer; + _scsServer.ClientConnected += ScsServer_ClientConnected; + _scsServer.ClientDisconnected += ScsServer_ClientDisconnected; + _serviceObjects = new ThreadSafeSortedList<string, ServiceObject>(); + _serviceClients = new ThreadSafeSortedList<long, IScsServiceClient>(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Starts service application. + /// </summary> + public void Start() + { + _scsServer.Start(); + } + + /// <summary> + /// Stops service application. + /// </summary> + public void Stop() + { + _scsServer.Stop(); + } + + /// <summary> + /// Adds a service object to this service application. + /// Only single service object can be added for a service interface type. + /// </summary> + /// <typeparam name="TServiceInterface">Service interface type</typeparam> + /// <typeparam name="TServiceClass">Service class type. Must be delivered from ScsService and must implement TServiceInterface.</typeparam> + /// <param name="service">An instance of TServiceClass.</param> + /// <exception cref="ArgumentNullException">Throws ArgumentNullException if service argument is null</exception> + /// <exception cref="Exception">Throws Exception if service is already added before</exception> + public void AddService<TServiceInterface, TServiceClass>(TServiceClass service) + where TServiceClass : ScsService, TServiceInterface + where TServiceInterface : class + { + if (service == null) + { + throw new ArgumentNullException("service"); + } + + var type = typeof(TServiceInterface); + if(_serviceObjects[type.Name] != null) + { + throw new Exception("Service '" + type.Name + "' is already added before."); + } + + _serviceObjects[type.Name] = new ServiceObject(type, service); + } + + /// <summary> + /// Removes a previously added service object from this service application. + /// It removes object according to interface type. + /// </summary> + /// <typeparam name="TServiceInterface">Service interface type</typeparam> + /// <returns>True: removed. False: no service object with this interface</returns> + public bool RemoveService<TServiceInterface>() + where TServiceInterface : class + { + return _serviceObjects.Remove(typeof(TServiceInterface).Name); + } + + #endregion + + #region Private methods + + /// <summary> + /// Handles ClientConnected event of _scsServer object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void ScsServer_ClientConnected(object sender, ServerClientEventArgs e) + { + var requestReplyMessenger = new RequestReplyMessenger<IScsServerClient>(e.Client); + requestReplyMessenger.MessageReceived += Client_MessageReceived; + requestReplyMessenger.Start(); + + var serviceClient = ScsServiceClientFactory.CreateServiceClient(e.Client, requestReplyMessenger); + _serviceClients[serviceClient.ClientId] = serviceClient; + OnClientConnected(serviceClient); + } + + /// <summary> + /// Handles ClientDisconnected event of _scsServer object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void ScsServer_ClientDisconnected(object sender, ServerClientEventArgs e) + { + var serviceClient = _serviceClients[e.Client.ClientId]; + if (serviceClient == null) + { + return; + } + + _serviceClients.Remove(e.Client.ClientId); + OnClientDisconnected(serviceClient); + } + + /// <summary> + /// Handles MessageReceived events of all clients, evaluates each message, + /// finds appropriate service object and invokes appropriate method. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void Client_MessageReceived(object sender, MessageEventArgs e) + { + //Get RequestReplyMessenger object (sender of event) to get client + var requestReplyMessenger = (RequestReplyMessenger<IScsServerClient>) sender; + + //Cast message to ScsRemoteInvokeMessage and check it + var invokeMessage = e.Message as ScsRemoteInvokeMessage; + if (invokeMessage == null) + { + return; + } + + try + { + //Get client object + var client = _serviceClients[requestReplyMessenger.Messenger.ClientId]; + if (client == null) + { + requestReplyMessenger.Messenger.Disconnect(); + return; + } + + //Get service object + var serviceObject = _serviceObjects[invokeMessage.ServiceClassName]; + if (serviceObject == null) + { + SendInvokeResponse(requestReplyMessenger, invokeMessage, null, new ScsRemoteException("There is no service with name '" + invokeMessage.ServiceClassName + "'")); + return; + } + + //Invoke method + try + { + object returnValue; + //Set client to service, so user service can get client + //in service method using CurrentClient property. + serviceObject.Service.CurrentClient = client; + try + { + returnValue = serviceObject.InvokeMethod(invokeMessage.MethodName, invokeMessage.Parameters); + } + finally + { + //Set CurrentClient as null since method call completed + serviceObject.Service.CurrentClient = null; + } + + //Send method invocation return value to the client + SendInvokeResponse(requestReplyMessenger, invokeMessage, returnValue, null); + } + catch (TargetInvocationException ex) + { + var innerEx = ex.InnerException; + SendInvokeResponse(requestReplyMessenger, invokeMessage, null, new ScsRemoteException(innerEx.Message + Environment.NewLine + "Service Version: " + serviceObject.ServiceAttribute.Version, innerEx)); + return; + } + catch (Exception ex) + { + SendInvokeResponse(requestReplyMessenger, invokeMessage, null, new ScsRemoteException(ex.Message + Environment.NewLine + "Service Version: " + serviceObject.ServiceAttribute.Version, ex)); + return; + } + } + catch (Exception ex) + { + SendInvokeResponse(requestReplyMessenger, invokeMessage, null, new ScsRemoteException("An error occured during remote service method call.", ex)); + return; + } + } + + /// <summary> + /// Sends response to the remote application that invoked a service method. + /// </summary> + /// <param name="client">Client that sent invoke message</param> + /// <param name="requestMessage">Request message</param> + /// <param name="returnValue">Return value to send</param> + /// <param name="exception">Exception to send</param> + private static void SendInvokeResponse(IMessenger client, IScsMessage requestMessage, object returnValue, ScsRemoteException exception) + { + try + { + client.SendMessage( + new ScsRemoteInvokeReturnMessage + { + RepliedMessageId = requestMessage.MessageId, + ReturnValue = returnValue, + RemoteException = exception + }); + } + catch + { + + } + } + + /// <summary> + /// Raises ClientConnected event. + /// </summary> + /// <param name="client"></param> + private void OnClientConnected(IScsServiceClient client) + { + var handler = ClientConnected; + if (handler != null) + { + handler(this, new ServiceClientEventArgs(client)); + } + } + + /// <summary> + /// Raises ClientDisconnected event. + /// </summary> + /// <param name="client"></param> + private void OnClientDisconnected(IScsServiceClient client) + { + var handler = ClientDisconnected; + if (handler != null) + { + handler(this, new ServiceClientEventArgs(client)); + } + } + + #endregion + + #region ServiceObject class + + /// <summary> + /// Represents a user service object. + /// It is used to invoke methods on a ScsService object. + /// </summary> + private sealed class ServiceObject + { + /// <summary> + /// The service object that is used to invoke methods on. + /// </summary> + public ScsService Service { get; private set; } + + /// <summary> + /// ScsService attribute of Service object's class. + /// </summary> + public ScsServiceAttribute ServiceAttribute { get; private set; } + + /// <summary> + /// This collection stores a list of all methods of service object. + /// Key: Method name + /// Value: Informations about method. + /// </summary> + private readonly SortedList<string, MethodInfo> _methods; + + /// <summary> + /// Creates a new ServiceObject. + /// </summary> + /// <param name="serviceInterfaceType">Type of service interface</param> + /// <param name="service">The service object that is used to invoke methods on</param> + public ServiceObject(Type serviceInterfaceType, ScsService service) + { + Service = service; + var classAttributes = serviceInterfaceType.GetCustomAttributes(typeof(ScsServiceAttribute), true); + if (classAttributes.Length <= 0) + { + throw new Exception("Service interface (" + serviceInterfaceType.Name + ") must has ScsService attribute."); + } + + ServiceAttribute = classAttributes[0] as ScsServiceAttribute; + _methods = new SortedList<string, MethodInfo>(); + foreach (var methodInfo in serviceInterfaceType.GetMethods()) + { + _methods.Add(methodInfo.Name, methodInfo); + } + } + + /// <summary> + /// Invokes a method of Service object. + /// </summary> + /// <param name="methodName">Name of the method to invoke</param> + /// <param name="parameters">Parameters of method</param> + /// <returns>Return value of method</returns> + public object InvokeMethod(string methodName, params object[] parameters) + { + //Check if there is a method with name methodName + if (!_methods.ContainsKey(methodName)) + { + throw new Exception("There is not a method with name '" + methodName + "' in service class."); + } + + //Get method + var method = _methods[methodName]; + + //Invoke method and return invoke result + return method.Invoke(Service, parameters); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsServiceAttribute.cs b/src/Scs/Communication/ScsServices/Service/ScsServiceAttribute.cs new file mode 100644 index 0000000..e73a2d4 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsServiceAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Any SCS Service interface class must has this attribute. + /// </summary> + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)] + public class ScsServiceAttribute : Attribute + { + /// <summary> + /// Service Version. This property can be used to indicate the code version. + /// This value is sent to client application on an exception, so, client application can know that service version is changed. + /// Default value: NO_VERSION. + /// </summary> + public string Version { get; set; } + + /// <summary> + /// Creates a new ScsServiceAttribute object. + /// </summary> + public ScsServiceAttribute() + { + Version = "NO_VERSION"; + } + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsServiceBuilder.cs b/src/Scs/Communication/ScsServices/Service/ScsServiceBuilder.cs new file mode 100644 index 0000000..1860532 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsServiceBuilder.cs @@ -0,0 +1,21 @@ +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Server; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// This class is used to build ScsService applications. + /// </summary> + public static class ScsServiceBuilder + { + /// <summary> + /// Creates a new SCS Service application using an EndPoint. + /// </summary> + /// <param name="endPoint">EndPoint that represents address of the service</param> + /// <returns>Created SCS service application</returns> + public static IScsServiceApplication CreateService(ScsEndPoint endPoint) + { + return new ScsServiceApplication(ScsServerFactory.CreateServer(endPoint)); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsServiceClient.cs b/src/Scs/Communication/ScsServices/Service/ScsServiceClient.cs new file mode 100644 index 0000000..eda5caa --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsServiceClient.cs @@ -0,0 +1,146 @@ +using System; +using System.Runtime.Remoting.Proxies; +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.EndPoints; +using Hik.Communication.Scs.Communication.Messengers; +using Hik.Communication.Scs.Server; +using Hik.Communication.ScsServices.Communication; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Implements IScsServiceClient. + /// It is used to manage and monitor a service client. + /// </summary> + internal class ScsServiceClient : IScsServiceClient + { + #region Public events + + /// <summary> + /// This event is raised when this client is disconnected from server. + /// </summary> + public event EventHandler Disconnected; + + #endregion + + #region Public properties + + /// <summary> + /// Unique identifier for this client. + /// </summary> + public long ClientId + { + get { return _serverClient.ClientId; } + } + + ///<summary> + /// Gets endpoint of remote application. + ///</summary> + public ScsEndPoint RemoteEndPoint + { + get { return _serverClient.RemoteEndPoint; } + } + + /// <summary> + /// Gets the communication state of the Client. + /// </summary> + public CommunicationStates CommunicationState + { + get + { + return _serverClient.CommunicationState; + } + } + + #endregion + + #region Private fields + + /// <summary> + /// Reference to underlying IScsServerClient object. + /// </summary> + private readonly IScsServerClient _serverClient; + + /// <summary> + /// This object is used to send messages to client. + /// </summary> + private readonly RequestReplyMessenger<IScsServerClient> _requestReplyMessenger; + + /// <summary> + /// Last created proxy object to invoke remote medhods. + /// </summary> + private RealProxy _realProxy; + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new ScsServiceClient object. + /// </summary> + /// <param name="serverClient">Reference to underlying IScsServerClient object</param> + /// <param name="requestReplyMessenger">RequestReplyMessenger to send messages</param> + public ScsServiceClient(IScsServerClient serverClient, RequestReplyMessenger<IScsServerClient> requestReplyMessenger) + { + _serverClient = serverClient; + _serverClient.Disconnected += Client_Disconnected; + _requestReplyMessenger = requestReplyMessenger; + } + + #endregion + + #region Public methods + + /// <summary> + /// Closes client connection. + /// </summary> + public void Disconnect() + { + _serverClient.Disconnect(); + } + + /// <summary> + /// Gets the client proxy interface that provides calling client methods remotely. + /// </summary> + /// <typeparam name="T">Type of client interface</typeparam> + /// <returns>Client interface</returns> + public T GetClientProxy<T>() where T : class + { + _realProxy = new RemoteInvokeProxy<T, IScsServerClient>(_requestReplyMessenger); + return (T)_realProxy.GetTransparentProxy(); + } + + #endregion + + #region Private methods + + /// <summary> + /// Handles disconnect event of _serverClient object. + /// </summary> + /// <param name="sender">Source of event</param> + /// <param name="e">Event arguments</param> + private void Client_Disconnected(object sender, EventArgs e) + { + _requestReplyMessenger.Stop(); + OnDisconnected(); + } + + #endregion + + #region Event raising methods + + /// <summary> + /// Raises Disconnected event. + /// </summary> + private void OnDisconnected() + { + var handler = Disconnected; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + #endregion + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ScsServiceClientFactory.cs b/src/Scs/Communication/ScsServices/Service/ScsServiceClientFactory.cs new file mode 100644 index 0000000..abbd955 --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ScsServiceClientFactory.cs @@ -0,0 +1,23 @@ +using Hik.Communication.Scs.Communication; +using Hik.Communication.Scs.Communication.Messengers; +using Hik.Communication.Scs.Server; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// This class is used to create service client objects that is used in server-side. + /// </summary> + internal static class ScsServiceClientFactory + { + /// <summary> + /// Creates a new service client object that is used in server-side. + /// </summary> + /// <param name="serverClient">Underlying server client object</param> + /// <param name="requestReplyMessenger">RequestReplyMessenger object to send/receive messages over serverClient</param> + /// <returns></returns> + public static IScsServiceClient CreateServiceClient(IScsServerClient serverClient, RequestReplyMessenger<IScsServerClient> requestReplyMessenger) + { + return new ScsServiceClient(serverClient, requestReplyMessenger); + } + } +} diff --git a/src/Scs/Communication/ScsServices/Service/ServiceClientEventArgs.cs b/src/Scs/Communication/ScsServices/Service/ServiceClientEventArgs.cs new file mode 100644 index 0000000..deab72a --- /dev/null +++ b/src/Scs/Communication/ScsServices/Service/ServiceClientEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Hik.Communication.ScsServices.Service +{ + /// <summary> + /// Stores service client informations to be used by an event. + /// </summary> + public class ServiceClientEventArgs : EventArgs + { + /// <summary> + /// Client that is associated with this event. + /// </summary> + public IScsServiceClient Client { get; private set; } + + /// <summary> + /// Creates a new ServiceClientEventArgs object. + /// </summary> + /// <param name="client">Client that is associated with this event</param> + public ServiceClientEventArgs(IScsServiceClient client) + { + Client = client; + } + } +} diff --git a/src/Scs/Diagrams/ChannelsDiagram.cd b/src/Scs/Diagrams/ChannelsDiagram.cd new file mode 100644 index 0000000..55ddc50 --- /dev/null +++ b/src/Scs/Diagrams/ChannelsDiagram.cd @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Communication.Channels.CommunicationChannelBase" Collapsed="true"> + <Position X="4" Y="0.75" Width="2" /> + <TypeIdentifier> + <HashCode>AAQAAAIAQCAAIC4QACAAAAQAAAAgAAAAAAEAAgAAAgA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\CommunicationChannelBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Channels.Tcp.TcpCommunicationChannel" Collapsed="true"> + <Position X="4" Y="1.75" Width="2" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + <Compartment Name="Methods" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AAQAAAAgAAABAgIAAAAAAAAAAAAgAAAAAAMAAABgAQA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\Tcp\TcpCommunicationChannel.cs</FileName> + </TypeIdentifier> + </Class> + <Interface Name="Hik.Communication.Scs.Communication.Channels.ICommunicationChannel"> + <Position X="0.5" Y="3.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAQAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAEAAgAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\ICommunicationChannel.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.Scs.Communication.IMessenger"> + <Position X="0.5" Y="0.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAIAQQACAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\IMessenger.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ConnListenerDiagram.cd b/src/Scs/Diagrams/ConnListenerDiagram.cd new file mode 100644 index 0000000..b84ed96 --- /dev/null +++ b/src/Scs/Diagrams/ConnListenerDiagram.cd @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Communication.Channels.ConnectionListenerBase" Collapsed="true"> + <Position X="3.75" Y="0.75" Width="2" /> + <TypeIdentifier> + <HashCode>EAAAAACAACAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\ConnectionListenerBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Channels.Tcp.TcpConnectionListener" Collapsed="true"> + <Position X="3.75" Y="1.75" Width="2" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + <Compartment Name="Methods" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>BAAAAQAAACAAAAACAAABAAAACAAAAAAAIAAAAEAAAQA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\Tcp\TcpConnectionListener.cs</FileName> + </TypeIdentifier> + </Class> + <Interface Name="Hik.Communication.Scs.Communication.Channels.IConnectionListener"> + <Position X="0.5" Y="0.75" Width="3" /> + <TypeIdentifier> + <HashCode>EAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Channels\IConnectionListener.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/EndPointsDiagram.cd b/src/Scs/Diagrams/EndPointsDiagram.cd new file mode 100644 index 0000000..cf37040 --- /dev/null +++ b/src/Scs/Diagrams/EndPointsDiagram.cd @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Communication.EndPoints.ScsEndPoint"> + <Position X="1.25" Y="1.25" Width="3.75" /> + <TypeIdentifier> + <HashCode>AAACAAAAAAAAAAAAAAAAAAAAAABABAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\EndPoints\ScsEndPoint.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.Scs.Communication.EndPoints.Tcp.ScsTcpEndPoint"> + <Position X="5.75" Y="1.25" Width="3" /> + <InheritanceLine Type="Hik.Communication.Scs.Communication.EndPoints.ScsEndPoint" FixedFromPoint="true"> + <Path> + <Point X="5" Y="1.875" /> + <Point X="5.75" Y="1.875" /> + </Path> + </InheritanceLine> + <TypeIdentifier> + <HashCode>AAACAAAAAAAAAAAAAACAAAAAAABAAAAAAAAAAAAAAEA=</HashCode> + <FileName>Communication\Scs\Communication\EndPoints\Tcp\ScsTcpEndPoint.cs</FileName> + </TypeIdentifier> + </Class> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/MessageObjectsDiagram.cd b/src/Scs/Diagrams/MessageObjectsDiagram.cd new file mode 100644 index 0000000..e7c64fd --- /dev/null +++ b/src/Scs/Diagrams/MessageObjectsDiagram.cd @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="NameAndType"> + <Class Name="Hik.Communication.Scs.Communication.Messages.ScsTextMessage"> + <Position X="5" Y="3.5" Width="1.5" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\ScsTextMessage.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Messages.PingMessage"> + <Position X="2.75" Y="3.5" Width="1.5" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\PingMessage.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Messages.ScsMessage"> + <Position X="4.75" Y="1" Width="2" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAgAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\ScsMessage.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Messages.ScsRawDataMessage"> + <Position X="7.25" Y="3.5" Width="1.75" /> + <TypeIdentifier> + <HashCode>BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\ScsRawDataMessage.cs</FileName> + </TypeIdentifier> + </Class> + <Interface Name="Hik.Communication.Scs.Communication.Messages.IScsMessage"> + <Position X="2.75" Y="1" Width="1.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAgAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\IScsMessage.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/RequestReplyMessengerDiagram.cd b/src/Scs/Diagrams/RequestReplyMessengerDiagram.cd new file mode 100644 index 0000000..79a0471 --- /dev/null +++ b/src/Scs/Diagrams/RequestReplyMessengerDiagram.cd @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Communication.Messengers.RequestReplyMessenger<T>"> + <Position X="0.5" Y="3.75" Width="5.5" /> + <Members> + <Method Name="Dispose" Hidden="true" /> + <Property Name="LastReceivedMessageTime" Hidden="true" /> + <Property Name="LastSentMessageTime" Hidden="true" /> + <Property Name="Messenger" Hidden="true" /> + <Method Name="Messenger_MessageReceived" Hidden="true" /> + <Method Name="Messenger_MessageSent" Hidden="true" /> + <Method Name="OnMessageReceived" Hidden="true" /> + <Method Name="OnMessageSent" Hidden="true" /> + <Method Name="RequestReplyMessenger" Hidden="true" /> + <Property Name="WireProtocol" Hidden="true" /> + </Members> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + <Compartment Name="Events" Collapsed="true" /> + </Compartments> + <NestedTypes> + <Class Name="Hik.Communication.Scs.Communication.Messengers.RequestReplyMessenger<T>.WaitingMessage" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>Communication\Scs\Communication\Messengers\RequestReplyMessenger.cs</NewMemberFileName> + </TypeIdentifier> + </Class> + <Enum Name="Hik.Communication.Scs.Communication.Messengers.RequestReplyMessenger<T>.WaitingMessageStates" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>Communication\Scs\Communication\Messengers\RequestReplyMessenger.cs</NewMemberFileName> + </TypeIdentifier> + </Enum> + </NestedTypes> + <TypeIdentifier> + <HashCode>ABAEABBgQDAAICYQACAAACQAAAAAAEIAIAAAAAAAAgg=</HashCode> + <FileName>Communication\Scs\Communication\Messengers\RequestReplyMessenger.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.Scs.Communication.Messengers.IMessenger"> + <Position X="0.5" Y="0.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAIAQQACAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Messengers\IMessenger.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/RmiMessagesDiagram.cd b/src/Scs/Diagrams/RmiMessagesDiagram.cd new file mode 100644 index 0000000..1e8fdfb --- /dev/null +++ b/src/Scs/Diagrams/RmiMessagesDiagram.cd @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.ScsServices.Communication.Messages.ScsRemoteInvokeMessage"> + <Position X="0.5" Y="2.5" Width="2.75" /> + <Compartments> + <Compartment Name="Methods" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAIACAAAAAAA=</HashCode> + <FileName>Communication\ScsServices\Communication\Messages\ScsRemoteInvokeMessage.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.ScsServices.Communication.Messages.ScsRemoteInvokeReturnMessage"> + <Position X="3.5" Y="2.5" Width="3.5" /> + <Compartments> + <Compartment Name="Methods" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAEEAAAAAAAAAgAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\ScsServices\Communication\Messages\ScsRemoteInvokeReturnMessage.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Messages.ScsMessage"> + <Position X="2" Y="0.5" Width="2.75" /> + <Compartments> + <Compartment Name="Methods" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAgAA=</HashCode> + <FileName>Communication\Scs\Communication\Messages\ScsMessage.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ScsClientDiagram.cd b/src/Scs/Diagrams/ScsClientDiagram.cd new file mode 100644 index 0000000..42f4042 --- /dev/null +++ b/src/Scs/Diagrams/ScsClientDiagram.cd @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Client.ScsClientBase" Collapsed="true"> + <Position X="1" Y="4.75" Width="4" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + <Compartment Name="Properties" Collapsed="true" /> + <Compartment Name="Events" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>CAQAAAIAQCABZCwQACAAAKQAAAAQAAIAAQEAAgAEQgA=</HashCode> + <FileName>Communication\Scs\Client\ScsClientBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="Hik.Communication.Scs.Client.Tcp.ScsTcpClient"> + <Position X="1" Y="5.75" Width="4" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAQAAAA=</HashCode> + <FileName>Communication\Scs\Client\Tcp\ScsTcpClient.cs</FileName> + </TypeIdentifier> + </Class> + <Interface Name="Hik.Communication.Scs.Client.IConnectableClient"> + <Position X="3.5" Y="0.5" Width="2" /> + <TypeIdentifier> + <HashCode>AAQAAAAAAAAAQAgAAAAAAIAAAAAAAAAAAAAAAgAEAAA=</HashCode> + <FileName>Communication\Scs\Client\IConnectableClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.Scs.Client.IScsClient" Collapsed="true"> + <Position X="2" Y="3.75" Width="2" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Client\IScsClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.Scs.Communication.IMessenger"> + <Position X="0.5" Y="0.5" Width="2.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAIAQQACAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\IMessenger.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ScsServerClientDiagram.cd b/src/Scs/Diagrams/ScsServerClientDiagram.cd new file mode 100644 index 0000000..67c39cf --- /dev/null +++ b/src/Scs/Diagrams/ScsServerClientDiagram.cd @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Server.ScsServerClient" Collapsed="true"> + <Position X="4" Y="3.5" Width="3" /> + <TypeIdentifier> + <HashCode>CAQAAAIAQAAAICwQgCAAACQAAAAQAAAAAAEAAgAAAgA=</HashCode> + <FileName>Communication\Scs\Server\ScsServerClient.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.Scs.Server.IScsServerClient"> + <Position X="4" Y="0.5" Width="3" /> + <TypeIdentifier> + <HashCode>AAQAAAAAAAAAAAgAgAAAAAAAAAAAAAAAAAEAAgAAAAA=</HashCode> + <FileName>Communication\Scs\Server\IScsServerClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.Scs.Communication.IMessenger"> + <Position X="0.5" Y="0.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAIAQQACAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\IMessenger.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ScsServerDiagram.cd b/src/Scs/Diagrams/ScsServerDiagram.cd new file mode 100644 index 0000000..5a35b95 --- /dev/null +++ b/src/Scs/Diagrams/ScsServerDiagram.cd @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Server.Tcp.ScsTcpServer"> + <Position X="0.5" Y="4.5" Width="3.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Server\Tcp\ScsTcpServer.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="Hik.Communication.Scs.Server.ScsServerBase" Collapsed="true"> + <Position X="0.5" Y="3.5" Width="3.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAGEIBAAAAKAAQAAAAAAAAAAAJAAAIAAAACA=</HashCode> + <FileName>Communication\Scs\Server\ScsServerBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.Scs.Server.IScsServer"> + <Position X="0.5" Y="0.5" Width="3.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAACAAAAAAACAAAAAAAAAAAAAAJAAAIAAAACA=</HashCode> + <FileName>Communication\Scs\Server\IScsServer.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ScsServiceClientDiagram.cd b/src/Scs/Diagrams/ScsServiceClientDiagram.cd new file mode 100644 index 0000000..5cb9470 --- /dev/null +++ b/src/Scs/Diagrams/ScsServiceClientDiagram.cd @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.ScsServices.Client.ScsServiceClient<T>" Collapsed="true"> + <Position X="4.25" Y="2.25" Width="2" /> + <TypeIdentifier> + <HashCode>QAQABAIAECEAQEgAAAgAAIAAAAAAABIAAAAAAgAAAAA=</HashCode> + <FileName>Communication\ScsServices\Client\ScsServiceClient.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.Scs.Client.IConnectableClient"> + <Position X="0.5" Y="0.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAQAAAAAAAAAQAgAAAAAAIAAAAAAAAAAAAAAAgAAAAA=</HashCode> + <FileName>Communication\Scs\Client\IConnectableClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.ScsServices.Client.IScsServiceClient<T>"> + <Position X="4.25" Y="0.5" Width="2" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAA=</HashCode> + <FileName>Communication\ScsServices\Client\IScsServiceClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ScsServiceDiagram.cd b/src/Scs/Diagrams/ScsServiceDiagram.cd new file mode 100644 index 0000000..6de8205 --- /dev/null +++ b/src/Scs/Diagrams/ScsServiceDiagram.cd @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.ScsServices.Service.ScsServiceApplication" Collapsed="true"> + <Position X="0.5" Y="3.25" Width="2.5" /> + <NestedTypes> + <Class Name="Hik.Communication.ScsServices.Service.ScsServiceApplication.ServiceObject" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>Communication\ScsServices\Service\ScsServiceApplication.cs</NewMemberFileName> + </TypeIdentifier> + </Class> + </NestedTypes> + <TypeIdentifier> + <HashCode>AAAAAAAAAGAABCIAACAAAQAAAAAAAAKAJAAAIDAAAAQ=</HashCode> + <FileName>Communication\ScsServices\Service\ScsServiceApplication.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.ScsServices.Service.IScsServiceApplication"> + <Position X="0.5" Y="0.5" Width="4.25" /> + <TypeIdentifier> + <HashCode>AAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAJAAAICAAAAQ=</HashCode> + <FileName>Communication\ScsServices\Service\IScsServiceApplication.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/ServiceSideClientDiagram.cd b/src/Scs/Diagrams/ServiceSideClientDiagram.cd new file mode 100644 index 0000000..db73465 --- /dev/null +++ b/src/Scs/Diagrams/ServiceSideClientDiagram.cd @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.ScsServices.Service.ScsServiceClient" Collapsed="true"> + <Position X="4" Y="0.75" Width="1.5" /> + <TypeIdentifier> + <HashCode>AAQABAIAAAEgAAgAgAAAAAAAAAAAABAAAAAAAgAAAAA=</HashCode> + <FileName>Communication\ScsServices\Service\ScsServiceClient.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.ScsServices.Service.IScsServiceClient"> + <Position X="0.5" Y="0.5" Width="3.25" /> + <TypeIdentifier> + <HashCode>AAQAAAAAAAAAAAgAgAAAAAAAAAAAAAAAAAAAAgAAAAA=</HashCode> + <FileName>Communication\ScsServices\Service\IScsServiceClient.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Diagrams/WireProtocolsDiagram.cd b/src/Scs/Diagrams/WireProtocolsDiagram.cd new file mode 100644 index 0000000..ab569c4 --- /dev/null +++ b/src/Scs/Diagrams/WireProtocolsDiagram.cd @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature"> + <Class Name="Hik.Communication.Scs.Communication.Protocols.BinarySerialization.BinarySerializationProtocol" Collapsed="true"> + <Position X="4.75" Y="0.5" Width="2.5" /> + <NestedTypes> + <Class Name="Hik.Communication.Scs.Communication.Protocols.BinarySerialization.BinarySerializationProtocol.DeserializationAppDomainBinder" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>Communication\Scs\Communication\Protocols\BinarySerialization\BinarySerializationProtocol.cs</NewMemberFileName> + </TypeIdentifier> + </Class> + </NestedTypes> + <TypeIdentifier> + <HashCode>AACAAAACAAAABAABQQMAAIAAAAAACAAAAAAQAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Protocols\BinarySerialization\BinarySerializationProtocol.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="Hik.Communication.Scs.Communication.Protocols.BinarySerialization.BinarySerializationProtocolFactory" Collapsed="true"> + <Position X="4.75" Y="2.25" Width="2.5" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Protocols\BinarySerialization\BinarySerializationProtocolFactory.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Interface Name="Hik.Communication.Scs.Communication.Protocols.IScsWireProtocol"> + <Position X="0.5" Y="0.5" Width="4" /> + <TypeIdentifier> + <HashCode>AACAAAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Protocols\IScsWireProtocol.cs</FileName> + </TypeIdentifier> + </Interface> + <Interface Name="Hik.Communication.Scs.Communication.Protocols.IScsWireProtocolFactory"> + <Position X="0.5" Y="2.25" Width="4" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>Communication\Scs\Communication\Protocols\IScsWireProtocolFactory.cs</FileName> + </TypeIdentifier> + </Interface> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/Scs/Properties/AssemblyInfo.cs b/src/Scs/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6601ece --- /dev/null +++ b/src/Scs/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Scs")] +[assembly: AssemblyDescription("Simple Client/Server Framework")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Halil ibrahim Kalkan")] +[assembly: AssemblyProduct("Scs")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("20dfc06b-5632-4956-beaf-a5e0bb126bb5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.0.1")] +[assembly: AssemblyFileVersion("1.1.0.1")] diff --git a/src/Scs/Scs.csproj b/src/Scs/Scs.csproj new file mode 100644 index 0000000..66bfd66 --- /dev/null +++ b/src/Scs/Scs.csproj @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.30703</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{0DC81B09-3ABF-4BB3-8C08-4E8EE4432BDC}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Hik</RootNamespace> + <AssemblyName>Scs</AssemblyName> + <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <TargetFrameworkProfile>Client</TargetFrameworkProfile> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <DocumentationFile>bin\Debug\Scs.XML</DocumentationFile> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <DocumentationFile>bin\Release\Scs.XML</DocumentationFile> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Collections\ThreadSafeSortedList.cs" /> + <Compile Include="Communication\ScsServices\Client\IScsServiceClient.cs" /> + <Compile Include="Communication\ScsServices\Client\ScsServiceClient.cs" /> + <Compile Include="Communication\ScsServices\Client\ScsServiceClientBuilder.cs" /> + <Compile Include="Communication\ScsServices\Communication\AutoConnectRemoteInvokeProxy.cs" /> + <Compile Include="Communication\ScsServices\Communication\Messages\ScsRemoteException.cs" /> + <Compile Include="Communication\ScsServices\Communication\Messages\ScsRemoteInvokeMessage.cs" /> + <Compile Include="Communication\ScsServices\Communication\Messages\ScsRemoteInvokeReturnMessage.cs" /> + <Compile Include="Communication\ScsServices\Communication\RemoteInvokeProxy.cs" /> + <Compile Include="Communication\Scs\Communication\Messengers\RequestReplyMessenger.cs" /> + <Compile Include="Communication\ScsServices\Service\IScsServiceApplication.cs" /> + <Compile Include="Communication\ScsServices\Service\IScsServiceClient.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsService.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsServiceApplication.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsServiceAttribute.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsServiceBuilder.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsServiceClient.cs" /> + <Compile Include="Communication\ScsServices\Service\ScsServiceClientFactory.cs" /> + <Compile Include="Communication\ScsServices\Service\ServiceClientEventArgs.cs" /> + <Compile Include="Communication\Scs\Client\ClientReConnecter.cs" /> + <Compile Include="Communication\Scs\Client\IConnectableClient.cs" /> + <Compile Include="Communication\Scs\Client\IScsClient.cs" /> + <Compile Include="Communication\Scs\Client\ScsClientBase.cs" /> + <Compile Include="Communication\Scs\Client\ScsClientFactory.cs" /> + <Compile Include="Communication\Scs\Client\Tcp\ScsTcpClient.cs" /> + <Compile Include="Communication\Scs\Client\Tcp\TcpHelper.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\CommunicationChannelBase.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\CommunicationChannelEventArgs.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\ConnectionListenerBase.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\ICommunicationChannel.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\IConnectionListener.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\Tcp\TcpCommunicationChannel.cs" /> + <Compile Include="Communication\Scs\Communication\Channels\Tcp\TcpConnectionListener.cs" /> + <Compile Include="Communication\Scs\Communication\CommunicationStateException.cs" /> + <Compile Include="Communication\Scs\Communication\CommunicationStates.cs" /> + <Compile Include="Communication\Scs\Communication\CommunicationException.cs" /> + <Compile Include="Communication\Scs\Communication\EndPoints\ScsEndPoint.cs" /> + <Compile Include="Communication\Scs\Communication\EndPoints\Tcp\ScsTcpEndPoint.cs" /> + <Compile Include="Communication\Scs\Communication\Messengers\IMessenger.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\IScsMessage.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\MessageEventArgs.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\PingMessage.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\ScsMessage.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\ScsRawDataMessage.cs" /> + <Compile Include="Communication\Scs\Communication\Messages\ScsTextMessage.cs" /> + <Compile Include="Communication\Scs\Communication\Protocols\BinarySerialization\BinarySerializationProtocol.cs" /> + <Compile Include="Communication\Scs\Communication\Protocols\BinarySerialization\BinarySerializationProtocolFactory.cs" /> + <Compile Include="Communication\Scs\Communication\Protocols\IScsWireProtocol.cs" /> + <Compile Include="Communication\Scs\Communication\Protocols\IScsWireProtocolFactory.cs" /> + <Compile Include="Communication\Scs\Communication\Protocols\WireProtocolManager.cs" /> + <Compile Include="Communication\Scs\Communication\Messengers\SynchronizedMessenger.cs" /> + <Compile Include="Communication\Scs\Server\IScsServer.cs" /> + <Compile Include="Communication\Scs\Server\IScsServerClient.cs" /> + <Compile Include="Communication\Scs\Server\ScsServerBase.cs" /> + <Compile Include="Communication\Scs\Server\ScsServerClient.cs" /> + <Compile Include="Communication\Scs\Server\ScsServerFactory.cs" /> + <Compile Include="Communication\Scs\Server\ScsServerManager.cs" /> + <Compile Include="Communication\Scs\Server\ServerClientEventArgs.cs" /> + <Compile Include="Communication\Scs\Server\Tcp\ScsTcpServer.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Threading\SequentialItemProcessor.cs" /> + <Compile Include="Threading\Timer.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Diagrams\ChannelsDiagram.cd" /> + <None Include="Diagrams\ConnListenerDiagram.cd" /> + <None Include="Diagrams\EndPointsDiagram.cd" /> + <None Include="Diagrams\MessageObjectsDiagram.cd" /> + <None Include="Diagrams\RequestReplyMessengerDiagram.cd" /> + <None Include="Diagrams\RmiMessagesDiagram.cd" /> + <None Include="Diagrams\ScsClientDiagram.cd" /> + <None Include="Diagrams\ScsServerClientDiagram.cd" /> + <None Include="Diagrams\ScsServerDiagram.cd" /> + <None Include="Diagrams\ScsServiceClientDiagram.cd" /> + <None Include="Diagrams\ScsServiceDiagram.cd" /> + <None Include="Diagrams\ServiceSideClientDiagram.cd" /> + <None Include="Diagrams\WireProtocolsDiagram.cd" /> + </ItemGroup> + <ItemGroup> + <Content Include="Changes.txt" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/src/Scs/Threading/SequentialItemProcessor.cs b/src/Scs/Threading/SequentialItemProcessor.cs new file mode 100644 index 0000000..185aae1 --- /dev/null +++ b/src/Scs/Threading/SequentialItemProcessor.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Hik.Threading +{ + /// <summary> + /// This class is used to process items sequentially in a multithreaded manner. + /// </summary> + /// <typeparam name="TItem">Type of item to process</typeparam> + public class SequentialItemProcessor<TItem> + { + #region Private fields + + /// <summary> + /// The method delegate that is called to actually process items. + /// </summary> + private readonly Action<TItem> _processMethod; + + /// <summary> + /// Item queue. Used to process items sequentially. + /// </summary> + private readonly Queue<TItem> _queue; + + /// <summary> + /// A reference to the current Task that is processing an item in + /// ProcessItem method. + /// </summary> + private Task _currentProcessTask; + + /// <summary> + /// Indicates state of the item processing. + /// </summary> + private bool _isProcessing; + + /// <summary> + /// A boolean value to control running of SequentialItemProcessor. + /// </summary> + private bool _isRunning; + + /// <summary> + /// An object to synchronize threads. + /// </summary> + private readonly object _syncObj = new object(); + + #endregion + + #region Constructor + + /// <summary> + /// Creates a new SequentialItemProcessor object. + /// </summary> + /// <param name="processMethod">The method delegate that is called to actually process items</param> + public SequentialItemProcessor(Action<TItem> processMethod) + { + _processMethod = processMethod; + _queue = new Queue<TItem>(); + } + + #endregion + + #region Public methods + + /// <summary> + /// Adds an item to queue to process the item. + /// </summary> + /// <param name="item">Item to add to the queue</param> + public void EnqueueMessage(TItem item) + { + //Add the item to the queue and start a new Task if needed + lock (_syncObj) + { + if (!_isRunning) + { + return; + } + + _queue.Enqueue(item); + + if (!_isProcessing) + { + _currentProcessTask = Task.Factory.StartNew(ProcessItem); + } + } + } + + /// <summary> + /// Starts processing of items. + /// </summary> + public void Start() + { + _isRunning = true; + } + + /// <summary> + /// Stops processing of items and waits stopping of current item. + /// </summary> + public void Stop() + { + _isRunning = false; + + //Clear all incoming messages + lock (_syncObj) + { + _queue.Clear(); + } + + //Check if is there a message that is being processed now + if (!_isProcessing) + { + return; + } + + //Wait current processing task to finish + try + { + _currentProcessTask.Wait(); + } + catch + { + + } + } + + #endregion + + #region Private methods + + /// <summary> + /// This method runs on a new seperated Task (thread) to process + /// items on the queue. + /// </summary> + private void ProcessItem() + { + //Try to get an item from queue to process it. + TItem itemToProcess; + lock (_syncObj) + { + if (!_isRunning || _isProcessing) + { + return; + } + + if (_queue.Count <= 0) + { + return; + } + + _isProcessing = true; + itemToProcess = _queue.Dequeue(); + } + + //Process the item (by calling the _processMethod delegate) + _processMethod(itemToProcess); + + //Process next item if available + lock (_syncObj) + { + _isProcessing = false; + if (!_isRunning || _queue.Count <= 0) + { + return; + } + + //Start a new task + _currentProcessTask = Task.Factory.StartNew(ProcessItem); + } + } + + #endregion + } +} diff --git a/src/Scs/Threading/Timer.cs b/src/Scs/Threading/Timer.cs new file mode 100644 index 0000000..331f6e7 --- /dev/null +++ b/src/Scs/Threading/Timer.cs @@ -0,0 +1,167 @@ +using System; +using System.Threading; + +namespace Hik.Threading +{ + /// <summary> + /// This class is a timer that performs some tasks periodically. + /// </summary> + public class Timer + { + #region Public events + + /// <summary> + /// This event is raised periodically according to Period of Timer. + /// </summary> + public event EventHandler Elapsed; + + #endregion + + #region Public fields + + /// <summary> + /// Task period of timer (as milliseconds). + /// </summary> + public int Period { get; set; } + + /// <summary> + /// Indicates whether timer raises Elapsed event on Start method of Timer for once. + /// Default: False. + /// </summary> + public bool RunOnStart { get; set; } + + #endregion + + #region Private fields + + /// <summary> + /// This timer is used to perfom the task at spesified intervals. + /// </summary> + private readonly System.Threading.Timer _taskTimer; + + /// <summary> + /// Indicates that whether timer is running or stopped. + /// </summary> + private volatile bool _running; + + /// <summary> + /// Indicates that whether performing the task or _taskTimer is in sleep mode. + /// This field is used to wait executing tasks when stopping Timer. + /// </summary> + private volatile bool _performingTasks; + + #endregion + + #region Constructors + + /// <summary> + /// Creates a new Timer. + /// </summary> + /// <param name="period">Task period of timer (as milliseconds)</param> + public Timer(int period) + : this(period, false) + { + + } + + /// <summary> + /// Creates a new Timer. + /// </summary> + /// <param name="period">Task period of timer (as milliseconds)</param> + /// <param name="runOnStart">Indicates whether timer raises Elapsed event on Start method of Timer for once</param> + public Timer(int period, bool runOnStart) + { + Period = period; + RunOnStart = runOnStart; + _taskTimer = new System.Threading.Timer(TimerCallBack, null, Timeout.Infinite, Timeout.Infinite); + } + + #endregion + + #region Public methods + + /// <summary> + /// Starts the timer. + /// </summary> + public void Start() + { + _running = true; + _taskTimer.Change(RunOnStart ? 0 : Period, Timeout.Infinite); + } + + /// <summary> + /// Stops the timer. + /// </summary> + public void Stop() + { + lock (_taskTimer) + { + _running = false; + _taskTimer.Change(Timeout.Infinite, Timeout.Infinite); + } + } + + /// <summary> + /// Waits the service to stop. + /// </summary> + public void WaitToStop() + { + lock (_taskTimer) + { + while (_performingTasks) + { + Monitor.Wait(_taskTimer); + } + } + } + + #endregion + + #region Private methods + + /// <summary> + /// This method is called by _taskTimer. + /// </summary> + /// <param name="state">Not used argument</param> + private void TimerCallBack(object state) + { + lock (_taskTimer) + { + if (!_running || _performingTasks) + { + return; + } + + _taskTimer.Change(Timeout.Infinite, Timeout.Infinite); + _performingTasks = true; + } + + try + { + if (Elapsed != null) + { + Elapsed(this, new EventArgs()); + } + } + catch + { + + } + finally + { + lock (_taskTimer) + { + _performingTasks = false; + if (_running) + { + _taskTimer.Change(Period, Timeout.Infinite); + } + + Monitor.Pulse(_taskTimer); + } + } + } + + #endregion + } +} |