Click here to Skip to main content
15,894,825 members
Articles / Programming Languages / C#

Web-Cam SecureChat

Rate me:
Please Sign up or sign in to vote.
4.94/5 (16 votes)
12 Mar 2010CPOL6 min read 93.8K   7.4K   70  
This article will explain how to create a simple chat program using this remoting technology, which supports web-cam and sending files.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using Pfz.Caching;
using Pfz.Extensions.DictionaryExtensions;
using Pfz.Extensions.MonitorLockExtensions;
using Pfz.InterfaceWrapping.EventArguments;
using Pfz.Threading;

namespace Pfz.Remoting
{
	/// <summary>
	/// Remoting server class.
	/// In it, you register the classes you want to provide remoting for and
	/// the static methods. The remoting is done via interfaces, so methods
	/// and properites that are not visible by the interfaces cannot be used.
	/// Objects returned as interfaces are also remoted, but object that are
	/// returned by the real type are serialized, if possible, or an exception
	/// is thrown.
	/// </summary>
	public sealed class RemotingServer:
		ThreadSafeExceptionAwareDisposable
	{
		#region Private and internal fields
			internal Dictionary<string, Type> fRegisteredTypes = new Dictionary<string, Type>();
			internal HashSet<RemotingClient> fClients = new HashSet<RemotingClient>();
			internal volatile Dictionary<string, MethodInfo> fStaticMethods = new Dictionary<string, MethodInfo>();
			
			internal HashSet<Type> fAcceptedCryptographies;
			internal Dictionary<string, HashSet<string>> fAcceptedCryptographiesDictionary;
			
			private IListener fListener;
		#endregion
		
		#region Constructor
			/// <summary>
			/// Creates the server.
			/// </summary>
			public RemotingServer()
			{
				MustDisposeAllOnConnectionLost = true;
			}
		#endregion
		#region Dispose
			/// <summary>
			/// Stops the server and frees all it's resources.
			/// </summary>
			/// <param name="disposing">
			/// true if called from Dispose(), and false if called from destructor.
			/// </param>
			protected override void Dispose(bool disposing)
			{
				if (disposing)
				{
					GCUtils.Collected -= p_Collected;
					
					var listener = fListener;
					if (listener != null)
					{
						fListener = null;
						listener.Dispose();
					}
					
					var clients = fClients;
					if (clients != null)
					{
						fClients = null;
						
						clients.LockWithTimeout
						(
							delegate
							{
								if (clients.Count > 0)
									foreach(RemotingClient client in clients)
										client.Dispose();
							}
						);
					}
				}

		 		 base.Dispose(disposing);
			}
		#endregion
		#region p_Collected
			private void p_Collected()
			{
				try
				{
					if (WasDisposed)
					{
						GCUtils.Collected -= p_Collected;
						return;
					}
					
					AbortSafe.UnabortableLock
					(
						fClients,
						() => fClients.TrimExcess()
					);
				}
				catch
				{
				}
			}
		#endregion
		
		#region Properties
			#region DisposeAllOnConnectionLost
				/// <summary>
				/// Gets or sets a property indicating that objects that were remoted
				/// must be disposed when the connection is lost.
				/// </summary>
				public bool MustDisposeAllOnConnectionLost { get; set; }
			#endregion
			#region MustUseAsyncVoidCalls
				/// <summary>
				/// Set this property to true if you want to call voids asynchronously.
				/// This also avoids receiving exceptions from such calls.
				/// </summary>
				public bool MustUseAsyncVoidCalls { get; set; }
			#endregion
			#region CryptographyMode
				private CryptographyMode fCryptographyMode;
				
				/// <summary>
				/// Gets the cryptography mode used by this server.
				/// </summary>
				public CryptographyMode CryptographyMode
				{
					get
					{
						return fCryptographyMode;
					}
					set
					{
						if (value == CryptographyMode.Forbidden && fAcceptedCryptographies != null)
							throw new RemotingException("SetCryptographyAsForbidden can't be called when there are already registered cryptographies.");
							
						fCryptographyMode = value;
					}
				}
			#endregion
		#endregion
		#region Methods
			#region Register
				/// <summary>
				/// Registers using the interface.FullName as the name param.
				/// </summary>
				/// <param name="interfaceType">The type of interface to register.</param>
				/// <param name="classType">The type of object that will be used for such interface.</param>
				public void Register(Type interfaceType, Type classType)
				{
					if (interfaceType == null)
						throw new ArgumentNullException("interfaceType");
						
					if (!interfaceType.IsInterface)
						throw new ArgumentException("The given interfaceType is not an interface type.", "interfaceType");
						
					if (classType == null)
						throw new ArgumentNullException("classType");
					
					if (!interfaceType.IsAssignableFrom(classType))
						throw new ArgumentException("classType must implement the given interface type.");
					
					Register(interfaceType.FullName, classType);
				}
				
				/// <summary>
				/// Registers a type to be instantiated when the specified name is 
				/// requested. But remember, only the interfaces are acessible by the
				/// client, not the real class.
				/// </summary>
				/// <param name="name">The name to register to be acessible by clients.</param>
				/// <param name="type">An instantiable type.</param>
				public void Register(string name, Type type)
				{
					if (name == null)
						throw new ArgumentNullException("name");
						
					if (type == null)
						throw new ArgumentNullException("type");
						
					if (type.IsAbstract)
						throw new ArgumentException("type must be an instantiable type.", "type");
					
					fRegisteredTypes.Add(name, type);
				}
			#endregion
			#region RegisterStaticMethod
				/// <summary>
				/// Registers an static method, using only it's name as the way to be
				/// acessible by clients.
				/// </summary>
				/// <param name="method">The static method to register.</param>
				public void RegisterStaticMethod(MethodInfo method)
				{
					if (method == null)
						throw new ArgumentNullException("method");
					
					RegisterStaticMethod(method.Name, method);
				}
				
				/// <summary>
				/// Registers an static method with the specified name.
				/// This is useful if you have two static methods with the same name,
				/// as you can't register them that way.
				/// </summary>
				/// <param name="name">The name to register the method to be acessible by clients.</param>
				/// <param name="method">The static method that will be registered.</param>
				public void RegisterStaticMethod(string name, MethodInfo method)
				{
					if (name == null)
						throw new ArgumentNullException("name");
					
					if (method == null)
						throw new ArgumentNullException("method");
					
					if (!method.IsStatic)
						throw new ArgumentException("method must be static.", "method");
					
					fStaticMethods.Add(name, method);
				}
			#endregion
			
			#region AddForcedSerialization
				internal HashSet<Type> fForcedSerializations = new HashSet<Type>();
				internal Dictionary<string, HashSet<string>> fForcedSerializationsDictionary  = new Dictionary<string, HashSet<string>>();
				/// <summary>
				/// Adds a class type (and it's sub-classes) to be serialized, even
				/// when it is requested as an interface.
				/// </summary>
				/// <param name="type">The base type to be always serialized.</param>
				public void AddForcedSerialization(Type type)
				{
					if (type == null)
						throw new ArgumentNullException("type");

					if (fForcedSerializations.Add(type))
					{
						string assemblyName = type.Assembly.FullName;
						
						var typeNames = fForcedSerializationsDictionary.GetOrCreateValue(assemblyName);
						typeNames.Add(type.FullName);
					}
				}
			#endregion

			#region RegisterAcceptedCryptography
				/// <summary>
				/// Registers valid cryptographies. If none is registered, all cryptographies
				/// are considered valid.
				/// </summary>
				public void RegisterAcceptedCryptography<T>()
				where
					T: SymmetricAlgorithm, new()
				{
					if (CryptographyMode == CryptographyMode.Forbidden)
						throw new RemotingException("RegisterAcceptedCryptography can't be called when CryptographyMode is set to Forbidden.");
					
					if (fAcceptedCryptographies == null)
					{
						fAcceptedCryptographies = new HashSet<Type>();
						fAcceptedCryptographiesDictionary = new Dictionary<string, HashSet<string>>();
					}
					
					if (fAcceptedCryptographies.Add(typeof(T)))
					{
						string assemblyName = typeof(T).Assembly.FullName;
					
						var types = fAcceptedCryptographiesDictionary.GetOrCreateValue(assemblyName);
						types.Add(typeof(T).FullName);
					}
				}
			#endregion
			
			#region Run
				/// <summary>
				/// Starts the server on the given port.
				/// This method will only return when the server is disposed, so
				/// it's a good practice to call this method from another thread.
				/// </summary>
				public void Run(int port)
				{
					TcpListener listener = new TcpListener(IPAddress.Any, port);
					Run(listener);
				}
				
				/// <summary>
				/// Starts the server using the given TcpListener, which must
				/// not be started.
				/// </summary>
				public void Run(TcpListener listener)
				{
					var wrapper = new TcpListenerWrapper(listener, false);
					Run(wrapper);
				}
				
				/// <summary>
				/// Starts the server using the given IListener, which must
				/// not be started.
				/// </summary>
				public void Run(IListener listener)
				{
					DisposeLock.LockWithTimeout
					(
						delegate
						{
							CheckUndisposed();
								
							if (fListener != null)
								throw new RemotingException("You can only call RemotingServer.Start() once.");
							
							fListener = listener;
						}
					);
					
					fRegisteredTypes = new Dictionary<string, Type>(fRegisteredTypes);
					fStaticMethods = new Dictionary<string, MethodInfo>(fStaticMethods);
					
					GCUtils.Collected += p_Collected;
					
					bool mustReturn = true;
					DisposeLock.LockWithTimeout
					(
						delegate
						{
							if (WasDisposed)
								return;
								
							listener.Start();
							mustReturn = false;
						}
					);
					
					if (mustReturn)
						return;
					
					try
					{
						Box<object> box = new Box<object>();
						while(true)
						{
							var stream = listener.Accept(box);
							int buffersLength = 8 * 1024;
							RemotingClient connected = new RemotingClient(this, box.Value, stream, buffersLength, i_GettingStream, listener.CreateChanneller);
							GC.KeepAlive(connected);
						}
					}
					catch(Exception exception)
					{
						if (!WasDisposed)
						{
							Dispose(exception);
							throw;
						}
					}
				}
			#endregion
			
			#region CreateIpcConnection
				/// <summary>
				/// Creates a connection using a named pipe.
				/// Returns a delegate to start the connection, so you can do some
				/// work before starting to wait for the client.
				/// </summary>
				public Func<RemotingClient> CreateNamedPipeConnection(string pipeName)
				{
					var stream = DuplexStream.CreateNamedPipeServer(pipeName, false);
					
					return delegate()
					{
						stream.ReadStream.WaitForConnection();
						stream.WriteStream.WaitForConnection();
						
						var result = new RemotingClient(this, stream, stream, 8 * 1024, i_GettingStream, p_CreateNamedPipeChanneller);
						return result;
					};
				}
			#endregion
			#region p_CreateNamedPipeChanneller
				private static IChanneller p_CreateNamedPipeChanneller(Stream stream, EventHandler<ChannelCreatedEventArgs> eventHandler)
				{
					return new NamedPipeChanneller(stream, eventHandler);
				}
			#endregion
		#endregion
		#region Events
			#region Redirectors
				internal void i_RegisterForEvents(RemotingClient client)
				{
					client.UserChannelCreated += p_UserChannelCreated;
				
					client.BeforeInvokeLocalMethod += p_BeforeInvokeLocalMethod;
					client.AfterInvokeLocalMethod += p_AfterInvokeLocalMethod;
					client.BeforeInvokeRemoteMethod += p_BeforeInvokeRemoteMethod;
					client.AfterInvokeRemoteMethod += p_AfterInvokeRemoteMethod;

					client.BeforeInvokeLocalPropertyGet += p_BeforeInvokeLocalPropertyGet;
					client.AfterInvokeLocalPropertyGet += p_AfterInvokeLocalPropertyGet;
					client.BeforeInvokeRemotePropertyGet += p_BeforeInvokeRemotePropertyGet;
					client.AfterInvokeRemotePropertyGet += p_AfterInvokeRemotePropertyGet;

					client.BeforeInvokeLocalPropertySet += p_BeforeInvokeLocalPropertySet;
					client.AfterInvokeLocalPropertySet += p_AfterInvokeLocalPropertySet;
					client.BeforeInvokeRemotePropertySet += p_BeforeInvokeRemotePropertySet;
					client.AfterInvokeRemotePropertySet += p_AfterInvokeRemotePropertySet;

					client.BeforeInvokeLocalEventAdd += p_BeforeInvokeLocalEventAdd;
					client.AfterInvokeLocalEventAdd += p_AfterInvokeLocalEventAdd;
					client.BeforeInvokeRemoteEventAdd += p_BeforeInvokeRemoteEventAdd;
					client.AfterInvokeRemoteEventAdd += p_AfterInvokeRemoteEventAdd;

					client.BeforeInvokeLocalEventRemove += p_BeforeInvokeLocalEventRemove;
					client.AfterInvokeLocalEventRemove += p_AfterInvokeLocalEventRemove;
					client.BeforeInvokeRemoteEventRemove += p_BeforeInvokeRemoteEventRemove;
					client.AfterInvokeRemoteEventRemove += p_AfterInvokeRemoteEventRemove;
				}
				
				internal void i_GettingStream(object sender, GettingStreamEventArgs args)
				{
					var _event = GettingStream;
					if (_event != null)
						_event(this, args);
				}
				
				private void p_UserChannelCreated(object sender, ChannelCreatedEventArgs args)
				{
					var _event = UserChannelCreated;
					if (_event != null)
						_event(this, args);
				}

				private void p_BeforeInvokeLocalMethod(object sender, InvokeMethodEventArgs args)
				{
					if (BeforeInvokeLocalMethod != null)
						BeforeInvokeLocalMethod(this, args);
				}
				private void p_AfterInvokeLocalMethod(object sender, InvokeMethodEventArgs args)
				{
					if (AfterInvokeLocalMethod != null)
						AfterInvokeLocalMethod(this, args);
				}
				private void p_BeforeInvokeRemoteMethod(object sender, InvokeMethodEventArgs args)
				{
					if (BeforeInvokeRemoteMethod != null)
						BeforeInvokeRemoteMethod(this, args);
				}
				private void p_AfterInvokeRemoteMethod(object sender, InvokeMethodEventArgs args)
				{
					if (AfterInvokeRemoteMethod != null)
						AfterInvokeRemoteMethod(this, args);
				}

				private void p_BeforeInvokeLocalPropertyGet(object sender, InvokePropertyEventArgs args)
				{
					if (BeforeInvokeLocalPropertyGet != null)
						BeforeInvokeLocalPropertyGet(this, args);
				}
				private void p_AfterInvokeLocalPropertyGet(object sender, InvokePropertyEventArgs args)
				{
					if (AfterInvokeLocalPropertyGet != null)
						AfterInvokeLocalPropertyGet(this, args);
				}
				private void p_BeforeInvokeRemotePropertyGet(object sender, InvokePropertyEventArgs args)
				{
					if (BeforeInvokeRemotePropertyGet != null)
						BeforeInvokeRemotePropertyGet(this, args);
				}
				private void p_AfterInvokeRemotePropertyGet(object sender, InvokePropertyEventArgs args)
				{
					if (AfterInvokeRemotePropertyGet != null)
						AfterInvokeRemotePropertyGet(this, args);
				}

				private void p_BeforeInvokeLocalPropertySet(object sender, InvokePropertyEventArgs args)
				{
					if (BeforeInvokeLocalPropertySet != null)
						BeforeInvokeLocalPropertySet(this, args);
				}
				private void p_AfterInvokeLocalPropertySet(object sender, InvokePropertyEventArgs args)
				{
					if (AfterInvokeLocalPropertySet != null)
						AfterInvokeLocalPropertySet(this, args);
				}
				private void p_BeforeInvokeRemotePropertySet(object sender, InvokePropertyEventArgs args)
				{
					if (BeforeInvokeRemotePropertySet != null)
						BeforeInvokeRemotePropertySet(this, args);
				}
				private void p_AfterInvokeRemotePropertySet(object sender, InvokePropertyEventArgs args)
				{
					if (AfterInvokeRemotePropertySet != null)
						AfterInvokeRemotePropertySet(this, args);
				}

				private void p_BeforeInvokeLocalEventAdd(object sender, InvokeEventEventArgs args)
				{
					if (BeforeInvokeLocalEventAdd != null)
						BeforeInvokeLocalEventAdd(this, args);
				}
				private void p_AfterInvokeLocalEventAdd(object sender, InvokeEventEventArgs args)
				{
					if (AfterInvokeLocalEventAdd != null)
						AfterInvokeLocalEventAdd(this, args);
				}
				private void p_BeforeInvokeRemoteEventAdd(object sender, InvokeEventEventArgs args)
				{
					if (BeforeInvokeRemoteEventAdd != null)
						BeforeInvokeRemoteEventAdd(this, args);
				}
				private void p_AfterInvokeRemoteEventAdd(object sender, InvokeEventEventArgs args)
				{
					if (AfterInvokeRemoteEventAdd != null)
						AfterInvokeRemoteEventAdd(this, args);
				}

				private void p_BeforeInvokeLocalEventRemove(object sender, InvokeEventEventArgs args)
				{
					if (BeforeInvokeLocalEventRemove != null)
						BeforeInvokeLocalEventRemove(this, args);
				}
				private void p_AfterInvokeLocalEventRemove(object sender, InvokeEventEventArgs args)
				{
					if (AfterInvokeLocalEventRemove != null)
						AfterInvokeLocalEventRemove(this, args);
				}
				private void p_BeforeInvokeRemoteEventRemove(object sender, InvokeEventEventArgs args)
				{
					if (BeforeInvokeRemoteEventRemove != null)
						BeforeInvokeRemoteEventRemove(this, args);
				}
				private void p_AfterInvokeRemoteEventRemove(object sender, InvokeEventEventArgs args)
				{
					if (AfterInvokeRemoteEventRemove != null)
						AfterInvokeRemoteEventRemove(this, args);
				}
			#endregion
			
			/// <summary>
			/// Event called in a new thread when a client channel is created by a 
			/// request on the other side.
			/// </summary>
			public event EventHandler<ChannelCreatedEventArgs> UserChannelCreated;
			
			/// <summary>
			/// Event called just before the stream begins to be used.
			/// The Stream parameter is the default tcp/ip stream by default, but
			/// you can modify it.
			/// </summary>
			public event EventHandler<GettingStreamEventArgs> GettingStream;
			
			/// <summary>
			/// Event called just before a remote call invokes a local method.
			/// </summary>
			public event EventHandler<InvokeMethodEventArgs> BeforeInvokeLocalMethod;
			
			/// <summary>
			/// Event called just after invoking a local method from a remote call.
			/// </summary>
			public event EventHandler<InvokeMethodEventArgs> AfterInvokeLocalMethod;
			
			/// <summary>
			/// Event called just before a local method is redirected to a remote invoke.
			/// </summary>
			public event EventHandler<InvokeMethodEventArgs> BeforeInvokeRemoteMethod;
			
			/// <summary>
			/// Event called just after the remote method was executed.
			/// </summary>
			public event EventHandler<InvokeMethodEventArgs> AfterInvokeRemoteMethod;

			/// <summary>
			/// Event called just before a remote call invokes a local property get.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> BeforeInvokeLocalPropertyGet;

			/// <summary>
			/// Event called just after a remote call invokes a local property get.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> AfterInvokeLocalPropertyGet;
			
			/// <summary>
			/// Event called just before redirecting a property get to a remote object.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> BeforeInvokeRemotePropertyGet;
			
			/// <summary>
			/// Event called just after redirecting a property get to a remote object.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> AfterInvokeRemotePropertyGet;

			/// <summary>
			/// Event called just before a remote call invokes a local property set.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> BeforeInvokeLocalPropertySet;
			
			/// <summary>
			/// Event called just after a remote call invokes a local property set.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> AfterInvokeLocalPropertySet;
			
			/// <summary>
			/// Event called just before redirecting a property set to a remote object.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> BeforeInvokeRemotePropertySet;
			
			/// <summary>
			/// Event called just after redirecting a property set to a remote object.
			/// </summary>
			public event EventHandler<InvokePropertyEventArgs> AfterInvokeRemotePropertySet;

			
			/// <summary>
			/// Event called just before a remote call adds an event handler to a local object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> BeforeInvokeLocalEventAdd;
			
			/// <summary>
			/// Event called just after a remote call adds an event handler to a local object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> AfterInvokeLocalEventAdd;
			
			/// <summary>
			/// Event called just before redirecting an event add to a remote object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> BeforeInvokeRemoteEventAdd;
			
			/// <summary>
			/// Event called just after redirecting an event add to a remote object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> AfterInvokeRemoteEventAdd;


			/// <summary>
			/// Event called just before a remote call removes an event handler from a local object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> BeforeInvokeLocalEventRemove;
			
			/// <summary>
			/// Event called just after a remote call removes an event handler from a local object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> AfterInvokeLocalEventRemove;
			
			/// <summary>
			/// Event called just before an event remove is redirected to a remote object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> BeforeInvokeRemoteEventRemove;
			
			/// <summary>
			/// Event called just after an event remove is redirected to a remote object.
			/// </summary>
			public event EventHandler<InvokeEventEventArgs> AfterInvokeRemoteEventRemove;
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions