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
}
}