├── .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 |
--------------------------------------------------------------------------------