Click or drag to resize

Protocol Class

Represents one protocol.
Inheritance Hierarchy

Namespace:  Demo3D.Net
Assembly:  Demo3D.IO (in Demo3D.IO.dll) Version: 15.0.2.11458
Syntax
C#
[TypeConverterAttribute(typeof(ExpandableObjectConverter))]
public abstract class Protocol : IDisposable

The Protocol type exposes the following members.

Constructors
Properties
  NameDescription
Public propertyChannels
List of all channels.
Public propertyDefaultChannel
The default channel.
Public propertyName
The protocol name.
Public propertyStatic memberRegistry
A register of all protocols.
Public propertyRequired
Required protocols and services.
Public propertySupportedServices
Returns a list of supported service types. These are the services that ProtocolSocket.FindService can return.
Top
Methods
  NameDescription
Public methodAddChannel
Adds a channel.
Protected methodCreateCanonicalAddress

Returns the full address. Eg add in a default port number. Should return an address that uniquely identifies the peer.

The address should explicitly contain any information required to connect (including any defaults), but exclude any connection parameters, routing information, etc, unless required to identify the peer. Ambiguous parts of the address should be standardised - eg where information is case insensitive, it should be converted to lower case. Any parts of the address not required should be removed.

(The framework will automatically convert hostnames to an appropriate IP address, and will set the scheme correctly, so no need to modify those parts.)

Protected methodCreateDownStreamAddress
Creates the downstream address given an upstream address and a downstream protocol. Normally this does not need to be overridden, but there are some cases where it's required. For example, the downstream address returned by COTP for "cotp://host/tsap" would be "tpkt://host:102". Or you can return null to stop the protocol stack from building downstream protocol instances. You might do this if you want to take control of building the downstream protocol instances, or nor build them at all.
Public methodDispose
Releases all protocol resources.
Public methodFindChannel
Returns the named channel, or null.
Protected methodNewInstance
Create a new server/client protocol instance.
Public methodRegisterService
Registers a service.
Public methodRemoveChannel
Removes a channel.
Public methodSupportsService
Returns whether a particular service is supported.
Public methodToString
Returns the name of the protocol.
(Overrides ObjectToString.)
Public methodUnregisterService
Unregisters a service.
Top
Examples

The following example shows the most basic implementation of a Protocol.

C#
using System.Threading.Tasks;
using Demo3D.Net;

namespace Examples.Net.ExampleProtocol {
    /// <summary>
    /// An interface that describes the services (the IO methods) that your protocol supports.
    /// Typically a protocol would implement one of the standard services (such as <see cref="IPacketIOService"/>).
    /// </summary>
    interface IExampleService {
        // Put whatever methods users of your protocol would expect in here.
    }

    /// <summary>
    /// An instance of a protocol.  Must inherit from <see cref="ProtocolInstance"/>, and should
    /// support a set of services.  The simplest way to support services is to implement the
    /// services interface (<see cref="IExampleService"/>) and to advertise those services in the
    /// <see cref="ExampleProtocol.NewInstance(ProtocolHead, ProtocolAddress)"/> constructor.
    /// </summary>
    /// <remarks>
    /// See <see cref="TCPExample.Client.TcpClientProtocol.TcpClientConnection"/> for an
    /// example implementing the TCP protocol.
    /// </remarks>
    class ExampleProtocolInstance : ProtocolInstance, IExampleService {
        public ExampleProtocolInstance(Protocol protocol, ProtocolHead head, ProtocolAddress address) : base(protocol, head, address, false, null) { }

        /// <summary>
        /// Initializes the socket.
        /// </summary>
        /// <param name="sync">If true, the Task returned must be guaranteed to be complete.</param>
        protected override Task InitializeAsync(bool sync) {
            // Any initialization code goes here.
            return Task.CompletedTask;
        }

        /// <summary>
        /// Indicates whether the instance is still running.
        /// </summary>
        protected override bool InternalRunning => base.InternalRunning;  // Return true if the connection is established

        /// <summary>
        /// Opens or reopens the instance.
        /// </summary>
        /// <param name="sync">If true, the Task returned must be guaranteed to be complete.</param>
        /// <param name="flags">Additional flags.</param>
        protected override Task InternalOpenAsync(bool sync, Flags flags) {
            // Open your connection here.
            return Task.CompletedTask;
        }

        /// <summary>
        /// Forcibly shuts down the underlying protocol, causing all other users to throw an error.
        /// </summary>
        protected override void InternalShutdown() {
            // Forcibly shut the connection down.
        }

        /// <summary>
        /// Performs a controlled close.
        /// </summary>
        protected override void InternalClose() {
            // Called to close the connection.
            InternalShutdown();
        }

        // Implement any IExampleService methods here.
    }

    /// <summary>
    /// A protocol inherits from <see cref="Protocol"/>.
    /// </summary>
    /// See <see cref="TCPExample.Client.TcpClientProtocol"/> for an example implementing the TCP protocol,
    /// or <see cref="TCPExample.Server.TcpServerProtocol"/> for an example implementing a TCP server.
    /// </remarks>
    class ExampleProtocol : Protocol {
        /// <summary>
        /// Constructs a new protocol.
        /// You need to give your protocol a name, and advertise which services it supports.
        /// </summary>
        public ExampleProtocol() : base("Example", typeof(IExampleService)) { }

        /// <summary>
        /// Create a new server/client protocol instance.
        /// At the minimum, you need to implement this method to return a new instance of your protocol.
        /// </summary>
        /// <param name="head">The socket head which is a required parameter to the ProtocolInstance constructor.</param>
        /// <param name="protocolAddress">The protocol address.</param>
        /// <returns>A new ProtocolInstance.</returns>
        protected override ProtocolInstance NewInstance(ProtocolHead head, ProtocolAddress protocolAddress) {
            return new ExampleProtocolInstance(this, head, protocolAddress);
        }

        /// <summary>
        /// Registers the protocol with Demo3D.Net.
        /// You need to call this method somewhere in your code in order to register this protocol with Demo3D.Net.
        /// </summary>
        public static void Register() {
            Registry.Register(new ExampleProtocol());
        }
    }
}
Examples

Example showing a TCP client.

C#
using System.ComponentModel;
using System.IO;
using System.Net.Sockets;
using System.Threading.Tasks;
using Demo3D.IO;
using Demo3D.Net;

using TcpClient = System.Net.Sockets.TcpClient;

namespace Examples.Net.TCPExample.Client {
    /// <summary>
    /// TCP address editor.
    /// </summary>
    public class TcpAddressEditor : ProtocolAddressPropertyBagEditor {
        string host;
        int    port;

        /// <summary>
        /// The server host.
        /// </summary>
        [Description("The server host.")]
        [Category("Connection")]
        public string Host {
            get { return host; }
            set {
                if (host != value) {
                    host = value;
                    NotifyPropertyChanged();
                }
            }
        }

        /// <summary>
        /// The server port.
        /// </summary>
        [Description("The server port.")]
        [Category("Connection")]
        public int Port {
            get { return port; }
            set {
                if (port != value) {
                    port = value;
                    NotifyPropertyChanged();
                }
            }
        }

        /// <summary>
        /// Returns a ProtocolAddress that represents the configuration defined by this object.
        /// </summary>
        /// <returns>The protocol address according to the current setting of the editor properties.</returns>
        public override ProtocolAddress GetAddress() {
            return new ProtocolAddressBuilder("tcp", host, port).Address;
        }

        /// <summary>
        /// Sets this configuration object properties from the ProtocolAddress given.
        /// </summary>
        /// <param name="address">The current address.</param>
        public override void SetAddress(ProtocolAddress address) {
            host = address.Host;
            port = address.Port;
        }

        /// <summary>
        /// Returns the next property that needs to be edited to complete the address.
        /// </summary>
        /// <returns>The name of the property, or null.</returns>
        public override string NextProperty() {
            if (string.IsNullOrWhiteSpace(this.Host)) return nameof(this.Host);
            if (this.Port <= 0)                       return nameof(this.Port);
            return null;
        }
    }

    [ProtocolAddressEditor(Editor = typeof(TcpAddressEditor))]
    public sealed class TcpClientProtocol : Protocol {
        class TcpClientConnection : ProtocolInstance, IByteStreamService {
            TcpClient     tcpClient;
            NetworkStream stream;

            internal TcpClientConnection(Protocol protocol, ProtocolHead head, ProtocolAddress peerAddress)
                : base(protocol, head, peerAddress, true, null) {
            }

            protected override bool InternalRunning {
                get { return tcpClient != null && tcpClient.Connected; }
            }

            protected override async Task InternalOpenAsync(bool sync, Flags flags) {
                string host = this.Address.Host;
                int    port = this.Address.IsDefaultPort ? 0 : this.Address.Port;

                if (!this.Address.HasHost || port <= 0) {
                    throw Exceptions.ProtocolError("Host or port not configured", this);
                }

                tcpClient = new TcpClient();

                if (sync) tcpClient.Connect(host, port);
                else      await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);

                stream = tcpClient.GetStream();
            }

            protected override void InternalShutdown() {
                try { tcpClient?.Client?.Shutdown(SocketShutdown.Both); }
                catch { }
            }

            protected override void InternalClose() {
                if (tcpClient == null) return;

                InternalShutdown();

                try { tcpClient?.Client?.Close(); }
                catch { }

                try { tcpClient?.Close(); }
                catch { }

                stream    = null;
                tcpClient = null;
            }

            bool   IByteStreamService.DataAvailable { get { return stream.DataAvailable; } }
            Stream IByteStreamService.Stream        { get { return stream;               } }
        }

        TcpClientProtocol() : base("TCP", typeof(IByteStreamService)) { }

        public static void Register() {
            Registry.Register(new TcpClientProtocol());
        }

        protected override ProtocolInstance NewInstance(ProtocolHead head, ProtocolAddress protocolAddress) {
             return new TcpClientConnection(this, head, protocolAddress);
        }
    }
}
Examples

Example showing a TCP server.

C#
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Demo3D.IO;
using Demo3D.Net;

using TcpClient = System.Net.Sockets.TcpClient;

namespace Examples.Net.TCPExample.Server {
    public sealed class TcpServerProtocol : Protocol {
        class TcpServerConnection : ProtocolInstance, IByteStreamService {
            TcpClient     tcpClient;
            NetworkStream stream;

            internal TcpServerConnection(TcpClient tcpClient, Protocol protocol, ProtocolHead head, ProtocolAddress peerAddress)
                : base(protocol, head, peerAddress, true, null) {
                this.tcpClient = tcpClient;
                stream         = tcpClient.GetStream();
            }

            protected override bool InternalRunning {
                get { return tcpClient != null && tcpClient.Connected; }
            }

            protected override Task InternalOpenAsync(bool sync, Flags flags) {
                throw new Exception("Cannot reconnect");
            }

            protected override void InternalShutdown() {
                try { tcpClient?.Client?.Shutdown(SocketShutdown.Both); }
                catch { }
            }

            protected override void InternalClose() {
                if (tcpClient == null) return;

                InternalShutdown();

                try { tcpClient?.Client?.Close(); }
                catch { }

                try { tcpClient?.Close(); }
                catch { }

                stream    = null;
                tcpClient = null;
            }

            bool   IByteStreamService.DataAvailable { get { return stream.DataAvailable; } }
            Stream IByteStreamService.Stream        { get { return stream;               } }
        }

        sealed class TcpServer : ProtocolInstance, IProtocolServerService {
            TcpListener listener;

            internal TcpServer(Protocol protocol, ProtocolHead head, ProtocolAddress protocolAddress)
                : base(protocol, head, protocolAddress, false, null) {
            }

            protected override bool InternalRunning { get { return listener != null; } }

            protected override async Task InternalOpenAsync(bool sync, Flags flags) {
                int       port = this.Address.IsDefaultPort ? 0 : this.Address.Port;
                IPAddress ipAddress;

                switch (this.Address.Host) {
                    case "":
                    case ProtocolAddress.AnyHost:
                        ipAddress = IPAddress.Any;
                        break;

                    default:
                        ipAddress = await IPv4.HostToIPAddressAsync(sync, this.Address.Host).ConfigureAwait(false);
                        break;
                }

                listener = new TcpListener(ipAddress, port);
                listener.Start();
            }

            protected override void InternalShutdown() {
                try { listener?.Server.Close(); }
                catch { }
            }

            protected override void InternalClose() {
                try { listener?.Stop(); }
                catch { }
                listener = null;
            }

            async Task<ProtocolInstance> IProtocolServerService.InternalAcceptAsync(bool sync, ProtocolHead head, ProtocolInstance downStreamConnection) {
                TcpClient client;

                if (sync) client = listener.AcceptTcpClient();
                else      client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);

                var endPoint = (IPEndPoint)client.Client.RemoteEndPoint;
                var address  = new ProtocolAddressBuilder("tcp", endPoint.Address.ToString(), endPoint.Port).Address;

                return new TcpServerConnection(client, this.Protocol, head, address);
            }
        }

        TcpServerProtocol() : base("TCP", typeof(IProtocolServerService), typeof(IByteStreamService)) { }

        public static void Register() {
            Registry.Register(new TcpServerProtocol());
        }

        protected override ProtocolInstance NewInstance(ProtocolHead head, ProtocolAddress protocolAddress) {
            return new TcpServer(this, head, protocolAddress);
        }
    }
}
See Also