├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md └── SocketHelpers ├── SocketHelpers.nuspec ├── SocketHelpers.sln └── SocketHelpers ├── Discovery ├── Base │ ├── ServiceDiscovererBase.cs │ └── ServiceWorkerBase.cs ├── ByteArrayServiceDiscoverer.cs ├── Extensions │ └── DiscoveryExtensions.cs ├── IDiscoveryPayload.cs ├── ServiceDefinition │ ├── FuncyJsonServiceDefinition.cs │ ├── IServiceDefinition.cs │ ├── JsonSerializedServiceDefinition.cs │ └── TypedServiceDefinition.cs ├── ServiceDiscoverer.cs └── ServicePublisher.cs ├── Extensions ├── AsyncExtensions.cs └── SocketReadingExtensions.cs ├── Messaging ├── IMessage.cs ├── IProxy.cs ├── JsonProtocolMessenger.cs ├── JsonProtocolMessengerMessageType.cs ├── JsonProtocolQueueItem.cs ├── MergableSubject.cs └── MessageHub.cs ├── Properties └── AssemblyInfo.cs ├── SocketHelpers.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # LightSwitch generated files 187 | GeneratedArtifacts/ 188 | _Pvt_Extensions/ 189 | ModelManifest.xml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | _This changelog refers to nuget package releases._ 2 | 3 | #### 0.0.1-alpha (2015-04-08) 4 | 5 | First published to NuGet. This is all alpha stuff - APIs are likely to change. 6 | Includes: 7 | 8 | - **Service Discovery** - See the `Discovery` namespace, overview [here](http://ryandavis.io/service-discovery-in-mobile-apps/) and sample code in the [README](https://github.com/rdavisau/sockethelpers-for-pcl/blob/master/README.md). 9 | - **Typed Message Transmission** - See the `Messaging` namespace and sample code in the [README](https://github.com/rdavisau/sockethelpers-for-pcl/blob/master/README.md). 10 | - **Hub Routing** - Also hidden in the `Messaging` namespace. No sample code for this yet. Basic functionality works, but this is the least developed part of the library. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ryan Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Socket Helpers Plugin for Xamarin and Windows (PCL) 2 | 3 | This library aims to provide useful functionality around the base [sockets-for-pcl](https://github.com/rdavisau/sockets-for-pcl/) classes, including hub-style communications, custom protocol helpers and support for typed messaging, and error handling/life cycle and reliability options. 4 | 5 | There is a NuGet package with [here](https://www.nuget.org/packages/rda.SocketHelpers) with the latest functionality. This is all alpha so it is possible that new versions will break existing code. 6 | 7 | #### Service Discovery 8 | Alpha code for service discovery is now included in the project. 9 | Please see [here](http://ryandavis.io/service-discovery-in-mobile-apps/) for an overview of the design and usage. 10 | 11 | **Creating a basic inline service definition** 12 | ```csharp 13 | // responds to all requests with its ip/port as a string 14 | var serviceDef = new FuncyJsonServiceDefinition() 15 | { 16 | DiscoveryRequestFunc = () => "EHLO", 17 | ResponseForRequestFunc = _ => String.Format("{0}:{1}", myIP, myPort) 18 | }; 19 | ``` 20 | 21 | **Host side - publishing according to the service definition** 22 | ```csharp 23 | // set up publisher and start listening 24 | var publisher = serviceDef.CreateServicePublisher(); 25 | publisher.Publish(); 26 | ``` 27 | 28 | **Client side - discovering services according to the service definition** 29 | ```csharp 30 | // set up discoverer and response handler 31 | var discoverer = serviceDef.CreateServiceDiscoverer(); 32 | discover.DiscoveredServices.Subscribe(svc => /* handle your responses */); 33 | 34 | // start sending discovery requests 35 | discoverer.StartDiscovering(); 36 | ``` 37 | #### Typed Message Transmission 38 | Alpha code for strongly typed object transmission is included in the project via `JsonProtocolMessenger`. 39 | `JsonProtocolMessenger` wraps a `TcpSocketClient` and facilitates sending and receiving strongly typed objects. It exposes an `IObservable` of messages received, and a `Send(TMessage message)` method for sending to the other party. Serialisation is handled by JSON.NET. 40 | 41 | **Connecting** 42 | 43 | Here, `client` is a connected `TcpSocketClient`. `TMessage` is a type from which all your messages derive, `object` is fine if you have no overarching base class. 44 | ```csharp 45 | // wrap a connected socket in a JsonProtocolMessenger, start running the send/receive functions 46 | var messenger = new JsonProtocolMessenger(newClient); 47 | messenger.StartExecuting(); 48 | ``` 49 | 50 | **Receiving Messages** 51 | 52 | To handle received messages subscribe to the `Messages` property directly, or perform Rx operations over it as neccessary. 53 | Messages are not replayed or cached, only messages received from the point of a subscription onwards will be fire for that subscription. If you are expecting to receive messages immediately on connect, you should set up those subscriptions before calling `StartExecuting`. 54 | ```csharp 55 | // e.g. log any messages we receive to the console 56 | messenger.Messages 57 | .Subscribe(msg=> Debug.WriteLine(msg)); 58 | 59 | // e.g. print the names of people who send us HelloMessages to the console 60 | messenger.Messages 61 | .OfType() // only HelloMessages will pass through here 62 | .Subscribe(msg => Debug.WriteLine("{0} says hello.", msg.Name)); 63 | 64 | // e.g. don't proceed until we get a ReadyMessage from the other end 65 | // this kind of subscription has to be made *after* StartExecuting has been called. 66 | await messenger 67 | .Messages 68 | .OfType 69 | .FirstAsync() 70 | .ToTask(); 71 | ``` 72 | 73 | **Sending Messages** 74 | 75 | Call `Send` on `JsonProtocolMessenger`. 76 | ```csharp 77 | var msg = new Message { Content = "Hi" }; 78 | messenger.Send(msg); 79 | ``` 80 | 81 | **Type Resolution** 82 | 83 | `JsonProtocolMessenger` includes the object type name as part of the protocol when sending, so that the receiving end knows what it should deserialize to. When a message is received, `JsonProtocolMessenger` uses `Type.GetType(typeName)` to resolve the type. This might fail to resolve the type in some obscure situations, notably when running in a LINQPad query and transmitting an object whose class is defined in the query itself. To help `JsonProtocolMessenger` find your type, you can add assemblies to its `AdditionalTypeResolutionAssemblies` property. These assemblies will be searched if the initial `Type.GetType()` call does not return a type. In the LINQPad case specifically, you should also add `#DEFINE NONEST` to the top of your query to prevent it from nesting types within its `UserQuery` class. 84 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rda.SocketHelpers 6 | Socket Helpers for PCL projects (alpha) 7 | 0.0.3-alpha 8 | Ryan Davis 9 | Ryan Davis 10 | https://raw.githubusercontent.com/rdavisau/sockethelpers-for-pcl/master/LICENSE.md 11 | https://github.com/rdavisau/sockethelpers-for-pcl 12 | false 13 | 2015 Ryan Davis 14 | https://raw.githubusercontent.com/rdavisau/sockets-for-pcl/master/_meta/icon/icon.png 15 | 16 | 0.0.3 - Improvements to ServiceDiscovery - response types must now implement IDiscoveryPayload, allowing sockethelpers to populate the information automatically. 17 | 0.0.2 - Improvements to JsonProtocolMessenger. 18 | 0.0.1 - Alpha code for service discovery and typed message transmission. See the project page for more information. 19 | 20 | PCL-friendly helpers for common socket tasks, built on top of the sockets-for-pcl project. 21 | 22 | Cross-platform TCP and UDP socket API for Xamarin iOS/Android/Forms, Windows Phone 8/8.1, Windows Store and Windows Desktop. 23 | sockets, tcp, udp, multicast, xamarin, pcl, xam.pcl, windows phone, winphone, wp8, winrt, android, xamarin.forms, ios 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 38 | 40 | 41 | 42 | 44 | 46 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHelpers", "SocketHelpers\SocketHelpers.csproj", "{49EF63DE-16C1-4A67-B94F-DF0982ABB74E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7EDC6457-A528-4D1A-ACFE-A3A9DBEFB213}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\CHANGELOG.md = ..\CHANGELOG.md 11 | ..\LICENSE.md = ..\LICENSE.md 12 | ..\README.md = ..\README.md 13 | SocketHelpers.nuspec = SocketHelpers.nuspec 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {49EF63DE-16C1-4A67-B94F-DF0982ABB74E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {49EF63DE-16C1-4A67-B94F-DF0982ABB74E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {49EF63DE-16C1-4A67-B94F-DF0982ABB74E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {49EF63DE-16C1-4A67-B94F-DF0982ABB74E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/Base/ServiceDiscovererBase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Sockets.Plugin; 4 | using Sockets.Plugin.Abstractions; 5 | 6 | namespace SocketHelpers.Discovery 7 | { 8 | /// 9 | /// Abstract base class of ServiceDiscoverer. Contains the low-level discovery flow control. 10 | /// 11 | /// 12 | public abstract class ServiceDiscovererBase : ServiceWorkerBase 13 | where TServiceDefinition : IServiceDefinition 14 | { 15 | protected ServiceDiscovererBase() 16 | { 17 | _backingReceiver.MessageReceived += OnMessageReceived; 18 | } 19 | 20 | protected ServiceDiscovererBase(TServiceDefinition serviceDefinition) 21 | : base(serviceDefinition) 22 | { 23 | _backingReceiver.MessageReceived += OnMessageReceived; 24 | } 25 | 26 | public bool SendOnAllInterfaces { get; set; } 27 | private bool _listening = false; 28 | 29 | public async Task Discover() 30 | { 31 | var msg = _serviceDefinition.DiscoveryRequest(); 32 | 33 | if (!_listening) 34 | { 35 | await _backingReceiver.StartListeningAsync(_serviceDefinition.ResponsePort); 36 | _listening = true; 37 | } 38 | 39 | // TODO: Investigate the network conditions that allow/prevent broadcast traffic 40 | // Typically sending to 255.255.255.255 suffices. However, on some corporate networks the traffic does not 41 | // appear to be routed even within the same subnet. Currently, a packet is sent to the broadcast address 42 | // of each interface and the global broadcast address. This can result in multiple responses if a publisher 43 | // is bound across several interfaces. Multiple responses are easily consolidated if the payload format 44 | // contains a service guid; ideally we do not need to require users to incorporate that. 45 | // 46 | // Options: 47 | // - Detect when broadcast wont work, only send out on individual interfaces in these cases 48 | // - Consolidation responses within ServiceDiscoverer internally, only OnNext for unique services 49 | // This could require TPayloadFormat to be constrained to an interface that can carry the consolidated 50 | // information - for example, List of the interface addresses that responded to the request. 51 | if (SendOnAllInterfaces) 52 | { 53 | var ifs = 54 | (await CommsInterface.GetAllInterfacesAsync()).Where(ci => ci.IsUsable && !ci.IsLoopback).ToList(); 55 | foreach (var if0 in ifs) 56 | { 57 | await _backingReceiver.SendToAsync(msg, if0.BroadcastAddress, _serviceDefinition.DiscoveryPort); 58 | } 59 | } 60 | else 61 | { 62 | await _backingReceiver.SendToAsync(msg, "255.255.255.255", _serviceDefinition.DiscoveryPort); 63 | } 64 | } 65 | 66 | protected abstract void OnMessageReceived(object sender, UdpSocketMessageReceivedEventArgs e); 67 | } 68 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/Base/ServiceWorkerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sockets.Plugin; 3 | 4 | namespace SocketHelpers.Discovery 5 | { 6 | /// 7 | /// Abstract base class for ServicePublisher and ServiceDiscoverer. 8 | /// 9 | /// 10 | public abstract class ServiceWorkerBase 11 | where TServiceDefinition : IServiceDefinition 12 | { 13 | protected TServiceDefinition _serviceDefinition; 14 | protected UdpSocketReceiver _backingReceiver = new UdpSocketReceiver(); 15 | 16 | protected ServiceWorkerBase() 17 | { 18 | } 19 | 20 | protected ServiceWorkerBase(TServiceDefinition serviceDefinition) 21 | { 22 | _serviceDefinition = serviceDefinition; 23 | } 24 | 25 | /// 26 | /// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage 27 | /// collection. 28 | /// 29 | ~ServiceWorkerBase() 30 | { 31 | Dispose(false); 32 | } 33 | 34 | private void Dispose(bool disposing) 35 | { 36 | if (!disposing) return; 37 | if (_backingReceiver != null) 38 | ((IDisposable) _backingReceiver).Dispose(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ByteArrayServiceDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using Sockets.Plugin.Abstractions; 5 | 6 | namespace SocketHelpers.Discovery 7 | { 8 | public class ByteArrayServiceDiscoverer : ServiceDiscovererBase 9 | { 10 | public ByteArrayServiceDiscoverer(IServiceDefinition definition) : base(definition) 11 | { 12 | } 13 | 14 | protected override void OnMessageReceived(object sender, UdpSocketMessageReceivedEventArgs e) 15 | { 16 | _discoveredServices.OnNext(e.ByteData); 17 | } 18 | 19 | private readonly Subject _discoveredServices = new Subject(); 20 | 21 | public IObservable DiscoveredServices 22 | { 23 | get { return _discoveredServices.AsObservable(); } 24 | } 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/Extensions/DiscoveryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHelpers.Discovery 2 | { 3 | public static class DiscoveryExtensions 4 | { 5 | public static ByteArrayServiceDiscoverer CreateServiceDiscoverer(this IServiceDefinition serviceDefinition) 6 | { 7 | return new ByteArrayServiceDiscoverer(serviceDefinition); 8 | } 9 | 10 | public static ServicePublisher CreateServicePublisher(this IServiceDefinition serviceDefinition) 11 | { 12 | return new ServicePublisher(serviceDefinition); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/IDiscoveryPayload.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHelpers.Discovery 2 | { 3 | public interface IDiscoveryPayload 4 | { 5 | string RemoteAddress { get; set; } 6 | int RemotePort { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServiceDefinition/FuncyJsonServiceDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocketHelpers.Discovery 4 | { 5 | /// 6 | /// Provides Funcs for DiscoveryRequest and Reponse methods that allow inline definition of service discovery protocols 7 | /// 8 | /// 9 | /// 10 | public class FuncyJsonServiceDefinition : 11 | JsonSerializedServiceDefinition 12 | where TPayloadFormat : IDiscoveryPayload 13 | { 14 | public Func DiscoveryRequestFunc { get; set; } 15 | public Func ResponseForRequestFunc { get; set; } 16 | 17 | public override TSeekFormat DiscoveryRequest() 18 | { 19 | return DiscoveryRequestFunc(); 20 | } 21 | 22 | public override TPayloadFormat ResponseFor(TSeekFormat seekData) 23 | { 24 | return ResponseForRequestFunc(seekData); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServiceDefinition/IServiceDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHelpers.Discovery 2 | { 3 | public interface IServiceDefinition 4 | { 5 | // TODO: Investigate possibility to remove ResponsePort 6 | // The ReponsePort is the port to which the Discoverer listens in order to receive a 7 | // response from the Publisher when it sends a request. Ideally, we do not bind the 8 | // Discoverer to a port as it does not need to be known ahead of time. Instead, we 9 | // should let the operating system select the port and have the Publisher send back to 10 | // whatever port was chosen (it is included in the UdpMessageReceived eventargs). 11 | // Currently sockets-for-pcl does not support UDP binding without specifying the port. 12 | // With this change made it should be possible to removed ResponsePort from IServiceDefinition 13 | 14 | int DiscoveryPort { get; set; } 15 | int ResponsePort { get; set; } 16 | 17 | byte[] DiscoveryRequest(); 18 | byte[] ResponseFor(byte[] seekData); 19 | } 20 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServiceDefinition/JsonSerializedServiceDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using SocketHelpers.Extensions; 4 | 5 | namespace SocketHelpers.Discovery 6 | { 7 | /// 8 | /// Abstract implementation of `IServiceDefinition` that allows typed discovery and response formats, with JSON.NET 9 | /// serialization built-in. 10 | /// Classes that derive from `JsonSerializedServiceDefinition` must specify the `TSeekFormat` and `TPayloadFormat` type 11 | /// parameters. 12 | /// 13 | /// 14 | /// 15 | public abstract class JsonSerializedServiceDefinition : 16 | TypedServiceDefinition 17 | where TPayloadFormat : IDiscoveryPayload 18 | { 19 | public override byte[] MessageToBytes(TSeekFormat message) 20 | { 21 | return message.AsUTF8JsonByteArray(); 22 | } 23 | 24 | public override TSeekFormat BytesToMessage(byte[] bytes) 25 | { 26 | var json = Encoding.UTF8.GetString(bytes, 0, bytes.Length); 27 | TSeekFormat seekMsg = JsonConvert.DeserializeObject(json); 28 | 29 | return seekMsg; 30 | } 31 | 32 | public override byte[] PayloadToBytes(TPayloadFormat payload) 33 | { 34 | return payload == null ? null : payload.AsUTF8JsonByteArray(); 35 | } 36 | 37 | public override TPayloadFormat BytesToPayload(byte[] bytes) 38 | { 39 | var json = Encoding.UTF8.GetString(bytes, 0, bytes.Length); 40 | TPayloadFormat payload = JsonConvert.DeserializeObject(json); 41 | 42 | return payload; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServiceDefinition/TypedServiceDefinition.cs: -------------------------------------------------------------------------------- 1 | using SocketHelpers.Discovery; 2 | 3 | namespace SocketHelpers.Discovery 4 | { 5 | /// 6 | /// Abstract implementation of `IServiceDefinition` that allows typed discovery and response formats. 7 | /// Classes that derive from `TypedServiceDefinition` must implement type-to-byte[] serialization methods. 8 | /// 9 | /// 10 | /// 11 | public abstract class TypedServiceDefinition : IServiceDefinition 12 | where TPayloadFormat : IDiscoveryPayload 13 | { 14 | public ServicePublisher> CreateServicePublisher() 15 | { 16 | return new ServicePublisher>(this); 17 | } 18 | 19 | public ServiceDiscoverer, TRequestFormat, TPayloadFormat> CreateServiceDiscoverer() 20 | { 21 | return new ServiceDiscoverer, TRequestFormat, TPayloadFormat>(this); 22 | } 23 | 24 | /// 25 | /// Constructor for TypedServiceDefinition 26 | /// 27 | protected TypedServiceDefinition() 28 | { 29 | // default ports 30 | DiscoveryPort = 30000; 31 | ResponsePort = 30001; 32 | } 33 | 34 | public int DiscoveryPort { get; set; } 35 | public int ResponsePort { get; set; } 36 | 37 | byte[] IServiceDefinition.DiscoveryRequest() 38 | { 39 | var typedRequest = DiscoveryRequest(); 40 | var requestBytes = MessageToBytes(typedRequest); 41 | 42 | return requestBytes; 43 | } 44 | 45 | byte[] IServiceDefinition.ResponseFor(byte[] seekData) 46 | { 47 | var typedSeekData = BytesToMessage(seekData); 48 | var typedResponse = ResponseFor(typedSeekData); 49 | 50 | return PayloadToBytes(typedResponse); 51 | } 52 | 53 | public abstract TRequestFormat DiscoveryRequest(); 54 | public abstract TPayloadFormat ResponseFor(TRequestFormat seekData); 55 | 56 | public abstract byte[] MessageToBytes(TRequestFormat message); 57 | public abstract TRequestFormat BytesToMessage(byte[] bytes); 58 | public abstract byte[] PayloadToBytes(TPayloadFormat payload); 59 | public abstract TPayloadFormat BytesToPayload(byte[] bytes); 60 | } 61 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServiceDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using Sockets.Plugin.Abstractions; 5 | 6 | namespace SocketHelpers.Discovery 7 | { 8 | /// 9 | /// Acts as the service discoverer and sends discovery requests according to the protocol defined by 10 | /// `TServiceDefinition`. 11 | /// 12 | /// 13 | /// 14 | /// 15 | public class ServiceDiscoverer : 16 | ServiceDiscovererBase 17 | where TServiceDefinition : TypedServiceDefinition 18 | where TPayloadFormat : IDiscoveryPayload 19 | { 20 | public ServiceDiscoverer(TServiceDefinition definition) : base(definition) 21 | { 22 | } 23 | 24 | protected override void OnMessageReceived(object sender, UdpSocketMessageReceivedEventArgs e) 25 | { 26 | var payload = _serviceDefinition.BytesToPayload(e.ByteData); 27 | 28 | // add the remote host data 29 | var port = -1; 30 | Int32.TryParse(e.RemotePort, out port); 31 | 32 | payload.RemoteAddress = e.RemoteAddress; 33 | payload.RemotePort = port; 34 | 35 | // pump the discovery response 36 | _discoveredServices.OnNext(payload); 37 | } 38 | 39 | private readonly Subject _discoveredServices = new Subject(); 40 | 41 | public IObservable DiscoveredServices 42 | { 43 | get { return _discoveredServices.AsObservable(); } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Discovery/ServicePublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Sockets.Plugin.Abstractions; 3 | 4 | namespace SocketHelpers.Discovery 5 | { 6 | /// 7 | /// Acts as the service publisher and responds to discovery requests according to the protocol defined by 8 | /// `TServiceDefinition`. 9 | /// 10 | /// 11 | public class ServicePublisher : ServiceWorkerBase 12 | where TServiceDefinition : IServiceDefinition 13 | { 14 | public ServicePublisher(TServiceDefinition serviceDefinition) : base(serviceDefinition) 15 | { 16 | } 17 | 18 | public async Task Publish() 19 | { 20 | _backingReceiver.MessageReceived += OnMessageReceived; 21 | await _backingReceiver.StartListeningAsync(_serviceDefinition.DiscoveryPort); 22 | } 23 | 24 | public async Task Unpublish() 25 | { 26 | _backingReceiver.MessageReceived -= OnMessageReceived; 27 | await _backingReceiver.StopListeningAsync(); 28 | } 29 | 30 | private void OnMessageReceived(object sender, UdpSocketMessageReceivedEventArgs e) 31 | { 32 | var source = e.RemoteAddress; 33 | var messageData = e.ByteData; 34 | 35 | var response = _serviceDefinition.ResponseFor(messageData); 36 | if (response == null) 37 | return; 38 | 39 | _backingReceiver.SendToAsync(response, source, _serviceDefinition.ResponsePort); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Extensions/AsyncExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace SocketHelpers.Extensions 9 | { 10 | // I don't remember where this is taken/adapted from. 11 | public static class AsyncExtensions 12 | { 13 | public static async Task TimeoutAfter(this Task task, TimeSpan timeout, CancellationTokenSource cancellationTokenSource = null) 14 | { 15 | if (task == await Task.WhenAny(task, Task.Delay(timeout))) 16 | return await task; 17 | else 18 | { 19 | if (cancellationTokenSource != null) 20 | cancellationTokenSource.Cancel(); 21 | 22 | throw new TimeoutException(); 23 | } 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Extensions/SocketReadingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | 8 | namespace SocketHelpers.Extensions 9 | { 10 | public static class SocketReadingExtensions 11 | { 12 | public static async Task ReadByteAsync(this Stream s, CancellationToken canceller = default(CancellationToken)) 13 | { 14 | var buf = new byte[1]; 15 | await s.ReadAsync(buf, 0, 1, canceller); 16 | 17 | return buf[0]; 18 | } 19 | 20 | public static async Task ReadBytesAsync(this Stream s, int byteCount, CancellationToken canceller = default(CancellationToken)) 21 | { 22 | var buf = new byte[byteCount]; 23 | await s.ReadAsync(buf, 0, byteCount, canceller); 24 | 25 | return buf; 26 | } 27 | 28 | public static int AsInt32(this byte[] bs) 29 | { 30 | return BitConverter.ToInt32(bs, 0); 31 | } 32 | 33 | public static byte[] AsByteArray(this int i) 34 | { 35 | return BitConverter.GetBytes(i); 36 | } 37 | 38 | public static string AsUTF8String(this byte[] bs) 39 | { 40 | return Encoding.UTF8.GetString(bs, 0, bs.Length); 41 | } 42 | 43 | public static byte[] AsUTF8ByteArray(this string s) 44 | { 45 | return Encoding.UTF8.GetBytes(s); 46 | } 47 | 48 | public static string AsJson(this object o) 49 | { 50 | return JsonConvert.SerializeObject(o); 51 | } 52 | 53 | public static byte[] AsUTF8JsonByteArray(this object o) 54 | { 55 | return o.AsJson().AsUTF8ByteArray(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/IMessage.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace SocketHelpers.Messaging 5 | { 6 | public interface IMessage 7 | { 8 | string FromGuid { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/IProxy.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHelpers.Messaging 2 | { 3 | public interface IProxy 4 | { 5 | string ProxyGuid { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/JsonProtocolMessenger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reactive.Linq; 6 | using System.Reactive.Subjects; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Newtonsoft.Json; 11 | using SocketHelpers.Extensions; 12 | using Sockets.Plugin; 13 | using Splat; 14 | using Sockets.Plugin.Abstractions; 15 | 16 | namespace SocketHelpers.Messaging 17 | { 18 | public class MessengerDisconnectedEventArgs : EventArgs 19 | { 20 | public DisconnectionType DisconnectionType { get; set;} 21 | 22 | public MessengerDisconnectedEventArgs(DisconnectionType disconnectionType) 23 | { 24 | DisconnectionType = disconnectionType; 25 | } 26 | } 27 | 28 | public enum DisconnectionType : byte 29 | { 30 | Graceful = 0x0, 31 | ApplicationSuspended = 0x1, 32 | ApplicationTerminated = 0x2, 33 | Unexpected = 0xFF 34 | } 35 | 36 | public class JsonProtocolMessenger : IEnableLogger where TMessage : class 37 | { 38 | readonly ITcpSocketClient _client; 39 | 40 | public EventHandler Disconnected; 41 | 42 | private readonly Subject> _sendSubject = new Subject>(); 43 | private Subject _messageSubject = new Subject(); 44 | public IObservable Messages { get { return _messageSubject.AsObservable(); } } 45 | 46 | private CancellationTokenSource _executeCancellationSource; 47 | 48 | private List _additionalTypeResolutionAssemblies = new List(); 49 | public List AdditionalTypeResolutionAssemblies { get { return _additionalTypeResolutionAssemblies; } set { _additionalTypeResolutionAssemblies = value; } } 50 | 51 | public JsonProtocolMessenger(ITcpSocketClient client) 52 | { 53 | _client = client; 54 | _messageSubject = new Subject(); 55 | } 56 | 57 | public void Send(TMessage message) 58 | { 59 | var wrapper = new JsonProtocolQueueItem 60 | { 61 | MessageType = JsonProtocolMessengerMessageType.StandardMessage, 62 | Payload = message 63 | }; 64 | 65 | _sendSubject.OnNext(wrapper); 66 | } 67 | 68 | public async Task Disconnect(DisconnectionType disconnectionType) 69 | { 70 | const int failedDisconnectTimeoutSeconds = 5; 71 | 72 | var wrapper = new JsonProtocolDisconnectionQueueItem 73 | { 74 | MessageType = JsonProtocolMessengerMessageType.DisconnectMessage, 75 | DisconnectionType = disconnectionType 76 | }; 77 | 78 | _sendSubject.OnNext(wrapper); 79 | 80 | // this lets you await the *sending* of the disconnection 81 | // (i.e. not just the queuing of it) 82 | // this way we dont' actually disconnect until after we have told people 83 | // TODO: in case it somehow doesn't happen, timeout after a bit 84 | await wrapper.Delivered; 85 | StopExecuting(); 86 | } 87 | 88 | public void StartExecuting() 89 | { 90 | if (_executeCancellationSource != null && !_executeCancellationSource.IsCancellationRequested) 91 | _executeCancellationSource.Cancel(); 92 | 93 | _executeCancellationSource = new CancellationTokenSource(); 94 | 95 | // json object protocol 96 | // first byte = messageType { std, disconnect, ... } 97 | // next 4 typeNameLength - n 98 | // next 4 = messageLength - m 99 | // next n+m = type+message 100 | 101 | _sendSubject 102 | .Subscribe(async queueItem => 103 | { 104 | var canceller = _executeCancellationSource.Token; 105 | 106 | if (queueItem.MessageType != JsonProtocolMessengerMessageType.StandardMessage && queueItem.MessageType != JsonProtocolMessengerMessageType.DisconnectMessage) 107 | throw new InvalidOperationException("There's no code for sending other message types (please feel free to add some)"); 108 | 109 | switch (queueItem.MessageType) 110 | { 111 | case JsonProtocolMessengerMessageType.StandardMessage: 112 | { 113 | this.Log().Debug(String.Format("SEND: {0}", queueItem.Payload.AsJson())); 114 | 115 | var payload = queueItem.Payload; 116 | 117 | var typeNameBytes = payload.GetType().FullName.AsUTF8ByteArray(); 118 | var messageBytes = payload.AsJson().AsUTF8ByteArray(); 119 | 120 | var typeNameSize = typeNameBytes.Length.AsByteArray(); 121 | var messageSize = messageBytes.Length.AsByteArray(); 122 | 123 | var allBytes = new[] 124 | { 125 | new [] { (byte) JsonProtocolMessengerMessageType.StandardMessage }, 126 | typeNameSize, 127 | messageSize, 128 | typeNameBytes, 129 | messageBytes 130 | } 131 | .SelectMany(b => b) 132 | .ToArray(); 133 | 134 | await _client.WriteStream.WriteAsync(allBytes, 0, allBytes.Length, canceller); 135 | await _client.WriteStream.FlushAsync(canceller); 136 | 137 | break; 138 | } 139 | 140 | case JsonProtocolMessengerMessageType.DisconnectMessage: 141 | { 142 | var dcItem = queueItem as JsonProtocolDisconnectionQueueItem; 143 | 144 | this.Log().Debug(String.Format("SEND DISC: {0}", dcItem.DisconnectionType)); 145 | 146 | var allBytes = new[] 147 | { 148 | (byte) JsonProtocolMessengerMessageType.DisconnectMessage, 149 | (byte) dcItem.DisconnectionType 150 | }; 151 | 152 | await _client.WriteStream.WriteAsync(allBytes, 0, allBytes.Length, canceller); 153 | await _client.WriteStream.FlushAsync(canceller); 154 | 155 | dcItem.DidSend(); 156 | 157 | break; 158 | } 159 | } 160 | 161 | }, err => this.Log().Debug(err.Message)); 162 | 163 | Observable.Defer(() => 164 | Observable.Start(async () => 165 | { 166 | var canceller = _executeCancellationSource.Token; 167 | 168 | while (!canceller.IsCancellationRequested) 169 | { 170 | byte[] messageTypeBuf = new byte[1]; 171 | var count = await _client.ReadStream.ReadAsync(messageTypeBuf, 0, 1, canceller); 172 | 173 | if (count == 0) 174 | { 175 | _executeCancellationSource.Cancel(); 176 | 177 | this.Log().Error("Unexpected disconnection"); 178 | 179 | if(Disconnected != null) 180 | Disconnected(this, new MessengerDisconnectedEventArgs(DisconnectionType.Unexpected)); 181 | 182 | return; 183 | } 184 | 185 | var messageType = (JsonProtocolMessengerMessageType)messageTypeBuf[0]; 186 | 187 | switch (messageType) 188 | { 189 | case JsonProtocolMessengerMessageType.StandardMessage: 190 | 191 | var typeNameLength = (await _client.ReadStream.ReadBytesAsync(sizeof(int), canceller)).AsInt32(); 192 | var messageLength = (await _client.ReadStream.ReadBytesAsync(sizeof(int), canceller)).AsInt32(); 193 | 194 | var typeNameBytes = await _client.ReadStream.ReadBytesAsync(typeNameLength, canceller); 195 | var messageBytes = await _client.ReadStream.ReadBytesAsync(messageLength, canceller); 196 | 197 | var typeName = typeNameBytes.AsUTF8String(); 198 | var messageJson = messageBytes.AsUTF8String(); 199 | 200 | var type = Type.GetType(typeName) ?? 201 | AdditionalTypeResolutionAssemblies 202 | .Select(a => Type.GetType(String.Format("{0}, {1}", typeName, a.FullName))) 203 | .FirstOrDefault(t => t != null); 204 | 205 | if (type == null) 206 | { 207 | this.Log().Warn(String.Format("Received a message of type '{0}' but couldn't resolve it using GetType() directly or when qualified by any of the specified AdditionalTypeResolutionAssemblies: [{1}]", typeName, String.Join(",", AdditionalTypeResolutionAssemblies.Select(a=> a.FullName)))); 208 | continue; 209 | } 210 | 211 | var msg = JsonConvert.DeserializeObject(messageJson, type) as TMessage; 212 | 213 | this.Log().Debug(String.Format("RECV: {0}", msg.AsJson())); 214 | 215 | _messageSubject.OnNext(msg); 216 | 217 | break; 218 | 219 | case JsonProtocolMessengerMessageType.DisconnectMessage: 220 | var disconnectionType = (DisconnectionType) (await _client.ReadStream.ReadByteAsync(canceller)); 221 | this.Log().Debug(String.Format("RECV DISC: {0}", disconnectionType)); 222 | 223 | if(Disconnected != null) 224 | Disconnected(this, new MessengerDisconnectedEventArgs(disconnectionType)); 225 | 226 | StopExecuting(); 227 | 228 | return; 229 | } 230 | 231 | } 232 | 233 | }).Catch(Observable.Empty()) 234 | ).Retry() 235 | .Subscribe(_ => this.Log().Debug("MessageReader OnNext"), 236 | ex => this.Log().Debug("MessageReader OnError - " + ex.Message), 237 | () => this.Log().Debug("MessageReader OnCompleted")); 238 | } 239 | 240 | public void StopExecuting() 241 | { 242 | _messageSubject.OnCompleted(); 243 | _messageSubject = new Subject(); 244 | 245 | _executeCancellationSource.Cancel(); 246 | _executeCancellationSource = null; 247 | } 248 | } 249 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/JsonProtocolMessengerMessageType.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHelpers.Messaging 2 | { 3 | public enum JsonProtocolMessengerMessageType : byte 4 | { 5 | StandardMessage = 0x0, 6 | DisconnectMessage = 0x10 7 | } 8 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/JsonProtocolQueueItem.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive; 2 | using System.Threading.Tasks; 3 | namespace SocketHelpers.Messaging 4 | { 5 | internal class JsonProtocolQueueItem 6 | { 7 | public JsonProtocolMessengerMessageType MessageType { get; set; } 8 | public TMessage Payload { get; set; } 9 | } 10 | 11 | // rethinking life choices.. 12 | internal class JsonProtocolDisconnectionQueueItem : JsonProtocolQueueItem 13 | { 14 | public DisconnectionType DisconnectionType { get; set; } 15 | 16 | private TaskCompletionSource _delivered; 17 | public Task Delivered { get; private set; } 18 | 19 | public JsonProtocolDisconnectionQueueItem() 20 | { 21 | _delivered = new TaskCompletionSource(); 22 | Delivered = _delivered.Task; 23 | } 24 | 25 | internal void DidSend() 26 | { 27 | _delivered.SetResult(Unit.Default); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/MergableSubject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Linq; 4 | using System.Reactive.Subjects; 5 | 6 | namespace SocketHelpers.Messaging 7 | { 8 | public class MergableSubject : IDisposable 9 | { 10 | readonly Subject _backingSub = new Subject(); 11 | public IObservable SubscriptionLine { get { return _backingSub.AsObservable(); } } 12 | 13 | readonly Dictionary _subscriptions = new Dictionary(); 14 | 15 | public void Merge(IObservable source, object key = null) 16 | { 17 | var sub = source.Subscribe(_backingSub.OnNext, _backingSub.OnError); 18 | _subscriptions.Add(key ?? source, sub); 19 | } 20 | 21 | public void Unmerge(object key) 22 | { 23 | IDisposable sub; 24 | if (!_subscriptions.TryGetValue(key, out sub)) return; 25 | 26 | _subscriptions.Remove(key); 27 | sub.Dispose(); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | foreach (var sub in _subscriptions.Values) 33 | sub.Dispose(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Messaging/MessageHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Sockets.Plugin; 9 | using Splat; 10 | 11 | namespace SocketHelpers.Messaging 12 | { 13 | public class MessageHub : IEnableLogger 14 | where TProxy : IProxy, new() 15 | where TMessage : class, IMessage 16 | { 17 | private readonly Dictionary _guidToProxy = new Dictionary(); 18 | 19 | private readonly Dictionary _proxyToSocketLookup = 20 | new Dictionary(); 21 | 22 | private readonly Dictionary _socketToProxyLookup = 23 | new Dictionary(); 24 | 25 | private readonly Dictionary> _socketToJsonSenderLookup = 26 | new Dictionary>(); 27 | 28 | private readonly List _proxies = new List(); 29 | private readonly List _socketClients = new List(); 30 | 31 | private readonly List> _jsonSenders = 32 | new List>(); 33 | 34 | private readonly TcpSocketListener _listener = new TcpSocketListener(); 35 | 36 | private readonly MergableSubject _allIncomingMessages = new MergableSubject(); 37 | 38 | private List _additionalTypeResolutionAssemblies = new List(); 39 | public List AdditionalTypeResolutionAssemblies { get { return _additionalTypeResolutionAssemblies; } set { _additionalTypeResolutionAssemblies = value; } } 40 | 41 | public IObservable AllMessages 42 | { 43 | get { return _allIncomingMessages.SubscriptionLine; } 44 | } 45 | 46 | private readonly Subject _clientConnected = new Subject(); 47 | private readonly Subject _clientDisconnected = new Subject(); 48 | 49 | public IObservable ClientConnected 50 | { 51 | get { return _clientConnected.AsObservable(); } 52 | } 53 | 54 | public IObservable ClientDisconnected 55 | { 56 | get { return _clientDisconnected.AsObservable(); } 57 | } 58 | 59 | public TProxy ProxyForGuid(string guid) 60 | { 61 | return _guidToProxy[guid]; 62 | } 63 | 64 | public MessageHub() 65 | { 66 | _listener.ConnectionReceived += (sender, args) => ConnectionReceived((TcpSocketClient)args.SocketClient); 67 | } 68 | 69 | private void ConnectionReceived(TcpSocketClient newClient) 70 | { 71 | var proxy = new TProxy {ProxyGuid = Guid.NewGuid().ToString()}; 72 | var protocolMessenger = new JsonProtocolMessenger(newClient) 73 | { 74 | AdditionalTypeResolutionAssemblies = AdditionalTypeResolutionAssemblies 75 | }; 76 | 77 | _proxies.Add(proxy); 78 | _socketClients.Add(newClient); 79 | _jsonSenders.Add(protocolMessenger); 80 | 81 | _guidToProxy.Add(proxy.ProxyGuid, proxy); 82 | _proxyToSocketLookup.Add(proxy, newClient); 83 | _socketToProxyLookup.Add(newClient, proxy); 84 | _socketToJsonSenderLookup.Add(newClient, protocolMessenger); 85 | 86 | _allIncomingMessages.Merge(protocolMessenger.Messages.Select(m => { m.FromGuid = proxy.ProxyGuid; return m; })); 87 | 88 | _clientConnected.OnNext(proxy); 89 | 90 | protocolMessenger.StartExecuting(); 91 | } 92 | 93 | public Task StartListeningAsync(int port) 94 | { 95 | return _listener.StartListeningAsync(port); 96 | } 97 | 98 | public Task StopListeningAsync() 99 | { 100 | return _listener.StopListeningAsync(); 101 | } 102 | 103 | public Task DisconnectAllClients() 104 | { 105 | return Task.Run(() => 106 | { 107 | 108 | _socketClients.ToList().AsParallel().ForAll(async socketClient => 109 | { 110 | try 111 | { 112 | var proxy = _socketToProxyLookup[socketClient]; 113 | 114 | _socketClients.Remove(socketClient); 115 | _proxies.Remove(proxy); 116 | _proxyToSocketLookup.Remove(proxy); 117 | _socketToProxyLookup.Remove(socketClient); 118 | 119 | await socketClient.DisconnectAsync(); 120 | 121 | } 122 | catch (Exception e) 123 | { 124 | this.Log().Error(String.Format("Error disconnecting client - {0}", e.Message)); 125 | } 126 | }); 127 | 128 | }); 129 | } 130 | 131 | public Task SendToAsync(TMessage message, TProxy proxy) 132 | { 133 | var socket = _proxyToSocketLookup[proxy]; 134 | var json = _socketToJsonSenderLookup[socket]; 135 | 136 | return Task.Run(() => json.Send(message)); 137 | } 138 | 139 | public void SendAll(TMessage message) 140 | { 141 | foreach (var jsonSender in _jsonSenders) 142 | { 143 | jsonSender.Send(message); 144 | } 145 | } 146 | 147 | public Task SendAllAsync(TMessage message) 148 | { 149 | return Task.Run(() => 150 | { 151 | SendAll(message); 152 | }); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("SocketHelpers")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SocketHelpers")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: NeutralResourcesLanguage("en")] 17 | 18 | // Version information for an assembly consists of the following four values: 19 | // 20 | // Major Version 21 | // Minor Version 22 | // Build Number 23 | // Revision 24 | // 25 | // You can specify all the values or you can default the Build and Revision Numbers 26 | // by using the '*' as shown below: 27 | // [assembly: AssemblyVersion("1.0.*")] 28 | 29 | [assembly: AssemblyVersion("0.0.1")] 30 | [assembly: AssemblyFileVersion("0.0.1")] -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/SocketHelpers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10.0 6 | Debug 7 | AnyCPU 8 | {49EF63DE-16C1-4A67-B94F-DF0982ABB74E} 9 | Library 10 | Properties 11 | SocketHelpers 12 | SocketHelpers 13 | en-US 14 | 512 15 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | Profile111 17 | v4.5 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | bin\Debug\SocketHelpers.xml 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | bin\Release\SocketHelpers.xml 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ..\packages\Newtonsoft.Json.6.0.8\lib\portable-net45+wp80+win8+wpa81+aspnetcore50\Newtonsoft.Json.dll 65 | 66 | 67 | ..\packages\rda.SocketsForPCL.1.1.8\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Sockets.Plugin.dll 68 | 69 | 70 | ..\packages\rda.SocketsForPCL.1.1.8\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\Sockets.Plugin.Abstractions.dll 71 | 72 | 73 | ..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll 74 | 75 | 76 | ..\packages\Rx-Core.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Core.dll 77 | 78 | 79 | ..\packages\Rx-Interfaces.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Interfaces.dll 80 | 81 | 82 | ..\packages\Rx-Linq.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Linq.dll 83 | 84 | 85 | ..\packages\Rx-PlatformServices.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.PlatformServices.dll 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | rem cd $(SolutionDir) 95 | rem $(SolutionDir)autopacker.bat 96 | 97 | 104 | -------------------------------------------------------------------------------- /SocketHelpers/SocketHelpers/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------