mattercontrol/PrinterCommunication/RemotingLite/src/ServiceHost.cs
larsbrubaker 2d3d26a68d Working to get the collision stuff working with mesh groups
Put in the RomotingLite project to start on TigerSong
2014-10-09 11:29:33 -07:00

342 lines
13 KiB
C#

/*************************************************************************************************
* RemotingLite
* ------
* A light framework for making remote method invocations using TCP/IP. It is based loosely on
* Windows Communication Foundation, and is meant to provide programmers with the same API
* regardless of whether they write software for the Microsoft .NET platform or the Mono .NET
* platform.
* Consult the documentation and example applications for information about how to use this API.
*
* Author : Frank Thomsen
* http : http://sector0.dk
* Concact : http://sector0.dk/?q=contact
* Information : http://sector0.dk/?q=node/27
* Licence : Free. If you use this, please let me know.
*
* Please feel free to contact me with ideas, bugs or improvements.
*************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Reflection;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection.Emit;
using System.IO.Compression;
namespace RemotingLite
{
[Serializable]
internal class MethodSyncInfo
{
public int MethodIdent { get; set; }
public string MethodName { get; set; }
public Type[] ParameterTypes { get; set; }
}
public class ServiceHost : IDisposable
{
private bool _isOpen = false;
private TcpListener _listener;
private List<TcpClient> _clients;
private bool _continueListening;
private object _continueListeningLock = new object();
private object _singletonInstance;
private IPEndPoint _endPoint;
private bool _useThreadPool = false;
private Dictionary<int, MethodInfo> _interfaceMethods;
private Dictionary<int, bool[]> _methodParametersByRef;
private ParameterTransferHelper _parameterTransferHelper = new ParameterTransferHelper();
private bool Continue
{
get
{
lock (_continueListeningLock)
{
return _continueListening;
}
}
set
{
lock (_continueListeningLock)
{
_continueListening = value;
}
}
}
/// <summary>
/// Get or set whether the host should use regular or thread pool threads.
/// </summary>
public bool UseThreadPool
{
get { return _useThreadPool; }
set
{
if (_isOpen)
throw new Exception("The host is already open");
_useThreadPool = value;
}
}
/// <summary>
/// Constructs an instance of the host and starts listening for incoming connections.
/// All listener threads are regular background threads.
///
/// NOTE: the instance created from the specified type is not automatically thread safe!
/// </summary>
/// <param name="remotedType">The remoted type. This must have a default constructor</param>
/// <param name="port">The port number for incoming requests</param>
public ServiceHost(Type remotedType, int port)
: this(Activator.CreateInstance(remotedType), port)
{
}
/// <summary>
/// Constructs an instance of the host and starts listening for incoming connections.
/// All listener threads are regular background threads.
///
/// NOTE: the instance is not automatically thread safe!
/// </summary>
/// <param name="singletonInstance">The singleton instance of the service</param>
/// <param name="port">The port number for incoming requests</param>
public ServiceHost(object singletonInstance, int port)
{
IPAddress[] addressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
IPAddress address = null;
foreach (var a in addressList)
if (a.AddressFamily == AddressFamily.InterNetwork)
{
address = a;
break;
}
_endPoint = new IPEndPoint(address, port);
_listener = new TcpListener(_endPoint);
_clients = new List<TcpClient>();
_continueListening = true;
_singletonInstance = singletonInstance;
CreateMethodMap();
}
/// <summary>
/// Loads all methods from interfaces and assigns an identifier
/// to each. These are later synchronized with the client.
/// </summary>
private void CreateMethodMap()
{
var interfaces = _singletonInstance.GetType().GetInterfaces();
_interfaceMethods = new Dictionary<int, MethodInfo>();
_methodParametersByRef = new Dictionary<int, bool[]>();
var currentMethodIdent = 0;
foreach (var interfaceType in interfaces)
{
var methodInfos = interfaceType.GetMethods();
foreach (var mi in methodInfos)
{
_interfaceMethods.Add(currentMethodIdent, mi);
var parameterInfos = mi.GetParameters();
var isByRef = new bool[parameterInfos.Length];
for (int i = 0; i < isByRef.Length; i++)
isByRef[i] = parameterInfos[i].ParameterType.IsByRef;
_methodParametersByRef.Add(currentMethodIdent, isByRef);
currentMethodIdent++;
}
}
}
/// <summary>
/// Gets the end point this host is listening on
/// </summary>
public IPEndPoint EndPoint
{
get { return _endPoint; }
}
/// <summary>
/// Opens the host and starts a listener thread. This listener thread spawns a new thread (or uses a
/// thread pool thread) for each incoming connection.
/// </summary>
public void Open()
{
//start listening in the background
if (_useThreadPool)
ThreadPool.QueueUserWorkItem(ListenerThreadProc);
else
{
Thread t = new Thread(ListenerThreadProc);
t.IsBackground = true;
t.Start();
}
_isOpen = true;
}
/// <summary>
/// Closes the host and calls Dispose().
/// </summary>
public void Close()
{
Dispose();
}
/// <summary>
/// Listens for incoming requests.
///
/// This method runs in a separate thread
/// </summary>
/// <param name="state"></param>
private void ListenerThreadProc(object state)
{
_listener.Start();
while (Continue)
{
try
{
TcpClient client = _listener.AcceptTcpClient();
_clients.Add(client);
if (_useThreadPool)
ThreadPool.QueueUserWorkItem(ClientThreadProc, client);
else
{
Thread t = new Thread(ClientThreadProc);
t.IsBackground = true;
t.Start(client);
}
}
catch (SocketException) //this is normal since the thread is a background thread
{
Continue = false;
}
}
}
/// <summary>
/// This method handles all requests from a single client.
///
/// There is one thread running this method for each connected client.
/// </summary>
/// <param name="state"></param>
private void ClientThreadProc(object state)
{
TcpClient client = (TcpClient)state;
client.NoDelay = true;
Stream stream = client.GetStream();
BinaryReader binReader = new BinaryReader(stream);
BinaryWriter binWriter = new BinaryWriter(stream);
bool doContinue = true;
do
{
try
{
MemoryStream ms;
BinaryFormatter formatter = new BinaryFormatter();
//read message type
MessageType messageType = (MessageType)binReader.ReadInt32();
switch (messageType)
{
case MessageType.SyncInterface:
//Create a list of sync infos from the dictionary
var syncInfos = new List<MethodSyncInfo>();
foreach(var kvp in _interfaceMethods)
{
var parameters = kvp.Value.GetParameters();
var parameterTypes = new Type[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
parameterTypes[i] = parameters[i].ParameterType;
syncInfos.Add(new MethodSyncInfo { MethodIdent = kvp.Key, MethodName = kvp.Value.Name, ParameterTypes = parameterTypes });
}
//send the sync data back to the client
ms = new MemoryStream();
formatter.Serialize(ms, syncInfos);
ms.Seek(0, SeekOrigin.Begin);
binWriter.Write((int)ms.Length);
binWriter.Write(ms.ToArray());
binWriter.Flush();
stream.Flush();
break;
case MessageType.MethodInvocation:
//read the method identifier
int methodHashCode = binReader.ReadInt32();
if (_interfaceMethods.ContainsKey(methodHashCode))
{
var method = _interfaceMethods[methodHashCode];
var isByRef = _methodParametersByRef[methodHashCode];
//read parameter data
var parameters = _parameterTransferHelper.ReceiveParameters(binReader);
//invoke the method
object[] returnParameters;
var returnMessageType = MessageType.ReturnValues;
try
{
object returnValue = method.Invoke(_singletonInstance, parameters);
//the result to the client is the return value (null if void) and the input parameters
returnParameters = new object[1 + parameters.Length];
returnParameters[0] = returnValue;
for (int i = 0; i < parameters.Length; i++)
returnParameters[i + 1] = isByRef[i] ? parameters[i] : null;
}
catch (Exception ex)
{
//an exception was caught. Rethrow it client side
returnParameters = new object[] { ex };
returnMessageType = MessageType.ThrowException;
}
//send the result back to the client
// (1) write the message type
binWriter.Write((int)returnMessageType);
// (2) write the return parameters
_parameterTransferHelper.SendParameters(binWriter, returnParameters);
}
else
binWriter.Write((int)MessageType.UnknownMethod);
//flush
binWriter.Flush();
stream.Flush();
break;
case MessageType.TerminateConnection:
doContinue = false;
break;
default:
break;
}
}
catch (Exception) //do not resume operation on this thread if any errors are unhandled.
{
doContinue = false;
}
}
while (doContinue);
client.Close();
binReader.Close();
stream.Close();
lock (_clients)
{
_clients.Remove(client);
}
}
#region IDisposable Members
public void Dispose()
{
_isOpen = false;
Continue = false;
_listener.Stop();
foreach (TcpClient client in _clients)
client.Close();
}
#endregion
}
}