summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Scs.sln30
-rw-r--r--src/Scs/Changes.txt37
-rw-r--r--src/Scs/Collections/ThreadSafeSortedList.cs197
-rw-r--r--src/Scs/Communication/Scs/Client/ClientReConnecter.cs109
-rw-r--r--src/Scs/Communication/Scs/Client/IConnectableClient.cs43
-rw-r--r--src/Scs/Communication/Scs/Client/IScsClient.cs13
-rw-r--r--src/Scs/Communication/Scs/Client/ScsClientBase.cs330
-rw-r--r--src/Scs/Communication/Scs/Client/ScsClientFactory.cs30
-rw-r--r--src/Scs/Communication/Scs/Client/Tcp/ScsTcpClient.cs40
-rw-r--r--src/Scs/Communication/Scs/Client/Tcp/TcpHelper.cs49
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelBase.cs176
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/CommunicationChannelEventArgs.cs24
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/ConnectionListenerBase.cs38
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/ICommunicationChannel.cs38
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/IConnectionListener.cs26
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpCommunicationChannel.cs210
-rw-r--r--src/Scs/Communication/Scs/Communication/Channels/Tcp/TcpConnectionListener.cs124
-rw-r--r--src/Scs/Communication/Scs/Communication/CommunicationException.cs50
-rw-r--r--src/Scs/Communication/Scs/Communication/CommunicationStateException.cs50
-rw-r--r--src/Scs/Communication/Scs/Communication/CommunicationStates.cs18
-rw-r--r--src/Scs/Communication/Scs/Communication/EndPoints/ScsEndPoint.cs67
-rw-r--r--src/Scs/Communication/Scs/Communication/EndPoints/Tcp/ScsTcpEndPoint.cs84
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/IScsMessage.cs18
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/MessageEventArgs.cs24
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/PingMessage.cs44
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/ScsMessage.cs59
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/ScsRawDataMessage.cs59
-rw-r--r--src/Scs/Communication/Scs/Communication/Messages/ScsTextMessage.cs58
-rw-r--r--src/Scs/Communication/Scs/Communication/Messengers/IMessenger.cs44
-rw-r--r--src/Scs/Communication/Scs/Communication/Messengers/RequestReplyMessenger.cs387
-rw-r--r--src/Scs/Communication/Scs/Communication/Messengers/SynchronizedMessenger.cs216
-rw-r--r--src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocol.cs315
-rw-r--r--src/Scs/Communication/Scs/Communication/Protocols/BinarySerialization/BinarySerializationProtocolFactory.cs17
-rw-r--r--src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocol.cs39
-rw-r--r--src/Scs/Communication/Scs/Communication/Protocols/IScsWireProtocolFactory.cs14
-rw-r--r--src/Scs/Communication/Scs/Communication/Protocols/WireProtocolManager.cs28
-rw-r--r--src/Scs/Communication/Scs/Server/IScsServer.cs42
-rw-r--r--src/Scs/Communication/Scs/Server/IScsServerClient.cs38
-rw-r--r--src/Scs/Communication/Scs/Server/ScsServerBase.cs168
-rw-r--r--src/Scs/Communication/Scs/Server/ScsServerClient.cs223
-rw-r--r--src/Scs/Communication/Scs/Server/ScsServerFactory.cs20
-rw-r--r--src/Scs/Communication/Scs/Server/ScsServerManager.cs24
-rw-r--r--src/Scs/Communication/Scs/Server/ServerClientEventArgs.cs24
-rw-r--r--src/Scs/Communication/Scs/Server/Tcp/ScsTcpServer.cs35
-rw-r--r--src/Scs/Communication/ScsServices/Client/IScsServiceClient.cs24
-rw-r--r--src/Scs/Communication/ScsServices/Client/ScsServiceClient.cs274
-rw-r--r--src/Scs/Communication/ScsServices/Client/ScsServiceClientBuilder.cs36
-rw-r--r--src/Scs/Communication/ScsServices/Communication/AutoConnectRemoteInvokeProxy.cs57
-rw-r--r--src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteException.cs51
-rw-r--r--src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeMessage.cs36
-rw-r--r--src/Scs/Communication/ScsServices/Communication/Messages/ScsRemoteInvokeReturnMessage.cs33
-rw-r--r--src/Scs/Communication/ScsServices/Communication/RemoteInvokeProxy.cs63
-rw-r--r--src/Scs/Communication/ScsServices/Service/IScsServiceApplication.cs49
-rw-r--r--src/Scs/Communication/ScsServices/Service/IScsServiceClient.cs44
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsService.cs43
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsServiceApplication.cs370
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsServiceAttribute.cs26
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsServiceBuilder.cs21
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsServiceClient.cs146
-rw-r--r--src/Scs/Communication/ScsServices/Service/ScsServiceClientFactory.cs23
-rw-r--r--src/Scs/Communication/ScsServices/Service/ServiceClientEventArgs.cs24
-rw-r--r--src/Scs/Diagrams/ChannelsDiagram.cd37
-rw-r--r--src/Scs/Diagrams/ConnListenerDiagram.cd30
-rw-r--r--src/Scs/Diagrams/EndPointsDiagram.cd24
-rw-r--r--src/Scs/Diagrams/MessageObjectsDiagram.cd40
-rw-r--r--src/Scs/Diagrams/RequestReplyMessengerDiagram.cd47
-rw-r--r--src/Scs/Diagrams/RmiMessagesDiagram.cd35
-rw-r--r--src/Scs/Diagrams/ScsClientDiagram.cd45
-rw-r--r--src/Scs/Diagrams/ScsServerClientDiagram.cd26
-rw-r--r--src/Scs/Diagrams/ScsServerDiagram.cd26
-rw-r--r--src/Scs/Diagrams/ScsServiceClientDiagram.cd26
-rw-r--r--src/Scs/Diagrams/ScsServiceDiagram.cd26
-rw-r--r--src/Scs/Diagrams/ServiceSideClientDiagram.cd19
-rw-r--r--src/Scs/Diagrams/WireProtocolsDiagram.cd41
-rw-r--r--src/Scs/Properties/AssemblyInfo.cs36
-rw-r--r--src/Scs/Scs.csproj135
-rw-r--r--src/Scs/Threading/SequentialItemProcessor.cs172
-rw-r--r--src/Scs/Threading/Timer.cs167
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&lt;T&gt;">
+ <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&lt;T&gt;.WaitingMessage" Collapsed="true">
+ <TypeIdentifier>
+ <NewMemberFileName>Communication\Scs\Communication\Messengers\RequestReplyMessenger.cs</NewMemberFileName>
+ </TypeIdentifier>
+ </Class>
+ <Enum Name="Hik.Communication.Scs.Communication.Messengers.RequestReplyMessenger&lt;T&gt;.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&lt;T&gt;" 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&lt;T&gt;">
+ <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
+ }
+}