Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

A Flexible Plugin System

, 3 Sep 2008
A generic plugin system used to load and manage plugins
fadd-15373.zip
trunk
dlls
xunit.dll
Examples
Plugins
ExampleApplication.Shared
Properties
ExampleApplication
Properties
ExamplePlugin.Shared
Properties
ExamplePlugin
Properties
Fadd.Globalization.Yaml
fadd.snk
Properties
Tests
fadd
Commands
Net
Tests
fadd.snk
Globalization
Tests
Logging
Plugins
Properties
Tests
Validation
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Xunit;

namespace Fadd.Commands.Net
{
    /// <summary>
    /// This class creates a command channel which can be used to send a command to a remote end point.
    /// </summary>
    public class CommandChannel : IDisposable
    {
        /// <summary>
        /// Try to use this port if it's not occupied.
        /// </summary>
        public const int DefaultPort = 9813;

        private readonly ICommandDispatcher _dispatcher;
        //private readonly ObjectCopier _copier = new ObjectCopier();
        private const int CommandTimeout = 10000;
        private int _lastSequenceNumber;
        private readonly Dictionary<int, CommandMapping> _packets = new Dictionary<int, CommandMapping>();
        private BinaryChannel _channel;

        /// <summary>
        /// Invoked when client disconnects
        /// </summary>
        public event DisconnectedHandler Disconnected = delegate{};

        #region CommandMapping
        private class CommandMapping
        {
            private readonly int _sequenceNumber;
            private readonly ManualResetEvent _event;
            private Command _command;
            public CommandMapping(int sequenceNumber)
            {
                _event = new ManualResetEvent(false);
                _sequenceNumber = sequenceNumber;
            }

            public int SequenceNumber
            {
                get { return _sequenceNumber; }
            }

            public Command Command
            {
                get { return _command; }
                set { _command = value; }
            }

            public bool Wait()
            {
                return _event.WaitOne(CommandTimeout, true);
            }

            public void Signal()
            {
                _event.Set();
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandChannel"/> class.
        /// </summary>
        /// <param name="dispatcher">Dispatcher used to invoke commands comming through the channel.</param>
        public CommandChannel(ICommandDispatcher dispatcher)
        {
            Check.Require(dispatcher, "dispatcher");
            _dispatcher = dispatcher;
        }

        private bool OnRemoteCommand(object source, CommandEventArgs args)
        {
            if (!(args.Command is IRemote))
                return false;

            CommandMapping mapping = CreateMapping();
            CommandPacket packet = new CommandPacket(mapping.SequenceNumber, args.Command);
            _channel.Send(packet);
            if (mapping.Wait())
                ((IRemote)args.Command).CopyReply(mapping.Command);
//                _copier.Copy(mapping.Command, args.Command);

            _packets.Remove(mapping.SequenceNumber);

            //todo: Log timeout
            return true;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandChannel"/> class.
        /// </summary>
        /// <param name="dispatcher">Dispatcher used to invoke commands comming through the channel.</param>
        /// <param name="socket">Socket used to transport commands.</param>
        public CommandChannel(ICommandDispatcher dispatcher, Socket socket)
            : this(dispatcher, new BinaryChannel(socket))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandChannel"/> class.
        /// </summary>
        /// <param name="dispatcher">Dispatcher used to invoke commands comming through the channel.</param>
        /// <param name="channel">Channel used to tranport commands.</param>
        public CommandChannel(ICommandDispatcher dispatcher, BinaryChannel channel)
        {
            Check.Require(channel, "channel");
            Check.Require(dispatcher, "dispatcher");

            _channel = channel;
            _channel.ObjectReceived += OnObjectReceived;
            _channel.Disconnected += OnDisconnected;
            _dispatcher = dispatcher;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="CommandChannel"/> is reclaimed by garbage collection.
        /// </summary>
        ~CommandChannel()
        {
            Dispose();
        }

        /// <summary>
        /// Send unhandled commands through the tunnel.
        /// </summary>
        /// <remarks>
        /// Off by defualt.
        /// </remarks>
        /// <param name="value"></param>
        public void ProcessUnhandledCommands(bool value)
        {
            if (value)
                _dispatcher.Unhandled += OnRemoteCommand;
            else
                _dispatcher.Unhandled -= OnRemoteCommand;
        }

        /// <summary>
        /// Handle commands tagged with the <see cref="IRemote"/> interface.
        /// </summary>
        /// <param name="value"></param>
        /// <remarks>Off by default.</remarks>
        public void ProcessRemoteCommands(bool value)
        {
            if (value)
                _dispatcher.Add(typeof(IRemote), OnRemoteCommand);
            else
                _dispatcher.Remove(typeof(IRemote), OnRemoteCommand);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (_channel != null)
            {
                _channel.Dispose();
                _channel = null;
            }
            Disconnected = null;
            //_copier.Dispose();
        }

        private void OnDisconnected(object source, DisconnectedEventArgs args)
        {
            Disconnected(this, args);
        }

        /// <summary>
        /// True if socket should auto reconnect connection is closed due to any network error.
        /// </summary>
        public bool AutoReconnect
        {
            set { _channel.ShouldReconnect = value; }
        }

        /// <summary>
        /// Connect to other side.
        /// </summary>
        /// <param name="endPoint"></param>
        /// <exception cref="InvalidOperationException"></exception>
        public void Open(IPEndPoint endPoint)
        {
            try
            {
                if (_channel == null)
                {
                    _channel = new BinaryChannel();
                    _channel.ObjectReceived += OnObjectReceived;
                    _channel.Disconnected += OnDisconnected;
                }
                _channel.Open(endPoint);
            }
            catch (SocketException err)
            {
                throw new InvalidOperationException("Failed to open channel.", err);
            }
        }

        /// <summary>
        /// Close command tunnel
        /// </summary>
        public void Close()
        {
            _channel.ObjectReceived -= OnObjectReceived;
            _channel.Disconnected -= OnDisconnected;
            _channel.Close();
        }

        private void OnObjectReceived(object source, ObjectReceivedEventArgs args)
        {
            CommandPacket packet = (CommandPacket) args.Object;
            if (packet.Reply)
            {
                lock (_packets)
                {
                    if (_packets.ContainsKey(packet.SequenceNumber))
                    {
                        CommandMapping mapping = _packets[packet.SequenceNumber];
                        mapping.Command = packet.Command;
                        _packets.Remove(packet.SequenceNumber);
                        mapping.Signal();
                    }
                }
            }
            else
            {
                _dispatcher.Invoke(packet.Command, OnRemoteCommand);
                packet.Reply = true;
                _channel.Send(packet);
            }
        }


        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            _channel.Close();
        }

        internal static void GetClientServer(out BinaryChannel server, out BinaryChannel client)
        {
            client = new BinaryChannel();
            client.ShouldReconnect = false;
            int port = 8291;
            TcpListener listener = new TcpListener(IPAddress.Any, port);
            listener.Start();
            IAsyncResult res = listener.BeginAcceptSocket(null, null);

            client.Open(new IPEndPoint(IPAddress.Loopback, port));
            Socket socket = listener.EndAcceptSocket(res);
            listener.Stop();
            server = new BinaryChannel(socket);
        }

        [Fact]
        private static void TestChannels()
        {
            BinaryChannel server, client;
            GetClientServer(out server, out client);

            ManualResetEvent _wait = new ManualResetEvent(false);

            server.ObjectReceived += delegate(object source, ObjectReceivedEventArgs args)
                                         {
                                             Assert.IsType<TestCommand>(args.Object);
                                             TestCommand cmd = (TestCommand) args.Object;
                                             Assert.Equal("jonas", cmd.UserName);
                                             cmd.MyList.Add("Hello world!");
                                             server.Send(cmd);
                                         };

            client.ObjectReceived += delegate(object source, ObjectReceivedEventArgs args)
                                         {
                                             Assert.IsType<TestCommand>(args.Object);
                                             TestCommand cmd = (TestCommand) args.Object;
                                             Assert.Equal("jonas", cmd.UserName);
                                             Assert.Equal(1, cmd.MyList.Count);
                                             Assert.Equal("Hello world!", cmd.MyList[0]);
                                             _wait.Set();
                                         };

            client.Send(new TestCommand("jonas"));
            Assert.True(_wait.WaitOne(30000, false));
        }

        [Fact]
        private static void TestTunnels()
        {
            BinaryChannel serverChannel, clientChannel;
            GetClientServer(out serverChannel, out clientChannel);

            // setup server side
            CommandManager serverMgr = new CommandManager();
            serverMgr.Add(typeof(TestCommand), OnTestTunnelCommand);
            CommandChannel server = new CommandChannel(serverMgr, serverChannel);


            // setup client side.
            CommandManager clientMgr = new CommandManager();
            CommandChannel client = new CommandChannel(clientMgr, clientChannel);
            client.ProcessUnhandledCommands(true);

            // invoke command
            TestCommand cmd = new TestCommand("arne");
            clientMgr.Invoke(cmd);

            // validate that the server fixed it.
            Assert.Equal(1, cmd.MyList.Count);
            Assert.Equal("Hello World!", cmd.MyList[0]);
        }

        private static bool OnTestTunnelCommand(object source, CommandEventArgs args)
        {
            TestCommand cmd = (TestCommand) args.Command;
            Assert.Equal("arne", cmd.UserName);
            cmd.MyList.Add("Hello World!");
            return true;
        }

        private CommandMapping CreateMapping()
        {
            CommandMapping mapping = new CommandMapping(++_lastSequenceNumber);
            if (_lastSequenceNumber >= 65535)
                _lastSequenceNumber = 0;
            _packets.Add(mapping.SequenceNumber, mapping);
            return mapping;
        }
    }
}

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 GNU Lesser General Public License (LGPLv3)

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

| Advertise | Privacy | Mobile
Web02 | 2.8.140814.1 | Last Updated 3 Sep 2008
Article Copyright 2008 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid