diff --git a/Platform.Communication.csproj b/Platform.Communication.csproj new file mode 100644 index 0000000..9b91bba --- /dev/null +++ b/Platform.Communication.csproj @@ -0,0 +1,31 @@ + + + + LinksPlatform's Platform.Communication Class Library + Konstantin Diachenko + Platform.Communication + 0.1.0 + Konstantin Diachenko + netstandard2.0 + Platform.Communication + Platform.Communication + Communication;Protocols;Gexf;Udp;UdpSender;UdpReceiver + https://raw.githubusercontent.com/linksplatform/Documentation/18469f4d033ee9a5b7b84caab9c585acab2ac519/doc/Avatar-rainbow-icon-64x64.png + https://github.com/linksplatform/Communication + LGPL-3.0-only + true + git + git://github.com/linksplatform/Communication + false + false + false + true + snupkg + + + + + + + + diff --git a/Platform.Communication.sln b/Platform.Communication.sln new file mode 100644 index 0000000..c4c8b76 --- /dev/null +++ b/Platform.Communication.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29102.190 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Platform.Communication", "Platform.Communication.csproj", "{8B98EB46-0A79-45F7-AD16-CA7C17F1452C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B98EB46-0A79-45F7-AD16-CA7C17F1452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B98EB46-0A79-45F7-AD16-CA7C17F1452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B98EB46-0A79-45F7-AD16-CA7C17F1452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B98EB46-0A79-45F7-AD16-CA7C17F1452C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {98DDC0A8-01B8-4DF5-8EB7-74AA61EADDCE} + EndGlobalSection +EndGlobal diff --git a/Protocol/Gexf/Edge.cs b/Protocol/Gexf/Edge.cs new file mode 100644 index 0000000..0898b8a --- /dev/null +++ b/Protocol/Gexf/Edge.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; + +namespace Platform.Communication.Protocol.Gexf +{ + public class Edge + { + public const string ElementName = "edge"; + public const string IdAttributeName = "id"; + public const string SourceAttributeName = "source"; + public const string TargetAttributeName = "target"; + public const string LabelAttributeName = "label"; + + [XmlAttribute(AttributeName = IdAttributeName)] + public long Id { get; set; } + + [XmlAttribute(AttributeName = SourceAttributeName)] + public long Source { get; set; } + + [XmlAttribute(AttributeName = TargetAttributeName)] + public long Target { get; set; } + + [XmlAttribute(AttributeName = LabelAttributeName)] + public string Label { get; set; } + + public void WriteXml(XmlWriter writer) => WriteXml(writer, Id, Source, Target, Label); + + public static void WriteXml(XmlWriter writer, long id, long sourceNodeId, long targetNodeId, string label = null) + { + // + writer.WriteStartElement(ElementName); + + writer.WriteAttributeString(IdAttributeName, id.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString(SourceAttributeName, sourceNodeId.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString(TargetAttributeName, targetNodeId.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrWhiteSpace(label)) + writer.WriteAttributeString(LabelAttributeName, label); + + writer.WriteEndElement(); + } + } +} diff --git a/Protocol/Gexf/Enums.cs b/Protocol/Gexf/Enums.cs new file mode 100644 index 0000000..e178508 --- /dev/null +++ b/Protocol/Gexf/Enums.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace Platform.Communication.Protocol.Gexf +{ + public enum GraphMode + { + [XmlEnum(Name = "static")] + Static, + + [XmlEnum(Name = "dynamic")] + Dynamic + } + + public enum GraphDefaultEdgeType + { + [XmlEnum(Name = "directed")] + Directed + } +} diff --git a/Protocol/Gexf/Gexf.cs b/Protocol/Gexf/Gexf.cs new file mode 100644 index 0000000..7579cfc --- /dev/null +++ b/Protocol/Gexf/Gexf.cs @@ -0,0 +1,53 @@ +using System; +using System.Xml; +using System.Xml.Serialization; + +namespace Platform.Communication.Protocol.Gexf +{ + [XmlRoot(ElementName = ElementName, Namespace = Namespace)] + public class Gexf + { + public const string ElementName = "gexf"; + public const string Namespace = "http://www.gexf.net/1.2draft"; + public const string VersionAttributeName = "version"; + public const string GraphElementName = "graph"; + public const string CurrentVersion = "1.2"; + + [XmlAttribute(AttributeName = VersionAttributeName)] + public string Version { get; set; } + + [XmlElement(ElementName = GraphElementName)] + public Graph Graph { get; set; } + + public Gexf() + { + Version = CurrentVersion; + Graph = new Graph(); + } + + public void WriteXml(XmlWriter writer) + { + void writeGraph() => Graph.WriteXml(writer); + + WriteXml(writer, writeGraph, Version); + } + + public static void WriteXml(XmlWriter writer, Action writeGraph, string version = CurrentVersion) + { + writer.WriteStartDocument(); + writer.WriteStartElement(ElementName, Namespace); + writer.WriteAttributeString(VersionAttributeName, version); + + writeGraph(); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + public static void WriteXml(XmlWriter writer, Action writeNodes, Action writeEdges, string version = CurrentVersion, + GraphMode mode = GraphMode.Static, GraphDefaultEdgeType defaultEdgeType = GraphDefaultEdgeType.Directed) + { + WriteXml(writer, () => Graph.WriteXml(writer, writeNodes, writeEdges, mode, defaultEdgeType), version); + } + } +} diff --git a/Protocol/Gexf/Graph.cs b/Protocol/Gexf/Graph.cs new file mode 100644 index 0000000..f3a16c9 --- /dev/null +++ b/Protocol/Gexf/Graph.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; + +namespace Platform.Communication.Protocol.Gexf +{ + public class Graph + { + public const string ElementName = "graph"; + public const string ModeAttributeName = "mode"; + public const string DefaultEdgeTypeAttributeName = "defaultedgetype"; + public const string NodesElementName = "nodes"; + public const string NodeElementName = "node"; + public const string EdgesElementName = "edges"; + public const string EdgeElementName = "edge"; + + [XmlAttribute(AttributeName = ModeAttributeName)] + public GraphMode Mode { get; set; } + + [XmlAttribute(AttributeName = DefaultEdgeTypeAttributeName)] + public GraphDefaultEdgeType DefaultEdgeType { get; set; } + + [XmlArray(ElementName = NodesElementName)] + [XmlArrayItem(ElementName = NodeElementName)] + public List Nodes { get; set; } + + [XmlArray(ElementName = EdgesElementName)] + [XmlArrayItem(ElementName = EdgeElementName)] + public List Edges { get; set; } + + public Graph() + { + Nodes = new List(); + Edges = new List(); + } + + public void WriteXml(XmlWriter writer) + { + void writeNodes() + { + for (var i = 0; i < Nodes.Count; i++) + Nodes[i].WriteXml(writer); + } + + void writeEdges() + { + for (var i = 0; i < Edges.Count; i++) + Edges[i].WriteXml(writer); + } + + WriteXml(writer, writeNodes, writeEdges, Mode, DefaultEdgeType); + } + + public static void WriteXml(XmlWriter writer, Action writeNodes, Action writeEdges, GraphMode mode = GraphMode.Static, GraphDefaultEdgeType defaultEdgeType = GraphDefaultEdgeType.Directed) + { + writer.WriteStartElement(ElementName); + + writer.WriteAttributeString(ModeAttributeName, mode.ToString().ToLower()); + writer.WriteAttributeString(DefaultEdgeTypeAttributeName, defaultEdgeType.ToString().ToLower()); + + writer.WriteStartElement(NodesElementName); + + writeNodes(); + + writer.WriteEndElement(); + + writer.WriteStartElement(EdgesElementName); + + writeEdges(); + + writer.WriteEndElement(); + + writer.WriteEndElement(); + } + } +} diff --git a/Protocol/Gexf/Node.cs b/Protocol/Gexf/Node.cs new file mode 100644 index 0000000..0f2dffd --- /dev/null +++ b/Protocol/Gexf/Node.cs @@ -0,0 +1,32 @@ +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; + +namespace Platform.Communication.Protocol.Gexf +{ + public class Node + { + public const string ElementName = "node"; + public const string IdAttributeName = "id"; + public const string LabelAttributeName = "label"; + + [XmlAttribute(AttributeName = IdAttributeName)] + public long Id { get; set; } + + [XmlAttribute(AttributeName = LabelAttributeName)] + public string Label { get; set; } + + public void WriteXml(XmlWriter writer) => WriteXml(writer, Id, Label); + + public static void WriteXml(XmlWriter writer, long id, string label) + { + // + writer.WriteStartElement(ElementName); + + writer.WriteAttributeString(IdAttributeName, id.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString(LabelAttributeName, label); + + writer.WriteEndElement(); + } + } +} diff --git a/Protocol/Udp/UdpClientExtensions.cs b/Protocol/Udp/UdpClientExtensions.cs new file mode 100644 index 0000000..fefa79d --- /dev/null +++ b/Protocol/Udp/UdpClientExtensions.cs @@ -0,0 +1,24 @@ +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using Platform.Threading; +using Platform.Helpers; + +namespace Platform.Communication.Protocol.Udp +{ + public static class UdpClientExtensions + { + private static readonly Encoding DefaultEncoding = Singleton.Get(() => Encoding.GetEncoding(0)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SendString(this UdpClient udp, IPEndPoint ipEndPoint, string message) + { + var bytes = DefaultEncoding.GetBytes(message); + return udp.SendAsync(bytes, bytes.Length, ipEndPoint).AwaitResult(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReceiveString(this UdpClient udp) => DefaultEncoding.GetString(udp.ReceiveAsync().AwaitResult().Buffer); + } +} diff --git a/Protocol/Udp/UdpReceiver.cs b/Protocol/Udp/UdpReceiver.cs new file mode 100644 index 0000000..7d591ed --- /dev/null +++ b/Protocol/Udp/UdpReceiver.cs @@ -0,0 +1,108 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using Platform.Disposables; +using Platform.Helpers; + +namespace Platform.Communication.Protocol.Udp +{ + public delegate void MessageHandlerCallback(string message); + + /// + /// Представляет получателя сообщений по протоколу UDP. + /// + /// + /// TODO: Попробовать ThreadPool / Tasks + /// + public class UdpReceiver : DisposableBase //-V3073 + { + private const int DefaultPort = 15000; + + private bool _receiverRunning; + private Thread _thread; + private readonly int _listenPort; + private readonly UdpClient _udp; + private readonly MessageHandlerCallback _messageHandler; + + public bool Available => _udp.Available > 0; + + public UdpReceiver(int listenPort, bool autoStart, MessageHandlerCallback messageHandler) + { + _udp = new UdpClient(listenPort); + _listenPort = listenPort; + _messageHandler = messageHandler; + + if (autoStart) Start(); + } + + public UdpReceiver(int listenPort, MessageHandlerCallback messageHandler) + : this(listenPort, true, messageHandler) + { + } + + public UdpReceiver(MessageHandlerCallback messageHandler) + : this(DefaultPort, true, messageHandler) + { + } + + public UdpReceiver() + : this(DefaultPort, true, message => { }) + { + } + + public void Start() + { + if (!_receiverRunning && _thread == null) + { + _receiverRunning = true; + _thread = new Thread(Receiver); + _thread.Start(); + } + } + + public void Stop() + { + if (_receiverRunning && _thread != null) + { + _receiverRunning = false; + + // Send Packet to itself to switch Receiver from Receiving. + // TODO: Test new stopper + var loopback = new IPEndPoint(IPAddress.Loopback, _listenPort); + new UdpClient().SendAsync(new byte[0], 0, loopback).ContinueWith(Disposable.DisposeIfDisposable); + + _thread.Join(); + _thread = null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Receive() => _udp.ReceiveString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReceiveAndHandle() => _messageHandler(Receive()); + + // Функция извлекающая пришедшие сообщения + // и работающая в отдельном потоке. + private void Receiver() + { + while (_receiverRunning) + { + try { ReceiveAndHandle(); } + catch (Exception exception) + { + Global.OnIgnoredException(exception); + } + } + } + + protected override void DisposeCore(bool manual, bool wasDisposed) + { + if (!wasDisposed) + Stop(); + Disposable.TryDispose(_udp); + } + } +} \ No newline at end of file diff --git a/Protocol/Udp/UdpSender.cs b/Protocol/Udp/UdpSender.cs new file mode 100644 index 0000000..db1ca49 --- /dev/null +++ b/Protocol/Udp/UdpSender.cs @@ -0,0 +1,42 @@ +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using Platform.Disposables; + +namespace Platform.Communication.Protocol.Udp +{ + /// + /// Представляет отправителя сообщений по протоколу UDP. + /// + public class UdpSender : DisposableBase //-V3073 + { + private readonly UdpClient _udp; + private readonly IPEndPoint _ipendpoint; + + public UdpSender(IPEndPoint ipendpoint) + { + _udp = new UdpClient(); + _ipendpoint = ipendpoint; + } + + public UdpSender(IPAddress address, int port) + : this(new IPEndPoint(address, port)) + { + } + + public UdpSender(string hostname, int port) + : this(IPAddress.Parse(hostname), port) + { + } + + public UdpSender(int port) + : this(IPAddress.Loopback, port) + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Send(string message) => _udp.SendString(_ipendpoint, message); + + protected override void DisposeCore(bool manual, bool wasDisposed) => Disposable.TryDispose(_udp); + } +} \ No newline at end of file diff --git a/push-nuget.bat b/push-nuget.bat new file mode 100644 index 0000000..4ab67b5 --- /dev/null +++ b/push-nuget.bat @@ -0,0 +1,6 @@ +dotnet pack -c Release +cd bin\Release\ +nuget push -Source https://api.nuget.org/v3/index.json *.nupkg +del *.nupkg +del *.snupkg +cd ..\.. \ No newline at end of file