├── .gitignore ├── README.md └── src ├── Beacon └── BeaconDemo │ ├── BeaconDemo.csproj │ ├── Bus.cs │ ├── Program.cs │ └── _ReadMe.txt ├── Brokerless Reliability (Freelance Pattern) └── Model One │ ├── Freelance.ModelOne.Client │ ├── Freelance.ModelOne.Client.csproj │ └── Program.cs │ └── Freelance.ModelOne.Server │ ├── Freelance.ModelOne.Server.csproj │ └── Program.cs ├── Directory.Build.props ├── Directory.Build.targets ├── HelloWorld ├── HelloWorld.csproj └── Program.cs ├── InterBrokerRouter ├── Client.cs ├── InterBrokerRouter.csproj ├── Program.cs ├── StartInterBrokerRouter.bat ├── Worker.cs └── doc │ ├── Description.txt │ └── InterBrokerRouter.pdf ├── Load Balancing Pattern ├── Extended Request Reply │ ├── RequestReplyBroker │ │ ├── RequestReplyBroker.cs │ │ └── RequestReplyBroker.csproj │ ├── RequestReplyClient │ │ ├── RequestReplyClient.cs │ │ └── RequestReplyClient.csproj │ └── RequestReplyWorker │ │ ├── RequestReplyWorker.cs │ │ └── RequestReplyWorker.csproj ├── ROUTERbrokerDEALERworkers │ ├── Program.cs │ └── ROUTERbrokerDEALERworkers.csproj └── ROUTERbrokerREQworkers │ ├── Program.cs │ └── ROUTERbrokerREQworkers.csproj ├── Majordomo ├── MDPBrokerProcess │ ├── MDPBrokerProcess.csproj │ └── MDPBrokerProgram.cs ├── MDPClientAsyncExample │ ├── MDPClientAsyncExample.csproj │ └── MDPClientAsyncExampleProgram.cs ├── MDPClientExample │ ├── MDPClientExample.csproj │ └── MDPClientExampleProgram.cs ├── MDPCommons │ ├── IMDPBroker.cs │ ├── IMDPClient.cs │ ├── IMDPClientAsync.cs │ ├── IMDPWorker.cs │ ├── MDPCommand.cs │ ├── MDPCommons.csproj │ ├── MDPConstants.cs │ ├── MDPLogEventArgs.cs │ ├── MDPReplyEventArgs.cs │ └── MMICode.cs ├── MDPServiceDiscoveryClientExample │ ├── MDPServiceDiscoveryClientExample.csproj │ └── MDPServiceDiscoveryProgram.cs ├── MDPWorkerExample │ ├── MDPWorkerExample.csproj │ └── MDPWorkerExampleProgram.cs ├── MajordomoProtocol │ ├── MDPBroker.cs │ ├── MDPClient.cs │ ├── MDPClientAsync.cs │ ├── MDPWorker.cs │ ├── MajordomoProtocol.csproj │ ├── Service.cs │ └── Worker.cs ├── MajordomoProtocolTests │ ├── MDPBrokerTests.cs │ ├── MDPClientAsyncTests.cs │ ├── MDPClientTests.cs │ ├── MDPWorkerTests.cs │ ├── MajordomoProtocolTests.csproj │ └── ServiceTests.cs ├── StartMDPExample - Verbose.bat ├── StartMDPExample.bat └── StartMDPServiceDiscoveringClientExample.bat ├── Multithreading └── Multithreaded Service │ └── MultithreadedService │ ├── MultithreadedService.csproj │ └── Program.cs ├── Pirate Pattern ├── Lazy Pirate │ ├── LazyPirate.Client │ │ ├── LazyPirate.Client.csproj │ │ └── Program.cs │ ├── LazyPirate.Client2 │ │ ├── LazyPirate.Client2.csproj │ │ └── Program.cs │ ├── LazyPirate.Server │ │ ├── LazyPirate.Server.csproj │ │ └── Program.cs │ └── LazyPirate.Server2 │ │ ├── LazyPirate.Server2.csproj │ │ └── Program.cs ├── Paranoid Pirate │ ├── ParanoidPirate.Client │ │ ├── ParanoidPirate.Client.csproj │ │ └── Program.cs │ ├── ParanoidPirate.Queue │ │ ├── Commons.cs │ │ ├── ParanoidPirate.Queue.csproj │ │ ├── Program.cs │ │ ├── Worker.cs │ │ └── Workers.cs │ ├── ParanoidPirate.Worker │ │ ├── ParanoidPirate.Worker.csproj │ │ └── Program.cs │ └── StartParanoidPirate.bat └── Simple Pirate │ ├── SimplePirate.Client │ ├── Program.cs │ └── SimplePirate.Client.csproj │ ├── SimplePirate.Queue │ ├── Program.cs │ └── SimplePirate.Queue.csproj │ └── SimplePirate.Worker │ ├── Program.cs │ └── SimplePirate.Worker.csproj ├── Samples.sln ├── Titanic Pattern ├── TitanicBroker │ ├── ITitanicIO.cs │ ├── RequestEntry.cs │ ├── TitanicBroker.cs │ ├── TitanicClient.cs │ ├── TitanicFileIO.cs │ ├── TitanicMemoryIO.cs │ ├── TitanicOperation.cs │ └── TitanicProtocol.csproj ├── TitanicCommons │ ├── ITitanicBroker.cs │ ├── ITitanicClient.cs │ ├── ITitanicConvert.cs │ ├── TitanicCommons.csproj │ ├── TitanicLogEventArgs.cs │ └── TitanicReturnCode.cs ├── TitanicPatternTests │ ├── TestEntities │ │ ├── FakeCloseMDPWorker.cs │ │ ├── FakeDispatchMDPClient.cs │ │ ├── FakeReplyMDPWorker.cs │ │ ├── FakeRequestMDPWorker.cs │ │ ├── MDPTestClientForTitanicClient.cs │ │ └── TestEntity.cs │ ├── TitanicBrokerTests.cs │ ├── TitanicClientTests.cs │ ├── TitanicFileIOTests.cs │ ├── TitanicMemoryIOTests.cs │ └── TitanicPatternTests.csproj └── TitanicUsageExample │ ├── StartTitanicClient1000ReqestsExample.bat │ ├── StartTitanicClientExample.bat │ ├── StartTitanicExample - Test.bat │ ├── StartTitanicExample - Verbose.bat │ ├── TitanicBrokerProcess │ ├── TitanicBrokerMain.cs │ └── TitanicBrokerProcess.csproj │ ├── TitanicClientExample │ ├── TitanicClientExample.csproj │ └── TitanicExampleClient.cs │ └── TitanicWorkerExample │ ├── TitanicWorker.cs │ └── TitanicWorkerExample.csproj └── WeatherUpdater ├── WeatherUpdateClient ├── WeatherUpdateClient.cs └── WeatherUpdateClient.csproj └── WeatherUpdateServer ├── WeatherUpdateServer.cs └── WeatherUpdateServer.csproj /.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 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This repo hosts samples showing the use of [NetMQ](https://github.com/zeromq/netmq), an implementation of ZeroMQ for .NET. 4 | 5 | These sample patterns are mostly from the [ZeroMQ Guide](https://zguide.zeromq.org/), which is recommended reading for working with NetMQ and ZeroMQ in general. 6 | 7 | Following are the current samples: 8 | 9 | * Beacon 10 | * Brokerless Reliability (Freelance pattern) 11 | * Hello World 12 | * Inter Broker Worker 13 | * Load Balancing Pattern 14 | * Majordomo Pattern 15 | * Multithreading 16 | * Pirate Pattern 17 | * Titanic Pattern 18 | * Weather Update 19 | 20 | Feel free to make your own contribution and make a pull request. 21 | -------------------------------------------------------------------------------- /src/Beacon/BeaconDemo/BeaconDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Beacon/BeaconDemo/Bus.cs: -------------------------------------------------------------------------------- 1 | namespace BeaconDemo; 2 | 3 | /// 4 | /// Originally from the Docs\beacon.md documentation with small modifications for this demo 5 | /// 6 | internal class Bus 7 | { 8 | // Actor Protocol 9 | public const string PublishCommand = "P"; 10 | public const string GetHostAddressCommand = "GetHostAddress"; 11 | public const string AddedNodeCommand = "AddedNode"; 12 | public const string RemovedNodeCommand = "RemovedNode"; 13 | 14 | // Dead nodes timeout 15 | private readonly TimeSpan m_deadNodeTimeout = TimeSpan.FromSeconds(10); 16 | 17 | // we will use this to check if we already know about the node 18 | public class NodeKey 19 | { 20 | public NodeKey(string name, int port) 21 | { 22 | Name = name; 23 | Port = port; 24 | Address = $"tcp://{name}:{port}"; 25 | HostName = Dns.GetHostEntry(name).HostName; 26 | } 27 | 28 | public string Name { get; } 29 | public int Port { get; } 30 | public string Address { get; } 31 | public string HostName { get;} 32 | 33 | protected bool Equals(NodeKey other) 34 | { 35 | return string.Equals(Name, other.Name) && Port == other.Port; 36 | } 37 | 38 | public override bool Equals(object obj) 39 | { 40 | if (obj is null) return false; 41 | if (ReferenceEquals(this, obj)) return true; 42 | if (obj.GetType() != this.GetType()) return false; 43 | return Equals((NodeKey)obj); 44 | } 45 | 46 | public override int GetHashCode() 47 | { 48 | unchecked 49 | { 50 | return ((Name?.GetHashCode() ?? 0) * 397) ^ Port; 51 | } 52 | } 53 | 54 | public override string ToString() 55 | { 56 | return Address; 57 | } 58 | } 59 | 60 | private readonly int m_broadcastPort; 61 | 62 | private readonly NetMQActor m_actor; 63 | 64 | private PublisherSocket m_publisher; 65 | private SubscriberSocket m_subscriber; 66 | private NetMQBeacon m_beacon; 67 | private NetMQPoller m_poller; 68 | private PairSocket m_shim; 69 | private readonly Dictionary m_nodes; // value is the last time we "saw" this node 70 | private int m_randomPort; 71 | 72 | private Bus(int broadcastPort) 73 | { 74 | m_nodes = new Dictionary(); 75 | m_broadcastPort = broadcastPort; 76 | m_actor = NetMQActor.Create(RunActor); 77 | } 78 | 79 | /// 80 | /// Creates a new message bus actor. All communication with the bus is 81 | /// through the returned . 82 | /// 83 | public static NetMQActor Create(int broadcastPort) 84 | { 85 | Bus node = new(broadcastPort); 86 | return node.m_actor; 87 | } 88 | 89 | private void RunActor(PairSocket shim) 90 | { 91 | // save the shim to the class to use later 92 | m_shim = shim; 93 | 94 | // create all subscriber, publisher and beacon 95 | using (m_subscriber = new SubscriberSocket()) 96 | using (m_publisher = new PublisherSocket()) 97 | using (m_beacon = new NetMQBeacon()) 98 | { 99 | // listen to actor commands 100 | m_shim.ReceiveReady += OnShimReady; 101 | 102 | // subscribe to all messages 103 | m_subscriber.Subscribe(""); 104 | 105 | // we bind to a random port, we will later publish this port 106 | // using the beacon 107 | m_randomPort = m_subscriber.BindRandomPort("tcp://*"); 108 | Console.WriteLine("Bus subscriber is bound to {0}", m_subscriber.Options.LastEndpoint); 109 | 110 | // listen to incoming messages from other publishers, forward them to the shim 111 | m_subscriber.ReceiveReady += OnSubscriberReady; 112 | 113 | // configure the beacon to listen on the broadcast port 114 | Console.WriteLine("Beacon is being configured to UDP port {0}", m_broadcastPort); 115 | m_beacon.Configure(m_broadcastPort); 116 | 117 | // publishing the random port to all other nodes 118 | Console.WriteLine("Beacon is publishing the Bus subscriber port {0}", m_randomPort); 119 | m_beacon.Publish(m_randomPort.ToString(), TimeSpan.FromSeconds(1)); 120 | 121 | // Subscribe to all beacon on the port 122 | Console.WriteLine("Beacon is subscribing to all beacons on UDP port {0}", m_broadcastPort); 123 | m_beacon.Subscribe(""); 124 | 125 | // listen to incoming beacons 126 | m_beacon.ReceiveReady += OnBeaconReady; 127 | 128 | // Create a timer to clear dead nodes 129 | NetMQTimer timer = new(TimeSpan.FromSeconds(1)); 130 | timer.Elapsed += ClearDeadNodes; 131 | 132 | // Create and configure the poller with all sockets and the timer 133 | m_poller = new NetMQPoller { m_shim, m_subscriber, m_beacon, timer }; 134 | 135 | // signal the actor that we finished with configuration and 136 | // ready to work 137 | m_shim.SignalOK(); 138 | 139 | // polling until cancelled 140 | m_poller.Run(); 141 | } 142 | } 143 | 144 | private void OnShimReady(object sender, NetMQSocketEventArgs e) 145 | { 146 | // new actor command 147 | string command = m_shim.ReceiveFrameString(); 148 | 149 | // check if we received end shim command 150 | if (command == NetMQActor.EndShimMessage) 151 | { 152 | // we cancel the socket which dispose and exist the shim 153 | m_poller.Stop(); 154 | } 155 | else if (command == PublishCommand) 156 | { 157 | // it is a publish command 158 | // we just forward everything to the publisher until end of message 159 | NetMQMessage message = m_shim.ReceiveMultipartMessage(); 160 | m_publisher.SendMultipartMessage(message); 161 | } 162 | else if (command == GetHostAddressCommand) 163 | { 164 | var address = m_beacon.BoundTo + ":" + m_randomPort; 165 | m_shim.SendFrame(address); 166 | } 167 | } 168 | 169 | private void OnSubscriberReady(object sender, NetMQSocketEventArgs e) 170 | { 171 | // we got a new message from the bus 172 | // let's forward everything to the shim 173 | NetMQMessage message = m_subscriber.ReceiveMultipartMessage(); 174 | m_shim.SendMultipartMessage(message); 175 | } 176 | 177 | private void OnBeaconReady(object sender, NetMQBeaconEventArgs e) 178 | { 179 | // we got another beacon 180 | // let's check if we already know about the beacon 181 | var message = m_beacon.Receive(); 182 | int.TryParse(message.String, out int port); 183 | 184 | NodeKey node = new(message.PeerHost, port); 185 | 186 | // check if node already exist 187 | if (!m_nodes.ContainsKey(node)) 188 | { 189 | // we have a new node, let's add it and connect to subscriber 190 | m_nodes.Add(node, DateTime.Now); 191 | m_publisher.Connect(node.Address); 192 | m_shim.SendMoreFrame(AddedNodeCommand).SendFrame(node.Address); 193 | } 194 | else 195 | { 196 | //Console.WriteLine("Node {0} is not a new beacon.", node); 197 | m_nodes[node] = DateTime.Now; 198 | } 199 | } 200 | 201 | private void ClearDeadNodes(object sender, NetMQTimerEventArgs e) 202 | { 203 | // create an array with the dead nodes 204 | var deadNodes = m_nodes. 205 | Where(n => DateTime.Now > n.Value + m_deadNodeTimeout) 206 | .Select(n => n.Key).ToArray(); 207 | 208 | // remove all the dead nodes from the nodes list and disconnect from the publisher 209 | foreach (var node in deadNodes) 210 | { 211 | m_nodes.Remove(node); 212 | m_publisher.Disconnect(node.Address); 213 | m_shim.SendMoreFrame(RemovedNodeCommand).SendFrame(node.Address); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Beacon/BeaconDemo/Program.cs: -------------------------------------------------------------------------------- 1 | // Originally from http://netmq.readthedocs.org/en/latest/beacon/ 2 | 3 | using BeaconDemo; 4 | 5 | Console.Title = "NetMQ Beacon Demo"; 6 | 7 | // Create a bus using broadcast port 9999 8 | // All communication with the bus is through the returned actor 9 | var actor = Bus.Create(9999); 10 | 11 | actor.SendFrame(Bus.GetHostAddressCommand); 12 | var hostAddress = actor.ReceiveFrameString(); 13 | 14 | Console.Title = $"NetMQ Beacon Demo at {hostAddress}"; 15 | 16 | // beacons publish every second, so wait a little longer than that to 17 | // let all the other nodes connect to our new node 18 | Thread.Sleep(1100); 19 | 20 | // publish a hello message 21 | // note we can use NetMQSocket send and receive extension methods 22 | actor.SendMoreFrame(Bus.PublishCommand).SendMoreFrame("Hello?").SendFrame(hostAddress); 23 | 24 | // receive messages from other nodes on the bus 25 | while (true) 26 | { 27 | // actor is receiving messages forwarded by the Bus subscriber 28 | string message = actor.ReceiveFrameString(); 29 | switch (message) 30 | { 31 | case "Hello?": 32 | // another node is saying hello 33 | var fromHostAddress = actor.ReceiveFrameString(); 34 | var msg = fromHostAddress + " says Hello?"; 35 | Console.WriteLine(msg); 36 | 37 | // send back a welcome message via the Bus publisher 38 | msg = hostAddress + " says Welcome!"; 39 | actor.SendMoreFrame(Bus.PublishCommand).SendFrame(msg); 40 | break; 41 | case Bus.AddedNodeCommand: 42 | var addedAddress = actor.ReceiveFrameString(); 43 | Console.WriteLine("Added node {0} to the Bus", addedAddress); 44 | break; 45 | case Bus.RemovedNodeCommand: 46 | var removedAddress = actor.ReceiveFrameString(); 47 | Console.WriteLine("Removed node {0} from the Bus", removedAddress); 48 | break; 49 | default: 50 | // it's probably a welcome message 51 | Console.WriteLine(message); 52 | break; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Beacon/BeaconDemo/_ReadMe.txt: -------------------------------------------------------------------------------- 1 | This project demonstrates the use of NetMQBeacon as set for the Docs\beacon.md. 2 | The same file is also at http://netmq.readthedocs.org/en/latest/beacon/ 3 | To see how this works, start multiple instances and see how they interact with each other. 4 | 5 | -------------------------------------------------------------------------------- /src/Brokerless Reliability (Freelance Pattern)/Model One/Freelance.ModelOne.Client/Freelance.ModelOne.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Brokerless Reliability (Freelance Pattern)/Model One/Freelance.ModelOne.Client/Program.cs: -------------------------------------------------------------------------------- 1 | const int RequestTimeout = 1000; // ms 2 | const int MaxRetries = 50; // Before we abandon 3 | 4 | var endpoints = new List 5 | { 6 | "tcp://*** Add your first endpoint ***", 7 | "tcp://*** Add your second endpoint ***" 8 | }; 9 | 10 | if (endpoints.Count == 1) 11 | { 12 | for (int i = 0; i < MaxRetries; i++) 13 | { 14 | if (TryRequest(endpoints[0], $"Hello from endpoint {endpoints[0]}")) 15 | break; 16 | } 17 | } 18 | else if (endpoints.Count > 1) 19 | { 20 | foreach (string endpoint in endpoints) 21 | { 22 | if (TryRequest(endpoint, $"Hello from endpoint {endpoint}")) 23 | break; 24 | } 25 | } 26 | else 27 | { 28 | Console.WriteLine("No endpoints"); 29 | } 30 | 31 | Console.WriteLine("Press any key to exit..."); 32 | Console.ReadKey(); 33 | 34 | static bool TryRequest(string endpoint, string requestString) 35 | { 36 | Console.WriteLine("Trying echo service at {0}", endpoint); 37 | 38 | using var client = new RequestSocket(); 39 | client.Options.Linger = TimeSpan.Zero; 40 | 41 | client.Connect(endpoint); 42 | 43 | client.SendFrame(requestString); 44 | client.ReceiveReady += ClientOnReceiveReady; 45 | bool pollResult = client.Poll(TimeSpan.FromMilliseconds(RequestTimeout)); 46 | client.ReceiveReady -= ClientOnReceiveReady; 47 | client.Disconnect(endpoint); 48 | return pollResult; 49 | } 50 | 51 | static void ClientOnReceiveReady(object sender, NetMQSocketEventArgs args) 52 | { 53 | Console.WriteLine("Server replied ({0})", args.Socket.ReceiveFrameString()); 54 | } -------------------------------------------------------------------------------- /src/Brokerless Reliability (Freelance Pattern)/Model One/Freelance.ModelOne.Server/Freelance.ModelOne.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Brokerless Reliability (Freelance Pattern)/Model One/Freelance.ModelOne.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | const uint PortNumber = 5555; 4 | 5 | using var response = new ResponseSocket(); 6 | 7 | string address = GetComputerLanIP(); 8 | 9 | if (!string.IsNullOrEmpty(address)) 10 | { 11 | Console.WriteLine("Binding tcp://{0}:{1}", address, PortNumber); 12 | response.Bind($"tcp://{address}:{PortNumber}"); 13 | 14 | while (true) 15 | { 16 | string msg = response.ReceiveFrameString(out bool hasMore); 17 | if (string.IsNullOrEmpty(msg)) 18 | { 19 | Console.WriteLine("No msg received."); 20 | break; 21 | } 22 | 23 | Console.WriteLine("Msg received! {0}", msg); 24 | response.SendFrame(msg, hasMore); 25 | 26 | Thread.Sleep(1000); 27 | } 28 | 29 | response.Options.Linger = TimeSpan.Zero; 30 | } 31 | else 32 | { 33 | Console.WriteLine("Wrong IP address"); 34 | } 35 | 36 | Console.WriteLine("Press any key to exit..."); 37 | Console.ReadKey(); 38 | 39 | static string GetComputerLanIP() 40 | { 41 | string strHostName = Dns.GetHostName(); 42 | IPHostEntry ipEntry = Dns.GetHostEntry(strHostName); 43 | 44 | foreach (var ipAddress in ipEntry.AddressList) 45 | { 46 | if (ipAddress.AddressFamily == AddressFamily.InterNetwork) 47 | { 48 | return ipAddress.ToString(); 49 | } 50 | } 51 | 52 | return ""; 53 | } 54 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | 10 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/HelloWorld/HelloWorld.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/HelloWorld/Program.cs: -------------------------------------------------------------------------------- 1 | Console.Title = "NetMQ HelloWorld"; 2 | 3 | var server = new ResponseSocket("@tcp://localhost:5556"); 4 | var client = new RequestSocket("tcp://localhost:5556"); 5 | 6 | client.SendFrame("Hello"); 7 | 8 | Console.WriteLine("From Client: {0}", server.ReceiveFrameString()); 9 | 10 | server.SendFrame("Hi Back"); 11 | 12 | Console.WriteLine("From Server: {0}", client.ReceiveFrameString()); 13 | 14 | Console.WriteLine(); 15 | Console.Write("Press any key to exit..."); 16 | Console.ReadKey(); 17 | -------------------------------------------------------------------------------- /src/InterBrokerRouter/Client.cs: -------------------------------------------------------------------------------- 1 | namespace InterBrokerRouter; 2 | 3 | public class Client 4 | { 5 | private readonly string m_localFrontendAddress; 6 | private readonly string m_monitorAddress; 7 | private readonly byte m_id; 8 | 9 | /// 10 | /// This client will connect its to the frontend of the broker and 11 | /// its to the broker's as monitor. 12 | /// It will send a messages and wait at most 10 seconds for an answer before sending an error message via monitor. 13 | /// If an answer is received it will send a success message via monitor. 14 | /// 15 | /// The local frontend address of the broker. 16 | /// The monitor address of the broker. 17 | /// The identity of the client. 18 | public Client(string localFrontendAddress, string monitorAddress, byte id) 19 | { 20 | m_monitorAddress = monitorAddress; 21 | m_localFrontendAddress = localFrontendAddress; 22 | m_id = id; 23 | } 24 | 25 | public void Run() 26 | { 27 | Console.WriteLine("[CLIENT {0}] Starting", m_id); 28 | 29 | var rnd = new Random(m_id); 30 | // each request shall have an unique id in order to recognize an reply for a request 31 | var messageId = new byte[5]; 32 | // create clientId for messages 33 | var clientId = new[] { m_id }; 34 | // a flag to signal that an answer has arrived 35 | bool messageAnswered = false; 36 | // we use a poller because we have a socket and a timer to monitor 37 | using var clientPoller = new NetMQPoller(); 38 | using var client = new RequestSocket(); 39 | using var monitor = new PushSocket(); 40 | client.Connect(m_localFrontendAddress); 41 | monitor.Connect(m_monitorAddress); 42 | 43 | client.Options.Identity = new[] { m_id }; 44 | var timer = new NetMQTimer((int)TimeSpan.FromSeconds(10).TotalMilliseconds); 45 | 46 | // use as flag to indicate exit 47 | var exit = false; 48 | 49 | // every 10 s check if message has been received, if not then send error message and ext 50 | // and restart timer otherwise 51 | timer.Elapsed += (s, e) => 52 | { 53 | if (messageAnswered) 54 | { 55 | e.Timer.Enable = true; 56 | } 57 | else 58 | { 59 | var msg = $"[CLIENT {m_id}] ERR - EXIT - lost message {messageId}"; 60 | // send an error message 61 | monitor.SendFrame(msg); 62 | 63 | // if poller is started than stop it 64 | if (clientPoller.IsRunning) 65 | clientPoller.Stop(); 66 | // mark the required exit 67 | exit = true; 68 | } 69 | }; 70 | 71 | // process arriving answers 72 | client.ReceiveReady += (s, e) => 73 | { 74 | // mark the arrival of an answer 75 | messageAnswered = true; 76 | // worker is supposed to answer with our request id 77 | var reply = e.Socket.ReceiveMultipartMessage(); 78 | 79 | if (reply.FrameCount == 0) 80 | { 81 | // something went wrong 82 | monitor.SendFrame($"[CLIENT {m_id}] Received an empty message!"); 83 | // mark the exit flag to ensure the exit 84 | exit = true; 85 | } 86 | else 87 | { 88 | var sb = new StringBuilder(); 89 | 90 | // create success message 91 | foreach (var frame in reply) 92 | sb.Append("[" + frame.ConvertToString() + "]"); 93 | 94 | // send the success message 95 | monitor.SendFrame($"[CLIENT {m_id}] Received answer {sb.ToString()}"); 96 | } 97 | }; 98 | 99 | // add socket & timer to poller 100 | clientPoller.Add(client); 101 | clientPoller.Add(timer); 102 | 103 | // start poller in another thread to allow the continued processing 104 | clientPoller.RunAsync(); 105 | 106 | // if true the message has been answered 107 | // the 0th message is always answered 108 | messageAnswered = true; 109 | 110 | while (!exit) 111 | { 112 | // simulate sporadic activity by randomly delaying 113 | Thread.Sleep((int)TimeSpan.FromSeconds(rnd.Next(5)).TotalMilliseconds); 114 | 115 | // only send next message if the previous one has been replied to 116 | if (messageAnswered) 117 | { 118 | // generate random 5 byte as identity for for the message 119 | rnd.NextBytes(messageId); 120 | 121 | messageAnswered = false; 122 | 123 | // create message [client adr][empty][message id] and send it 124 | var msg = new NetMQMessage(); 125 | 126 | msg.Push(messageId); 127 | msg.Push(NetMQFrame.Empty); 128 | msg.Push(clientId); 129 | 130 | client.SendMultipartMessage(msg); 131 | } 132 | } 133 | 134 | // stop poller if needed 135 | if (clientPoller.IsRunning) 136 | clientPoller.Stop(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/InterBrokerRouter/InterBrokerRouter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/InterBrokerRouter/StartInterBrokerRouter.bat: -------------------------------------------------------------------------------- 1 | cd bin\Debug 2 | 3 | start InterBrokerRouter.exe 5555 5575 4 | start InterBrokerRouter.exe 5575 5555 5 | -------------------------------------------------------------------------------- /src/InterBrokerRouter/Worker.cs: -------------------------------------------------------------------------------- 1 | namespace InterBrokerRouter; 2 | 3 | public class Worker 4 | { 5 | private readonly string m_localBackendAddress; 6 | private readonly byte m_id; 7 | 8 | /// 9 | /// The worker connects its to the broker's backend . 10 | /// Upon startup, the worker sends a READY message to the broker. 11 | /// The worker receives a message from the broker and simply returns it after printing it on screen. 12 | /// It simulates work by sleeping an arbitrary period of up to two seconds. 13 | /// 14 | /// The broker's backend address to connect to. 15 | /// The id of the worker. 16 | public Worker(string localBackEndAddress, byte id) 17 | { 18 | m_localBackendAddress = localBackEndAddress; 19 | m_id = id; 20 | } 21 | 22 | public void Run() 23 | { 24 | var rnd = new Random(m_id); 25 | 26 | using var worker = new RequestSocket(); 27 | worker.Connect(m_localBackendAddress); 28 | 29 | Console.WriteLine("[WORKER {0}] Connected & READY", m_id); 30 | 31 | // build READY message 32 | var msg = new NetMQMessage(); 33 | var ready = NetMQFrame.Copy(new[] { Program.WorkerReady }); 34 | 35 | msg.Append(ready); 36 | msg.Push(NetMQFrame.Empty); 37 | msg.Push(new[] { m_id }); 38 | 39 | // and send to broker 40 | worker.SendMultipartMessage(msg); 41 | 42 | while (true) 43 | { 44 | // wait for a request - the REQ might be from a local client or a cloud request 45 | var request = worker.ReceiveMultipartMessage(); 46 | 47 | if (request.FrameCount < 3) 48 | { 49 | Console.WriteLine("[WORKER {0}] ERR - received an empty message", m_id); 50 | break; // something went wrong -> exit 51 | } 52 | 53 | Console.WriteLine("[WORKER {0}] received", m_id); 54 | 55 | foreach (var frame in request) 56 | Console.WriteLine("\t[{0}", frame.ConvertToString()); 57 | 58 | // simulate working for an arbitrary time < 2s 59 | Thread.Sleep(rnd.Next(2000)); 60 | // simply send back what we received 61 | worker.SendMultipartMessage(request); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/InterBrokerRouter/doc/Description.txt: -------------------------------------------------------------------------------- 1 | Clients 2 | create and connect to broker's localFrontend 3 | create and connect to monitor 4 | send messages 5 | if no answer within x seconds 6 | send info to monitor 7 | 8 | 9 | Worker 10 | create and connect to broker's load balancer localBackend 11 | 12 | 13 | Broker 14 | create and wire up all sockets local/cloud/state/Frontend & -Backend as well as stateFront-/-backend 15 | - localFrontend talkes to clients 16 | - localBackend talks to workers 17 | - cloudFrontend talks to peer broker as if they were clients 18 | - cloudBackend talks to peer broker as if they were worker 19 | - stateBackend publishes state messages 20 | - stateFrontend subscribes to all stateBackends to collecet messages 21 | - monitor collects messsages from clients 22 | -------------------------------------------------------------------------------- /src/InterBrokerRouter/doc/InterBrokerRouter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/Samples/4bc12d98a242be698c8ccc0a04c0fcb666a1fa6e/src/InterBrokerRouter/doc/InterBrokerRouter.pdf -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyBroker/RequestReplyBroker.cs: -------------------------------------------------------------------------------- 1 | var frontend = new RouterSocket("@tcp://127.0.0.1:5559"); 2 | var backend = new DealerSocket("@tcp://127.0.0.1:5560"); 3 | 4 | // Handler for messages coming in to the frontend 5 | frontend.ReceiveReady += (s, e) => 6 | { 7 | var msg = e.Socket.ReceiveMultipartMessage(); 8 | backend.SendMultipartMessage(msg); // Relay this message to the backend 9 | }; 10 | 11 | // Handler for messages coming in to the backend 12 | backend.ReceiveReady += (s, e) => 13 | { 14 | var msg = e.Socket.ReceiveMultipartMessage(); 15 | frontend.SendMultipartMessage(msg); // Relay this message to the frontend 16 | }; 17 | 18 | using var poller = new NetMQPoller { backend, frontend }; 19 | 20 | // Listen out for events on both sockets and raise events when messages come in 21 | poller.Run(); 22 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyBroker/RequestReplyBroker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyClient/RequestReplyClient.cs: -------------------------------------------------------------------------------- 1 | using var client = new RequestSocket(">tcp://127.0.0.1:5559"); 2 | 3 | for (var i = 0; i < 10; i++) 4 | { 5 | var msg = new NetMQMessage(); 6 | msg.Append("Message_" + i); 7 | client.SendMultipartMessage(msg); 8 | Console.WriteLine("Sent Message {0}", msg.Last.ConvertToString()); 9 | 10 | var response = client.ReceiveMultipartMessage(); 11 | Console.WriteLine("Received Message {0}", response.Last.ConvertToString()); 12 | } 13 | 14 | Console.ReadKey(); 15 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyClient/RequestReplyClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyWorker/RequestReplyWorker.cs: -------------------------------------------------------------------------------- 1 | const string WorkerEndpoint = "tcp://127.0.0.1:5560"; 2 | 3 | using var worker = new ResponseSocket(); 4 | 5 | worker.Connect(WorkerEndpoint); 6 | 7 | while (true) 8 | { 9 | var msg = worker.ReceiveMultipartMessage(); 10 | 11 | Console.WriteLine("Processing Message {0}", msg.Last.ConvertToString()); 12 | 13 | Thread.Sleep(500); 14 | 15 | worker.SendFrame(msg.Last.ConvertToString()); 16 | } 17 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/Extended Request Reply/RequestReplyWorker/RequestReplyWorker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/ROUTERbrokerDEALERworkers/Program.cs: -------------------------------------------------------------------------------- 1 | const int PortNumber = 5555; 2 | 3 | // We have two workers, here we copy the code, normally these would run on different boxes… 4 | 5 | var random = new Random(DateTime.Now.Millisecond); 6 | var workers = new[] { new Thread(WorkerTaskA), new Thread(WorkerTaskB) }; 7 | 8 | using var client = new RouterSocket(); 9 | 10 | client.Bind($"tcp://localhost:{PortNumber}"); 11 | 12 | foreach (var thread in workers) 13 | { 14 | thread.Start(PortNumber); 15 | } 16 | 17 | // Wait for threads to connect, since otherwise the messages we send won't be routable. 18 | Thread.Sleep(1000); 19 | 20 | for (int taskNumber = 0; taskNumber < 1000; taskNumber++) 21 | { 22 | // Send two message parts, first the address… 23 | client.SendMoreFrame(random.Next(3) > 0 ? Encoding.Unicode.GetBytes("A") : Encoding.Unicode.GetBytes("B")); 24 | 25 | // And then the workload 26 | client.SendFrame("This is the workload"); 27 | } 28 | 29 | client.SendMoreFrame(Encoding.Unicode.GetBytes("A")); 30 | client.SendFrame("END"); 31 | 32 | client.SendMoreFrame(Encoding.Unicode.GetBytes("B")); 33 | client.SendFrame("END"); 34 | 35 | Console.ReadKey(); 36 | 37 | static void WorkerTaskA(object portNumber) 38 | { 39 | using var worker = new DealerSocket(); 40 | worker.Options.Identity = Encoding.Unicode.GetBytes("A"); 41 | worker.Connect($"tcp://localhost:{portNumber}"); 42 | 43 | int total = 0; 44 | 45 | bool end = false; 46 | 47 | while (!end) 48 | { 49 | string request = worker.ReceiveFrameString(); 50 | 51 | if (request == "END") 52 | { 53 | end = true; 54 | } 55 | else 56 | { 57 | total++; 58 | } 59 | } 60 | 61 | Console.WriteLine("A Received: {0}", total); 62 | } 63 | 64 | static void WorkerTaskB(object portNumber) 65 | { 66 | using var worker = new DealerSocket(); 67 | worker.Options.Identity = Encoding.Unicode.GetBytes("B"); 68 | worker.Connect($"tcp://localhost:{portNumber}"); 69 | 70 | int total = 0; 71 | 72 | bool end = false; 73 | 74 | while (!end) 75 | { 76 | string request = worker.ReceiveFrameString(); 77 | 78 | if (request == "END") 79 | { 80 | end = true; 81 | } 82 | else 83 | { 84 | total++; 85 | } 86 | } 87 | 88 | Console.WriteLine("B Received: {0}", total); 89 | } -------------------------------------------------------------------------------- /src/Load Balancing Pattern/ROUTERbrokerDEALERworkers/ROUTERbrokerDEALERworkers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Load Balancing Pattern/ROUTERbrokerREQworkers/Program.cs: -------------------------------------------------------------------------------- 1 | const int WorkersCount = 10; 2 | const int PortNumber = 5555; 3 | 4 | var workers = new List(WorkersCount); 5 | 6 | using var client = new RouterSocket(); 7 | 8 | string cnn = $"tcp://localhost:{PortNumber}"; 9 | client.Bind(cnn); 10 | Console.WriteLine("[B] Connect to {0}", cnn); 11 | 12 | for (int workerNumber = 0; workerNumber < WorkersCount; workerNumber++) 13 | { 14 | workers.Add(new Thread(WorkerTask)); 15 | workers[workerNumber].Start(PortNumber); 16 | } 17 | 18 | for (int taskNumber = 0; taskNumber < WorkersCount*10; taskNumber++) 19 | { 20 | // LRU worker is next waiting in queue 21 | string address = client.ReceiveFrameString(); 22 | //Console.WriteLine("[B] Message received: {0}", address); 23 | string empty = client.ReceiveFrameString(); 24 | //Console.WriteLine("[B] Message received: {0}", empty); 25 | string ready = client.ReceiveFrameString(); 26 | //Console.WriteLine("[B] Message received: {0}", ready); 27 | 28 | client.SendMoreFrame(address); 29 | //Console.WriteLine("[B] Message sent: {0}", address); 30 | client.SendMoreFrame(""); 31 | //Console.WriteLine("[B] Message sent: {0}", ""); 32 | client.SendFrame("This is the workload"); 33 | //Console.WriteLine("[B] Message sent: {0}", "This is the workload"); 34 | } 35 | 36 | // Now ask mamas to shut down and report their results 37 | for (int taskNbr = 0; taskNbr < WorkersCount; taskNbr++) 38 | { 39 | string address = client.ReceiveFrameString(); 40 | //Console.WriteLine("[B] Message received: {0}", address); 41 | string empty = client.ReceiveFrameString(); 42 | //Console.WriteLine("[B] Message received: {0}", empty); 43 | string ready = client.ReceiveFrameString(); 44 | //Console.WriteLine("[B] Message received: {0}", ready); 45 | 46 | client.SendMoreFrame(address); 47 | //Console.WriteLine("[B] Message sent: {0}", address); 48 | client.SendMoreFrame(""); 49 | //Console.WriteLine("[B] Message sent: {0}", ""); 50 | client.SendFrame("END"); 51 | //Console.WriteLine("[B] Message sent: {0}", "END"); 52 | } 53 | 54 | Console.ReadLine(); 55 | 56 | return; 57 | 58 | static void WorkerTask(object portNumber) 59 | { 60 | var random = new Random(DateTime.Now.Millisecond); 61 | 62 | using var worker = new RequestSocket(); 63 | // We use a string identity for ease here 64 | string id = ZHelpers.SetID(worker, Encoding.Unicode); 65 | string cnn = $"tcp://localhost:{portNumber}"; 66 | worker.Connect(cnn); 67 | Console.WriteLine("[W] ID {0} connect to {1}", id, cnn); 68 | 69 | int total = 0; 70 | 71 | bool end = false; 72 | while (!end) 73 | { 74 | // Tell the router we're ready for work 75 | worker.SendFrame("Ready"); 76 | //Console.WriteLine("[W] Message sent: {0}", msg); 77 | 78 | // Get workload from router, until finished 79 | string workload = worker.ReceiveFrameString(); 80 | //Console.WriteLine("[W] Workload received: {0}", workload); 81 | 82 | if (workload == "END") 83 | { 84 | end = true; 85 | } 86 | else 87 | { 88 | total++; 89 | 90 | Thread.Sleep(random.Next(1, 1000)); // Simulate 'work' 91 | } 92 | } 93 | 94 | Console.WriteLine("ID ({0}) processed: {1} tasks", Encoding.Unicode.GetString(worker.Options.Identity), total); 95 | } 96 | 97 | public static class ZHelpers 98 | { 99 | public static string SetID(NetMQSocket client, Encoding unicode) 100 | { 101 | var str = Guid.NewGuid().ToString(); // client.GetHashCode().ToString(); 102 | client.Options.Identity = unicode.GetBytes(str); 103 | return str; 104 | } 105 | } -------------------------------------------------------------------------------- /src/Load Balancing Pattern/ROUTERbrokerREQworkers/ROUTERbrokerREQworkers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Majordomo/MDPBrokerProcess/MDPBrokerProcess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MDPBrokerProcess/MDPBrokerProgram.cs: -------------------------------------------------------------------------------- 1 | using MajordomoProtocol; 2 | 3 | namespace MDPBrokerProcess; 4 | 5 | internal static class MDPBrokerProgram 6 | { 7 | private static bool s_verbose, s_debug; 8 | 9 | private static async Task Main (string[] args) 10 | { 11 | if ((args.Length == 1 && args[0] != "-v") || args.Length > 1) 12 | { 13 | Console.WriteLine ("MDPBrokerProcess [-v(erbose) OR -h(elp)]"); 14 | Console.WriteLine ("\t-v => verbose"); 15 | Console.WriteLine ("\tto stop processing use CTRL+C"); 16 | 17 | return; 18 | } 19 | 20 | Console.WriteLine ("MDP Broker - Majordomo Protocol V0.1\n"); 21 | Console.WriteLine ("\tto stop processing use CTRL+C"); 22 | 23 | s_verbose = args.Length > 0 && args[0] == "-v"; 24 | s_debug = args.Length == 2 && args[1] == "-d"; 25 | 26 | Console.WriteLine ("\nMessageLevel: Verbose = {0} and Debug = {1}", s_verbose, s_debug); 27 | 28 | // used to signal to stop the broker process 29 | var cts = new CancellationTokenSource (); 30 | 31 | // trapping Ctrl+C as exit signal! 32 | Console.CancelKeyPress += (s, e) => 33 | { 34 | e.Cancel = true; 35 | cts.Cancel (); 36 | }; 37 | 38 | Console.WriteLine ("Starting Broker ..."); 39 | 40 | try 41 | { 42 | await RunBroker (cts); 43 | } 44 | catch (AggregateException ex) 45 | { 46 | Console.WriteLine ("ERROR:"); 47 | foreach (var e in ex.InnerExceptions) 48 | { 49 | Console.WriteLine ("{0}", e.Message); 50 | Console.WriteLine ("{0}", e.StackTrace); 51 | Console.WriteLine ("---------------"); 52 | } 53 | Console.WriteLine ("exit - any key"); 54 | Console.ReadKey (); 55 | } 56 | catch (Exception ex) 57 | { 58 | Console.WriteLine ("ERROR:"); 59 | Console.WriteLine (ex.Message); 60 | Console.WriteLine (ex.StackTrace); 61 | if (ex.InnerException != null) 62 | { 63 | Console.WriteLine ("---------------"); 64 | Console.WriteLine (ex.InnerException.Message); 65 | Console.WriteLine (ex.InnerException.StackTrace); 66 | Console.WriteLine ("---------------"); 67 | } 68 | Console.WriteLine ("exit - any key"); 69 | Console.ReadKey (); 70 | } 71 | } 72 | 73 | 74 | private static async Task RunBroker (CancellationTokenSource cts) 75 | { 76 | using var broker = new MDPBroker("tcp://localhost:5555"); 77 | broker.LogInfoReady += (s, e) => Console.WriteLine(e.Info); 78 | 79 | if (s_verbose) 80 | broker.DebugInfoReady += (s, e) => Console.WriteLine(e.Info); 81 | 82 | await broker.Run(cts.Token); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Majordomo/MDPClientAsyncExample/MDPClientAsyncExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MDPClientAsyncExample/MDPClientAsyncExampleProgram.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | using MajordomoProtocol; 4 | 5 | namespace MDPClientExample; 6 | 7 | internal static class MDPClientAsyncExampleProgram 8 | { 9 | /// 10 | /// usage: MDPClientAsyncExample [-v] [-rn] (1 ;lt n ;lt 100000 / Default == 10) 11 | /// 12 | /// implements a MDPClientAsync API usage 13 | /// 14 | private static void Main(string[] args) 15 | { 16 | if (args.Length == 1 || args.Length > 2) 17 | { 18 | if (!(args[0] == "-v" || args[0].Contains("-r"))) 19 | { 20 | Console.WriteLine("MDPClientExample [-v(erbose) OR -h(elp)]"); 21 | Console.WriteLine("\t-v => verbose"); 22 | Console.WriteLine("\tto stop processing use CTRL+C"); 23 | 24 | return; 25 | } 26 | } 27 | 28 | const string service_name = "echo"; 29 | const int max_runs = 100000; 30 | 31 | bool verbose = false; 32 | int runs = 10; 33 | 34 | if (args.Length >= 1) 35 | { 36 | if (args.Length == 1 && args[0] == "-v") 37 | verbose = true; 38 | else if (args.Length == 2) 39 | verbose = args[0] == "-v" || args[1] == "-v"; 40 | 41 | if (args[0].Contains("-r") || args.Length > 1) 42 | { 43 | if (args[0].Contains("-r")) 44 | runs = GetInt(args[0]); 45 | else if (args[1].Contains("-r")) 46 | runs = GetInt(args[1]); 47 | } 48 | } 49 | 50 | runs = runs == -1 ? 10 : runs > max_runs ? max_runs : runs; 51 | 52 | var id = new[] { (byte)'C', (byte)'1' }; 53 | 54 | var watch = new Stopwatch(); 55 | 56 | Console.WriteLine("Starting MDPClient and will send {0} requests to service <{1}>.", runs, service_name); 57 | Console.WriteLine("(writes '.' for every 100 and '|' for every 1000 requests)\n"); 58 | 59 | try 60 | { 61 | // create MDP client and set verboseness && use automatic disposal 62 | using var session = new MDPClientAsync("tcp://localhost:5555", id); 63 | 64 | if (verbose) 65 | session.LogInfoReady += (s, e) => Console.WriteLine("{0}", e.Info); 66 | 67 | session.ReplyReady += (s, e) => Console.WriteLine("{0}", e.Reply); 68 | 69 | // just give everything time to settle in 70 | Thread.Sleep(500); 71 | 72 | watch.Start(); 73 | 74 | for (int count = 0; count < runs; count++) 75 | { 76 | var request = new NetMQMessage(); 77 | // set the request data 78 | request.Push("Hello World!"); 79 | // send the request to the service 80 | session.Send(service_name, request); 81 | 82 | if (count % 1000 == 0) 83 | Console.Write("|"); 84 | else 85 | if (count % 100 == 0) 86 | Console.Write("."); 87 | } 88 | 89 | watch.Stop(); 90 | Console.Write("\nStop receiving with any key!"); 91 | Console.ReadKey(); 92 | } 93 | catch (Exception ex) 94 | { 95 | Console.WriteLine("ERROR:"); 96 | Console.WriteLine(ex.Message); 97 | Console.WriteLine(ex.StackTrace); 98 | 99 | return; 100 | } 101 | 102 | var time = watch.Elapsed; 103 | 104 | Console.WriteLine("{0} request/replies in {1} ms processed! Took {2:N3} ms per REQ/REP", 105 | runs, 106 | time.TotalMilliseconds, 107 | time.TotalMilliseconds / runs); 108 | 109 | Console.Write("\nExit with any key!"); 110 | Console.ReadKey(); 111 | } 112 | 113 | private static int GetInt(string s) 114 | { 115 | var num = s.Remove(0, 2); 116 | 117 | var success = int.TryParse(num, out int runs); 118 | 119 | return success ? runs : -1; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Majordomo/MDPClientExample/MDPClientExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MDPClientExample/MDPClientExampleProgram.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | using MajordomoProtocol; 4 | 5 | namespace MDPClientExample; 6 | 7 | internal static class MDPClientExampleProgram 8 | { 9 | /// 10 | /// usage: MDPClientExample [-v] [-rn] (1 ;lt n ;lt 100000 / Default == 10) 11 | /// 12 | /// implements a MDPClient API usage 13 | /// 14 | private static void Main(string[] args) 15 | { 16 | if (args.Length == 1 || args.Length > 2) 17 | { 18 | if (!(args[0] == "-v" || args[0].Contains("-r"))) 19 | { 20 | Console.WriteLine("MDPClientExample [-v(erbose) OR -h(elp)]"); 21 | Console.WriteLine("\t-v => verbose"); 22 | Console.WriteLine("\tto stop processing use CTRL+C"); 23 | 24 | return; 25 | } 26 | } 27 | 28 | const string service_name = "echo"; 29 | const int max_runs = 100000; 30 | 31 | bool verbose = false; 32 | int runs = 10; 33 | 34 | if (args.Length >= 1) 35 | { 36 | if (args.Length == 1 && args[0] == "-v") 37 | verbose = true; 38 | else if (args.Length == 2) 39 | verbose = args[0] == "-v" || args[1] == "-v"; 40 | 41 | if (args[0].Contains("-r") || args.Length > 1) 42 | { 43 | if (args[0].Contains("-r")) 44 | runs = GetInt(args[0]); 45 | else if (args[1].Contains("-r")) 46 | runs = GetInt(args[1]); 47 | } 48 | } 49 | 50 | runs = runs == -1 ? 10 : runs > max_runs ? max_runs : runs; 51 | 52 | var id = new[] { (byte)'C', (byte)'1' }; 53 | 54 | var watch = new Stopwatch(); 55 | 56 | Console.WriteLine("Starting MDPClient and will send {0} requests to service <{1}>.", runs, service_name); 57 | Console.WriteLine("(writes '.' for every 100 and '|' for every 1000 requests)\n"); 58 | 59 | try 60 | { 61 | // create MDP client and set verboseness && use automatic disposal 62 | using var session = new MDPClient("tcp://localhost:5555", id); 63 | if (verbose) 64 | session.LogInfoReady += (s, e) => Console.WriteLine("{0}", e.Info); 65 | 66 | // just give everything time to settle in 67 | Thread.Sleep(500); 68 | 69 | watch.Start(); 70 | 71 | for (int count = 0; count < runs; count++) 72 | { 73 | var request = new NetMQMessage(); 74 | // set the request data 75 | request.Push("Hello World!"); 76 | // send the request to the service 77 | var reply = session.Send(service_name, request); 78 | 79 | if (reply is null) 80 | break; 81 | 82 | if (count % 1000 == 0) 83 | Console.Write("|"); 84 | else 85 | if (count % 100 == 0) 86 | Console.Write("."); 87 | } 88 | 89 | watch.Stop(); 90 | } 91 | catch (Exception ex) 92 | { 93 | Console.WriteLine("ERROR:"); 94 | Console.WriteLine(ex.Message); 95 | Console.WriteLine(ex.StackTrace); 96 | 97 | return; 98 | } 99 | 100 | var time = watch.Elapsed; 101 | 102 | Console.WriteLine("{0} request/replies in {1} ms processed! Took {2:N3} ms per REQ/REP", 103 | runs, 104 | time.TotalMilliseconds, 105 | time.TotalMilliseconds / runs); 106 | 107 | Console.Write("\nExit with any key!"); 108 | Console.ReadKey(); 109 | } 110 | 111 | private static int GetInt(string s) 112 | { 113 | var num = s.Remove(0, 2); 114 | 115 | var success = int.TryParse(num, out int runs); 116 | 117 | return success ? runs : -1; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/IMDPBroker.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | /// 4 | /// Implements a Broker according to Majordomo Protocol v0.1 5 | /// it implements this broker asynchronous and handles all administrative work 6 | /// if Run is called it automatically will Connect to the endpoint given 7 | /// it however allows to alter that endpoint via Bind 8 | /// it registers any worker with its service 9 | /// it routes requests from clients to waiting workers offering the service the client has requested 10 | /// as soon as they become available 11 | /// 12 | /// Services can/must be requested with a request, a.k.a. data to process 13 | /// 14 | /// CLIENT CLIENT CLIENT CLIENT 15 | /// "Coffee" "Water" "Coffee" "Tea" 16 | /// | | | | 17 | /// +-----------+-----+------+---------+ 18 | /// | 19 | /// BROKER 20 | /// | 21 | /// +-----------+-----------+ 22 | /// | | | 23 | /// "Tea" "Coffee" "Water" 24 | /// WORKER WORKER WORKER 25 | /// 26 | /// 27 | public interface IMDPBroker : IDisposable 28 | { 29 | /// 30 | /// sets or gets the timeout period for waiting for messages 31 | /// 32 | TimeSpan HeartbeatInterval { get; } 33 | 34 | /// 35 | /// sets or gets the number of heartbeat cycles before the communication 36 | /// is deemed to be lost 37 | /// 38 | int HeartbeatLiveliness { get; set; } 39 | 40 | /// 41 | /// broadcast logging information via this event 42 | /// 43 | event EventHandler LogInfoReady; 44 | 45 | /// 46 | /// broadcast elaborate debugging info if subscribed to 47 | /// 48 | event EventHandler DebugInfoReady; 49 | 50 | /// 51 | /// broker binds his socket to endpoint given at ctor 52 | /// the broker must not have started operation 53 | /// 54 | /// The bind operation failed. Most likely because 'endpoint' is malformed! 55 | /// 56 | /// broker uses the same endpoint to communicate with clients and workers(!) 57 | /// 58 | void Bind(); 59 | 60 | /// 61 | /// broker binds to the specified endpoint, if already bound will unbind and 62 | /// then bind to the endpoint 63 | /// the broker must not have started operation! 64 | /// 65 | /// new endpoint to bind to 66 | /// Can not change binding while operating! 67 | void Bind(string endpoint); 68 | 69 | /// 70 | /// starts a broker on a previously or automatically bound socket/port and 71 | /// processes messages according to Majordomo Protocol V0.1 72 | /// 73 | /// used to signal the broker to abandon 74 | /// Can not change binding while operating! 75 | Task Run(CancellationToken token); 76 | } 77 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/IMDPClient.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | public interface IMDPClient : IDisposable 4 | { 5 | /// 6 | /// sets or gets the timeout period for waiting for messages 7 | /// 8 | TimeSpan Timeout { get; set; } 9 | 10 | /// 11 | /// sets or gets the number of tries before the communication 12 | /// is deemed to be lost 13 | /// 14 | int Retries { get; set; } 15 | 16 | /// 17 | /// returns the address of the broker the client is connected to 18 | /// 19 | string Address { get; } 20 | 21 | /// 22 | /// returns the name of the client 23 | /// 24 | byte[] Identity { get; } 25 | 26 | /// 27 | /// send a request to a broker for a specific service and receive the reply 28 | /// 29 | /// the name of the service requested 30 | /// the request message to process by service 31 | /// the reply from service 32 | NetMQMessage Send (string serviceName, NetMQMessage request); 33 | 34 | /// 35 | /// broadcast logging info via this event 36 | /// 37 | event EventHandler LogInfoReady; 38 | } -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/IMDPClientAsync.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | public interface IMDPClientAsync : IDisposable 4 | { 5 | /// 6 | /// sets or gets the timeout period that a client can stay connected 7 | /// without receiving any messages from broker 8 | /// 9 | TimeSpan Timeout { get; set; } 10 | 11 | /// 12 | /// returns the address of the broker the client is connected to 13 | /// 14 | string Address { get; } 15 | 16 | /// 17 | /// returns the name of the client 18 | /// 19 | byte[] Identity { get; } 20 | 21 | /// 22 | /// send a async request to a broker for a specific service 23 | /// 24 | /// the name of the service requested 25 | /// the request message to process by service 26 | void Send (string serviceName, NetMQMessage request); 27 | 28 | /// 29 | /// reply to an asyncronous request 30 | /// 31 | event EventHandler ReplyReady; 32 | 33 | /// 34 | /// broadcast logging info via this event 35 | /// 36 | event EventHandler LogInfoReady; 37 | } -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/IMDPWorker.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | // define the possible message types for the worker MDP 4 | 5 | public interface IMDPWorker : IDisposable 6 | { 7 | /// 8 | /// sen a heartbeat every specified milliseconds 9 | /// 10 | TimeSpan HeartbeatDelay { get; set; } 11 | 12 | /// 13 | /// delay in milliseconds between reconnects 14 | /// 15 | TimeSpan ReconnectDelay { get; set; } 16 | 17 | /// 18 | /// broadcast logging information via this event 19 | /// 20 | event EventHandler LogInfoReady; 21 | 22 | /// 23 | /// sends it's reply and waits for a new request 24 | /// 25 | /// reply to the received request 26 | /// a request 27 | /// 28 | /// upon connection the first receive a worker does, he must 29 | /// pass a null reply in order to initiate the REQ-REP 30 | /// ping-pong 31 | /// 32 | NetMQMessage Receive(NetMQMessage reply); 33 | } 34 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MDPCommand.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | /// 4 | /// the available MDP commands 5 | /// 6 | /// amended it Kill in order to force a worker to stop gracefully 7 | /// 8 | public enum MDPCommand 9 | { 10 | Kill = 0x00, 11 | Ready = 0x01, 12 | Request = 0x02, 13 | Reply = 0x03, 14 | Heartbeat = 0x04, 15 | Disconnect = 0x05 16 | } -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MDPCommons.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MDPConstants.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | public static class MDPConstants 4 | { 5 | public const int HEARTBEAT_LIVENESS = 4; // 3-5 is reasonable 6 | public const int HEARTBEAT_INTERVAL_IN_MILIS = 2500; 7 | 8 | public const string MDP_CLIENT_HEADER = "MDPC01"; 9 | public const string MDP_WORKER_HEADER = "MDPW01"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MDPLogEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | public class MDPLogEventArgs : EventArgs 4 | { 5 | public string Info { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MDPReplyEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | public class MDPReplyEventArgs : EventArgs 4 | { 5 | public NetMQMessage Reply { get; private set; } 6 | 7 | public Exception Exception { get; private set; } 8 | 9 | public MDPReplyEventArgs(NetMQMessage reply) 10 | { 11 | Reply = reply; 12 | } 13 | 14 | public MDPReplyEventArgs(NetMQMessage reply, Exception exception) 15 | : this(reply) 16 | { 17 | Exception = exception; 18 | } 19 | 20 | public bool HasError() 21 | { 22 | return (Exception != null ? true : false); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Majordomo/MDPCommons/MMICode.cs: -------------------------------------------------------------------------------- 1 | namespace MDPCommons; 2 | 3 | /// 4 | /// the available return codes for the service discovery 5 | /// 6 | public enum MmiCode 7 | { 8 | Ok = 200, 9 | Pending = 300, 10 | Unknown = 400, 11 | Failure = 501 12 | } 13 | -------------------------------------------------------------------------------- /src/Majordomo/MDPServiceDiscoveryClientExample/MDPServiceDiscoveryClientExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MDPServiceDiscoveryClientExample/MDPServiceDiscoveryProgram.cs: -------------------------------------------------------------------------------- 1 | using MajordomoProtocol; 2 | 3 | namespace MDPServiceDiscoveryClientExample; 4 | 5 | internal class MDPServiceDiscoveryProgram 6 | { 7 | /// 8 | /// usage: MDPServiceDiscoveryClientExample [-v] 9 | /// 10 | /// implements a MDPClient API usage with Service Discovery 11 | /// 12 | private static void Main (string[] args) 13 | { 14 | const string service_to_lookup = "echo"; 15 | const string service_discovery = "mmi.service"; 16 | 17 | var verbose = args.Length == 1 && args[0] == "-v"; 18 | 19 | var id = Encoding.ASCII.GetBytes ("SDC01"); 20 | 21 | // give WORKER & BROKER time to settle 22 | Thread.Sleep (250); 23 | 24 | using (var session = new MDPClient ("tcp://localhost:5555", id)) 25 | { 26 | if (verbose) 27 | session.LogInfoReady += (s, e) => Console.WriteLine ("{0}", e.Info); 28 | 29 | var request = new NetMQMessage (); 30 | // set the service name 31 | request.Push (service_to_lookup); 32 | // send the request to service discovery 33 | var reply = session.Send (service_discovery, request); 34 | 35 | if (reply != null && !reply.IsEmpty) 36 | { 37 | var answer = reply.First.ConvertToString (); 38 | 39 | Console.WriteLine ("Lookup {0} service returned: {1}/{2}", service_to_lookup, answer, reply); 40 | } 41 | else 42 | Console.WriteLine ("ERROR: no response from broker, seems like broker is NOT running!"); 43 | } 44 | 45 | Console.Write ("Exit with any key."); 46 | Console.ReadKey (); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Majordomo/MDPWorkerExample/MDPWorkerExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MDPWorkerExample/MDPWorkerExampleProgram.cs: -------------------------------------------------------------------------------- 1 | using MajordomoProtocol; 2 | 3 | namespace MDPWorkerExample; 4 | 5 | internal static class MDPWorkerExampleProgram 6 | { 7 | /// 8 | /// Implements a MDPWorker API usage 9 | /// 10 | /// 11 | /// Usage: MDPWorkerExample [-v] 12 | /// 13 | private static void Main (string[] args) 14 | { 15 | var verbose = args.Length == 1 && args[0] == "-v"; 16 | var exit = false; 17 | const string service_name = "echo"; 18 | 19 | // trapping Ctrl+C as exit signal! 20 | Console.CancelKeyPress += (s, e) => 21 | { 22 | e.Cancel = true; 23 | exit = true; 24 | }; 25 | 26 | var id = new[] { (byte) 'W', (byte) '1' }; 27 | 28 | Console.WriteLine ("Starting the MDP Worker offering service <{0}>", service_name); 29 | Console.WriteLine ("To exit CTRL-C!"); 30 | 31 | try 32 | { 33 | // create worker offering the service 'echo' 34 | using var session = new MDPWorker("tcp://localhost:5555", service_name, id); 35 | session.HeartbeatDelay = TimeSpan.FromMilliseconds(10000); 36 | // logging info to be displayed on screen 37 | if (verbose) 38 | session.LogInfoReady += (s, e) => Console.WriteLine("{0}", e.Info); 39 | 40 | // there is no initial reply 41 | NetMQMessage reply = null; 42 | 43 | while (!exit) 44 | { 45 | // send the reply and wait for a request 46 | var request = session.Receive(reply); 47 | 48 | if (verbose) 49 | Console.WriteLine("Received: {0}", request); 50 | 51 | // was the worker interrupted 52 | if (request is null) 53 | break; 54 | // echo the request 55 | reply = request; 56 | } 57 | } 58 | catch (Exception ex) 59 | { 60 | Console.WriteLine ("ERROR:"); 61 | Console.WriteLine ("{0}", ex.Message); 62 | Console.WriteLine ("{0}", ex.StackTrace); 63 | 64 | Console.WriteLine ("exit - any key"); 65 | Console.ReadKey (); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocol/MDPClient.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace MajordomoProtocol; 4 | 5 | /// 6 | /// implements a client skeleton for Majordomo Protocol V0.1 7 | /// 8 | public class MDPClient : IMDPClient 9 | { 10 | private readonly string m_mdpClient = MDPConstants.MDP_CLIENT_HEADER; 11 | 12 | private NetMQSocket m_client; // the socket to communicate with the broker 13 | 14 | private readonly string m_brokerAddress; 15 | private readonly byte[] m_identity; 16 | private bool m_connected; // used as flag true if a connection has been made 17 | private string m_serviceName; // need that as storage for the event handler 18 | private NetMQMessage m_reply; // container for the received reply from broker 19 | 20 | /// 21 | /// sets or gets the timeout period for waiting for messages 22 | /// 23 | public TimeSpan Timeout { get; set; } 24 | 25 | /// 26 | /// sets or gets the number of tries before the communication 27 | /// is deemed to be lost 28 | /// 29 | public int Retries { get; set; } 30 | 31 | /// 32 | /// returns the address of the broker the client is connected to 33 | /// 34 | public string Address => m_brokerAddress; 35 | 36 | /// 37 | /// returns the name of the client 38 | /// 39 | public byte[] Identity => m_identity; 40 | 41 | /// 42 | /// if client has a log message available if fires this event 43 | /// 44 | public event EventHandler LogInfoReady; 45 | 46 | /// 47 | /// setup the client with standard values 48 | /// verbose == false 49 | /// timeout == 2500 50 | /// reties == 3 51 | /// 52 | private MDPClient() 53 | { 54 | m_client = null; 55 | Timeout = TimeSpan.FromMilliseconds(2500); 56 | Retries = 3; 57 | m_connected = false; 58 | } 59 | 60 | /// 61 | /// setup the client, use standard values and parameters 62 | /// 63 | /// address the broker can be connected to 64 | /// if present will become the name for the client socket, encoded in UTF8 65 | public MDPClient(string brokerAddress, byte[] identity = null) 66 | : this() 67 | { 68 | if (string.IsNullOrWhiteSpace(brokerAddress)) 69 | throw new ArgumentNullException(nameof(brokerAddress), "The broker address must not be null, empty or whitespace!"); 70 | 71 | m_identity = identity; 72 | m_brokerAddress = brokerAddress; 73 | } 74 | 75 | /// 76 | /// setup the client, use standard values and parameters 77 | /// 78 | /// address the broker can be connected to 79 | /// sets the name of the client (must be UTF8), if empty or white space it is ignored 80 | public MDPClient(string brokerAddress, string identity) 81 | : this() 82 | { 83 | if (string.IsNullOrWhiteSpace(brokerAddress)) 84 | throw new ArgumentNullException(nameof(brokerAddress), "The broker address must not be null, empty or whitespace!"); 85 | 86 | if (!string.IsNullOrWhiteSpace(identity)) 87 | m_identity = Encoding.UTF8.GetBytes(identity); 88 | 89 | m_brokerAddress = brokerAddress; 90 | } 91 | 92 | /// 93 | /// send a request to a broker for a specific service and receive the reply 94 | /// 95 | /// if the reply is not available within a specified time 96 | /// the client deems the connection as lost and reconnects 97 | /// for a specified number of times. if no reply is available 98 | /// throughout this procedure the client abandons 99 | /// the reply is checked for validity and returns the reply 100 | /// according to the verbose flag it reports about its activities 101 | /// 102 | /// the name of the service requested 103 | /// the request message to process by service 104 | /// the reply from service 105 | /// malformed message received 106 | /// malformed header received 107 | /// reply received from wrong service 108 | public NetMQMessage Send(string serviceName, NetMQMessage request) 109 | { 110 | if (string.IsNullOrWhiteSpace(serviceName)) 111 | throw new ApplicationException("serviceName must not be empty or null."); 112 | 113 | if (request is null) 114 | throw new ApplicationException("the request must not be null"); 115 | // memorize it for the event handler 116 | m_serviceName = serviceName; 117 | 118 | // if for any reason the socket is NOT connected -> connect it! 119 | if (!m_connected) 120 | Connect(); 121 | 122 | var message = new NetMQMessage(request); 123 | 124 | // prefix the request according to MDP specs 125 | // Frame 1: "MDPCxy" (six bytes MDP/Client x.y) 126 | // Frame 2: service name as printable string 127 | // Frame 3: request 128 | message.Push(serviceName); 129 | message.Push(m_mdpClient); 130 | 131 | Log($"[CLIENT INFO] sending {message} to service {serviceName}"); 132 | 133 | var retiesLeft = Retries; 134 | 135 | while (retiesLeft > 0) 136 | { 137 | // beware of an exception if broker has not picked up the message at all 138 | // because one can not send multiple times! it is strict REQ -> REP -> REQ ... 139 | m_client.SendMultipartMessage(message); 140 | 141 | Log($"[CLIENT WARNING] attempts left {retiesLeft}"); 142 | 143 | // Poll -> see ReceiveReady for event handling 144 | if (m_client.Poll(Timeout)) 145 | { 146 | // set by event handler 147 | return m_reply; 148 | } 149 | // if it failed assume communication dead and re-connect 150 | if (--retiesLeft > 0) 151 | { 152 | Log("[CLIENT WARNING] no reply, reconnecting ..."); 153 | 154 | Connect(); 155 | } 156 | } 157 | 158 | Log("[CLIENT ERROR] permanent error, abandoning!"); 159 | 160 | m_client.Dispose(); 161 | 162 | return null; 163 | } 164 | 165 | /// 166 | /// connects to the broker, if a socket already exists it will be disposed and 167 | /// a new socket created and connected 168 | /// MDP requires a REQUEST socket for a client 169 | /// 170 | /// NetMQContext must not be null 171 | /// if broker address is empty or null 172 | private void Connect() 173 | { 174 | if (m_client is not null) 175 | m_client.Dispose(); 176 | 177 | m_client = new RequestSocket(); 178 | 179 | if (m_identity != null) 180 | m_client.Options.Identity = m_identity; 181 | 182 | // attach the event handler for incoming messages 183 | m_client.ReceiveReady += ProcessReceiveReady; 184 | 185 | m_client.Connect(m_brokerAddress); 186 | 187 | m_connected = true; 188 | 189 | Log($"[CLIENT] connecting to broker at {m_brokerAddress}"); 190 | } 191 | 192 | /// 193 | /// handle the incoming messages 194 | /// 195 | /// 196 | /// socket strips [client adr][e] from message 197 | /// message -> [protocol header][service name][reply] 198 | /// [protocol header][service name][result code of service lookup] 199 | /// 200 | private void ProcessReceiveReady(object sender, NetMQSocketEventArgs e) 201 | { 202 | // a message is available within the timeout period 203 | var reply = m_client.ReceiveMultipartMessage(); 204 | 205 | Log($"\n[CLIENT INFO] received the reply {reply}\n"); 206 | 207 | // in production code malformed messages should be handled smarter 208 | if (reply.FrameCount < 3) 209 | throw new ApplicationException("[CLIENT ERROR] received a malformed reply"); 210 | 211 | var header = reply.Pop(); // [MDPHeader] <- [service name][reply] OR ['mmi.service'][return code] 212 | 213 | if (header.ConvertToString() != m_mdpClient) 214 | throw new ApplicationException($"[CLIENT INFO] MDP Version mismatch: {header}"); 215 | 216 | var service = reply.Pop(); // [service name or 'mmi.service'] <- [reply] OR [return code] 217 | 218 | if (service.ConvertToString() != m_serviceName) 219 | throw new ApplicationException($"[CLIENT INFO] answered by wrong service: {service.ConvertToString()}"); 220 | // now set the value for the reply of the send method! 221 | m_reply = reply; // [reply] OR [return code] 222 | } 223 | 224 | private void Log(string info) 225 | { 226 | if (!string.IsNullOrWhiteSpace(info)) 227 | OnLogInfoReady(new MDPLogEventArgs { Info = info }); 228 | } 229 | 230 | /// 231 | /// broadcast the logging information if someone is listening 232 | /// 233 | /// 234 | protected virtual void OnLogInfoReady(MDPLogEventArgs e) 235 | { 236 | LogInfoReady?.Invoke(this, e); 237 | } 238 | 239 | public void Dispose() 240 | { 241 | Dispose(true); 242 | GC.SuppressFinalize(this); 243 | } 244 | 245 | protected virtual void Dispose(bool disposing) 246 | { 247 | if (!disposing) 248 | return; 249 | 250 | // m_client might not have been created yet! 251 | if (m_client is not null) 252 | m_client.Dispose(); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocol/MajordomoProtocol.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 0.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocol/Service.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MajordomoTests")] 4 | namespace MajordomoProtocol; 5 | 6 | /// 7 | /// A broker local representation for a service. 8 | /// Act as a frame for worker offering the service. 9 | /// As well as for pending requests to workers. 10 | /// 11 | internal class Service 12 | { 13 | private readonly List m_workers; // list of known and active worker for this service 14 | private readonly List m_pendingRequests; // list of client requests for that service 15 | private readonly List m_waitingWorkers; // queue of workers waiting for requests FIFO! 16 | 17 | /// 18 | /// The service name. 19 | /// 20 | public string Name { get; } 21 | 22 | /// 23 | /// returns a readonly sequence of waiting workers - some of which may have expired 24 | /// 25 | /// 26 | /// we need to copy the array in order to allow the changing of it in an iteration 27 | /// since we will change the list while iterating we must operate on a copy 28 | /// 29 | public IEnumerable WaitingWorkers => m_waitingWorkers.ToArray (); 30 | 31 | /// 32 | /// Returns a list of requests pending for being send to workers 33 | /// 34 | public List PendingRequests => m_pendingRequests; 35 | 36 | /// 37 | /// Ctor for a service 38 | /// 39 | /// the service name 40 | public Service (string name) 41 | { 42 | Name = name; 43 | m_workers = new List (); 44 | m_pendingRequests = new List (); 45 | m_waitingWorkers = new List (); 46 | } 47 | 48 | /// 49 | /// Returns true if workers are waiting and requests are pending and false otherwise 50 | /// 51 | public bool CanDispatchRequests () 52 | { 53 | return m_waitingWorkers.Count > 0 && m_pendingRequests.Count > 0; 54 | } 55 | 56 | /// 57 | /// Returns true if workers exist and false otherwise. 58 | /// 59 | public bool DoWorkersExist () 60 | { 61 | return m_workers.Count > 0; 62 | } 63 | 64 | /// 65 | /// Get the longest waiting worker for this service and remove it from the waiting list 66 | /// 67 | /// the worker or if none is available null 68 | public Worker GetNextWorker () 69 | { 70 | var worker = m_waitingWorkers.Count == 0 ? null : m_waitingWorkers[0]; 71 | 72 | if (worker != null) 73 | m_waitingWorkers.Remove (worker); 74 | 75 | return worker; 76 | } 77 | 78 | /// 79 | /// Adds a worker to the waiting worker list and if it is not known it adds it to the known workers as well 80 | /// 81 | /// the worker to add 82 | public void AddWaitingWorker (Worker worker) 83 | { 84 | if (!IsKnown (worker)) 85 | m_workers.Add (worker); 86 | 87 | if (!IsWaiting (worker)) 88 | { 89 | // add to the end of the list 90 | // oldest is at the beginning of the list 91 | m_waitingWorkers.Add (worker); 92 | } 93 | } 94 | 95 | /// 96 | /// Deletes worker from the list of known workers and 97 | /// if the worker is registered for waiting removes it 98 | /// from that list as well 99 | /// in order to synchronize the deleting access to the 100 | /// local lists m_syncRoot is used to lock 101 | /// 102 | /// the worker to delete 103 | public void DeleteWorker (Worker worker) 104 | { 105 | if (IsKnown (worker.Id)) 106 | m_workers.Remove (worker); 107 | 108 | if (IsWaiting (worker)) 109 | m_waitingWorkers.Remove (worker); 110 | } 111 | 112 | /// 113 | /// Add the request to the pending requests. 114 | /// 115 | /// the message to send 116 | public void AddRequest (NetMQMessage message) 117 | { 118 | // add to the end, thus the oldest is the first element 119 | m_pendingRequests.Add (message); 120 | } 121 | 122 | /// 123 | /// Return the oldest pending request or null if non exists. 124 | /// 125 | /// 126 | /// no synchronization necessary since no concurrent access 127 | /// 128 | public NetMQMessage GetNextRequest () 129 | { 130 | // get one or null 131 | var request = m_pendingRequests.Count > 0 ? m_pendingRequests[0] : null; 132 | // remove from pending requests if it exists 133 | if (request is not null) 134 | m_pendingRequests.Remove (request); 135 | 136 | return request; 137 | } 138 | 139 | public override int GetHashCode () 140 | { 141 | return Name.GetHashCode (); 142 | } 143 | 144 | public override string ToString () 145 | { 146 | return $"Name = {Name} / Worker {m_workers.Count} - Waiting {m_waitingWorkers.Count} - Pending REQ {m_pendingRequests.Count}"; 147 | } 148 | 149 | public override bool Equals (object obj) 150 | { 151 | if (obj is null) 152 | return false; 153 | 154 | var other = obj as Service; 155 | 156 | return other is not null && Name == other.Name; 157 | } 158 | 159 | private bool IsKnown (string workerName) { return m_workers.Exists (w => w.Id == workerName); } 160 | 161 | private bool IsKnown (Worker worker) { return m_workers.Contains (worker); } 162 | 163 | private bool IsWaiting (Worker worker) { return m_waitingWorkers.Contains (worker); } 164 | } -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocol/Worker.cs: -------------------------------------------------------------------------------- 1 | namespace MajordomoProtocol; 2 | 3 | /// 4 | /// A broker local representation of a connected worker 5 | /// 6 | internal class Worker 7 | { 8 | // the id of the worker as string 9 | public string Id { get; } 10 | // identity of worker for routing 11 | public NetMQFrame Identity { get; private set; } 12 | // owing service if known 13 | public Service Service { get; set; } 14 | // when does the worker expire, if no heartbeat 15 | public DateTime Expiry { get; set; } 16 | 17 | public Worker(string id, NetMQFrame identity, Service service) 18 | { 19 | Id = id; 20 | Identity = identity; 21 | Service = service; 22 | } 23 | 24 | public override int GetHashCode() 25 | { 26 | return (Id + Service.Name).GetHashCode(); 27 | } 28 | 29 | public override string ToString() 30 | { 31 | return $"Name = {Id} / Service = {Service.Name} / Expires {Expiry.ToShortTimeString()}"; 32 | } 33 | 34 | public override bool Equals(object obj) 35 | { 36 | if (obj is null) 37 | return false; 38 | 39 | 40 | return obj is Worker other && Id == other.Id && Service.Name == other.Service.Name; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocolTests/MajordomoProtocolTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | MajordomoTests 5 | MajordomoTests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Majordomo/MajordomoProtocolTests/ServiceTests.cs: -------------------------------------------------------------------------------- 1 | using MajordomoProtocol; 2 | 3 | using NUnit.Framework; 4 | 5 | namespace MajordomoTests; 6 | 7 | [TestFixture] 8 | public class ServiceTests 9 | { 10 | [Test] 11 | public void ctor_New_ShouldReturnInstantiatedObject() 12 | { 13 | var service = new Service("echo"); 14 | 15 | Assert.That(service, Is.Not.Null); 16 | Assert.That(service.Name, Is.EqualTo("echo")); 17 | Assert.That(service.WaitingWorkers, Is.Not.Null); 18 | Assert.That(service.PendingRequests, Is.Not.Null); 19 | } 20 | 21 | [Test] 22 | public void DoWorkerExist_NoWorker_ShouldReturnFalse() 23 | { 24 | var service = new Service("tcp://*:5555"); 25 | 26 | Assert.That(service.DoWorkersExist(), Is.False); 27 | } 28 | 29 | [Test] 30 | public void AddWaitingWorker_KnownWorker_ShouldNotAdd() 31 | { 32 | var service = new Service("service"); 33 | var worker = new Worker("W001", new NetMQFrame("W001"), service); 34 | 35 | service.AddWaitingWorker(worker); 36 | service.AddWaitingWorker(worker); 37 | 38 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(1)); 39 | Assert.That(service.DoWorkersExist(), Is.True); 40 | } 41 | 42 | [Test] 43 | public void AddWaitingWorker_UnknownWorker_ShouldReturnAddToWaitingAndKnownWorkers() 44 | { 45 | var service = new Service("service"); 46 | var worker = new Worker("W001", new NetMQFrame("W001"), service); 47 | 48 | service.AddWaitingWorker(worker); 49 | 50 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(1)); 51 | Assert.That(service.DoWorkersExist(), Is.True); 52 | } 53 | 54 | [Test] 55 | public void DeleteWorker_KnownWorker_ShouldReturnAddToWaitingAndKnownWorkers() 56 | { 57 | Worker workerToDelete = null; 58 | var service = new Service("service"); 59 | 60 | for (var i = 0; i < 10; i++) 61 | { 62 | var id = $"W0{i:N3}"; 63 | var worker = new Worker(id, new NetMQFrame(id), service); 64 | 65 | if (i == 5) 66 | workerToDelete = worker; 67 | 68 | service.AddWaitingWorker(worker); 69 | } 70 | 71 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(10)); 72 | Assert.That(service.DoWorkersExist(), Is.True); 73 | 74 | service.DeleteWorker(workerToDelete); 75 | 76 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(9)); 77 | } 78 | 79 | [Test] 80 | public void GetNextWorker_SomeWaitingWorker_ShouldReturnOldestWorkerFirst() 81 | { 82 | Worker oldestWorker = null; 83 | var service = new Service("service"); 84 | 85 | for (var i = 0; i < 10; i++) 86 | { 87 | var id = $"W0{i:N3}"; 88 | var worker = new Worker(id, new NetMQFrame(id), service); 89 | 90 | if (i == 0) 91 | oldestWorker = worker; 92 | 93 | service.AddWaitingWorker(worker); 94 | } 95 | 96 | Assert.That(service.GetNextWorker(), Is.EqualTo(oldestWorker)); 97 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(9)); 98 | } 99 | 100 | [Test] 101 | public void GetNextWorker_GetAllWaitingWorkers_ShouldReturnEmptyWaitingAndLeaveKnownUnchanged() 102 | { 103 | var service = new Service("service"); 104 | 105 | for (var i = 0; i < 10; i++) 106 | { 107 | var id = $"W0{i:N3}"; 108 | var worker = new Worker(id, new NetMQFrame(id), service); 109 | service.AddWaitingWorker(worker); 110 | service.GetNextWorker(); 111 | } 112 | 113 | Assert.That(service.WaitingWorkers, Is.Empty); 114 | Assert.That(service.DoWorkersExist(), Is.True); 115 | } 116 | 117 | [Test] 118 | public void DeleteWorker_SomeWaitingWorker_ShouldReturnChangeWaitingAndKnown() 119 | { 120 | Worker workerToDelete = null; 121 | var service = new Service("service"); 122 | 123 | for (var i = 0; i < 10; i++) 124 | { 125 | var id = $"W0{i:N3}"; 126 | var worker = new Worker(id, new NetMQFrame(id), service); 127 | 128 | service.AddWaitingWorker(worker); 129 | if (i == 4) 130 | workerToDelete = worker; 131 | } 132 | 133 | service.DeleteWorker(workerToDelete); 134 | 135 | Assert.That(service.DoWorkersExist(), Is.True); 136 | Assert.That(service.WaitingWorkers.Count(), Is.EqualTo(9)); 137 | } 138 | 139 | [Test] 140 | public void DeleteWorker_AllWorker_ShouldEmptyWaitingAndKnown() 141 | { 142 | var service = new Service("service"); 143 | 144 | for (var i = 0; i < 10; i++) 145 | { 146 | var id = $"W0{i:N3}"; 147 | var worker = new Worker(id, new NetMQFrame(id), service); 148 | service.AddWaitingWorker(worker); 149 | service.DeleteWorker(worker); 150 | } 151 | 152 | Assert.That(service.WaitingWorkers, Is.Empty); 153 | Assert.That(service.DoWorkersExist(), Is.False); 154 | } 155 | 156 | [Test] 157 | public void AddRequest_OneMultiFrameRequest_ShouldChangePendingRequests() 158 | { 159 | var request = new NetMQMessage(); 160 | request.Push("DATA"); 161 | request.Push("SERVICE"); 162 | request.Push("HEADER"); 163 | 164 | var service = new Service("service"); 165 | 166 | service.AddRequest(request); 167 | 168 | Assert.That(service.PendingRequests.Count, Is.EqualTo(1)); 169 | } 170 | 171 | [Test] 172 | public void AddRequest_MultipleRequest_ShouldChangePendingRequests() 173 | { 174 | var service = new Service("service"); 175 | 176 | for (int i = 0; i < 10; i++) 177 | { 178 | var request = new NetMQMessage(); 179 | request.Push("DATA"); 180 | request.Push("SERVICE"); 181 | request.Push("HEADER"); 182 | 183 | service.AddRequest(request); 184 | } 185 | 186 | Assert.That(service.PendingRequests.Count, Is.EqualTo(10)); 187 | } 188 | 189 | [Test] 190 | public void GetNextRequest_SingleRequestExist_ShouldReturnOldesRequestAndDeleteFromPending() 191 | { 192 | var request = new NetMQMessage(); 193 | request.Push("DATA"); 194 | request.Push("SERVICE"); 195 | request.Push("HEADER"); 196 | 197 | var service = new Service("service"); 198 | 199 | service.AddRequest(request); 200 | 201 | Assert.That(service.GetNextRequest(), Is.EqualTo(request)); 202 | Assert.That(service.PendingRequests.Count, Is.EqualTo(0)); 203 | } 204 | 205 | [Test] 206 | public void GetNextRequest_MultipleRequestExist_ShouldReturnOldesRequestAndDeleteFromPending() 207 | { 208 | var service = new Service("service"); 209 | 210 | for (int i = 0; i < 10; i++) 211 | { 212 | var request = new NetMQMessage(); 213 | request.Push("DATA"); 214 | request.Push("SERVICE"); 215 | request.Push($"HEADER_{i}"); 216 | 217 | service.AddRequest(request); 218 | } 219 | 220 | for (int i = 0; i < 5; i++) 221 | { 222 | var req = service.GetNextRequest(); 223 | 224 | Assert.That(req.First.ConvertToString(), Is.EqualTo($"HEADER_{i}")); 225 | } 226 | 227 | Assert.That(service.PendingRequests.Count, Is.EqualTo(5)); 228 | } 229 | 230 | [Test] 231 | public void ToString_Simple_ShouldReturnFormattedInfo() 232 | { 233 | var service = new Service("service"); 234 | 235 | var s = service.ToString(); 236 | 237 | Assert.That(s, Is.EqualTo("Name = service / Worker 0 - Waiting 0 - Pending REQ 0")); 238 | } 239 | 240 | [Test] 241 | public void Equals_EqualReference_ShouldReturnTrue() 242 | { 243 | var service = new Service("service"); 244 | 245 | var other = service; 246 | 247 | Assert.That(service.Equals(other), Is.True); 248 | } 249 | 250 | [Test] 251 | public void Equals_EqualButDifferentServiceObjects_ShouldReturnTrue() 252 | { 253 | var service = new Service("service"); 254 | var other = new Service("service"); 255 | 256 | Assert.That(service.Equals(other), Is.True); 257 | } 258 | 259 | [Test] 260 | public void Equals_NotEqual_ShouldReturnFalse() 261 | { 262 | var service = new Service("service"); 263 | var other = new Service("echo"); 264 | 265 | Assert.That(service.Equals(other), Is.False); 266 | } 267 | 268 | [Test] 269 | public void Equals_DifferentTypes_ShouldReturnFalse() 270 | { 271 | var service = new Service("service"); 272 | var other = new Worker("id", new NetMQFrame("id"), service); 273 | 274 | Assert.That(service.Equals(other), Is.False); 275 | } 276 | 277 | [Test] 278 | public void GetHashCode_EqualButDifferentServiceObjects_ShouldReturnSameHashCode() 279 | { 280 | var service = new Service("service"); 281 | var other = new Service("service"); 282 | 283 | Assert.That(service.GetHashCode(), Is.EqualTo(other.GetHashCode())); 284 | } 285 | 286 | [Test] 287 | public void GetHashCode_DifferentServiceObjects_ShouldReturnSameHashCode() 288 | { 289 | var service = new Service("service"); 290 | var other = new Service("service1"); 291 | 292 | Assert.That(service.GetHashCode(), Is.Not.EqualTo(other.GetHashCode())); 293 | } 294 | } -------------------------------------------------------------------------------- /src/Majordomo/StartMDPExample - Verbose.bat: -------------------------------------------------------------------------------- 1 | start MDPBrokerProcess\bin\Debug\net48\MDPBrokerProcess.exe -v 2 | start MDPWorkerExample\bin\Debug\net48\MDPWorkerExample.exe -v 3 | start MDPClientExample\bin\Debug\net48\MDPClientExample.exe -r100 -------------------------------------------------------------------------------- /src/Majordomo/StartMDPExample.bat: -------------------------------------------------------------------------------- 1 | start MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 2 | start MDPWorkerExample\bin\Debug\MDPWorkerExample.exe 3 | start MDPClientExample\bin\Debug\MDPClientExample.exe -r10000 -------------------------------------------------------------------------------- /src/Majordomo/StartMDPServiceDiscoveringClientExample.bat: -------------------------------------------------------------------------------- 1 | start MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 2 | start MDPWorkerExample\bin\Debug\MDPWorkerExample.exe -v 3 | start MDPServiceDiscoveryClientExample\bin\Debug\MDPServiceDiscoveryClientExample.exe -v 4 | -------------------------------------------------------------------------------- /src/Multithreading/Multithreaded Service/MultithreadedService/MultithreadedService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Multithreading/Multithreaded Service/MultithreadedService/Program.cs: -------------------------------------------------------------------------------- 1 | using NetMQ.Devices; 2 | 3 | CancellationToken token; 4 | 5 | Console.Title = "NetMQ Multi-threaded Service"; 6 | 7 | var queue = new QueueDevice("tcp://localhost:5555", "tcp://localhost:5556", DeviceMode.Threaded); 8 | 9 | var source = new CancellationTokenSource(); 10 | token = source.Token; 11 | 12 | for (int threadId = 0; threadId < 10; threadId++) 13 | { 14 | _ = Task.Factory.StartNew(WorkerRoutine, token); 15 | } 16 | 17 | queue.Start(); 18 | 19 | var tasks = new List(); 20 | for (int i = 0; i < 1000; i++) 21 | { 22 | int clientId = i; 23 | tasks.Add(Task.Factory.StartNew(() => ClientRoutine(clientId))); 24 | } 25 | 26 | await Task.WhenAll(tasks.ToArray()); 27 | 28 | source.Cancel(); 29 | 30 | queue.Stop(); 31 | 32 | Console.WriteLine("Press ENTER to exit..."); 33 | Console.ReadLine(); 34 | 35 | void ClientRoutine(object clientId) 36 | { 37 | try 38 | { 39 | using var req = new RequestSocket(); 40 | req.Connect("tcp://localhost:5555"); 41 | 42 | byte[] message = Encoding.Unicode.GetBytes($"{clientId} Hello"); 43 | 44 | Console.WriteLine("Client {0} sent \"{0} Hello\"", clientId); 45 | req.SendFrame(message, message.Length); 46 | 47 | var response = req.ReceiveFrameString(Encoding.Unicode); 48 | Console.WriteLine("Client {0} received \"{1}\"", clientId, response); 49 | } 50 | catch (Exception ex) 51 | { 52 | Console.WriteLine("Exception on ClientRoutine: {0}", ex.Message); 53 | } 54 | } 55 | 56 | void WorkerRoutine() 57 | { 58 | try 59 | { 60 | using ResponseSocket rep = new(); 61 | rep.Options.Identity = Encoding.Unicode.GetBytes(Guid.NewGuid().ToString()); 62 | rep.Connect("tcp://localhost:5556"); 63 | //rep.Connect("inproc://workers"); 64 | rep.ReceiveReady += RepOnReceiveReady; 65 | while (!token.IsCancellationRequested) 66 | { 67 | rep.Poll(TimeSpan.FromMilliseconds(100)); 68 | } 69 | } 70 | catch (Exception ex) 71 | { 72 | Console.WriteLine("Exception on WorkerRoutine: {0}", ex.Message); 73 | throw; 74 | } 75 | } 76 | 77 | void RepOnReceiveReady(object sender, NetMQSocketEventArgs args) 78 | { 79 | try 80 | { 81 | NetMQSocket rep = args.Socket; 82 | 83 | byte[] message = rep.ReceiveFrameBytes(); 84 | 85 | //Thread.Sleep(1000); // Simulate 'work' 86 | 87 | byte[] response = 88 | Encoding.Unicode.GetBytes(Encoding.Unicode.GetString(message) + " World from worker " + Encoding.Unicode.GetString(rep.Options.Identity)); 89 | 90 | rep.TrySendFrame(response, response.Length); 91 | } 92 | catch (Exception ex) 93 | { 94 | Console.WriteLine("Exception on RepOnReceiveReady: {0}", ex.Message); 95 | throw; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Client/LazyPirate.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Client/Program.cs: -------------------------------------------------------------------------------- 1 | // Run both LazyPirate.Server and LazyPirate.Client simultaneously to see the demonstration of this pattern. 2 | 3 | const int RequestTimeout = 2500; 4 | const int RequestRetries = 10; 5 | const string ServerEndpoint = "tcp://127.0.0.1:5555"; 6 | 7 | int sequence = 0; 8 | bool expectReply = true; 9 | int retriesLeft = RequestRetries; 10 | 11 | Console.Title = "NetMQ LazyPirate Client"; 12 | 13 | RequestSocket client = CreateServerSocket(); 14 | 15 | while (retriesLeft > 0) 16 | { 17 | sequence++; 18 | Console.WriteLine("C: Sending ({0})", sequence); 19 | client.SendFrame(Encoding.Unicode.GetBytes(sequence.ToString())); 20 | expectReply = true; 21 | 22 | while (expectReply) 23 | { 24 | bool result = client.Poll(TimeSpan.FromMilliseconds(RequestTimeout)); 25 | 26 | if (result) 27 | continue; 28 | 29 | retriesLeft--; 30 | 31 | if (retriesLeft == 0) 32 | { 33 | Console.WriteLine("C: Server seems to be offline, abandoning"); 34 | break; 35 | } 36 | 37 | Console.WriteLine("C: No response from server, retrying..."); 38 | 39 | TerminateClient(client); 40 | 41 | client = CreateServerSocket(); 42 | client.SendFrame(Encoding.Unicode.GetBytes(sequence.ToString())); 43 | } 44 | } 45 | 46 | TerminateClient(client); 47 | 48 | void TerminateClient(NetMQSocket client) 49 | { 50 | client.Disconnect(ServerEndpoint); 51 | client.Close(); 52 | } 53 | 54 | RequestSocket CreateServerSocket() 55 | { 56 | Console.WriteLine("C: Connecting to server..."); 57 | 58 | var client = new RequestSocket(); 59 | client.Connect(ServerEndpoint); 60 | client.Options.Linger = TimeSpan.Zero; 61 | client.ReceiveReady += ClientOnReceiveReady; 62 | 63 | return client; 64 | } 65 | 66 | void ClientOnReceiveReady(object sender, NetMQSocketEventArgs args) 67 | { 68 | var reply = args.Socket.ReceiveFrameBytes(); 69 | string strReply = Encoding.Unicode.GetString(reply); 70 | 71 | if (int.Parse(strReply) == sequence) 72 | { 73 | Console.WriteLine("C: Server replied OK ({0})", strReply); 74 | retriesLeft = RequestRetries; 75 | expectReply = false; 76 | } 77 | else 78 | { 79 | Console.WriteLine("C: Malformed reply from server: {0}", strReply); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Client2/LazyPirate.Client2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Client2/Program.cs: -------------------------------------------------------------------------------- 1 | // Run both LazyPirate.Server2 and LazyPirate.Client2 simultaneously to see the demonstration of this pattern. 2 | // LazyPirate.Client and LazyPirate.Client2 are functionally equivalent. The first uses UniCode strings, the second uses ASCII strings. 3 | 4 | Console.Title = "NetMQ LazyPirate Client 2"; 5 | 6 | const string serverAddress = "tcp://127.0.0.1:5555"; 7 | const string requestString = "Hi"; 8 | 9 | var requestTimeout = TimeSpan.FromMilliseconds(2500); 10 | var requestRetries = 10; 11 | var requestMessage = new NetMQMessage(1); 12 | 13 | requestMessage.Append("Hi"); 14 | 15 | using var progressPublisher = new PublisherSocket(); 16 | 17 | const string pubSubAddress = "tcp://127.0.0.1:5556"; 18 | 19 | progressPublisher.Bind(pubSubAddress); 20 | 21 | SubscriberContinuousLoop(pubSubAddress, requestString); 22 | 23 | while (true) 24 | { 25 | var responseString = RequestSocket.RequestResponseMultipartMessageWithRetry(serverAddress, requestMessage, 26 | requestRetries, requestTimeout, progressPublisher); 27 | } 28 | 29 | static void SubscriberContinuousLoop(string pubSubAddress, string requestString) 30 | { 31 | Task.Factory.StartNew(() => 32 | { 33 | using var progressSubscriber = new SubscriberSocket(); 34 | progressSubscriber.Connect(pubSubAddress); 35 | progressSubscriber.SubscribeToAnyTopic(); 36 | while (true) 37 | { 38 | var topic = progressSubscriber.ReceiveFrameString(); 39 | //RequestSocket.ProgressTopic progressTopic; 40 | //Enum.TryParse(topic, out progressTopic); 41 | //switch (progressTopic) 42 | //{ 43 | // case RequestSocket.ProgressTopic.Send: 44 | // Console.WriteLine("C: Sending {0}", requestString); 45 | // break; 46 | // case RequestSocket.ProgressTopic.Retry: 47 | // Console.WriteLine("C: No response from server, retrying..."); 48 | // break; 49 | // case RequestSocket.ProgressTopic.Failure: 50 | // Console.WriteLine("C: Server seems to be offline, abandoning"); 51 | // break; 52 | // case RequestSocket.ProgressTopic.Success: 53 | // Console.WriteLine("C: Server replied OK"); 54 | // break; 55 | // default: 56 | // throw new ArgumentOutOfRangeException(); 57 | //} 58 | } 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Server/LazyPirate.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Server/Program.cs: -------------------------------------------------------------------------------- 1 | Console.Title = "NetMQ LazyPirate Server"; 2 | 3 | const string serverEndpoint = "tcp://127.0.0.1:5555"; 4 | 5 | var random = new Random(); 6 | 7 | using var server = new ResponseSocket(); 8 | 9 | Console.WriteLine("S: Binding address {0}", serverEndpoint); 10 | server.Bind(serverEndpoint); 11 | 12 | var cycles = 0; 13 | 14 | while (true) 15 | { 16 | byte[] request = server.ReceiveFrameBytes(); 17 | cycles++; 18 | 19 | if (cycles > 3 && random.Next(0, 10) == 0) 20 | { 21 | Console.WriteLine("S: Simulating a crash"); 22 | Thread.Sleep(5000); 23 | } 24 | else if (cycles > 3 && random.Next(0, 10) == 0) 25 | { 26 | Console.WriteLine("S: Simulating CPU overload"); 27 | Thread.Sleep(1000); 28 | } 29 | 30 | Console.WriteLine("S: Normal request ({0})", Encoding.Unicode.GetString(request)); 31 | server.SendFrame(request); 32 | } 33 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Server2/LazyPirate.Server2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Lazy Pirate/LazyPirate.Server2/Program.cs: -------------------------------------------------------------------------------- 1 | Console.Title = "NetMQ LazyPirate Server 2"; 2 | 3 | const string serverEndpoint = "tcp://127.0.0.1:5555"; 4 | 5 | var random = new Random(); 6 | 7 | using var server = new ResponseSocket(); 8 | 9 | Console.WriteLine("S: Binding address {0}", serverEndpoint); 10 | server.Bind(serverEndpoint); 11 | 12 | var cycles = 0; 13 | 14 | while (true) 15 | { 16 | var request = server.ReceiveFrameString(); 17 | cycles++; 18 | 19 | if (cycles > 3 && random.Next(0, 10) == 0) 20 | { 21 | Console.WriteLine("S: Simulating a crash"); 22 | Thread.Sleep(5000); 23 | } 24 | else if (cycles > 3 && random.Next(0, 10) == 0) 25 | { 26 | Console.WriteLine("S: Simulating CPU overload"); 27 | Thread.Sleep(1000); 28 | } 29 | 30 | Console.WriteLine("S: Normal request ({0})", request); 31 | server.SendFrame(request); 32 | } 33 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Client/ParanoidPirate.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using ParanoidPirate.Queue; 2 | 3 | namespace ParanoidPirate.Client; 4 | 5 | internal static class Program 6 | { 7 | private static int s_sequence; 8 | private static bool s_expectReply = true; 9 | private static int s_retriesLeft = Commons.RequestClientRetries; 10 | 11 | /// 12 | /// ParanoidPirate.Client [-v] 13 | /// 14 | /// implements a skeleton client of a Paranoid Pirate Pattern 15 | /// 16 | /// upon start it creates a REQ socket 17 | /// send out a message with a sequence number 18 | /// wait for a specified timespan for an answer 19 | /// if the answer has not been received within that timeframe 20 | /// write a message to screen 21 | /// since the socket is corrupted disconnect and dispose 22 | /// create a new REQ socket 23 | /// resend the message 24 | /// repeat that for a specified number of times 25 | /// 26 | private static void Main(string[] args) 27 | { 28 | Console.Title = "NetMQ ParanoidPirate Client"; 29 | 30 | var verbose = args.Length > 0 && args[0] == "-v"; 31 | var clientId = args.Length > 1 ? args[1] : "SoleClient"; 32 | 33 | // create the REQ socket and connect to QUEUE frontend 34 | // and hook up ReceiveReady event handler 35 | var client = CreateSocket(clientId); 36 | 37 | if (verbose) 38 | Console.WriteLine("[Client] Connected to Queue."); 39 | 40 | while (s_retriesLeft > 0) 41 | { 42 | s_sequence++; 43 | 44 | Console.WriteLine("[Client] Sending ({0})", s_sequence); 45 | 46 | client.SendFrame(Encoding.Unicode.GetBytes(s_sequence.ToString())); 47 | 48 | s_expectReply = true; 49 | 50 | while (s_expectReply) 51 | { 52 | if (client.Poll(TimeSpan.FromMilliseconds(Commons.RequestClientTimeout))) 53 | continue; 54 | 55 | // QUEUE has not answered in time 56 | s_retriesLeft--; 57 | 58 | if (s_retriesLeft == 0) 59 | { 60 | Console.WriteLine("[Client - ERROR] Server seems to be offline, abandoning!"); 61 | break; 62 | } 63 | 64 | Console.WriteLine("[Client - ERROR] No response from server, retrying..."); 65 | 66 | client.Disconnect(Commons.QueueFrontend); 67 | client.Close(); 68 | client.Dispose(); 69 | 70 | client = CreateSocket(clientId); 71 | // resend sequence message 72 | client.SendFrame(Encoding.Unicode.GetBytes(s_sequence.ToString())); 73 | } 74 | } 75 | 76 | // clean up! 77 | client.Disconnect(Commons.QueueFrontend); 78 | client.Dispose(); 79 | 80 | Console.Write("I am done! To exits press any key!"); 81 | } 82 | 83 | /// 84 | /// just to create the REQ socket 85 | /// 86 | /// the name for the client 87 | /// the connected REQ socket 88 | private static RequestSocket CreateSocket(string id) 89 | { 90 | var client = new RequestSocket 91 | { 92 | Options = 93 | { 94 | Identity = Encoding.UTF8.GetBytes(id), 95 | Linger = TimeSpan.Zero 96 | } 97 | }; 98 | 99 | // set the event to be called upon arrival of a message 100 | client.ReceiveReady += OnClientReceiveReady; 101 | client.Connect(Commons.QueueFrontend); 102 | 103 | return client; 104 | } 105 | 106 | /// 107 | /// handles the ReceiveReady event 108 | /// 109 | /// get the message and validates that the send data is correct 110 | /// prints an appropriate message on screen in either way 111 | /// 112 | private static void OnClientReceiveReady(object sender, NetMQSocketEventArgs e) 113 | { 114 | var reply = e.Socket.ReceiveFrameBytes(); 115 | var strReply = Encoding.Unicode.GetString(reply); 116 | 117 | if (Int32.Parse(strReply) == s_sequence) 118 | { 119 | Console.WriteLine("C: Server replied OK ({0})", strReply); 120 | 121 | s_retriesLeft = Commons.RequestClientRetries; 122 | s_expectReply = false; 123 | } 124 | else 125 | { 126 | Console.WriteLine("C: Malformed reply from server: {0}", strReply); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Queue/Commons.cs: -------------------------------------------------------------------------------- 1 | namespace ParanoidPirate.Queue; 2 | 3 | public static class Commons 4 | { 5 | public const int HeartbeatLiveliness = 3; // 3-5 is reasonable 6 | public const int HeartbeatInterval = 1000; // in ms 7 | public const int IntervalInit = 1000; // in ms 8 | public const int IntervalMax = 32000; // in ms 9 | 10 | public const string PPPReady = "READY"; 11 | public const string PPPHeartbeat = "HEARTBEAT"; 12 | public const int PPPTick = 500; // in ms 13 | 14 | public const string QueueFrontend = "tcp://127.0.0.1:5556"; 15 | public const string QueueBackend = "tcp://127.0.0.1:5557"; 16 | 17 | public const int RequestClientTimeout = 2500; // in ms - should be > 1000! 18 | public const int RequestClientRetries = 10; 19 | } 20 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Queue/ParanoidPirate.Queue.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Queue/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ParanoidPirate.Queue; 2 | 3 | public static class Program 4 | { 5 | /// 6 | /// ParanoidPirate.Queue [-v] 7 | /// 8 | /// Does load-balancing with heartbeating on worker tasks to detect 9 | /// crashed, blocked or slow running worker tasks . 10 | /// 11 | private static void Main(string[] args) 12 | { 13 | Console.Title = "NetMQ ParanoidPirate Queue"; 14 | 15 | // serves as flag for exiting the program 16 | var exit = false; 17 | // catch CTRL+C as exit command 18 | Console.CancelKeyPress += (s, e) => 19 | { 20 | e.Cancel = true; 21 | exit = true; 22 | }; 23 | 24 | var verbose = args.Length > 0 && args[0] == "-v"; 25 | 26 | using var frontend = new RouterSocket(); 27 | using var backend = new RouterSocket(); 28 | using var poller = new NetMQPoller(); 29 | frontend.Bind(Commons.QueueFrontend); 30 | backend.Bind(Commons.QueueBackend); 31 | 32 | var workers = new Workers(); 33 | 34 | // client sends to this socket 35 | frontend.ReceiveReady += (s, e) => 36 | { 37 | // only process incoming client requests 38 | // if we have workers available handle client requests as long as we have workers 39 | // storage capability of the socket otherwise and pick up later 40 | if (workers.Available) 41 | { 42 | // get all message frames! 43 | var request = frontend.ReceiveMultipartMessage(); 44 | 45 | if (verbose) 46 | Console.WriteLine("[QUEUE] received {0}", request); 47 | 48 | // get next available worker 49 | var worker = workers.Next(); 50 | // wrap message with worker's address 51 | var msg = Wrap(worker, request); 52 | 53 | if (verbose) 54 | Console.WriteLine("[QUEUE -> WORKER] sending {0}", msg); 55 | 56 | backend.SendMultipartMessage(msg); 57 | } 58 | }; 59 | 60 | // worker sends to this socket 61 | backend.ReceiveReady += (s, e) => 62 | { 63 | var msg = e.Socket.ReceiveMultipartMessage(); 64 | 65 | if (verbose) 66 | Console.WriteLine("[QUEUE <- WORKER] received {0}", msg); 67 | 68 | // use workers identity for load-balancing 69 | var workerIdentity = Unwrap(msg); 70 | var worker = new Worker(workerIdentity); 71 | workers.Ready(worker); 72 | // just convenience 73 | var readableWorkerId = workerIdentity.ConvertToString(); 74 | 75 | if (msg.FrameCount == 1) 76 | { 77 | var data = msg[0].ConvertToString(); 78 | // the message is either READY or HEARTBEAT or corrupted 79 | switch (data) 80 | { 81 | case Commons.PPPHeartbeat: 82 | Console.WriteLine("[QUEUE <- WORKER] Received a Heartbeat from {0}", 83 | readableWorkerId); 84 | break; 85 | case Commons.PPPReady: 86 | Console.WriteLine("[QUEUE <- WORKER] Received a READY form {0}", 87 | readableWorkerId); 88 | break; 89 | default: 90 | Console.WriteLine("[QUEUE <- WORKER] ERROR received an invalid message!"); 91 | break; 92 | } 93 | } 94 | else 95 | { 96 | if (verbose) 97 | Console.WriteLine("[QUEUE -> CLIENT] sending {0}", msg); 98 | 99 | frontend.SendMultipartMessage(msg); 100 | } 101 | }; 102 | 103 | var timer = new NetMQTimer(Commons.HeartbeatInterval); 104 | // every specified ms QUEUE shall send a heartbeat to all connected workers 105 | timer.Elapsed += (s, e) => 106 | { 107 | // send heartbeat to every worker 108 | foreach (var worker in workers) 109 | { 110 | var heartbeat = new NetMQMessage(); 111 | 112 | heartbeat.Push(new NetMQFrame(Commons.PPPHeartbeat)); 113 | heartbeat.Push(worker.Identity); 114 | 115 | Console.WriteLine("[QUEUE -> WORKER] sending heartbeat!"); 116 | 117 | backend.SendMultipartMessage(heartbeat); 118 | } 119 | // restart timer 120 | e.Timer.Enable = true; 121 | // remove all dead or expired workers 122 | workers.Purge(); 123 | }; 124 | 125 | if (verbose) 126 | Console.WriteLine("[QUEUE] Start listening!"); 127 | 128 | poller.Add(frontend); 129 | poller.Add(backend); 130 | poller.Add(timer); 131 | 132 | poller.RunAsync(); 133 | 134 | // hit CRTL+C to stop the while loop 135 | while (!exit) 136 | Thread.Sleep(100); 137 | } 138 | 139 | /// 140 | /// Strip the first frame of a message and the following if it is empty 141 | /// 142 | /// the first frame and changes the message 143 | private static NetMQFrame Unwrap(NetMQMessage msg) 144 | { 145 | var id = msg.Pop(); 146 | // forget the empty frame 147 | if (msg.First.IsEmpty) 148 | msg.Pop(); 149 | 150 | return id; 151 | } 152 | 153 | /// 154 | /// Wrap a message with the identity and an empty frame 155 | /// 156 | /// new created message 157 | private static NetMQMessage Wrap(NetMQFrame identity, NetMQMessage msg) 158 | { 159 | var result = new NetMQMessage(msg); 160 | 161 | result.PushEmptyFrame(); 162 | result.Push(identity); 163 | 164 | return result; 165 | } 166 | } -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Queue/Worker.cs: -------------------------------------------------------------------------------- 1 | namespace ParanoidPirate.Queue; 2 | 3 | public class Worker : IDisposable 4 | { 5 | public DateTime Expiry { get; set; } 6 | 7 | public NetMQFrame Identity { get; set; } 8 | 9 | public string Name 10 | { 11 | get { return Identity.ConvertToString(); } 12 | set { Identity = new NetMQFrame(value); } 13 | } 14 | 15 | public Worker(NetMQFrame id) 16 | { 17 | Identity = id; 18 | Expiry = DateTime.UtcNow + TimeSpan.FromMilliseconds(Commons.HeartbeatInterval*Commons.HeartbeatLiveliness); 19 | } 20 | 21 | public void Dispose() 22 | { 23 | Dispose(true); 24 | GC.SuppressFinalize(this); 25 | } 26 | 27 | protected virtual void Dispose(bool disposing) 28 | { 29 | if (!disposing) 30 | return; 31 | 32 | Identity = null; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Queue/Workers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace ParanoidPirate.Queue; 4 | 5 | public class Workers : IEnumerable 6 | { 7 | private readonly List m_workers = new(); 8 | 9 | /// 10 | /// true if there are workers available 11 | /// 12 | public bool Available => m_workers.Count > 0; 13 | 14 | /// 15 | /// stores a worker for a LRU pattern 16 | /// 17 | public void Ready(Worker worker) 18 | { 19 | m_workers.Add(worker); 20 | } 21 | 22 | /// 23 | /// a NetMQFrame with the identity of the next available worker 24 | /// or null if no worker is available 25 | /// 26 | public NetMQFrame Next() 27 | { 28 | if (m_workers.Count == 0) 29 | return null; 30 | 31 | // get the oldest worker 32 | var worker = m_workers[0]; 33 | // remove it from list 34 | m_workers.RemoveAt(0); 35 | 36 | return worker.Identity; 37 | } 38 | 39 | /// 40 | /// removes every worker which has exceeded his livetime 41 | /// 42 | public void Purge() 43 | { 44 | foreach (var worker in m_workers.Where(worker => worker.Expiry < DateTime.UtcNow).ToList()) 45 | m_workers.Remove(worker); 46 | 47 | } 48 | 49 | public IEnumerator GetEnumerator() 50 | { 51 | return m_workers.GetEnumerator(); 52 | } 53 | 54 | IEnumerator IEnumerable.GetEnumerator() 55 | { 56 | return GetEnumerator(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Worker/ParanoidPirate.Worker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/ParanoidPirate.Worker/Program.cs: -------------------------------------------------------------------------------- 1 | using ParanoidPirate.Queue; 2 | 3 | namespace ParanoidPirate.Worker; 4 | 5 | internal static class Program 6 | { 7 | /// 8 | /// ParanoidPirate.Worker [-v] 9 | /// 10 | /// The worker skeleton in the Paranoid Pirate Protocol 11 | /// 12 | /// It can detect if the queue died and vice versa by implementing hearbeating 13 | /// 14 | private static void Main(string[] args) 15 | { 16 | Console.Title = "NetMQ ParanoidPirate Worker"; 17 | 18 | var verbose = args.Length > 0 && args[0] == "-v"; 19 | 20 | var rnd = new Random(); 21 | var workerId = rnd.Next(100, 500); 22 | var worker = GetWorkerSocket(verbose, workerId); 23 | 24 | // if liveliness == 0 -> queue is considered dead/disconnected 25 | var liveliness = Commons.HeartbeatLiveliness; 26 | var interval = Commons.IntervalInit; 27 | var heartbeatAt = DateTime.UtcNow + TimeSpan.FromMilliseconds(Commons.HeartbeatInterval); 28 | var cycles = 0; 29 | var crash = false; 30 | 31 | // upon receiving a message this event handler is called 32 | // read the message, randomly simulate failure/problem 33 | // process message or heartbeat or crash 34 | worker.ReceiveReady += (s, e) => 35 | { 36 | var msg = e.Socket.ReceiveMultipartMessage(); 37 | 38 | // message is a NetMQMessage (!) 39 | // - 3 part envelope + content -> request 40 | // - 1 part HEARTBEAT -> heartbeat 41 | if (msg.FrameCount > 3) 42 | { 43 | // in order to test the robustness we simulate a couple of typical problems 44 | // e.g. worker crushing or running very slow 45 | // that is initiated after multiple cycles to give everything time to stabilize first 46 | cycles++; 47 | 48 | if (cycles > 3 && rnd.Next(5) == 0) 49 | { 50 | Console.WriteLine("[WORKER] simulating crashing!"); 51 | crash = true; 52 | return; 53 | } 54 | 55 | if (cycles > 3 && rnd.Next(3) == 0) 56 | { 57 | Console.WriteLine("[WORKER] Simulating CPU overload!"); 58 | Thread.Sleep(500); 59 | } 60 | 61 | if (verbose) 62 | Console.Write("[WORKER] working ...!"); 63 | 64 | // simulate high workload 65 | Thread.Sleep(10); 66 | 67 | if (verbose) 68 | Console.WriteLine("[WORKER] sending {0}", msg.ToString()); 69 | 70 | // answer 71 | e.Socket.SendMultipartMessage(msg); 72 | // reset liveliness 73 | liveliness = Commons.HeartbeatLiveliness; 74 | } 75 | else if (IsHeartbeatMessage(msg)) 76 | liveliness = Commons.HeartbeatLiveliness; 77 | else 78 | Console.WriteLine("[WORKER] Received invalid message!"); 79 | 80 | interval = Commons.IntervalInit; 81 | }; 82 | 83 | while (!crash) 84 | { 85 | // wait for incoming request for specified milliseconds 86 | // if no message arrived it will return false and true otherwise 87 | // any arriving message fires the ReceiveReady event (!) 88 | if (!worker.Poll(TimeSpan.FromMilliseconds(Commons.HeartbeatInterval))) 89 | { 90 | // the queue hasn't sent any messages for a while 91 | // -> destroy socket and recreate and -connect 92 | if (--liveliness == 0) 93 | { 94 | Console.WriteLine("\t[WORKER] heartbeat failure, can't reach the queue!"); 95 | Console.WriteLine("\t[WORKER] will reconnect in {0} ms ...", interval); 96 | 97 | Thread.Sleep(interval); 98 | // increase the interval each time we do not get any message in time 99 | if (interval < Commons.IntervalMax) 100 | interval *= 2; 101 | else 102 | { 103 | Console.WriteLine("[WORKER - ERROR] something went wrong - abandoning"); 104 | crash = true; 105 | break; 106 | } 107 | 108 | worker.Dispose(); 109 | worker = GetWorkerSocket(verbose, workerId); 110 | liveliness = Commons.HeartbeatLiveliness; 111 | } 112 | } 113 | // if it is time the worker will send a heartbeat so QUEUE can detect a dead worker 114 | if (DateTime.UtcNow > heartbeatAt) 115 | { 116 | heartbeatAt = DateTime.UtcNow + TimeSpan.FromMilliseconds(Commons.HeartbeatInterval); 117 | 118 | Console.WriteLine("[WORKER] sending heartbeat!"); 119 | 120 | worker.SendFrame(Commons.PPPHeartbeat); 121 | } 122 | } 123 | 124 | worker.Dispose(); 125 | 126 | Console.Write("I crashed! To exit press any key!"); 127 | Console.ReadKey(); 128 | } 129 | 130 | /// 131 | /// Create the DEALER socket and connect it to QUEUE backend. 132 | /// Set the identity. 133 | /// Send the initial READY message. 134 | /// 135 | private static DealerSocket GetWorkerSocket(bool verbose, int id) 136 | { 137 | var worker = new DealerSocket { Options = { Identity = Encoding.UTF8.GetBytes("Worker_" + id) } }; 138 | 139 | 140 | worker.Connect(Commons.QueueBackend); 141 | 142 | if (verbose) 143 | Console.WriteLine("[WORKER] {0} sending 'READY'.", Encoding.UTF8.GetString(worker.Options.Identity)); 144 | 145 | // send READY 146 | worker.SendFrame(Commons.PPPReady); 147 | 148 | return worker; 149 | } 150 | 151 | /// 152 | /// Check if message is a HEARTBEAT. 153 | /// 154 | private static bool IsHeartbeatMessage(NetMQMessage msg) 155 | { 156 | return msg.FrameCount == 1 && msg.First.ConvertToString() == Commons.PPPHeartbeat; 157 | } 158 | } -------------------------------------------------------------------------------- /src/Pirate Pattern/Paranoid Pirate/StartParanoidPirate.bat: -------------------------------------------------------------------------------- 1 | start .\ParanoidPirate.Queue\bin\debug\ParanoidPirate.Queue.exe 2 | 3 | start .\ParanoidPirate.Client\bin\debug\ParanoidPirate.Client.exe 4 | 5 | start .\ParanoidPirate.Worker\bin\debug\ParanoidPirate.Worker.exe 6 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Client/Program.cs: -------------------------------------------------------------------------------- 1 | const int RequestTimeout = 2500; 2 | const int RequestRetries = 10; 3 | const string ServerEndpoint = "tcp://localhost:5555"; 4 | 5 | string strSequenceSent = ""; 6 | bool expectReply = true; 7 | int retriesLeft = 0; 8 | 9 | retriesLeft = RequestRetries; 10 | 11 | var client = CreateServerSocket(); 12 | 13 | int sequence = 0; 14 | 15 | while (retriesLeft > 0) 16 | { 17 | sequence++; 18 | strSequenceSent = sequence.ToString() + " HELLO"; 19 | Console.WriteLine("C: Sending ({0})", strSequenceSent); 20 | client.SendFrame(Encoding.Unicode.GetBytes(strSequenceSent)); 21 | expectReply = true; 22 | 23 | while (expectReply) 24 | { 25 | bool result = client.Poll(TimeSpan.FromMilliseconds(RequestTimeout)); 26 | 27 | if (!result) 28 | { 29 | retriesLeft--; 30 | 31 | if (retriesLeft == 0) 32 | { 33 | Console.WriteLine("C: Server seems to be offline, abandoning"); 34 | break; 35 | } 36 | else 37 | { 38 | Console.WriteLine("C: No response from server, retrying.."); 39 | 40 | TerminateClient(client); 41 | 42 | client = CreateServerSocket(); 43 | client.SendFrame(Encoding.Unicode.GetBytes(strSequenceSent)); 44 | } 45 | } 46 | } 47 | } 48 | 49 | TerminateClient(client); 50 | 51 | void TerminateClient(RequestSocket client) 52 | { 53 | client.Disconnect(ServerEndpoint); 54 | client.Close(); 55 | } 56 | 57 | RequestSocket CreateServerSocket() 58 | { 59 | Console.WriteLine("C: Connecting to server..."); 60 | 61 | var guid = Guid.NewGuid(); 62 | var client = new RequestSocket 63 | { 64 | Options = 65 | { 66 | Linger = TimeSpan.Zero, 67 | Identity = Encoding.Unicode.GetBytes(guid.ToString()) 68 | } 69 | }; 70 | client.Connect(ServerEndpoint); 71 | client.ReceiveReady += ClientOnReceiveReady; 72 | 73 | return client; 74 | } 75 | 76 | void ClientOnReceiveReady(object sender, NetMQSocketEventArgs socket) 77 | { 78 | var reply = socket.Socket.ReceiveFrameBytes(); 79 | 80 | if (Encoding.Unicode.GetString(reply) == (strSequenceSent + " WORLD!")) 81 | { 82 | Console.WriteLine("C: Server replied OK ({0})", Encoding.Unicode.GetString(reply)); 83 | retriesLeft = RequestRetries; 84 | expectReply = false; 85 | } 86 | else 87 | { 88 | Console.WriteLine("C: Malformed reply from server: {0}", Encoding.Unicode.GetString(reply)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Client/SimplePirate.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Queue/Program.cs: -------------------------------------------------------------------------------- 1 | const string LRUReady = "READY"; 2 | const string FrontendEndpoint = "tcp://localhost:5555"; 3 | const string BackendEndpoint = "tcp://localhost:5556"; 4 | 5 | var frontend = new RouterSocket(); 6 | var backend = new RouterSocket(); 7 | 8 | // For Clients 9 | Console.WriteLine("Q: Binding frontend {0}", FrontendEndpoint); 10 | frontend.Bind(FrontendEndpoint); 11 | 12 | // For Workers 13 | Console.WriteLine("Q: Binding backend {0}", BackendEndpoint); 14 | backend.Bind(BackendEndpoint); 15 | 16 | // Logic of LRU loop 17 | // - Poll backend always, frontend only if 1+ worker ready 18 | // - If worker replies, queue worker as ready and forward reply 19 | // to client if necessary 20 | // - If client requests, pop next worker and send request to it 21 | 22 | // Queue of available workers 23 | var workerQueue = new Queue(); 24 | 25 | // Handle worker activity on backend 26 | backend.ReceiveReady += (s, e) => 27 | { 28 | // Queue worker address for LRU routing 29 | byte[] workerAddress = e.Socket.ReceiveFrameBytes(); 30 | 31 | // Use worker address for LRU routing 32 | workerQueue.Enqueue(workerAddress); 33 | 34 | // Second frame is empty 35 | e.Socket.SkipFrame(); 36 | 37 | // Third frame is READY or else a client reply address 38 | byte[] clientAddress = e.Socket.ReceiveFrameBytes(); 39 | 40 | // If client reply, send rest back to frontend 41 | // Forward message to client if it's not a READY 42 | if (Encoding.Unicode.GetString(clientAddress) != LRUReady) 43 | { 44 | e.Socket.SkipFrame(); // empty 45 | 46 | byte[] reply = e.Socket.ReceiveFrameBytes(); 47 | 48 | frontend.SendMoreFrame(clientAddress); 49 | frontend.SendMoreFrame(""); 50 | frontend.SendFrame(reply); 51 | } 52 | }; 53 | 54 | frontend.ReceiveReady += (s, e) => 55 | { 56 | // Now get next client request, route to next worker 57 | // Dequeue and drop the next worker address 58 | 59 | // Now get next client request, route to LRU worker 60 | // Client request is [address][empty][request] 61 | byte[] clientAddr = e.Socket.ReceiveFrameBytes(); 62 | e.Socket.SkipFrame(); // empty 63 | byte[] request = e.Socket.ReceiveFrameBytes(); 64 | 65 | try 66 | { 67 | byte[] deq = workerQueue.Dequeue(); 68 | backend.SendMoreFrame(deq); 69 | backend.SendMoreFrame(Encoding.Unicode.GetBytes("")); 70 | backend.SendMoreFrame(clientAddr); 71 | backend.SendMoreFrame(Encoding.Unicode.GetBytes("")); 72 | backend.SendFrame(request); 73 | } 74 | catch (Exception ex) 75 | { 76 | Console.WriteLine("Q: [FrontendOnReceiveReady] Dequeue exception: {0}", ex.ToString()); 77 | } 78 | }; 79 | 80 | while (true) 81 | { 82 | backend.Poll(TimeSpan.FromMilliseconds(500)); 83 | 84 | if (workerQueue.Count > 0) 85 | frontend.Poll(TimeSpan.FromMilliseconds(500)); 86 | } 87 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Queue/SimplePirate.Queue.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Worker/Program.cs: -------------------------------------------------------------------------------- 1 | const string LRUReady = "READY"; 2 | const string ServerEndpoint = "tcp://localhost:5556"; 3 | 4 | using var worker = new RequestSocket(); 5 | 6 | var random = new Random(DateTime.Now.Millisecond); 7 | var guid = Guid.NewGuid(); 8 | 9 | worker.Options.Identity = Encoding.Unicode.GetBytes(guid.ToString()); 10 | worker.Connect(ServerEndpoint); 11 | 12 | worker.ReceiveReady += (s, e) => 13 | { 14 | // Read and save all frames until we get an empty frame 15 | // In this example there is only 1 but it could be more 16 | byte[] address = worker.ReceiveFrameBytes(); 17 | worker.ReceiveFrameBytes(); // empty 18 | byte[] request = worker.ReceiveFrameBytes(); 19 | 20 | worker.SendMoreFrame(address); 21 | worker.SendMoreFrame(Encoding.Unicode.GetBytes("")); 22 | worker.SendFrame(Encoding.Unicode.GetBytes(Encoding.Unicode.GetString(request) + " WORLD!")); 23 | }; 24 | 25 | Console.WriteLine("W: {0} worker ready", guid); 26 | worker.SendFrame(Encoding.Unicode.GetBytes(LRUReady)); 27 | 28 | var cycles = 0; 29 | while (true) 30 | { 31 | cycles += 1; 32 | if (cycles > 3 && random.Next(0, 5) == 0) 33 | { 34 | Console.WriteLine("W: {0} simulating a crash", guid); 35 | Thread.Sleep(5000); 36 | } 37 | else if (cycles > 3 && random.Next(0, 5) == 0) 38 | { 39 | Console.WriteLine("W: {0} simulating CPU overload", guid); 40 | Thread.Sleep(3000); 41 | } 42 | Console.WriteLine("W: {0} normal reply", guid); 43 | 44 | worker.Poll(TimeSpan.FromMilliseconds(1000)); 45 | } 46 | -------------------------------------------------------------------------------- /src/Pirate Pattern/Simple Pirate/SimplePirate.Worker/SimplePirate.Worker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicBroker/ITitanicIO.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using TitanicCommons; 4 | 5 | namespace TitanicProtocol; 6 | 7 | public interface ITitanicIO 8 | { 9 | event EventHandler LogInfoReady; 10 | 11 | string TitanicDirectory { get; } 12 | string TitanicQueue { get; } 13 | 14 | // ====== RequestEntry handling 15 | 16 | RequestEntry GetRequestEntry (Guid id); 17 | 18 | IEnumerable GetRequestEntries ([NotNull] Func predicate); 19 | 20 | IEnumerable GetNotClosedRequestEntries (); 21 | 22 | void SaveRequestEntry ([NotNull] RequestEntry entry); 23 | 24 | void SaveNewRequestEntry (Guid id); 25 | 26 | void SaveNewRequestEntry (Guid id, [NotNull] NetMQMessage request); 27 | 28 | void SaveProcessedRequestEntry ([NotNull] RequestEntry entry); 29 | 30 | void CloseRequest (Guid id); 31 | 32 | // ====== Message handling 33 | 34 | NetMQMessage GetMessage (TitanicOperation op, Guid id); 35 | 36 | Task GetMessageAsync (TitanicOperation op, Guid id); 37 | 38 | bool SaveMessage (TitanicOperation op, Guid id, [NotNull] NetMQMessage message); 39 | 40 | Task SaveMessageAsync (TitanicOperation op, Guid id, [NotNull] NetMQMessage message); 41 | 42 | bool ExistsMessage (TitanicOperation op, Guid id); 43 | } 44 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicBroker/RequestEntry.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicProtocol; 2 | 3 | /// 4 | /// represents an entry of a request made 5 | /// 6 | public class RequestEntry : IEquatable 7 | { 8 | private byte m_state; 9 | // introduced strictly for readability (!) 10 | /// 11 | /// '+' indicates the request has been processed 12 | /// 13 | public static readonly byte Is_Processed = (byte) '+'; 14 | /// 15 | /// '-' indicates the request is pending 16 | /// 17 | public static readonly byte Is_Pending = (byte) '-'; 18 | /// 19 | /// 'o' indicates the request has been closed 20 | /// 21 | public static readonly byte Is_Closed = (byte) 'o'; 22 | 23 | /// 24 | /// the Guid of the request 25 | /// 26 | public Guid RequestId { get; set; } 27 | 28 | /// 29 | /// the position in a file, 0 based from the beginning of file 30 | /// 31 | public long Position { get; set; } 32 | 33 | /// 34 | /// the request made - only if needed 35 | /// 36 | public NetMQMessage Request { get; set; } 37 | 38 | /// 39 | /// true if the request has been processed and false otherwise 40 | /// + -> processed 41 | /// - -> pending 42 | /// o -> closed 43 | /// 44 | public byte State 45 | { 46 | get { return m_state; } 47 | // make sure only valid values are used(!) 48 | set { m_state = value == Is_Processed ? Is_Processed : value == Is_Pending ? Is_Pending : Is_Closed; } 49 | } 50 | 51 | public RequestEntry () 52 | { 53 | Position = -1; 54 | Request = null; 55 | RequestId = Guid.Empty; 56 | State = Is_Pending; 57 | } 58 | 59 | public override string ToString () 60 | { 61 | var status = State == Is_Processed ? "processed" : State == Is_Pending ? "pending" : "closed"; 62 | return $"Id={RequestId} / Position={Position} / IsProcessed={status} / Message={Request}"; 63 | } 64 | 65 | public override int GetHashCode () 66 | { 67 | return RequestId.GetHashCode (); 68 | } 69 | 70 | public override bool Equals (object obj) 71 | { 72 | return obj is not null && Equals (obj as RequestEntry); 73 | } 74 | 75 | public bool Equals (RequestEntry other) 76 | { 77 | if (other is null) 78 | return false; 79 | 80 | return RequestId == other.RequestId && State == other.State; 81 | } 82 | 83 | public static bool operator == (RequestEntry one, RequestEntry other) 84 | { 85 | if (ReferenceEquals (one, other)) 86 | return true; 87 | 88 | return one is not null && one.Equals (other); 89 | } 90 | 91 | public static bool operator != (RequestEntry one, RequestEntry other) 92 | { 93 | return !(one == other); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicBroker/TitanicMemoryIO.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Runtime.CompilerServices; 3 | 4 | using JetBrains.Annotations; 5 | 6 | using TitanicCommons; 7 | 8 | [assembly: InternalsVisibleTo("TitanicPatternTests")] 9 | namespace TitanicProtocol; 10 | 11 | /// 12 | /// this class handles the I/O for TITANIC 13 | /// if allows to transparently write, read, find and delete entries 14 | /// in memory (CocurrentQueue) 15 | /// 16 | /// it is fast but all information is lost in case this computer or 17 | /// the process dies! 18 | /// 19 | public class TitanicMemoryIO : ITitanicIO 20 | { 21 | /// 22 | /// dictionary to hold all requests and supporting data in memory 23 | /// Guid is the key and the RequestEntry the data 24 | /// 25 | private readonly ConcurrentDictionary m_titanicQueue; 26 | 27 | /// 28 | /// the hook for the handling of logging messages published 29 | /// 30 | public event EventHandler LogInfoReady; 31 | 32 | /// 33 | /// will throw an InvalidOperationException 34 | /// 35 | public string TitanicDirectory { get { throw new InvalidOperationException("In-Memory IO does not provide a directory."); } } 36 | 37 | /// 38 | /// will throw an InvalidOperationException 39 | /// 40 | public string TitanicQueue { get { throw new InvalidOperationException("In-Memory IO does not provide a file name for the titanic queue."); } } 41 | 42 | internal int NumberOfRequests => m_titanicQueue.Count; 43 | 44 | /// 45 | /// ctor 46 | /// 47 | public TitanicMemoryIO() 48 | { 49 | m_titanicQueue = new ConcurrentDictionary(); 50 | } 51 | 52 | #region REQUEST/REPLY QUEUE I/O HANDLING 53 | 54 | /// 55 | /// get the request entry identified by the GUID from the infrastructure 56 | /// 57 | /// 58 | /// a request entry or default(RequestEntry) if no request entry with the id exists 59 | /// is a 'null' reference. 60 | public RequestEntry GetRequestEntry(Guid id) 61 | { 62 | return m_titanicQueue.TryGetValue(id, out RequestEntry entry) ? entry : default; 63 | } 64 | 65 | /// 66 | /// gets all existing request entries from the infrastructure satisfying the specified predicate 67 | /// 68 | /// the predicate to satisfy 69 | /// a sequence of request entries if any or an empty sequence 70 | public IEnumerable GetRequestEntries(Func predicate) 71 | { 72 | if (m_titanicQueue.IsEmpty) 73 | return default(IEnumerable); 74 | 75 | var result = m_titanicQueue.Values.Where(predicate).ToArray(); 76 | 77 | return result.Length > 0 ? result : new RequestEntry[0]; 78 | } 79 | 80 | /// 81 | /// gets all request entries from the infrastructure which are NOT closed 82 | /// only State = (Is_Pending OR Is_Processed) are considered 83 | /// 84 | /// sequence of request entries 85 | public IEnumerable GetNotClosedRequestEntries() 86 | { 87 | return GetRequestEntries(e => e.State != RequestEntry.Is_Closed); 88 | } 89 | 90 | /// 91 | /// saves a request entry to the infrastructure 92 | /// 93 | /// the request entry to save 94 | /// max. number of elements exceeded, . 95 | /// is a 'null' reference. 96 | public void SaveRequestEntry(RequestEntry entry) 97 | { 98 | if (entry is null) 99 | { 100 | throw new ArgumentNullException(nameof(entry), "The RequestEntry to save must not be null!"); 101 | } 102 | 103 | m_titanicQueue.AddOrUpdate(entry.RequestId, entry, (id, e) => entry); 104 | } 105 | 106 | /// 107 | /// save a new request under a GUID 108 | /// 109 | /// the id of the request 110 | /// max. number of elements exceeded, . 111 | public void SaveNewRequestEntry(Guid id) 112 | { 113 | // if it is already processed or closed (but not yet deleted) -> forget it 114 | if (ExistsMessage(TitanicOperation.Reply, id) || ExistsMessage(TitanicOperation.Close, id)) 115 | return; 116 | 117 | // the entry could already exist - so check and use it if so 118 | var entry = ExistsMessage(TitanicOperation.Request, id) 119 | ? GetRequestEntry(id) 120 | : new RequestEntry { RequestId = id, Position = -1, State = RequestEntry.Is_Pending }; 121 | 122 | SaveRequestEntry(entry); 123 | } 124 | 125 | /// 126 | /// save a new request under a GUID and the request data itself as well 127 | /// 128 | /// the id of the request 129 | /// the request to save 130 | /// max. number of elements exceeded, . 131 | public void SaveNewRequestEntry(Guid id, NetMQMessage request) 132 | { 133 | var entry = new RequestEntry { RequestId = id, Position = -1, State = RequestEntry.Is_Pending, Request = request }; 134 | 135 | SaveRequestEntry(entry); 136 | } 137 | 138 | /// 139 | /// save a processed request entry and mark it as such 140 | /// 141 | /// the entry to save 142 | /// max. number of elements exceeded, . 143 | public void SaveProcessedRequestEntry(RequestEntry entry) 144 | { 145 | entry.State = RequestEntry.Is_Processed; 146 | 147 | SaveRequestEntry(entry); 148 | } 149 | 150 | /// 151 | /// remove a request from the queue 152 | /// 153 | /// the GUID of the request to close 154 | public void CloseRequest(Guid id) 155 | { 156 | if (id == Guid.Empty) 157 | return; 158 | m_titanicQueue.TryRemove(id, out _); 159 | } 160 | 161 | #endregion 162 | 163 | #region Message HANDLING 164 | 165 | /// 166 | /// get a NetMQ message identified by a GUID from the infrastructure 167 | /// 168 | /// the guid of the message to read, must not be empty 169 | /// defines whether it is a REQUEST or REPLY message 170 | public NetMQMessage GetMessage(TitanicOperation op, Guid id) => m_titanicQueue.TryGetValue(id, out RequestEntry entry) ? entry.Request : new NetMQMessage(); 171 | 172 | /// 173 | /// read a NetMQ message identified by a GUID asynchronously 174 | /// 175 | /// defines whether it is a REQUEST or REPLY message 176 | /// the guid of the message to read 177 | public Task GetMessageAsync(TitanicOperation op, Guid id) 178 | { 179 | var tcs = new TaskCompletionSource(); 180 | 181 | try 182 | { 183 | tcs.SetResult(GetMessage(op, id)); 184 | } 185 | catch (Exception ex) 186 | { 187 | tcs.SetException(ex); 188 | } 189 | 190 | return tcs.Task; 191 | } 192 | 193 | /// 194 | /// save a NetMQ message under a GUID 195 | /// 196 | /// defines whether it is a REQUEST or REPLY message 197 | /// the guid of the message to save 198 | /// the message to save 199 | /// max. number of elements exceeded, . 200 | public bool SaveMessage(TitanicOperation op, Guid id, NetMQMessage message) 201 | { 202 | var entry = new RequestEntry 203 | { 204 | RequestId = id, 205 | Request = message, 206 | State = GetStateFromOperation(op) 207 | }; 208 | 209 | m_titanicQueue.AddOrUpdate(id, entry, (i, oldValue) => entry); 210 | 211 | return true; 212 | } 213 | 214 | /// 215 | /// save a NetMQ message with a GUID asynchronously 216 | /// 217 | /// defines whether it is a REQUEST or REPLY message 218 | /// the guid of the message to save 219 | /// the message to save 220 | public Task SaveMessageAsync(TitanicOperation op, Guid id, NetMQMessage message) 221 | { 222 | var tcs = new TaskCompletionSource(); 223 | 224 | try 225 | { 226 | tcs.SetResult(SaveMessage(op, id, message)); 227 | } 228 | catch (Exception ex) 229 | { 230 | tcs.SetException(ex); 231 | } 232 | 233 | return tcs.Task; 234 | } 235 | 236 | /// 237 | /// test if a request or reply with the GUID exists 238 | /// 239 | /// commands whether it is for a REQUEST or a REPLY 240 | /// the GUID für the Request/Reply 241 | /// 242 | /// true if it exists and false otherwise and if 'op' is not one 243 | /// of the aforementioned 244 | /// 245 | public bool ExistsMessage(TitanicOperation op, Guid id) 246 | { 247 | return op != TitanicOperation.Close 248 | && m_titanicQueue.Any(e => e.Value.RequestId == id && e.Value.State == GetStateFromOperation(op)); 249 | } 250 | 251 | #endregion 252 | 253 | private static byte GetStateFromOperation(TitanicOperation op) 254 | { 255 | return op == TitanicOperation.Request ? RequestEntry.Is_Pending : RequestEntry.Is_Processed; 256 | } 257 | 258 | public int GetNumberOfRequests() => NumberOfRequests; 259 | 260 | private void Log([NotNull] string info) => OnLogInfoReady(new TitanicLogEventArgs { Info = info }); 261 | 262 | /// A delegate callback throws an exception. 263 | protected virtual void OnLogInfoReady(TitanicLogEventArgs e) 264 | { 265 | LogInfoReady?.Invoke(this, e); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicBroker/TitanicOperation.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicProtocol; 2 | 3 | /// 4 | /// defines the possible operations carried out by Titanic 5 | /// 6 | public enum TitanicOperation { Request, Reply, Close } -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicBroker/TitanicProtocol.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/ITitanicBroker.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace TitanicCommons; 4 | 5 | /// 6 | /// Wraps the MDP Broker with a layer of TITANIC which does the following 7 | /// + writes messages to disc to ensure that none gets lost 8 | /// + good for sporadically connecting clients/workers 9 | /// + it uses the Majordomo Protocol 10 | /// 11 | /// it implements this broker asynchronous and handles all administrative work 12 | /// if Run is called it automatically will Connect to the endpoint given 13 | /// it however allows to alter that endpoint via Bind 14 | /// it registers any worker with its service 15 | /// it routes requests from clients to waiting workers offering the service the client has requested 16 | /// as soon as they become available 17 | /// 18 | /// every client communicates with TITANIC and sends requests or a request for a reply 19 | /// Titanic answers with either a GUID identifying the request or with a reply for a request 20 | /// according to the transfered GUID 21 | /// Titanic in turn handles the communication with the broker transparently for the client 22 | /// Titanic is organized in three different services (threads) 23 | /// titanic.request -> storing the request and returning an GUID 24 | /// titanic.reply -> fetching a reply if one exists for an GUID and returning it 25 | /// titanic.close -> confirming that a reply has been stored and processed 26 | /// 27 | /// every request is answered with a GUID by Titanic to the client and if a client asks for the result 28 | /// of his request he must send this GUID to identify the respective request/answer 29 | /// 30 | /// Services can/must be requested with a request, a.k.a. data to process 31 | /// 32 | /// CLIENT CLIENT CLIENT CLIENT 33 | /// "titanic, "titanic, "titanic, "titanic, 34 | /// give me Coffee" give me Water" give me Coffee" give me Tea" 35 | /// | | | | 36 | /// +----------------+------+--------+------------------+ 37 | /// | 38 | /// | 39 | /// BROKER --------- TITANIC ------ DISC 40 | /// | 41 | /// | 42 | /// +-----------+-----------+ 43 | /// | | | 44 | /// "Tea" "Coffee" "Water" 45 | /// WORKER WORKER WORKER 46 | /// 47 | /// 48 | public interface ITitanicBroker : IDisposable 49 | { 50 | /// 51 | /// event for any logging information 52 | /// 53 | /// 54 | /// any subscriber will receive detailed processing information 55 | /// and can decide to process it as needed 56 | /// 57 | event EventHandler LogInfoReady; 58 | 59 | /// 60 | /// starts the processing of TITANIC Broker 61 | /// take optionally three workers for each main function of TitanicBroker 62 | /// 63 | /// MDP worker for processing requests 64 | /// MDP worker for processing replies 65 | /// MDP worker for processing close requests 66 | /// MDP client to generate requests to MDP Broker for processing 67 | /// 68 | /// it creates three thread as MDP workers. 69 | /// those are registering with a MDPBroker at a given IP address 70 | /// for processing REQUEST, REPLY and Close requests send via MDP clients. 71 | /// A mdp client is used to forward the requests made to titanic to the correct 72 | /// service providing mdp worker and collect that's reply 73 | /// the titanic commands are defined in TitanicCommons as TitanicOperation 74 | /// 75 | void Run (IMDPWorker requestWorker = null, 76 | IMDPWorker replyWorker = null, 77 | IMDPWorker closeWorker = null, 78 | IMDPClient serviceCallClient = null); 79 | } 80 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/ITitanicClient.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicCommons; 2 | 3 | public interface ITitanicClient : IDisposable 4 | { 5 | /// 6 | /// the API will publish log information over this event 7 | /// 8 | event EventHandler LogInfoReady; 9 | 10 | /// 11 | /// send a message and get a reply 12 | /// 13 | /// the service requested 14 | /// the actual request data as an array of bytes 15 | /// number of retries until the request is deemed to have failed, default == 3 16 | /// milliseconds to wait in between retries, default == 2500 17 | /// the reply as tuple containing a byte array, the result, and the status 18 | /// 19 | /// will return default (byte[]) if the result is invalid 20 | /// 21 | Tuple GetResult (string serviceName, byte[] request, int retries = 3, int waitInBetween = 2500); 22 | 23 | /// 24 | /// send a message and get a reply 25 | /// 26 | /// the service requested 27 | /// the actual request data as a string 28 | /// number of retries until the request is deemed to have failed, default == 3 29 | /// milliseconds to wait in between retries, default == 2500 30 | /// the reply as an array of bytes 31 | /// the reply as tuple containing a byte array, the result, and the status 32 | /// 33 | /// will return default (byte[]) if the result is invalid 34 | /// 35 | Tuple GetResult (string serviceName, string request, int retries = 3, int waitInBetween = 2500); 36 | 37 | /// 38 | /// send a message and get a reply 39 | /// 40 | /// the service requested 41 | /// the actual request data as a string 42 | /// the encoding to use for en- and decoding the string 43 | /// number of retries until the request is deemed to have failed, default == 3 44 | /// milliseconds to wait in between retries, default == 2500 45 | /// the reply as tuple containing a string, the result, and the status 46 | /// 47 | /// will return default (string) if the result is invalid 48 | /// 49 | Tuple GetResult (string serviceName, 50 | string request, 51 | Encoding enc, 52 | int retries = 3, 53 | int waitInBetween = 2500); 54 | 55 | /// 56 | /// send a message and get a reply 57 | /// 58 | /// the service requested 59 | /// the actual request data as an array of bytes 60 | /// number of retries until the request is deemed to have failed, default == 3 61 | /// milliseconds to wait in between retries, default == 2500 62 | /// the reply as tuple containing the specified return type, the result, and the status 63 | /// 64 | /// will return default (TOut) if the result is invalid 65 | /// 66 | Tuple GetResult (string serviceName, 67 | TIn request, 68 | int retries = 3, 69 | int waitInBetween = 2500) 70 | where TIn : ITitanicConvert 71 | where TOut : ITitanicConvert, new (); 72 | 73 | /// 74 | /// sending a request 75 | /// 76 | /// the service to send the request to 77 | /// the request data as an array of bytes 78 | /// the GUID of the request as a string 79 | Guid Request (string serviceName, byte[] request); 80 | 81 | /// 82 | /// sending a request 83 | /// 84 | /// the service to send the request to 85 | /// the request data as string 86 | /// the GUID of the request as a string 87 | Guid Request (string serviceName, string request); 88 | 89 | /// 90 | /// sending a request 91 | /// 92 | /// the service to send the request to 93 | /// the request data 94 | /// the GUID of the request as a string 95 | Guid Request (string serviceName, T request) where T : ITitanicConvert; 96 | 97 | /// 98 | /// gets a reply for a previous request 99 | /// 100 | /// the previous request's id 101 | /// max milliseconds to wait for the reply 102 | /// a tuple containing the result and a status of the reply 103 | Tuple Reply (Guid requestId, TimeSpan waitFor); 104 | 105 | /// 106 | /// gets a reply for a previous request 107 | /// 108 | /// the previous request's id 109 | /// the number of retires to perform for retrieving the reply 110 | /// milliseconds to wait in between the retrieval tries 111 | /// a tuple containing the result and a status of the reply 112 | Tuple Reply (Guid requestId, int retries, TimeSpan waitBetweenRetries); 113 | 114 | /// 115 | /// gets a reply for a previous request 116 | /// 117 | /// the previous request's id 118 | /// max milliseconds to wait for the reply 119 | /// a tuple containing the result and a status of the reply 120 | Tuple Reply (Guid requestId, TimeSpan waitFor) where T : ITitanicConvert, new (); 121 | 122 | /// 123 | /// gets a reply for a previous request 124 | /// 125 | /// the previous request's id 126 | /// the number of retires to perform for retrieving the reply 127 | /// milliseconds to wait in between the retrieval tries 128 | /// a tuple containing the result and a status of the reply 129 | Tuple Reply (Guid requestId, int retries, TimeSpan waitBetweenRetries) 130 | where T : ITitanicConvert, new (); 131 | 132 | /// 133 | /// closes a previous made request and the assigned reply 134 | /// 135 | /// the request's id 136 | void CloseRequest (Guid requestId); 137 | } 138 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/ITitanicConvert.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicCommons; 2 | 3 | /// 4 | /// must be implemented by any object used in generic api of client 5 | /// 6 | public interface ITitanicConvert 7 | { 8 | /// 9 | /// serializes the object on which it is invoked to an array of bytes 10 | /// 11 | /// type to work with 12 | /// an array of bytes representing the instance 13 | byte[] ConvertToBytes (); 14 | 15 | /// 16 | /// deserializes a previous serialized object of type 'T' 17 | /// 18 | /// the type which has been serialized 19 | /// byte array containing the serialized entity 20 | /// an instance generated 21 | T GenerateFrom (byte[] b); 22 | } 23 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/TitanicCommons.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/TitanicLogEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicCommons; 2 | 3 | public class TitanicLogEventArgs : EventArgs 4 | { 5 | public string Info { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicCommons/TitanicReturnCode.cs: -------------------------------------------------------------------------------- 1 | namespace TitanicCommons; 2 | 3 | /// 4 | /// defines the results of any titanic operation 5 | /// 6 | public enum TitanicReturnCode { Ok = 200, Pending = 300, Unknown = 400, Failure = 501 } 7 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/FakeCloseMDPWorker.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace TitanicProtocolTests.TestEntities; 4 | 5 | public class FakeCloseMDPWorker : IMDPWorker 6 | { 7 | public readonly AutoResetEvent waitHandle = new(false); 8 | 9 | public NetMQMessage Request { get; set; } 10 | public NetMQMessage Reply { get; set; } 11 | 12 | public void Dispose () { return; } 13 | 14 | public TimeSpan HeartbeatDelay { get; set; } 15 | 16 | public TimeSpan ReconnectDelay { get; set; } 17 | 18 | #pragma warning disable 67 19 | public event EventHandler LogInfoReady; 20 | #pragma warning restore 67 21 | 22 | public NetMQMessage Receive (NetMQMessage reply) 23 | { 24 | if (reply is null) 25 | { 26 | // upon the first call this is 'null' 27 | waitHandle.WaitOne (); 28 | // send back a Guid indicating the intended request to close 29 | return Request; 30 | } 31 | // reply should be [Ok] 32 | Reply = reply; 33 | 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/FakeDispatchMDPClient.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace TitanicProtocolTests.TestEntities; 4 | 5 | public class FakeDispatchMDPClient : IMDPClient 6 | { 7 | private int m_count; 8 | 9 | public readonly AutoResetEvent waitHandle = new(false); 10 | 11 | public void Dispose () { return; } 12 | 13 | public TimeSpan Timeout { get; set; } 14 | 15 | public int Retries { get; set; } 16 | 17 | public string Address => "NO ADDRESS"; 18 | 19 | public byte[] Identity => Encoding.UTF8.GetBytes ("NO IDENTITY"); 20 | 21 | public NetMQMessage Send (string serviceName, NetMQMessage request) 22 | { 23 | // first call is [mmi.service][servicename] 24 | // return is [Ok] 25 | // second call is [service][request] 26 | // return is [reply] 27 | m_count++; 28 | 29 | if (m_count == 1) 30 | { 31 | // proceed only if commanded to -> is automatically called by TitanicBroker.Run 32 | // and askes for a reply from a service, so wait until we want an answer 33 | waitHandle.WaitOne (); 34 | 35 | var reply = new NetMQMessage (); 36 | reply.Push (MmiCode.Ok.ToString ()); 37 | 38 | return reply; 39 | } 40 | 41 | // wait to proceed until signaled 42 | waitHandle.WaitOne (); 43 | 44 | return request; // as echo service :-) 45 | } 46 | 47 | #pragma warning disable 67 48 | public event EventHandler LogInfoReady; 49 | #pragma warning restore 67 50 | } 51 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/FakeReplyMDPWorker.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace TitanicProtocolTests.TestEntities; 4 | 5 | public class FakeReplyMDPWorker : IMDPWorker 6 | { 7 | public readonly AutoResetEvent waitHandle = new(false); 8 | 9 | public NetMQMessage Request { get; set; } 10 | public NetMQMessage Reply { get; set; } 11 | 12 | public void Dispose () { return; } 13 | 14 | public TimeSpan HeartbeatDelay { get; set; } 15 | 16 | public TimeSpan ReconnectDelay { get; set; } 17 | 18 | #pragma warning disable 67 19 | public event EventHandler LogInfoReady; 20 | #pragma warning restore 67 21 | 22 | public NetMQMessage Receive (NetMQMessage reply) 23 | { 24 | // upon the first call this is 'null' 25 | // on the second call it could be 26 | // a) [Ok][service][reply] 27 | // b) [Pending] 28 | // c) [Unknown] 29 | Reply = reply; 30 | 31 | if (reply is null) 32 | { 33 | // this is the initiation call 34 | // so return the request to make when allowed 35 | waitHandle.WaitOne (); 36 | 37 | return Request; // Guid of the request who's reply is asked for 38 | } 39 | 40 | waitHandle.WaitOne (); 41 | 42 | return null; // will result in a dieing TitanicReply Thread 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/FakeRequestMDPWorker.cs: -------------------------------------------------------------------------------- 1 | using MDPCommons; 2 | 3 | namespace TitanicProtocolTests.TestEntities; 4 | 5 | public class FakeRequestMDPWorker : IMDPWorker 6 | { 7 | public readonly AutoResetEvent waitHandle = new(false); 8 | 9 | public NetMQMessage Request { get; set; } 10 | public NetMQMessage Reply { get; set; } 11 | 12 | public void Dispose () { return; } 13 | 14 | public TimeSpan HeartbeatDelay { get; set; } 15 | 16 | public TimeSpan ReconnectDelay { get; set; } 17 | 18 | #pragma warning disable 67 19 | public event EventHandler LogInfoReady; 20 | #pragma warning restore 67 21 | 22 | public NetMQMessage Receive (NetMQMessage reply) 23 | { 24 | // upon the first call this is 'null' 25 | if (reply is null) 26 | return Request; // [service][request] 27 | 28 | // on the second call it should be [Ok][Guid] 29 | Reply = reply; 30 | 31 | waitHandle.WaitOne (); 32 | 33 | return null; // will result in a dieing TitanicRequest Thread 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/MDPTestClientForTitanicClient.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using MDPCommons; 4 | 5 | using TitanicCommons; 6 | 7 | using TitanicProtocol; 8 | 9 | namespace TitanicProtocolTests.TestEntities; 10 | 11 | /// 12 | /// injected in lieu of the really MDPClient to avoid the installing of the infrastructure 13 | /// just for testing 14 | /// 15 | internal class MDPTestClientForTitanicClient : IMDPClient 16 | { 17 | public event EventHandler LogInfoReady; 18 | 19 | public TimeSpan Timeout { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } 20 | 21 | public int Retries { get { throw new NotImplementedException (); } set { throw new NotImplementedException (); } } 22 | 23 | public string Address => "Fake Address"; 24 | 25 | public byte[] Identity => Encoding.UTF8.GetBytes ("Fake Identity"); 26 | 27 | /// 28 | /// return messages can be: 29 | /// 30 | /// REQUEST: 31 | /// returns -> [return code][Guid] 32 | /// REPLY 33 | /// returns -> [return code][reply] 34 | /// CLOSE 35 | /// in goes -> [titanic operation][request id] 36 | /// 37 | public NetMQMessage ReplyMessage { get; set; } 38 | 39 | /// 40 | /// use for defining a specific NetMQFrame to be pushed to reply message 41 | /// 42 | public NetMQFrame ReplyDataFrame { get; set; } 43 | 44 | public Guid RequestId { get; set; } 45 | 46 | public MDPTestClientForTitanicClient () 47 | { 48 | ReplyMessage = null; 49 | RequestId = Guid.Empty; 50 | } 51 | 52 | // messages can be: 53 | // 54 | // REQUEST: 55 | // in goes -> [service name][request] 56 | // returns -> [return code][Guid] 57 | // REPLY 58 | // in goes -> [request id] 59 | // returns -> [return code][reply] 60 | // CLOSE 61 | // in goes -> [request id] 62 | // 63 | public NetMQMessage Send (string serviceName, NetMQMessage message) 64 | { 65 | Log ($"requested service <{serviceName}> with request <{message}>"); 66 | 67 | if (ReplyMessage != null) 68 | return new NetMQMessage (ReplyMessage); // to keep it intact for multiple tries return a copy(!) 69 | 70 | var operation = (TitanicOperation) Enum.Parse (typeof (TitanicOperation), serviceName); 71 | var reply = new NetMQMessage (); 72 | 73 | switch (operation) 74 | { 75 | case TitanicOperation.Request: 76 | var id = RequestId == Guid.Empty ? Guid.NewGuid () : RequestId; 77 | reply.Push (id.ToString ()); 78 | reply.Push (TitanicReturnCode.Ok.ToString ()); 79 | break; 80 | case TitanicOperation.Reply: 81 | if (ReplyMessage == null) 82 | { 83 | reply.Push (ReplyDataFrame); 84 | reply.Push (TitanicReturnCode.Ok.ToString ()); 85 | } 86 | else 87 | reply = ReplyMessage; 88 | break; 89 | case TitanicOperation.Close: 90 | reply.Push (TitanicReturnCode.Ok.ToString ()); 91 | break; 92 | default: 93 | reply.Push (TitanicReturnCode.Failure.ToString ()); 94 | break; 95 | } 96 | 97 | Log ($"reply <{reply}>"); 98 | 99 | return reply; 100 | } 101 | 102 | public void Dispose () { return; } 103 | 104 | private void Log ([NotNull] string info) 105 | { 106 | OnLogInfoReady (new MDPLogEventArgs { Info = "[FAKE MDP CLIENT] " + info }); 107 | } 108 | 109 | protected virtual void OnLogInfoReady (MDPLogEventArgs e) 110 | { 111 | LogInfoReady?.Invoke (this, e); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TestEntities/TestEntity.cs: -------------------------------------------------------------------------------- 1 | using TitanicCommons; 2 | 3 | namespace TitanicProtocolTests.TestEntities; 4 | 5 | internal class TestEntity : ITitanicConvert 6 | { 7 | public Guid Id { get; set; } 8 | public string Name { get; set; } 9 | 10 | public TestEntity () 11 | { 12 | Id = Guid.NewGuid (); 13 | Name = Id.ToString (); 14 | } 15 | 16 | public byte[] ConvertToBytes () 17 | { 18 | var enc = Encoding.UTF8; 19 | 20 | var idAsBytes = Id.ToByteArray (); 21 | var nameAsBytes = enc.GetBytes (Name); 22 | 23 | var length = idAsBytes.Length + nameAsBytes.Length; 24 | 25 | var target = new byte[length]; 26 | 27 | Array.Copy (idAsBytes, target, idAsBytes.Length); 28 | Array.Copy (nameAsBytes, 0, target, idAsBytes.Length, nameAsBytes.Length); 29 | 30 | return target; 31 | } 32 | 33 | public TestEntity GenerateFrom (byte[] b) 34 | { 35 | const int length_of_guid = 16; 36 | 37 | var idAsBytes = new byte[length_of_guid]; 38 | var nameAsBytes = new byte[b.Length - length_of_guid]; 39 | var lengthOfName = b.Length - length_of_guid; 40 | 41 | Array.Copy (b, idAsBytes, length_of_guid); 42 | Array.Copy (b, length_of_guid, nameAsBytes, 0, lengthOfName); 43 | 44 | Id = new Guid (idAsBytes); 45 | Name = Encoding.UTF8.GetString (nameAsBytes); 46 | 47 | return this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TitanicClientTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using FluentAssertions; 3 | 4 | using MDPCommons; 5 | using TitanicCommons; 6 | using TitanicProtocol; 7 | using TitanicProtocolTests.TestEntities; 8 | 9 | namespace TitanicProtocolTests; 10 | 11 | [TestFixture] 12 | public class TitanicClientTests 13 | { 14 | [Test] 15 | public void ctor_withFakeClient_ShouldReturnvalidObject () 16 | { 17 | IMDPClient fakeMDPClient = new MDPTestClientForTitanicClient (); 18 | 19 | var sut = new TitanicClient (fakeMDPClient); 20 | 21 | sut.Should ().NotBeNull (); 22 | } 23 | 24 | [Test] 25 | public void Request_ValidRequestStringString_ShouldReturnExpectedGuid () 26 | { 27 | var expectedId = Guid.NewGuid (); 28 | var replyMessage = new NetMQMessage (); 29 | 30 | replyMessage.Push (expectedId.ToString ()); 31 | replyMessage.Push (TitanicReturnCode.Ok.ToString ()); 32 | 33 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage, RequestId = expectedId }; 34 | 35 | 36 | var sut = new TitanicClient (fakeMDPClient); 37 | var id = sut.Request ("echo", "Hello World!"); 38 | 39 | id.Should ().Be (expectedId.ToString ()); 40 | } 41 | 42 | [Test] 43 | public void Request_ValidRequestStringBytes_ShouldReturnExpectedGuid () 44 | { 45 | var expectedId = Guid.NewGuid (); 46 | var replyMessage = new NetMQMessage (); 47 | 48 | replyMessage.Push (expectedId.ToString ()); 49 | replyMessage.Push (TitanicReturnCode.Ok.ToString ()); 50 | 51 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage, RequestId = expectedId }; 52 | 53 | 54 | var sut = new TitanicClient (fakeMDPClient); 55 | var id = sut.Request ("echo", Encoding.UTF8.GetBytes ("Hello World!")); 56 | 57 | id.Should ().Be (expectedId.ToString ()); 58 | } 59 | 60 | [Test] 61 | public void Request_ValidRequestStringGeneric_ShouldReturnExpectedGuid () 62 | { 63 | var expectedId = Guid.NewGuid (); 64 | var replyMessage = new NetMQMessage (); 65 | 66 | replyMessage.Push (expectedId.ToString ()); 67 | replyMessage.Push (TitanicReturnCode.Ok.ToString ()); 68 | 69 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage, RequestId = expectedId }; 70 | 71 | var generic = new TestEntity (); 72 | var sut = new TitanicClient (fakeMDPClient); 73 | var id = sut.Request ("echo", generic.ConvertToBytes ()); 74 | 75 | id.Should ().Be (expectedId.ToString ()); 76 | } 77 | 78 | [Test] 79 | public void Request_NullOrEmptyServiceName_ShouldThrowArgumentNullException () 80 | { 81 | var fakeMDPClient = new MDPTestClientForTitanicClient (); 82 | 83 | var sut = new TitanicClient (fakeMDPClient); 84 | 85 | Assert.Throws (() => sut.Request (string.Empty, "Hello World")); 86 | } 87 | 88 | [Test] 89 | public void Reply_ExistingRequestStringTimeSpan_ShouldReturnExpectedReply () 90 | { 91 | const string expected_phrase = "Thank God Its Friday"; 92 | var replyFrame = new NetMQFrame (expected_phrase); 93 | 94 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 95 | var sut = new TitanicClient (fakeMDPClient); 96 | 97 | var reply = sut.Reply (Guid.NewGuid (), TimeSpan.FromMilliseconds (0)); 98 | 99 | reply.Item2.Should ().Be (TitanicReturnCode.Ok); 100 | Encoding.UTF8.GetString (reply.Item1).Should ().Be (expected_phrase); 101 | } 102 | 103 | [Test] 104 | public void Reply_ExistingRequestStringIntegerInteger_ShouldReturnExpectedReply () 105 | { 106 | const string expected_phrase = "Thank God Its Friday"; 107 | var replyFrame = new NetMQFrame (expected_phrase); 108 | 109 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 110 | var sut = new TitanicClient (fakeMDPClient); 111 | 112 | var reply = sut.Reply (Guid.NewGuid (), 0, TimeSpan.FromMilliseconds (0)); 113 | 114 | reply.Item2.Should ().Be (TitanicReturnCode.Ok); 115 | Encoding.UTF8.GetString (reply.Item1).Should ().Be (expected_phrase); 116 | } 117 | 118 | [Test] 119 | public void Reply_ExistingRequestStringTimeSpanGeneric_ShouldReturnExpectedReply () 120 | { 121 | var expected = new TestEntity (); 122 | var replyFrame = new NetMQFrame (expected.ConvertToBytes ()); 123 | 124 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 125 | var sut = new TitanicClient (fakeMDPClient); 126 | 127 | var reply = sut.Reply (Guid.NewGuid (), TimeSpan.FromMilliseconds (0)); 128 | 129 | reply.Item2.Should ().Be (TitanicReturnCode.Ok); 130 | var result = new TestEntity (); 131 | result.GenerateFrom (reply.Item1); 132 | 133 | result.Id.Should ().Be (expected.Id); 134 | result.Name.Should ().Be (expected.Name); 135 | } 136 | 137 | [Test] 138 | public void Reply_ExistingRequestStringIntegerTimeSpanGeneric_ShouldReturnExpectedReply () 139 | { 140 | var expected = new TestEntity (); 141 | var replyFrame = new NetMQFrame (expected.ConvertToBytes ()); 142 | 143 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 144 | var sut = new TitanicClient (fakeMDPClient); 145 | 146 | var reply = sut.Reply (Guid.NewGuid (), 0, TimeSpan.FromMilliseconds (0)); 147 | 148 | reply.Item2.Should ().Be (TitanicReturnCode.Ok); 149 | var result = new TestEntity (); 150 | result.GenerateFrom (reply.Item1); 151 | 152 | result.Id.Should ().Be (expected.Id); 153 | result.Name.Should ().Be (expected.Name); 154 | } 155 | 156 | [Test] 157 | public void Reply_InvalidReply_ShouldReturnPending () 158 | { 159 | var replyMessage = new NetMQMessage (); 160 | replyMessage.Push (TitanicReturnCode.Pending.ToString ()); 161 | 162 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage }; 163 | var sut = new TitanicClient (fakeMDPClient); 164 | 165 | var reply = sut.Reply (Guid.NewGuid (), TimeSpan.FromMilliseconds (0)); 166 | 167 | reply.Item2.Should ().Be (TitanicReturnCode.Pending); 168 | reply.Item1.Should ().BeNull (); 169 | } 170 | 171 | [Test] 172 | public void Reply_InvalidReply_ShouldReturnUnknown () 173 | { 174 | var replyMessage = new NetMQMessage (); 175 | replyMessage.Push (TitanicReturnCode.Unknown.ToString ()); 176 | 177 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage }; 178 | var sut = new TitanicClient (fakeMDPClient); 179 | 180 | var reply = sut.Reply (Guid.NewGuid (), TimeSpan.FromMilliseconds (0)); 181 | 182 | reply.Item2.Should ().Be (TitanicReturnCode.Unknown); 183 | reply.Item1.Should ().BeNull (); 184 | } 185 | 186 | [Test] 187 | public void Reply_InvalidReply_ShouldReturnFailure () 188 | { 189 | var replyMessage = new NetMQMessage (); 190 | replyMessage.Push (TitanicReturnCode.Failure.ToString ()); 191 | 192 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyMessage = replyMessage }; 193 | var sut = new TitanicClient (fakeMDPClient); 194 | 195 | var reply = sut.Reply (Guid.NewGuid (), TimeSpan.FromMilliseconds (0)); 196 | 197 | reply.Item2.Should ().Be (TitanicReturnCode.Failure); 198 | reply.Item1.Should ().BeNull (); 199 | } 200 | 201 | [Test] 202 | public void GetResult_StringBytes_ShouldReturnExpectedReply () 203 | { 204 | const string expected_phrase = "Thank God Its Friday"; 205 | var replyFrame = new NetMQFrame (expected_phrase); 206 | 207 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 208 | var sut = new TitanicClient (fakeMDPClient); 209 | 210 | var result = sut.GetResult ("echo", Encoding.UTF8.GetBytes (expected_phrase)); 211 | 212 | result.Item2.Should ().Be (TitanicReturnCode.Ok); 213 | result.Item1.Should ().BeSameAs (replyFrame.Buffer); 214 | } 215 | 216 | [Test] 217 | public void GetResult_StringString_ShouldReturnExpectedResult () 218 | { 219 | const string expected_phrase = "Thank God Its Friday"; 220 | var replyFrame = new NetMQFrame (expected_phrase); 221 | 222 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 223 | var sut = new TitanicClient (fakeMDPClient); 224 | 225 | var result = sut.GetResult ("echo", expected_phrase); 226 | 227 | result.Item2.Should ().Be (TitanicReturnCode.Ok); 228 | result.Item1.Should ().BeSameAs (replyFrame.Buffer); 229 | } 230 | 231 | [Test] 232 | public void GetResult_StringStringEncoding_ShouldReturnExpectedResult () 233 | { 234 | var enc = Encoding.Unicode; 235 | const string expected_phrase = "Thank God Its Friday"; 236 | var replyFrame = new NetMQFrame (enc.GetBytes (expected_phrase)); 237 | 238 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 239 | var sut = new TitanicClient (fakeMDPClient); 240 | 241 | var result = sut.GetResult ("echo", expected_phrase, enc); 242 | 243 | result.Item2.Should ().Be (TitanicReturnCode.Ok); 244 | result.Item1.Should ().Be (enc.GetString (replyFrame.Buffer)); 245 | } 246 | 247 | [Test] 248 | public void GetResult_StringIntegerGeneric_ReturnExpectedResult () 249 | { 250 | var expected = new TestEntity (); 251 | var replyFrame = new NetMQFrame (expected.ConvertToBytes ()); 252 | 253 | var fakeMDPClient = new MDPTestClientForTitanicClient { ReplyDataFrame = replyFrame }; 254 | var sut = new TitanicClient (fakeMDPClient); 255 | 256 | var result = sut.GetResult ("echo", expected); 257 | 258 | result.Item2.Should ().Be (TitanicReturnCode.Ok); 259 | result.Item1.Id.Should ().Be (expected.Id); 260 | result.Item1.Name.Should ().Be (expected.Name); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicPatternTests/TitanicPatternTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | TitanicProtocolTests 5 | TitanicProtocolTests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/StartTitanicClient1000ReqestsExample.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start ..\..\Majordomo\MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 3 | set /p verbose="Shall the test be run verbose? (yY/nN)" 4 | if %verbose%=="y" goto VERBOSE 5 | if %verbose%=="Y" goto VERBOSE 6 | goto SILENT 7 | 8 | VERBOSE: 9 | start TitanicBrokerProcess\bin\Debug\TitanicBrokerProcess.exe -v 10 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe -v 11 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe -v 12 | goto CLIENT 13 | 14 | SILENT: 15 | start TitanicBrokerProcess\bin\Debug\TitanicBrokerProcess.exe 16 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe 17 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe 18 | 19 | CLIENT: 20 | set /p runs="How many messages shall be send? " 21 | start TitanicClientExample\bin\Debug\TitanicClientExample.exe %runs% 22 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/StartTitanicClientExample.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start ..\..\Majordomo\MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 3 | start TitanicBrokerProcess\bin\Debug\TitanicBrokerProcess.exe -v 4 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe -v 5 | set/p runs="How many messages shall be send? " 6 | start TitanicClientExample\bin\Debug\TitanicClientExample.exe -n%runs% 7 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/StartTitanicExample - Test.bat: -------------------------------------------------------------------------------- 1 | start ..\..\Majordomo\MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 2 | start TitanicBrokerProcess\bin\Debug\TitanicBrokerProcess.exe 3 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe -v 4 | start TitanicClientExample\bin\Debug\TitanicClientExample.exe -v -n5 5 | 6 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/StartTitanicExample - Verbose.bat: -------------------------------------------------------------------------------- 1 | start ..\..\Majordomo\MDPBrokerProcess\bin\Debug\MDPBrokerProcess.exe 2 | start TitanicBrokerProcess\bin\Debug\TitanicBrokerProcess.exe -v 3 | start TitanicWorkerExample\bin\Debug\TitanicWorkerExample.exe -v 4 | set /p runs="How many messages shall be send (verbose mode)? " 5 | start TitanicClientExample\bin\Debug\TitanicClientExample.exe -v -n%runs% 6 | 7 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicBrokerProcess/TitanicBrokerMain.cs: -------------------------------------------------------------------------------- 1 | using TitanicProtocol; 2 | 3 | namespace TitanicBrokerProcess; 4 | 5 | internal class TitanicBrokerMain 6 | { 7 | private const ConsoleColor _broker = ConsoleColor.Cyan; 8 | private const ConsoleColor _request = ConsoleColor.Yellow; 9 | private const ConsoleColor _reply = ConsoleColor.White; 10 | private const ConsoleColor _close = ConsoleColor.Red; 11 | private const ConsoleColor _dispatch = ConsoleColor.Green; 12 | private const ConsoleColor _service_call = ConsoleColor.DarkGreen; 13 | 14 | private static void Main(string[] args) 15 | { 16 | var verbose = args.Length > 0 && args[0] == "-v"; 17 | 18 | var cts = new CancellationTokenSource(); 19 | 20 | // trapping Ctrl+C as exit signal! 21 | Console.CancelKeyPress += (s, e) => 22 | { 23 | e.Cancel = true; 24 | cts.Cancel(); 25 | }; 26 | 27 | Console.WriteLine("Starting Titanic Broker in {0} - mode.\n\n", verbose ? "verbose" : "silent"); 28 | 29 | using var titanic = new TitanicBroker(new TitanicMemoryIO()); 30 | if (verbose) 31 | titanic.LogInfoReady += (s, e) => PrintMessage(e.Info); 32 | 33 | try 34 | { 35 | Task.Factory.StartNew(() => titanic.Run(), cts.Token).Wait(cts.Token); 36 | } 37 | catch (AggregateException e) 38 | { 39 | Console.WriteLine(e.Flatten()); 40 | } 41 | finally 42 | { 43 | cts.Dispose(); 44 | } 45 | } 46 | 47 | // print coloured according to message producer 48 | private static void PrintMessage(string msg) 49 | { 50 | var original = Console.ForegroundColor; 51 | 52 | if (msg.Contains("BROKER")) 53 | { 54 | Console.ForegroundColor = _broker; 55 | } 56 | else if (msg.Contains("REQUEST")) 57 | { 58 | Console.ForegroundColor = _request; 59 | } 60 | else if (msg.Contains("REPLY")) 61 | { 62 | Console.ForegroundColor = _reply; 63 | } 64 | else if (msg.Contains("CLOSE")) 65 | { 66 | Console.ForegroundColor = _close; 67 | } 68 | else if (msg.Contains("DISPATCH")) 69 | { 70 | Console.ForegroundColor = _dispatch; 71 | } 72 | else 73 | { 74 | Console.ForegroundColor = _service_call; 75 | } 76 | 77 | Console.WriteLine(msg); 78 | 79 | Console.ForegroundColor = original; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicBrokerProcess/TitanicBrokerProcess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicClientExample/TitanicClientExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicClientExample/TitanicExampleClient.cs: -------------------------------------------------------------------------------- 1 | using TitanicCommons; 2 | 3 | using TitanicProtocol; 4 | 5 | namespace TitanicClientExample; 6 | 7 | /// 8 | /// usage: TitanicClientExample [-v] [-cN] 9 | /// 10 | /// implements a Titanic Client API usage 11 | /// 12 | /// -v => verbose 13 | /// -cN => repeat the request N times 14 | /// 15 | /// 16 | /// this is a 'naked' approach and should not be used in real apps 17 | /// the use or the client api should be extended for ease of use and 18 | /// behavior in order to allow responsive apps 19 | /// 20 | internal class TitanicExampleClient 21 | { 22 | private static bool s_verbose; 23 | private static int s_runs = 1; 24 | 25 | private static void Main (string[] arguments) 26 | { 27 | const string address = "tcp://localhost:5555"; 28 | //const string service_name = "echo"; 29 | 30 | SetParameter (arguments); 31 | 32 | Console.WriteLine ("[TitanicClient] Staring Titanic Client\n"); 33 | Console.WriteLine ("[TitanicClient] {0} / #{1} Messages\n\n", s_verbose ? "verbose" : "silent", s_runs); 34 | 35 | // wait to allow MDP/Titanic Broker to complete start up 36 | Thread.Sleep (500); 37 | 38 | using (ITitanicClient client = new TitanicClient (address)) 39 | { 40 | if (s_verbose) 41 | client.LogInfoReady += (s, e) => Console.WriteLine (e.Info); 42 | 43 | for (var i = 0; i < s_runs; i++) 44 | { 45 | Console.WriteLine ("[TitanicClient] Loop #{0} entered ...", i); 46 | 47 | var data = "ERROR! NO REPLY"; 48 | 49 | // does 5 retries to get the reply 50 | var reply = client.GetResult ("echo", "Hallo World", 5); 51 | 52 | if (reply == null) 53 | { 54 | Console.WriteLine ("\t[TitanicClient] ran into a problem, received 'null' in loop #{0}", i); 55 | continue; 56 | } 57 | 58 | data = reply.Item1 != null ? Encoding.UTF8.GetString (reply.Item1) : data; 59 | 60 | if (data != "Hallo World") 61 | Console.WriteLine ("\t[TitanicClient] Hallo World != {0} on loop #{1} with status {2}", data, i, reply.Item2); 62 | 63 | if (reply.Item1 is not null && reply.Item2 == TitanicReturnCode.Ok) 64 | Console.WriteLine ("\t[TitanicClient] Status = {0} - Reply = {1}", reply.Item2, data); 65 | } 66 | } 67 | 68 | Console.WriteLine ("\n[TitanicClient] To exit press any key!"); 69 | Console.ReadKey (); 70 | } 71 | 72 | private static void SetParameter (string[] arguments) 73 | { 74 | switch (arguments.Length) 75 | { 76 | case 0: 77 | return; 78 | case 1: 79 | if (arguments[0] == "-v") 80 | s_verbose = true; 81 | 82 | if (arguments[0].StartsWith ("-c")) 83 | GetCount (arguments[0]); 84 | break; 85 | } 86 | 87 | if (arguments.Length == 2) 88 | { 89 | if (arguments[0] == "-v" || arguments[1] == "-v") 90 | s_verbose = true; 91 | 92 | if (arguments[0].StartsWith ("-c")) 93 | GetCount (arguments[0]); 94 | else 95 | if (arguments[1].StartsWith ("-c")) 96 | GetCount (arguments[1]); 97 | } 98 | } 99 | 100 | private static void GetCount (string s) 101 | { 102 | var number = s.Replace (" ", "").Remove (0, 2); 103 | var n = int.Parse (number); 104 | s_runs = n <= 0 ? 1 : n; // min. 1 run 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicWorkerExample/TitanicWorker.cs: -------------------------------------------------------------------------------- 1 | using MajordomoProtocol; 2 | 3 | namespace TitanicWorkerExample; 4 | 5 | internal class TitanicWorker 6 | { 7 | /// 8 | /// usage: TitanicWorkerExample [-v] 9 | /// 10 | /// implements a MDPWorker API usage 11 | /// 12 | private static void Main (string[] args) 13 | { 14 | const string service_name = "echo"; 15 | var verbose = args.Length == 1 && args[0] == "-v"; 16 | var exit = false; 17 | 18 | // trapping Ctrl+C as exit signal! 19 | Console.CancelKeyPress += (s, e) => 20 | { 21 | e.Cancel = true; 22 | exit = true; 23 | }; 24 | var id = new[] { (byte) 'W', (byte) '1' }; 25 | 26 | Console.WriteLine ("Starting the Titanic Worker - offering service {0}", service_name); 27 | Console.WriteLine ("to exit - CTR-C\n"); 28 | 29 | try 30 | { 31 | // create worker offering the service 'echo' 32 | using var session = new MDPWorker("tcp://localhost:5555", service_name, id); 33 | // there is no inital reply 34 | NetMQMessage reply = null; 35 | 36 | while (!exit) 37 | { 38 | // send the reply and wait for a request 39 | var request = session.Receive(reply); 40 | 41 | if (verbose) 42 | Console.WriteLine("[TITANIC WORKER] Received: {0}", request); 43 | 44 | // was the worker interrupted 45 | if (request is null) 46 | break; 47 | // echo the request 48 | reply = request; 49 | 50 | if (verbose) 51 | Console.WriteLine("[TITANIC WORKER] Reply: {0}", request); 52 | } 53 | } 54 | catch (Exception ex) 55 | { 56 | Console.WriteLine ("[TITANIC WORKER] ERROR:"); 57 | Console.WriteLine ("{0}", ex.Message); 58 | Console.WriteLine ("{0}", ex.StackTrace); 59 | 60 | Console.WriteLine ("exit - any key"); 61 | Console.ReadKey (); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Titanic Pattern/TitanicUsageExample/TitanicWorkerExample/TitanicWorkerExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/WeatherUpdater/WeatherUpdateClient/WeatherUpdateClient.cs: -------------------------------------------------------------------------------- 1 | Console.Title = "NetMQ Weather Update Client"; 2 | 3 | const int zipToSubscribeTo = 10001; 4 | const int iterations = 100; 5 | 6 | int totalTemp = 0; 7 | int totalHumidity = 0; 8 | 9 | Console.WriteLine("Collecting updates for weather service for zipcode {0}...", zipToSubscribeTo); 10 | 11 | using var subscriber = new SubscriberSocket(); 12 | 13 | subscriber.Connect("tcp://127.0.0.1:5556"); 14 | subscriber.Subscribe(zipToSubscribeTo.ToString(CultureInfo.InvariantCulture)); 15 | 16 | for (int i = 0; i < iterations; i++) 17 | { 18 | string results = subscriber.ReceiveFrameString(); 19 | Console.Write("."); 20 | 21 | // "zip temp relh" ... "10001 84 23" -> ["10001", "84", "23"] 22 | string[] split = results.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 23 | 24 | int zip = int.Parse(split[0]); 25 | if (zip != zipToSubscribeTo) 26 | { 27 | throw new Exception($"Received message for unexpected zipcode: {zip} (expected {zipToSubscribeTo})"); 28 | } 29 | 30 | totalTemp += int.Parse(split[1]); 31 | totalHumidity += int.Parse(split[2]); 32 | } 33 | 34 | Console.WriteLine(); 35 | Console.WriteLine("Average temperature was: {0}", totalTemp/iterations); 36 | Console.WriteLine("Average relative humidity was: {0}", totalHumidity/iterations); 37 | -------------------------------------------------------------------------------- /src/WeatherUpdater/WeatherUpdateClient/WeatherUpdateClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/WeatherUpdater/WeatherUpdateServer/WeatherUpdateServer.cs: -------------------------------------------------------------------------------- 1 | Console.Title = "NetMQ Weather Update Server"; 2 | 3 | bool stopRequested = false; 4 | 5 | // Wire up the CTRL+C handler 6 | Console.CancelKeyPress += (sender, e) => stopRequested = true; 7 | 8 | Console.WriteLine("Publishing weather updates..."); 9 | 10 | using var publisher = new PublisherSocket(); 11 | 12 | publisher.Bind("tcp://127.0.0.1:5556"); 13 | 14 | var rng = new Random(); 15 | 16 | while (!stopRequested) 17 | { 18 | int zipcode = rng.Next(0, 99999); 19 | int temperature = rng.Next(-80, 135); 20 | int relhumidity = rng.Next(0, 90); 21 | 22 | publisher.SendFrame($"{zipcode} {temperature} {relhumidity}"); 23 | } 24 | -------------------------------------------------------------------------------- /src/WeatherUpdater/WeatherUpdateServer/WeatherUpdateServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------