├── .gitignore ├── Git synch this fork to upstream master.bat ├── LICENSE ├── NetMQ.ReactiveExtensions.SamplePublisher ├── App.config ├── NetMQ.ReactiveExtensions.SamplePublisher.xproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── packages.config └── project.json ├── NetMQ.ReactiveExtensions.SampleSubscriber ├── App.config ├── NetMQ.ReactiveExtensions.SampleSubscriber.xproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── packages.config └── project.json ├── NetMQ.ReactiveExtensions.Tests ├── GlobalTimeout.cs ├── NUnitUtils.cs ├── NetMQ.ReactiveExtensions.Tests.xproj ├── NetMQ_ReactiveExtensions_MessageTypes.cs ├── NetMQ_ReactiveExtensions_Test_Batch_1.cs ├── NetMQ_ReactiveExtensions_Test_Batch_2.cs ├── Properties │ └── AssemblyInfo.cs ├── RouterDealer_Test1.cs ├── RouterDealer_Test2.cs ├── SerializeExceptionToXml_Tests.cs ├── SerializeViaProtoBuf_Tests.cs ├── app.config ├── packages.config └── project.json ├── NetMQ.ReactiveExtensions.sln ├── NetMQ.ReactiveExtensions ├── .gitignore ├── AnonymousDisposable.cs ├── INetMqTransportShared.cs ├── IPublisherNetMq.cs ├── ISubjectNetMQ.cs ├── NetMQ.ReactiveExtensions.xproj ├── NetMqTransportShared.cs ├── Properties │ └── AssemblyInfo.cs ├── PublisherNetMq.cs ├── SerializeExceptionViaXml.cs ├── SerializeViaProtoBuf.cs ├── SubjectNetMQ.cs ├── SubscriberNetMq.cs ├── app.config ├── packages.config └── project.json ├── NuGet ├── .gitignore ├── NetMQ.ReactiveExtensions.nuspec └── nuget.config ├── README.md └── img ├── NetMQ.ico ├── NetMQLogo-128px.png ├── NetMQLogo-32px.png ├── NetMQLogo-400px.png ├── NetMQLogo-48px.png ├── NetMQLogo-64px.png └── NetMQLogo.svg /.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 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | *.md.bak 238 | *.bak 239 | -------------------------------------------------------------------------------- /Git synch this fork to upstream master.bat: -------------------------------------------------------------------------------- 1 | rem If we are are on a fork, synchronize this fork to the master. 2 | rem See https://help.github.com/articles/syncing-a-fork/ 3 | 4 | rem If this has already been done, then this next line will have no effect. 5 | git remote add upstream https://github.com/NetMQ/NetMQ.ReactiveExtensions 6 | git fetch upstream 7 | git checkout master 8 | git merge upstream/master 9 | pause 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/NetMQ.ReactiveExtensions.SamplePublisher.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 72bbf61c-b5ac-49c5-b4f6-0eeeb97a98cf 10 | NetMQ.ReactiveExtensions.SamplePublisher 11 | ..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | 17 | 18 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using ProtoBuf; 4 | 5 | namespace NetMQ.ReactiveExtensions.SamplePublisher 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | Console.Write("Reactive Extensions publisher demo:\n"); 12 | 13 | string endPoint = "tcp://127.0.0.1:56001"; 14 | if (args.Length >= 1) 15 | { 16 | endPoint = args[0]; 17 | } 18 | 19 | Console.Write("Endpoint: {0}\n", endPoint); 20 | 21 | // Debug: Subscribe to ourself. 22 | { 23 | var subscriber = new SubscriberNetMq(endPoint, loggerDelegate: msg => Console.Write(msg)); 24 | // Debug: subscribe to ourself. If you run the "SampleSubscriber" project now, you will see the same 25 | // messages appearing in that subscriber too. 26 | subscriber.Subscribe(message => 27 | { 28 | Console.Write("Received: {0}, '{1}'.\n", message.Num, message.Name); 29 | }); 30 | } 31 | 32 | // Publisher. 33 | { 34 | var publisher = new PublisherNetMq(endPoint, loggerDelegate: msg => Console.Write(msg)); 35 | 36 | int i = 0; 37 | while (true) 38 | { 39 | var message = new MyMessage(i, "Bob"); 40 | 41 | // When we call "OnNext", it binds a publisher to this endpoint endpoint. 42 | publisher.OnNext(message); 43 | 44 | Console.Write("Published: {0}, '{1}'.\n", message.Num, message.Name); 45 | Thread.Sleep(TimeSpan.FromMilliseconds(1000)); 46 | i++; 47 | } 48 | } 49 | 50 | // NOTE: If you run the "SampleSubscriber" project now, you will see the same messages appearing in the subscriber. 51 | } 52 | } 53 | 54 | [ProtoContract] 55 | public class MyMessage 56 | { 57 | public MyMessage() 58 | { 59 | 60 | } 61 | 62 | public MyMessage(int num, string name) 63 | { 64 | Num = num; 65 | Name = name; 66 | } 67 | 68 | [ProtoMember(1)] 69 | public int Num { get; set; } 70 | 71 | [ProtoMember(2)] 72 | public string Name { get; set; } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NetMQ.ReactiveExtensions.SampleServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NetMQ.ReactiveExtensions.SampleServer")] 13 | [assembly: AssemblyCopyright("Copyright ©Shane Tolmie 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("428889ac-3686-4930-b20d-ba0cbde35115")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.9.4.7")] 36 | [assembly: AssemblyFileVersion("0.9.4.7")] 37 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/packages.config: -------------------------------------------------------------------------------- 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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SamplePublisher/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "NetMQ.ReactiveExtensions": "1.0.0-*" 9 | }, 10 | "frameworks": { 11 | "net45": {}, 12 | "netcoreapp1.0": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.0.1" 17 | } 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/NetMQ.ReactiveExtensions.SampleSubscriber.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f8cad08f-532c-438b-9b7e-218dd6904840 10 | NetMQ.ReactiveExtensions.SampleSubscriber 11 | ..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | 17 | 18 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using ProtoBuf; 4 | 5 | namespace NetMQ.ReactiveExtensions.SampleSubscriber 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | Console.Write("Reactive Extensions subscriber demo:\n"); 12 | 13 | string endPoint = "tcp://127.0.0.1:56001"; 14 | if (args.Length >= 1) 15 | { 16 | endPoint = args[0]; 17 | } 18 | 19 | Console.Write("Endpoint: {0}\n", endPoint); 20 | 21 | SubscriberNetMq subject = new SubscriberNetMq(endPoint, loggerDelegate: msg => Console.Write(msg)); 22 | subject.Subscribe(message => 23 | { 24 | Console.Write("Received: {0}, '{1}'.\n", message.Num, message.Name); 25 | }); 26 | 27 | Console.WriteLine("[waiting for publisher - any key to exit]"); 28 | Console.ReadKey(); 29 | } 30 | } 31 | 32 | 33 | 34 | [ProtoContract] 35 | public class MyMessage 36 | { 37 | public MyMessage() 38 | { 39 | 40 | } 41 | 42 | public MyMessage(int num, string name) 43 | { 44 | Num = num; 45 | Name = name; 46 | } 47 | 48 | [ProtoMember(1)] 49 | public int Num { get; set; } 50 | 51 | [ProtoMember(2)] 52 | public string Name { get; set; } 53 | } 54 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NetMQ.ReactiveExtensions.SampleClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NetMQ.ReactiveExtensions.SampleClient")] 13 | [assembly: AssemblyCopyright("Copyright ©Shane Tolmie 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e9ce5a1c-0940-44a7-a03a-f3e26048ebb5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.9.4.7")] 36 | [assembly: AssemblyFileVersion("0.9.4.7")] 37 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/packages.config: -------------------------------------------------------------------------------- 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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.SampleSubscriber/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "NetMQ.ReactiveExtensions": "1.0.0-*" 9 | }, 10 | "frameworks": { 11 | "net45": {}, 12 | "netcoreapp1.0": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.0.1" 17 | } 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/GlobalTimeout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetMQ.ReactiveExtensions.Tests 4 | { 5 | public static class GlobalTimeout 6 | { 7 | static GlobalTimeout() 8 | { 9 | Timeout = TimeSpan.FromSeconds(60); 10 | } 11 | 12 | public static TimeSpan Timeout { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/NUnitUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using NUnit.Framework; 5 | 6 | namespace NetMQ.ReactiveExtensions.Tests 7 | { 8 | public static class NUnitUtils 9 | { 10 | /// 11 | /// Intent: Returns next free TCP/IP port. See 12 | /// http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net 13 | /// 14 | /// 15 | /// Yes. Quote: "I successfully used this technique to get a free port. I too was concerned about 16 | /// race-conditions, with some other process sneaking in and grabbing the recently-detected-as-free port. So I 17 | /// wrote a test with a forced Sleep(100) between var port = FreeTcpPort() and starting an HttpListener on the 18 | /// free port. I then ran 8 identical processes hammering on this in a loop. I could never hit the race 19 | /// condition. My anecdotal evidence (Win 7) is that the OS apparently cycles through the range of ephemeral 20 | /// ports (a few thousand) before coming around again. So the above snippet should be just fine." 21 | /// 22 | /// A free TCP/IP port. 23 | public static int TcpPortFree() 24 | { 25 | var l = new TcpListener(IPAddress.Loopback, 0); 26 | l.Start(); 27 | var port = ((IPEndPoint) l.LocalEndpoint).Port; 28 | l.Stop(); 29 | return port; 30 | } 31 | 32 | public static void PrintTestName() 33 | { 34 | Console.Write("\n\n*****\nTest: {0}\n*****\n", TestContext.CurrentContext.Test.Name); 35 | } 36 | 37 | public static void PrintElapsedTime(TimeSpan sw, int? max = null) 38 | { 39 | Console.Write("\n*****\nElapsed time: {0} milliseconds", sw.TotalMilliseconds); 40 | if (max != null) 41 | { 42 | Console.Write(" ({0:0,000}/sec)", (double) max/sw.TotalSeconds); 43 | } 44 | Console.Write("\n"); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/NetMQ.ReactiveExtensions.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 7d1cae81-2573-427b-a493-41d02a11eb30 10 | NetMQ.ReactiveExtensions.Tests 11 | ..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/NetMQ_ReactiveExtensions_MessageTypes.cs: -------------------------------------------------------------------------------- 1 | #region 2 | 3 | using ProtoBuf; 4 | 5 | // ReSharper disable InconsistentNaming 6 | 7 | #endregion 8 | 9 | // ReSharper disable ConvertClosureToMethodGroup 10 | 11 | namespace NetMQ.ReactiveExtensions.Tests 12 | { 13 | 14 | #region Message types. 15 | 16 | [ProtoContract] 17 | public class MyMessageClassType1 18 | { 19 | public MyMessageClassType1() 20 | { 21 | } 22 | 23 | public MyMessageClassType1(int num, string name) 24 | { 25 | Num = num; 26 | Name = name; 27 | } 28 | 29 | [ProtoMember(1)] 30 | public int Num { get; set; } 31 | 32 | [ProtoMember(2)] 33 | public string Name { get; set; } 34 | } 35 | 36 | [ProtoContract] 37 | public class MyMessageClassType2 38 | { 39 | public MyMessageClassType2() 40 | { 41 | } 42 | 43 | public MyMessageClassType2(int num, string name) 44 | { 45 | Num = num; 46 | Name = name; 47 | } 48 | 49 | [ProtoMember(1)] 50 | public int Num { get; set; } 51 | 52 | [ProtoMember(2)] 53 | public string Name { get; set; } 54 | } 55 | 56 | [ProtoContract] 57 | public struct MyMessageStructType1 58 | { 59 | public MyMessageStructType1(int num, string name) 60 | { 61 | Num = num; 62 | Name = name; 63 | } 64 | 65 | [ProtoMember(1)] 66 | public int Num { get; set; } 67 | 68 | [ProtoMember(2)] 69 | public string Name { get; set; } 70 | } 71 | 72 | [ProtoContract] 73 | public struct MyMessageStructType2 74 | { 75 | public MyMessageStructType2(int num, string name) 76 | { 77 | Num = num; 78 | Name = name; 79 | } 80 | 81 | [ProtoMember(1)] 82 | public int Num { get; set; } 83 | 84 | [ProtoMember(2)] 85 | public string Name { get; set; } 86 | } 87 | 88 | public class MessageNotSerializableByProtobuf 89 | { 90 | public int NotSerializable { get; set; } 91 | } 92 | 93 | [ProtoContract] 94 | public class ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure 95 | { 96 | public ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure() 97 | { 98 | } 99 | 100 | public ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(int num, string name) 101 | { 102 | Num = num; 103 | Name = name; 104 | } 105 | 106 | [ProtoMember(1)] 107 | public int Num { get; set; } 108 | 109 | [ProtoMember(2)] 110 | public string Name { get; set; } 111 | } 112 | 113 | #endregion 114 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/NetMQ_ReactiveExtensions_Test_Batch_1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using NUnit.Framework; 6 | // ReSharper disable InconsistentNaming 7 | 8 | namespace NetMQ.ReactiveExtensions.Tests 9 | { 10 | [TestFixture] 11 | public class NetMQ_ReactiveExtensions_Test_Batch_1 12 | { 13 | [Test] 14 | public void Can_Serialize_Class_Name_Longer_Then_Thirty_Two_Characters() 15 | { 16 | NUnitUtils.PrintTestName(); 17 | 18 | var sw = Stopwatch.StartNew(); 19 | 20 | var cd = new CountdownEvent(5); 21 | { 22 | var freePort = NUnitUtils.TcpPortFree(); 23 | 24 | var pubSub = 25 | new SubjectNetMQ( 26 | "tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 27 | pubSub.Subscribe(o => 28 | { 29 | Assert.IsTrue(o.Name == "Bob"); 30 | Console.Write("Test: Num={0}, Name={1}\n", o.Num, o.Name); 31 | cd.Signal(); 32 | }, 33 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 34 | 35 | pubSub.OnNext(new ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(38, "Bob")); 36 | pubSub.OnNext(new ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(39, "Bob")); 37 | pubSub.OnNext(new ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(40, "Bob")); 38 | pubSub.OnNext(new ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(41, "Bob")); 39 | pubSub.OnNext(new ClassNameIsLongerThenThirtyTwoCharactersForAbsolutelySure(42, "Bob")); 40 | } 41 | 42 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 43 | { 44 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 45 | } 46 | 47 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 48 | } 49 | 50 | 51 | [Test] 52 | public void Can_Serialize_Using_Protobuf_With_Class() 53 | { 54 | NUnitUtils.PrintTestName(); 55 | 56 | var sw = Stopwatch.StartNew(); 57 | 58 | List timeMilliseconds = new List(); 59 | 60 | var cd = new CountdownEvent(5); 61 | { 62 | var freePort = NUnitUtils.TcpPortFree(); 63 | 64 | timeMilliseconds.Add(sw.Elapsed); 65 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, 66 | loggerDelegate: Console.Write); 67 | timeMilliseconds.Add(sw.Elapsed); 68 | pubSub.Subscribe( 69 | o => 70 | { 71 | Assert.IsTrue(o.Name == "Bob"); 72 | Console.Write("Test: Num={0}, Name={1}\n", o.Num, o.Name); 73 | cd.Signal(); 74 | }, 75 | ex => 76 | { 77 | Console.WriteLine("Exception! {0}", ex.Message); 78 | }); 79 | timeMilliseconds.Add(sw.Elapsed); 80 | pubSub.OnNext(new MyMessageClassType1(38, "Bob")); 81 | timeMilliseconds.Add(sw.Elapsed); 82 | pubSub.OnNext(new MyMessageClassType1(39, "Bob")); 83 | timeMilliseconds.Add(sw.Elapsed); 84 | pubSub.OnNext(new MyMessageClassType1(40, "Bob")); 85 | timeMilliseconds.Add(sw.Elapsed); 86 | pubSub.OnNext(new MyMessageClassType1(41, "Bob")); 87 | timeMilliseconds.Add(sw.Elapsed); 88 | pubSub.OnNext(new MyMessageClassType1(42, "Bob")); 89 | timeMilliseconds.Add(sw.Elapsed); 90 | } 91 | 92 | for (int i = 0; i < timeMilliseconds.Count; i++) 93 | { 94 | var t = timeMilliseconds[i]; 95 | Console.WriteLine("- Stage {0}: {1:0,000} milliseconds", i, t.TotalMilliseconds); 96 | } 97 | 98 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 99 | { 100 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 101 | } 102 | 103 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 104 | } 105 | 106 | [Test] 107 | public void Can_Serialize_Using_Protobuf_With_Struct() 108 | { 109 | NUnitUtils.PrintTestName(); 110 | 111 | var sw = Stopwatch.StartNew(); 112 | 113 | var cd = new CountdownEvent(5); 114 | { 115 | var freePort = NUnitUtils.TcpPortFree(); 116 | 117 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, 118 | loggerDelegate: Console.Write); 119 | pubSub.Subscribe(o => 120 | { 121 | Assert.IsTrue(o.Name == "Bob"); 122 | Console.Write("Test: Num={0}, Name={1}\n", o.Num, o.Name); 123 | cd.Signal(); 124 | }, 125 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 126 | 127 | pubSub.OnNext(new MyMessageStructType1(38, "Bob")); 128 | pubSub.OnNext(new MyMessageStructType1(39, "Bob")); 129 | pubSub.OnNext(new MyMessageStructType1(40, "Bob")); 130 | pubSub.OnNext(new MyMessageStructType1(41, "Bob")); 131 | pubSub.OnNext(new MyMessageStructType1(42, "Bob")); 132 | } 133 | 134 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 135 | { 136 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 137 | } 138 | 139 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 140 | } 141 | 142 | [Test] 143 | public static void Disposing_Of_One_Does_Not_Dispose_Of_The_Other() 144 | { 145 | NUnitUtils.PrintTestName(); 146 | var sw = Stopwatch.StartNew(); 147 | 148 | var max = 1000; 149 | var cd = new CountdownEvent(max); 150 | { 151 | var freePort = NUnitUtils.TcpPortFree(); 152 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 153 | var d1 = pubSub.Subscribe(o => { cd.Signal(); }); 154 | 155 | var d2 = pubSub.Subscribe(o => { Assert.Fail(); }, 156 | ex => { Console.WriteLine("Exception in subscriber thread."); }); 157 | d2.Dispose(); 158 | 159 | for (var i = 0; i < max; i++) 160 | { 161 | pubSub.OnNext(i); 162 | } 163 | } 164 | 165 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 166 | { 167 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 168 | } 169 | 170 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 171 | } 172 | 173 | 174 | [Test] 175 | public void Initialize_Publisher_Then_Subscriber() 176 | { 177 | NUnitUtils.PrintTestName(); 178 | 179 | var sw = Stopwatch.StartNew(); 180 | 181 | var cd = new CountdownEvent(5); 182 | { 183 | var freePort = NUnitUtils.TcpPortFree(); 184 | 185 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 186 | 187 | // Forces the publisher to be initialized. Subscriber not set up yet, so this message will never get 188 | // delivered to the subscriber, which is what is should do. 189 | pubSub.OnNext(1); 190 | 191 | pubSub.Subscribe(o => 192 | { 193 | Assert.IsTrue(o != 1); 194 | Console.Write("Test 1: {0}\n", o); 195 | cd.Signal(); 196 | }, 197 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 198 | 199 | pubSub.OnNext(38); 200 | pubSub.OnNext(39); 201 | pubSub.OnNext(40); 202 | pubSub.OnNext(41); 203 | pubSub.OnNext(42); 204 | } 205 | 206 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 207 | { 208 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 209 | } 210 | 211 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 212 | } 213 | 214 | [Test] 215 | public void OnCompleted_Should_Get_Passed_To_Subscribers() 216 | { 217 | NUnitUtils.PrintTestName(); 218 | 219 | var sw = Stopwatch.StartNew(); 220 | 221 | List timeMilliseconds = new List(); 222 | 223 | var weAreDone = new CountdownEvent(1); 224 | { 225 | timeMilliseconds.Add(sw.Elapsed); 226 | var freePort = NUnitUtils.TcpPortFree(); 227 | timeMilliseconds.Add(sw.Elapsed); 228 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 229 | timeMilliseconds.Add(sw.Elapsed); 230 | pubSub.Subscribe( 231 | o => 232 | { 233 | // If this gets called more than max times, it will throw an exception as it is going through 0. 234 | //Console.Write("FAIL!"); 235 | //Assert.Fail(); 236 | }, 237 | ex => 238 | { 239 | Console.Write("FAIL!"); 240 | Assert.Fail(); 241 | }, 242 | () => 243 | { 244 | Console.Write("Pass!"); 245 | weAreDone.Signal(); 246 | }); 247 | timeMilliseconds.Add(sw.Elapsed); 248 | pubSub.OnCompleted(); 249 | timeMilliseconds.Add(sw.Elapsed); 250 | } 251 | 252 | for (int i = 0; i < timeMilliseconds.Count; i++) 253 | { 254 | var t = timeMilliseconds[i]; 255 | Console.WriteLine("- Stage {0}: {1:0,000} milliseconds", i, t.TotalMilliseconds); 256 | } 257 | 258 | if (weAreDone.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 259 | { 260 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 261 | } 262 | 263 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 264 | } 265 | 266 | [Test] 267 | public void OnException_Should_Get_Passed_To_Subscribers() 268 | { 269 | NUnitUtils.PrintTestName(); 270 | 271 | var sw = Stopwatch.StartNew(); 272 | 273 | var weAreDone = new CountdownEvent(1); 274 | var passed = false; 275 | { 276 | var freePort = NUnitUtils.TcpPortFree(); 277 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 278 | pubSub.Subscribe( 279 | o => 280 | { 281 | // If this gets called more than max times, it will throw an exception as it is going through 0. 282 | Assert.Fail(); 283 | }, 284 | ex => 285 | { 286 | Console.Write("Exception: {0}", ex.Message); 287 | if (ex.Message.Contains("passed") == true) 288 | { 289 | passed = true; 290 | } 291 | weAreDone.Signal(); 292 | }, 293 | () => { Assert.Fail(); }); 294 | 295 | pubSub.OnError(new Exception("passed")); 296 | } 297 | 298 | if (weAreDone.Wait(GlobalTimeout.Timeout) == false || passed == false) // Blocks until _countdown.Signal has been called. 299 | { 300 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 301 | } 302 | 303 | if (passed == false) // Blocks until _countdown.Signal has been called. 304 | { 305 | Assert.Fail("Expected exception text did not arrive back."); 306 | } 307 | 308 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 309 | } 310 | 311 | [Test] 312 | public void Simplest_Fanout_Sub() 313 | { 314 | NUnitUtils.PrintTestName(); 315 | 316 | var sw = Stopwatch.StartNew(); 317 | 318 | var cd = new CountdownEvent(3); 319 | { 320 | var freePort = NUnitUtils.TcpPortFree(); 321 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 322 | pubSub.Subscribe(o => 323 | { 324 | Assert.AreEqual(o, 42); 325 | Console.Write("PubTwoThreadFanoutSub1: {0}\n", o); 326 | cd.Signal(); 327 | }); 328 | pubSub.Subscribe(o => 329 | { 330 | Assert.AreEqual(o, 42); 331 | Console.Write("PubTwoThreadFanoutSub2: {0}\n", o); 332 | cd.Signal(); 333 | }); 334 | pubSub.Subscribe(o => 335 | { 336 | Assert.AreEqual(o, 42); 337 | Console.Write("PubTwoThreadFanoutSub3: {0}\n", o); 338 | cd.Signal(); 339 | }); 340 | 341 | pubSub.OnNext(42); 342 | } 343 | 344 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 345 | { 346 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 347 | } 348 | 349 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 350 | } 351 | 352 | [Test] 353 | public void Simplest_Test_Publisher_To_Subscriber() 354 | { 355 | NUnitUtils.PrintTestName(); 356 | 357 | var sw = Stopwatch.StartNew(); 358 | 359 | var cd = new CountdownEvent(5); 360 | { 361 | var freePort = NUnitUtils.TcpPortFree(); 362 | 363 | var publisher = new PublisherNetMq("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 364 | var subscriber = new SubscriberNetMq("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 365 | 366 | subscriber.Subscribe(o => 367 | { 368 | Console.Write("Test 1: {0}\n", o); 369 | cd.Signal(); 370 | }, 371 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 372 | 373 | publisher.OnNext(38); 374 | publisher.OnNext(39); 375 | publisher.OnNext(40); 376 | publisher.OnNext(41); 377 | publisher.OnNext(42); 378 | } 379 | 380 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 381 | { 382 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 383 | } 384 | 385 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 386 | } 387 | 388 | [Test] 389 | public void Simplest_Test_Subject() 390 | { 391 | NUnitUtils.PrintTestName(); 392 | 393 | var sw = Stopwatch.StartNew(); 394 | 395 | var cd = new CountdownEvent(5); 396 | { 397 | var freePort = NUnitUtils.TcpPortFree(); 398 | 399 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 400 | pubSub.Subscribe(o => 401 | { 402 | Console.Write("Test 1: {0}\n", o); 403 | cd.Signal(); 404 | }, 405 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 406 | 407 | pubSub.OnNext(38); 408 | pubSub.OnNext(39); 409 | pubSub.OnNext(40); 410 | pubSub.OnNext(41); 411 | pubSub.OnNext(42); 412 | } 413 | 414 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 415 | { 416 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 417 | } 418 | 419 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 420 | } 421 | } 422 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/NetMQ_ReactiveExtensions_Test_Batch_2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using NetMQ.Sockets; 5 | using NUnit.Framework; 6 | // ReSharper disable InconsistentNaming 7 | 8 | namespace NetMQ.ReactiveExtensions.Tests 9 | { 10 | [TestFixture] 11 | public class NetMQ_ReactiveExtensions_Test_Batch_2 12 | { 13 | [Test] 14 | public void If_Message_Not_Serializable_By_Protobuf_Throw_A_Meaningful_Error() 15 | { 16 | NUnitUtils.PrintTestName(); 17 | var sw = Stopwatch.StartNew(); 18 | 19 | var freePort = NUnitUtils.TcpPortFree(); 20 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, 21 | loggerDelegate: Console.Write); 22 | try 23 | { 24 | pubSub.OnNext(new MessageNotSerializableByProtobuf()); 25 | 26 | // We should have thrown an exception if the class was not serializable by ProtoBuf-Net. 27 | Assert.Fail(); 28 | } 29 | catch (InvalidOperationException ex) 30 | { 31 | Assert.True(ex.Message.ToLower().Contains("protobuf")); 32 | Console.Write("Pass - meaningful message thrown if class was not serializable by ProtoBuf-Net."); 33 | } 34 | catch (Exception) 35 | { 36 | Assert.Fail(); 37 | } 38 | 39 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 40 | } 41 | 42 | [Test] 43 | public void PubSub_Should_Not_Crash_If_No_Thread_Sleep() 44 | { 45 | NUnitUtils.PrintTestName(); 46 | var swAll = Stopwatch.StartNew(); 47 | 48 | using (var pub = new PublisherSocket()) 49 | { 50 | using (var sub = new SubscriberSocket()) 51 | { 52 | var freePort = NUnitUtils.TcpPortFree(); 53 | pub.Bind("tcp://127.0.0.1:" + freePort); 54 | sub.Connect("tcp://127.0.0.1:" + freePort); 55 | 56 | sub.Subscribe("*"); 57 | 58 | var sw = Stopwatch.StartNew(); 59 | { 60 | for (var i = 0; i < 50; i++) 61 | { 62 | pub.SendFrame("*"); // Ping. 63 | 64 | Console.Write("*"); 65 | string topic; 66 | var gotTopic = sub.TryReceiveFrameString(TimeSpan.FromMilliseconds(100), out topic); 67 | string ping; 68 | var gotPing = sub.TryReceiveFrameString(TimeSpan.FromMilliseconds(100), out ping); 69 | if (gotTopic) 70 | { 71 | Console.Write("\n"); 72 | break; 73 | } 74 | } 75 | } 76 | Console.WriteLine("Connected in {0} ms.", sw.ElapsedMilliseconds); 77 | } 78 | } 79 | NUnitUtils.PrintElapsedTime(swAll.Elapsed); 80 | } 81 | 82 | [Test] 83 | public void Send_Two_Types_Simultaneously_Over_Same_Transport() 84 | { 85 | NUnitUtils.PrintTestName(); 86 | var sw = Stopwatch.StartNew(); 87 | 88 | var cd1 = new CountdownEvent(5); 89 | var cd2 = new CountdownEvent(5); 90 | { 91 | var freePort = NUnitUtils.TcpPortFree(); 92 | 93 | var pubSub1 = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, 94 | loggerDelegate: Console.Write); 95 | var pubSub2 = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, 96 | loggerDelegate: Console.Write); 97 | pubSub1.Subscribe(o => 98 | { 99 | Assert.IsTrue(o.Name == "Bob"); 100 | Console.Write("Test 1: Num={0}, Name={1}\n", o.Num, o.Name); 101 | cd1.Signal(); 102 | }, 103 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 104 | 105 | pubSub2.Subscribe(o => 106 | { 107 | Assert.IsTrue(o.Name == "Bob"); 108 | Console.Write("Test 2: Num={0}, Name={1}\n", o.Num, o.Name); 109 | cd2.Signal(); 110 | }, 111 | ex => { Console.WriteLine("Exception! {0}", ex.Message); }); 112 | 113 | pubSub1.OnNext(new MyMessageStructType1(38, "Bob")); 114 | pubSub1.OnNext(new MyMessageStructType1(39, "Bob")); 115 | pubSub1.OnNext(new MyMessageStructType1(40, "Bob")); 116 | pubSub1.OnNext(new MyMessageStructType1(41, "Bob")); 117 | pubSub1.OnNext(new MyMessageStructType1(42, "Bob")); 118 | 119 | pubSub2.OnNext(new MyMessageStructType2(38, "Bob")); 120 | pubSub2.OnNext(new MyMessageStructType2(39, "Bob")); 121 | pubSub2.OnNext(new MyMessageStructType2(40, "Bob")); 122 | pubSub2.OnNext(new MyMessageStructType2(41, "Bob")); 123 | pubSub2.OnNext(new MyMessageStructType2(42, "Bob")); 124 | } 125 | 126 | if (cd1.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 127 | { 128 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 129 | } 130 | 131 | if (cd2.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 132 | { 133 | Assert.Fail("Timed out, this test should complete in {0} seconds.", GlobalTimeout.Timeout.TotalSeconds); 134 | } 135 | 136 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 137 | } 138 | 139 | [Test] 140 | public static void Speed_Test_Publisher_Subscriber() 141 | { 142 | NUnitUtils.PrintTestName(); 143 | 144 | var sw = Stopwatch.StartNew(); 145 | { 146 | var max = 100*1000; 147 | 148 | var cd = new CountdownEvent(max); 149 | var receivedNum = 0; 150 | { 151 | Console.Write("Speed test with {0} messages:\n", max); 152 | 153 | var freePort = NUnitUtils.TcpPortFree(); 154 | var publisher = new PublisherNetMq("tcp://127.0.0.1:" + freePort, 155 | loggerDelegate: Console.Write); 156 | var subscriber = new SubscriberNetMq("tcp://127.0.0.1:" + freePort, 157 | loggerDelegate: Console.Write); 158 | 159 | subscriber.Subscribe(i => 160 | { 161 | receivedNum++; 162 | cd.Signal(); 163 | if (i%10000 == 0) 164 | { 165 | //Console.Write("*"); 166 | } 167 | }); 168 | 169 | sw.Start(); 170 | for (var i = 0; i < max; i++) 171 | { 172 | publisher.OnNext(i); 173 | } 174 | } 175 | 176 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 177 | { 178 | Assert.Fail("\nTimed out, this test should complete in {0} seconds. receivedNum={1}", 179 | GlobalTimeout.Timeout.TotalSeconds, 180 | receivedNum); 181 | } 182 | 183 | // On my machine, achieved >120,000 messages per second. 184 | NUnitUtils.PrintElapsedTime(sw.Elapsed, max); 185 | } 186 | } 187 | 188 | [Test] 189 | public static void Speed_Test_Subject() 190 | { 191 | NUnitUtils.PrintTestName(); 192 | 193 | var sw = Stopwatch.StartNew(); 194 | { 195 | var max = 100*1000; 196 | 197 | var cd = new CountdownEvent(max); 198 | var receivedNum = 0; 199 | { 200 | Console.Write("Speed test with {0} messages:\n", max); 201 | 202 | var freePort = NUnitUtils.TcpPortFree(); 203 | var pubSub = new SubjectNetMQ("tcp://127.0.0.1:" + freePort, loggerDelegate: Console.Write); 204 | 205 | pubSub.Subscribe(i => 206 | { 207 | receivedNum++; 208 | cd.Signal(); 209 | if (i%10000 == 0) 210 | { 211 | //Console.Write("*"); 212 | } 213 | }); 214 | 215 | sw.Start(); 216 | for (var i = 0; i < max; i++) 217 | { 218 | pubSub.OnNext(i); 219 | } 220 | } 221 | 222 | if (cd.Wait(GlobalTimeout.Timeout) == false) // Blocks until _countdown.Signal has been called. 223 | { 224 | Assert.Fail("\nTimed out, this test should complete in {0} seconds. receivedNum={1}", 225 | GlobalTimeout.Timeout.TotalSeconds, receivedNum); 226 | } 227 | 228 | // On my machine, achieved >120,000 messages per second. 229 | NUnitUtils.PrintElapsedTime(sw.Elapsed, max); 230 | } 231 | } 232 | 233 | [Test] 234 | public void Test_Two_Subscribers() 235 | { 236 | NUnitUtils.PrintTestName(); 237 | var sw = Stopwatch.StartNew(); 238 | 239 | using (var pub = new PublisherSocket()) 240 | { 241 | using (var sub1 = new SubscriberSocket()) 242 | { 243 | using (var sub2 = new SubscriberSocket()) 244 | { 245 | var freePort = NUnitUtils.TcpPortFree(); 246 | pub.Bind("tcp://127.0.0.1:" + freePort); 247 | sub1.Connect("tcp://127.0.0.1:" + freePort); 248 | sub1.Subscribe("A"); 249 | sub2.Connect("tcp://127.0.0.1:" + freePort); 250 | sub2.Subscribe("B"); 251 | 252 | Thread.Sleep(500); 253 | 254 | var swInner = Stopwatch.StartNew(); 255 | { 256 | pub.SendFrame("A\n"); // Ping. 257 | { 258 | string topic; 259 | var pass1 = sub1.TryReceiveFrameString(TimeSpan.FromMilliseconds(250), out topic); 260 | if (pass1) 261 | { 262 | Console.Write(topic); 263 | } 264 | else 265 | { 266 | Assert.Fail(); 267 | } 268 | } 269 | pub.SendFrame("B\n"); // Ping. 270 | { 271 | string topic; 272 | var pass2 = sub2.TryReceiveFrameString(TimeSpan.FromMilliseconds(250), out topic); 273 | if (pass2) 274 | { 275 | Console.Write(topic); 276 | } 277 | else 278 | { 279 | Assert.Fail(); 280 | } 281 | } 282 | } 283 | Console.WriteLine("Connected in {0} ms.", swInner.ElapsedMilliseconds); 284 | } 285 | } 286 | } 287 | 288 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 289 | } 290 | } 291 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("NetMQ.ReactiveExtensions.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NetMQ.ReactiveExtensions.Tests")] 13 | [assembly: AssemblyCopyright("Copyright ©Shane Tolmie 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("e1e07a78-7831-4136-9d8d-c5f3ecb0224b")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("0.9.4.7")] 39 | [assembly: AssemblyFileVersion("0.9.4.7")] 40 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/RouterDealer_Test1.cs: -------------------------------------------------------------------------------- 1 | // Behavior changed from 3.4.4.4 -> 4.0.0.0-rc5. 2 | // In any case, this code is exploratory, as router/dealer is not used in this project. 3 | /*using System; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using NetMQ.Sockets; 8 | using NUnit.Framework; 9 | // ReSharper disable InconsistentNaming 10 | 11 | namespace NetMQ.ReactiveExtensions.Tests 12 | { 13 | [Ignore] 14 | [TestFixture] 15 | public class RouterDealer_Test1 16 | { 17 | private void PrintFrames(string operationType, NetMQMessage message) 18 | { 19 | for (var i = 0; i < message.FrameCount; i++) 20 | { 21 | Console.WriteLine("{0} Socket : Frame[{1}] = {2}", operationType, i, 22 | message[i].ConvertToString()); 23 | } 24 | } 25 | 26 | private void Client_ReceiveReady(object sender, NetMQSocketEventArgs e) 27 | { 28 | var hasmore = false; 29 | Msg msg; 30 | e.Socket.Receive(out hasmore); 31 | if (hasmore) 32 | { 33 | var result = e.Socket.ReceiveFrameString(out hasmore); 34 | Console.WriteLine("REPLY {0}", result); 35 | } 36 | } 37 | 38 | /// 39 | /// From Router-Dealer docs, see: https://github.com/zeromq/netmq/blob/master/docs/router-dealer.md 40 | /// 41 | [Test] 42 | public void Router_Dealer_Demonstrating_Messages_From_Subscribers_To_Publisher() 43 | { 44 | // NOTES 45 | // 1. Use ThreadLocal where each thread has 46 | // its own client DealerSocket to talk to server 47 | // 2. Each thread can send using it own socket 48 | // 3. Each thread socket is added to poller 49 | 50 | const int delay = 500; // millis 51 | 52 | var clientSocketPerThread = new ThreadLocal(); 53 | 54 | using (var server = new RouterSocket("@tcp://127.0.0.1:5556")) 55 | { 56 | using (var poller = new NetMQPoller()) 57 | { 58 | // Start some threads, each with its own DealerSocket 59 | // to talk to the server socket. Creates lots of sockets, 60 | // but no nasty race conditions no shared state, each 61 | // thread has its own socket, happy days. 62 | for (var i = 0; i < 4; i++) 63 | { 64 | Task.Factory.StartNew(state => 65 | { 66 | DealerSocket client = null; 67 | 68 | if (!clientSocketPerThread.IsValueCreated) 69 | { 70 | client = new DealerSocket(); 71 | client.Options.Identity = 72 | Encoding.Unicode.GetBytes(state.ToString()); 73 | client.Connect("tcp://127.0.0.1:5556"); 74 | client.ReceiveReady += Client_ReceiveReady; 75 | clientSocketPerThread.Value = client; 76 | poller.Add(client); 77 | } 78 | else 79 | { 80 | client = clientSocketPerThread.Value; 81 | } 82 | 83 | while (true) 84 | { 85 | var messageToServer = new NetMQMessage(); 86 | messageToServer.AppendEmptyFrame(); 87 | messageToServer.Append(state.ToString()); 88 | Console.WriteLine("======================================"); 89 | Console.WriteLine(" OUTGOING MESSAGE TO SERVER "); 90 | Console.WriteLine("======================================"); 91 | PrintFrames("Client Sending", messageToServer); 92 | client.SendMultipartMessage(messageToServer); 93 | Thread.Sleep(delay); 94 | } 95 | }, 96 | string.Format("client {0}", i), 97 | TaskCreationOptions.LongRunning); 98 | } 99 | 100 | // start the poller 101 | poller.RunAsync(); 102 | 103 | // server loop 104 | for (var i = 0; i < 6; i++) 105 | { 106 | var clientMessage = server.ReceiveMessage(); 107 | Console.WriteLine("======================================"); 108 | Console.WriteLine(" INCOMING CLIENT MESSAGE FROM CLIENT "); 109 | Console.WriteLine("======================================"); 110 | PrintFrames("Server receiving", clientMessage); 111 | if (clientMessage.FrameCount == 3) 112 | { 113 | var clientAddress = clientMessage[0]; 114 | var clientOriginalMessage = clientMessage[2].ConvertToString(); 115 | var response = string.Format("{0} back from server {1}", 116 | clientOriginalMessage, 117 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); 118 | var messageToClient = new NetMQMessage(); 119 | messageToClient.Append(clientAddress); 120 | messageToClient.AppendEmptyFrame(); 121 | messageToClient.Append(response); 122 | server.SendMultipartMessage(messageToClient); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | }*/ -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/RouterDealer_Test2.cs: -------------------------------------------------------------------------------- 1 | // Behavior changed from 3.4.4.4 -> 4.0.0.0-rc5. 2 | // In any case, this code is exploratory, as router/dealer is not used in this project. 3 | /*using System; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using NetMQ.Sockets; 8 | using NUnit.Framework; 9 | // ReSharper disable InconsistentNaming 10 | 11 | namespace NetMQ.ReactiveExtensions.Tests 12 | { 13 | [Ignore] 14 | [TestFixture] 15 | public class RouterDealer_Test2 16 | { 17 | private void PrintFrames(string operationType, NetMQMessage message) 18 | { 19 | for (var i = 0; i < message.FrameCount; i++) 20 | { 21 | Console.WriteLine("{0} Socket : Frame[{1}] = {2}", operationType, i, 22 | message[i].ConvertToString()); 23 | } 24 | } 25 | 26 | private void Client_ReceiveReady(object sender, NetMQSocketEventArgs e) 27 | { 28 | var hasmore = false; 29 | e.Socket.Receive(out hasmore); 30 | if (hasmore) 31 | { 32 | var result = e.Socket.ReceiveFrameString(out hasmore); 33 | Console.WriteLine("REPLY {0}", result); 34 | } 35 | } 36 | 37 | [Test] 38 | public void Messages_From_Dealer_To_Router() 39 | { 40 | var maxMessage = 5; 41 | var cd = new CountdownEvent(maxMessage); 42 | 43 | Console.Write("Test sending message from subscribers (dealer) to publisher(router).\n"); 44 | 45 | using (var publisher = new RouterSocket()) 46 | using (var subscriber = new DealerSocket()) 47 | using (var poller = new NetMQPoller {subscriber}) 48 | { 49 | var port = publisher.BindRandomPort("tcp://*"); 50 | subscriber.Connect("tcp://127.0.0.1:" + port); 51 | subscriber.ReceiveReady += (sender, e) => 52 | { 53 | var strs = e.Socket.ReceiveMultipartStrings(); 54 | foreach (var str in strs) 55 | { 56 | Console.WriteLine(str); 57 | } 58 | cd.Signal(); 59 | }; 60 | var clientId = Encoding.Unicode.GetBytes("ClientId"); 61 | subscriber.Options.Identity = clientId; 62 | 63 | const string request = "GET /\r\n"; 64 | 65 | const string response = "HTTP/1.0 200 OK\r\n" + 66 | "Content-Type: text/plain\r\n" + 67 | "\r\n" + 68 | "Hello, World!"; 69 | 70 | subscriber.SendFrame(request); 71 | 72 | var serverId = publisher.ReceiveFrameBytes(); 73 | Assert.AreEqual(request, publisher.ReceiveFrameString()); 74 | 75 | for (var i = 0; i < maxMessage; i++) 76 | { 77 | publisher.SendMoreFrame(serverId).SendFrame(response); 78 | } 79 | 80 | poller.RunAsync(); 81 | 82 | if (cd.Wait(TimeSpan.FromSeconds(10)) == false) // Blocks until _countdown.Signal has been called. 83 | { 84 | Assert.Fail("Timed out, this test should complete in less than 10 seconds."); 85 | } 86 | } 87 | } 88 | 89 | [Test] 90 | public void Messages_From_Router_To_Dealer() 91 | { 92 | Console.Write("Test sending message from publisher(router) to subscribers (dealer).\n"); 93 | 94 | var maxMessage = 5; 95 | var cd = new CountdownEvent(maxMessage); 96 | 97 | string endpoint; 98 | 99 | using (var publisher = new RouterSocket()) 100 | using (var subscriber = new DealerSocket()) 101 | using (var poller = new NetMQPoller {subscriber}) 102 | { 103 | publisher.Bind("tcp://127.0.0.1:0"); 104 | endpoint = publisher.Options.LastEndpoint; 105 | 106 | subscriber.Connect(endpoint); 107 | subscriber.ReceiveReady += (sender, e) => 108 | { 109 | var strs = e.Socket.ReceiveMultipartStrings(); 110 | foreach (var str in strs) 111 | { 112 | Console.WriteLine("Subscribe: " + str); 113 | } 114 | cd.Signal(); 115 | }; 116 | var clientId = Encoding.Unicode.GetBytes("ClientId"); 117 | subscriber.Options.Identity = clientId; 118 | 119 | const string request = "Ping"; 120 | 121 | // Work around "feature" of router/dealer: the publisher does not know the subscriber exists, until it 122 | // sends at least one message which makes it necessary to open the connection. I believe this is a 123 | // low-level feature of the TCP/IP transport. 124 | subscriber.SendFrame(request); // Ping. 125 | 126 | var serverId = publisher.ReceiveFrameBytes(); 127 | Assert.AreEqual(request, publisher.ReceiveFrameString()); 128 | 129 | for (var i = 0; i < maxMessage; i++) 130 | { 131 | var msg = string.Format("[message: {0}]", i); 132 | Console.Write("Publish: {0}\n", msg); 133 | publisher.SendMoreFrame(serverId).SendFrame(msg); 134 | } 135 | 136 | poller.RunAsync(); 137 | 138 | if (cd.Wait(TimeSpan.FromSeconds(10)) == false) // Blocks until _countdown.Signal has been called. 139 | { 140 | Assert.Fail("Timed out, this test should complete in less than 10 seconds."); 141 | } 142 | } 143 | } 144 | 145 | [Test] 146 | public void Messages_From_Router_To_Dealer_With_Subscription() 147 | { 148 | Console.Write("Test sending message from publisher(router) to subscribers (dealer).\n"); 149 | 150 | var maxMessage = 5; 151 | var cd = new CountdownEvent(maxMessage); 152 | 153 | string endpoint; 154 | 155 | using (var publisher = new RouterSocket()) 156 | using (var subscriber = new DealerSocket()) 157 | using (var poller = new NetMQPoller {subscriber}) 158 | { 159 | publisher.Bind("tcp://127.0.0.1:0"); 160 | endpoint = publisher.Options.LastEndpoint; 161 | 162 | subscriber.Connect(endpoint); 163 | subscriber.ReceiveReady += (sender, e) => 164 | { 165 | var strs = e.Socket.ReceiveMultipartStrings(); 166 | foreach (var str in strs) 167 | { 168 | Console.WriteLine("Subscribe: " + str); 169 | } 170 | cd.Signal(); 171 | }; 172 | var clientId = Encoding.Unicode.GetBytes("ClientIdTheIsLongerThen32BytesForSureAbsolutelySure"); 173 | subscriber.Options.Identity = clientId; 174 | 175 | const string request = "Ping"; 176 | 177 | // Work around "feature" of router/dealer: the publisher does not know the subscriber exists, until it 178 | // sends at least one message which makes it necessary to open the connection. I believe this is a 179 | // low-level feature of the TCP/IP transport. 180 | subscriber.SendFrame(request); // Ping. 181 | 182 | var serverId = publisher.ReceiveFrameBytes(); 183 | //Assert.AreEqual(request, publisher.ReceiveFrameString()); 184 | 185 | for (var i = 0; i < maxMessage; i++) 186 | { 187 | var msg = string.Format("[message: {0}]", i); 188 | Console.Write("Publish: {0}\n", msg); 189 | publisher.SendMoreFrame(serverId).SendFrame(msg); 190 | //publisher.SendMoreFrame("").SendFrame(msg); 191 | } 192 | 193 | poller.RunAsync(); 194 | 195 | if (cd.Wait(TimeSpan.FromSeconds(10)) == false) // Blocks until _countdown.Signal has been called. 196 | { 197 | Assert.Fail("Timed out, this test should complete in less than 10 seconds."); 198 | } 199 | } 200 | } 201 | 202 | /// 203 | /// Intent: Modified from example in Router-Dealer docs, see: 204 | /// https://github.com/zeromq/netmq/blob/master/docs/router-dealer.md 205 | /// 206 | [Test] 207 | public void Router_Dealer_Demonstrating_Messages_From_Publisher_To_Subscribers() 208 | { 209 | // NOTES 210 | // 1. Use ThreadLocal where each thread has 211 | // its own client DealerSocket to talk to server 212 | // 2. Each thread can send using it own socket 213 | // 3. Each thread socket is added to poller 214 | 215 | const int delay = 500; // millis 216 | 217 | var clientSocketPerThread = new ThreadLocal(); 218 | 219 | string endpoint; 220 | 221 | using (var server = new RouterSocket("@tcp://127.0.0.1:0")) 222 | // If we specify 0, it will choose a random port for us. 223 | { 224 | endpoint = server.Options.LastEndpoint; // Lets us know which port was chosen. 225 | Console.Write("Last endpoint, including port: {0}\n", server.Options.LastEndpoint); 226 | using (var poller = new NetMQPoller()) 227 | { 228 | // Start some threads, each with its own DealerSocket 229 | // to talk to the server socket. Creates lots of sockets, 230 | // but no nasty race conditions no shared state, each 231 | // thread has its own socket, happy days. 232 | for (var i = 0; i < 4; i++) 233 | { 234 | Task.Factory.StartNew(state => 235 | { 236 | DealerSocket client = null; 237 | 238 | if (!clientSocketPerThread.IsValueCreated) 239 | { 240 | client = new DealerSocket(); 241 | client.Options.Identity = 242 | Encoding.Unicode.GetBytes(state.ToString()); 243 | client.Connect(endpoint); 244 | //client.ReceiveReady += Client_ReceiveReady; 245 | clientSocketPerThread.Value = client; 246 | poller.Add(client); 247 | } 248 | else 249 | { 250 | client = clientSocketPerThread.Value; 251 | } 252 | 253 | Thread.Sleep(3000); // Wait until server is up. 254 | client.SendFrame("Ping"); 255 | 256 | while (true) 257 | { 258 | Console.Write("Client {0}: Waiting for ping...\n", i); 259 | // Work around "feature" of router/dealer: the publisher does not know the subscriber exists, until it 260 | // sends at least one message which makes it necessary to open the connection. I believe this is a 261 | // low-level feature of the TCP/IP transport. 262 | 263 | var clientMessage = client.ReceiveMultipartMessage(); 264 | Console.WriteLine("======================================"); 265 | Console.WriteLine(" INCOMING CLIENT MESSAGE FROM SERVER"); 266 | Console.WriteLine("======================================"); 267 | PrintFrames("Server receiving", clientMessage); 268 | } 269 | }, 270 | string.Format("client {0}", i), 271 | TaskCreationOptions.LongRunning); 272 | } 273 | 274 | // start the poller 275 | poller.RunAsync(); 276 | 277 | // server loop 278 | var sequenceNo = 0; 279 | for (var i = 0; i < 10; i++) 280 | { 281 | var messageToServer = new NetMQMessage(); 282 | messageToServer.AppendEmptyFrame(); 283 | messageToServer.Append(sequenceNo.ToString()); 284 | sequenceNo++; 285 | Console.WriteLine("======================================"); 286 | Console.WriteLine(" OUTGOING MESSAGE {0} TO CLIENTS ", sequenceNo); 287 | Console.WriteLine("======================================"); 288 | PrintFrames("Client Sending", messageToServer); 289 | server.SendMultipartMessage(messageToServer); 290 | Thread.Sleep(delay); 291 | } 292 | 293 | Console.WriteLine("Finished."); 294 | } 295 | } 296 | } 297 | } 298 | }*/ -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/SerializeExceptionToXml_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Xml; 7 | using System.Xml.Linq; 8 | using NUnit.Framework; 9 | 10 | // ReSharper disable InconsistentNaming 11 | 12 | namespace NetMQ.ReactiveExtensions.Tests 13 | { 14 | public class SerializeExceptionToXml_Tests 15 | { 16 | 17 | [TestFixture] 18 | public static class Serialize_Exception_To_Xml_Tests 19 | { 20 | [Test] 21 | public static void Convert_Exception_To_XML() 22 | { 23 | DivideByZeroException outerException = new DivideByZeroException(); 24 | ExceptionXElement xmlRaw; 25 | { 26 | try 27 | { 28 | var path = File.ReadAllLines("file does not exist"); 29 | } 30 | catch (Exception ex) 31 | { 32 | outerException = new DivideByZeroException("Outer exception", ex); 33 | } 34 | 35 | xmlRaw = new ExceptionXElement(outerException); 36 | } 37 | 38 | var xml = xmlRaw.ToString(); 39 | 40 | Assert.IsTrue(xml.ToLower().Contains("exception")); 41 | // TODO: Comment this in once .NET Core 1.1 is released. 42 | // Assert.IsTrue(xml.ToLower().Contains("DivideByZeroException")); 43 | Assert.IsTrue(xml.Contains("<")); 44 | Assert.IsTrue(xml.Contains(">")); 45 | } 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/SerializeViaProtoBuf_Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using NUnit.Framework; 3 | // ReSharper disable InconsistentNaming 4 | 5 | namespace NetMQ.ReactiveExtensions.Tests 6 | { 7 | [TestFixture] 8 | public static class SerializeViaProtoBuf_Tests 9 | { 10 | [Test] 11 | public static void Should_be_able_to_serialize_a_decimal() 12 | { 13 | NUnitUtils.PrintTestName(); 14 | 15 | Stopwatch sw = Stopwatch.StartNew(); 16 | 17 | const decimal x = 123.4m; 18 | var rawBytes = x.SerializeProtoBuf(); 19 | var original = rawBytes.DeserializeProtoBuf(); 20 | Assert.AreEqual(x, original); 21 | 22 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 23 | } 24 | 25 | [Test] 26 | public static void Should_be_able_to_serialize_a_string() 27 | { 28 | NUnitUtils.PrintTestName(); 29 | 30 | Stopwatch sw = Stopwatch.StartNew(); 31 | 32 | const string x = "Hello"; 33 | var rawBytes = x.SerializeProtoBuf(); 34 | var original = rawBytes.DeserializeProtoBuf(); 35 | Assert.AreEqual(x, original); 36 | 37 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 38 | } 39 | 40 | [Test] 41 | public static void Should_be_able_to_serialize_an_int() 42 | { 43 | NUnitUtils.PrintTestName(); 44 | 45 | Stopwatch sw = Stopwatch.StartNew(); 46 | 47 | const int x = 42; 48 | var rawBytes = x.SerializeProtoBuf(); 49 | var original = rawBytes.DeserializeProtoBuf(); 50 | Assert.AreEqual(x, original); 51 | 52 | NUnitUtils.PrintElapsedTime(sw.Elapsed); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/packages.config: -------------------------------------------------------------------------------- 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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "dependencies": { 4 | "NetMQ.ReactiveExtensions": "1.0.0-*" 5 | }, 6 | 7 | "testRunner": "nunit", 8 | 9 | "frameworks": { 10 | "netcoreapp1.0": { 11 | "dependencies": { 12 | "Microsoft.NETCore.App": { 13 | "type": "platform", 14 | "version": "1.0.1" 15 | }, 16 | "NUnit": "3.5.0", 17 | "dotnet-test-nunit": "3.4.0-beta-3" 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NetMQ.ReactiveExtensions", "NetMQ.ReactiveExtensions\NetMQ.ReactiveExtensions.xproj", "{1F0D6712-7390-446F-9DCC-E86480360496}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NetMQ.ReactiveExtensions.SamplePublisher", "NetMQ.ReactiveExtensions.SamplePublisher\NetMQ.ReactiveExtensions.SamplePublisher.xproj", "{72BBF61C-B5AC-49C5-B4F6-0EEEB97A98CF}" 9 | EndProject 10 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NetMQ.ReactiveExtensions.SampleSubscriber", "NetMQ.ReactiveExtensions.SampleSubscriber\NetMQ.ReactiveExtensions.SampleSubscriber.xproj", "{F8CAD08F-532C-438B-9B7E-218DD6904840}" 11 | EndProject 12 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NetMQ.ReactiveExtensions.Tests", "NetMQ.ReactiveExtensions.Tests\NetMQ.ReactiveExtensions.Tests.xproj", "{7D1CAE81-2573-427B-A493-41D02A11EB30}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {1F0D6712-7390-446F-9DCC-E86480360496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {1F0D6712-7390-446F-9DCC-E86480360496}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {1F0D6712-7390-446F-9DCC-E86480360496}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {1F0D6712-7390-446F-9DCC-E86480360496}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {72BBF61C-B5AC-49C5-B4F6-0EEEB97A98CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {72BBF61C-B5AC-49C5-B4F6-0EEEB97A98CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {72BBF61C-B5AC-49C5-B4F6-0EEEB97A98CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {72BBF61C-B5AC-49C5-B4F6-0EEEB97A98CF}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {F8CAD08F-532C-438B-9B7E-218DD6904840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {F8CAD08F-532C-438B-9B7E-218DD6904840}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {F8CAD08F-532C-438B-9B7E-218DD6904840}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F8CAD08F-532C-438B-9B7E-218DD6904840}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {7D1CAE81-2573-427B-A493-41D02A11EB30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {7D1CAE81-2573-427B-A493-41D02A11EB30}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {7D1CAE81-2573-427B-A493-41D02A11EB30}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {7D1CAE81-2573-427B-A493-41D02A11EB30}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | /NuGet.Build.bat 3 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/AnonymousDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace NetMQ.ReactiveExtensions 5 | { 6 | public sealed class AnonymousDisposable : IDisposable 7 | { 8 | private readonly Action _action; 9 | private int _disposed = 0; 10 | 11 | public AnonymousDisposable(Action action) 12 | { 13 | _action = action; 14 | } 15 | 16 | public void Dispose() 17 | { 18 | if (Interlocked.Exchange(ref _disposed, 1) == 0) 19 | { 20 | _action(); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/INetMqTransportShared.cs: -------------------------------------------------------------------------------- 1 | using NetMQ.Sockets; 2 | 3 | namespace NetMQ.ReactiveExtensions 4 | { 5 | public interface INetMqTransportShared 6 | { 7 | /// 8 | /// Intent: Get a publisher socket. If the same port is already opened, return it. 9 | /// 10 | /// ZeroMQ address, e.g. "tcp://127.0.0.1:56001". 11 | /// A publisher socket. If we already have one open, it will return the existing one. 12 | PublisherSocket GetSharedPublisherSocket(string addressZeroMq); 13 | 14 | /// 15 | /// Intent: Get a subscriber socket. If the same port is already opened, return it. 16 | /// 17 | /// ZeroMQ address, e.g. "tcp://127.0.0.1:56001". 18 | /// A subscriber socket. If we already have one open, it will return the existing one. 19 | SubscriberSocket GetSharedSubscriberSocket(string addressZeroMq); 20 | } 21 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/IPublisherNetMq.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetMQ.ReactiveExtensions 4 | { 5 | public interface IPublisherNetMq 6 | { 7 | string AddressZeroMq { get; set; } 8 | string SubscriberFilterName { get; set; } 9 | 10 | void Dispose(); 11 | void OnCompleted(); 12 | void OnError(Exception exception); 13 | void OnNext(T message); 14 | } 15 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/ISubjectNetMQ.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetMQ.ReactiveExtensions 4 | { 5 | /// 6 | /// Intent: Send messages anywhere on the network using Reactive Extensions (RX). Uses NetMQ as the transport layer. 7 | /// The API is a drop-in replacement for ISubject of T from Reactive Extensions, see 8 | /// https://github.com/NetMQ/NetMQ.ReactiveExtensions. 9 | /// 10 | /// 11 | public interface ISubjectNetMQ : IDisposable 12 | { 13 | /// 14 | /// Intent: Subscriber Filter name. Defaults to the type name T. Allows many types to get sent over the same 15 | /// transport connection. 16 | /// 17 | string SubscriberFilterName { get; } 18 | 19 | /// 20 | /// Intent: The current endpoint address specified in the constructor, e.g. "tcp://127.0.0.1:56001". 21 | /// 22 | string AddressZeroMq { get; } 23 | } 24 | 25 | /// 26 | /// Intent: Control when the network connection is created. 27 | /// 28 | public enum WhenToCreateNetworkConnection 29 | { 30 | /// 31 | /// Intent: Default mode. The network connection is lazily created on first use. 32 | /// 33 | LazyConnectOnFirstUse = 0, 34 | 35 | /// 36 | /// Intent: Bind to the publisher right now. 37 | /// 38 | SetupPublisherTransportNow = 1, 39 | 40 | /// 41 | /// Intent: Connect to the subscriber right now. 42 | /// 43 | SetupSubscriberTransportNow = 2, 44 | } 45 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/NetMQ.ReactiveExtensions.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 1f0d6712-7390-446f-9dcc-e86480360496 10 | NetMQ.ReactiveExtensions 11 | ..\artifacts\obj\$(MSBuildProjectName) 12 | .\bin\ 13 | 14 | 15 | 2.0 16 | 17 | 18 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/NetMqTransportShared.cs: -------------------------------------------------------------------------------- 1 | #region 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | using NetMQ.Monitoring; 7 | using NetMQ.Sockets; 8 | // ReSharper disable ConvertToAutoProperty 9 | #endregion 10 | 11 | // ReSharper disable ConvertIfStatementToNullCoalescingExpression 12 | // ReSharper disable InvertIf 13 | 14 | namespace NetMQ.ReactiveExtensions 15 | { 16 | /// 17 | /// Intent: We want to have one transport shared among all publishers and subscribers, in this process, if they 18 | /// happen to use the same TCP/IP port. 19 | /// 20 | public class NetMqTransportShared : INetMqTransportShared 21 | { 22 | private static volatile NetMqTransportShared _instance; 23 | private static readonly object _syncRoot = new object(); 24 | 25 | private NetMqTransportShared(Action loggerDelegate = null) 26 | { 27 | this._loggerDelegate = loggerDelegate; 28 | } 29 | 30 | #region HighwaterMark 31 | private int _highwaterMark = 2000 * 1000; 32 | 33 | /// 34 | /// Intent: Default amount of messages to queue in memory before discarding more. 35 | /// 36 | public int HighwaterMark 37 | { 38 | get { return _highwaterMark; } 39 | set { _highwaterMark = value; } 40 | } 41 | #endregion 42 | 43 | private readonly Action _loggerDelegate; 44 | 45 | /// 46 | /// Intent: Singleton. 47 | /// 48 | public static NetMqTransportShared Instance(Action loggerDelegate = null) 49 | { 50 | if (_instance == null) 51 | { 52 | lock (_syncRoot) 53 | { 54 | if (_instance == null) 55 | { 56 | _instance = new NetMqTransportShared(loggerDelegate); 57 | } 58 | } 59 | } 60 | 61 | return _instance; 62 | } 63 | 64 | #region Get Publisher Socket (if it's already been opened, we reuse it). 65 | /// 66 | /// Intent: See interface. 67 | /// 68 | public PublisherSocket GetSharedPublisherSocket(string addressZeroMq) 69 | { 70 | lock (_initializePublisherLock) 71 | { 72 | if (_dictAddressZeroMqToPublisherSocket.ContainsKey(addressZeroMq) == false) 73 | { 74 | _dictAddressZeroMqToPublisherSocket[addressZeroMq] = GetNewPublisherSocket(addressZeroMq); 75 | } 76 | return _dictAddressZeroMqToPublisherSocket[addressZeroMq]; 77 | } 78 | } 79 | 80 | #region Get publisher socket. 81 | private readonly object _initializePublisherLock = new object(); 82 | private readonly ManualResetEvent _publisherReadySignal = new ManualResetEvent(false); 83 | readonly Dictionary _dictAddressZeroMqToPublisherSocket = new Dictionary(); 84 | 85 | private PublisherSocket GetNewPublisherSocket(string addressZeroMq) 86 | { 87 | PublisherSocket publisherSocket; 88 | { 89 | _loggerDelegate?.Invoke(string.Format("Publisher socket binding to: {0}\n", addressZeroMq)); 90 | 91 | publisherSocket = new PublisherSocket(); 92 | 93 | // Corner case: wait until publisher socket is ready (see code below that waits for 94 | // "_publisherReadySignal"). 95 | NetMQMonitor monitor; 96 | { 97 | // Must ensure that we have a unique monitor name for every instance of this class. 98 | string endPoint = string.Format("inproc://#SubjectNetMQ#Publisher#{0}#{1}", addressZeroMq, Guid.NewGuid().ToString()); 99 | monitor = new NetMQMonitor(publisherSocket, 100 | endPoint, 101 | SocketEvents.Accepted | SocketEvents.Listening 102 | ); 103 | monitor.Accepted += Publisher_Event_Accepted; 104 | monitor.Listening += Publisher_Event_Listening; 105 | monitor.StartAsync(); 106 | } 107 | 108 | publisherSocket.Options.SendHighWatermark = this.HighwaterMark; 109 | try 110 | { 111 | publisherSocket.Bind(addressZeroMq); 112 | } 113 | catch (NetMQException ex) 114 | { 115 | // This is usually because the address is in use. 116 | throw new Exception(string.Format("Error E56874. Cannot bind publisher to '{0}'. 95% probability that this is caused by trying to bind a publisher to a port already in use by another process. To fix, choose a unique publisher port for this process. For more on this error, see 'Readme.md' (or the GitHub homepage for NetMQ.ReactiveExtensions).", addressZeroMq), ex); 117 | } 118 | 119 | // Corner case: wait until publisher socket is ready (see code below that sets "_publisherReadySignal"). 120 | { 121 | Stopwatch sw = Stopwatch.StartNew(); 122 | _publisherReadySignal.WaitOne(TimeSpan.FromMilliseconds(3000)); 123 | _loggerDelegate?.Invoke(string.Format("Publisher: Waited {0} ms for binding.\n", sw.ElapsedMilliseconds)); 124 | } 125 | { 126 | monitor.Accepted -= Publisher_Event_Accepted; 127 | monitor.Listening -= Publisher_Event_Listening; 128 | // Current issue with NegMQ: Cannot stop or dispose monitor, or else it stops the parent socket. 129 | //monitor.Stop(); 130 | //monitor.Dispose(); 131 | } 132 | } 133 | 134 | // Otherwise, the first item we publish may get missed by the subscriber. 500 milliseconds consistently works 135 | // locally, but occasionally fails on the AppVeyor build server. 650 milliseconds is optimal. 136 | using (EventWaitHandle wait = new ManualResetEvent(false)) 137 | { 138 | // Cannot use Thread.Sleep() here, as this is incompatible with .NET Core 1.0, Windows 8.0, 8.1, and 10. 139 | wait.WaitOne(TimeSpan.FromMilliseconds(650)); 140 | } 141 | 142 | return publisherSocket; 143 | } 144 | 145 | private void Publisher_Event_Listening(object sender, NetMQMonitorSocketEventArgs e) 146 | { 147 | _loggerDelegate?.Invoke(string.Format("Publisher event: {0}\n", e.SocketEvent)); 148 | _publisherReadySignal.Set(); 149 | } 150 | 151 | private void Publisher_Event_Accepted(object sender, NetMQMonitorSocketEventArgs e) 152 | { 153 | _loggerDelegate?.Invoke(string.Format("Publisher event: {0}\n", e.SocketEvent)); 154 | _publisherReadySignal.Set(); 155 | } 156 | #endregion 157 | #endregion 158 | 159 | #region Get Subscriber socket (if it's already been opened, we reuse it). 160 | private readonly object _initializeSubscriberLock = new object(); 161 | /// 162 | /// Intent: See interface. 163 | /// 164 | public SubscriberSocket GetSharedSubscriberSocket(string addressZeroMq) 165 | { 166 | lock (_initializeSubscriberLock) 167 | { 168 | // Must return a unique subscriber for every new ISubject of T. 169 | var subscriberSocket = new SubscriberSocket(); 170 | subscriberSocket.Options.ReceiveHighWatermark = this.HighwaterMark; 171 | subscriberSocket.Connect(addressZeroMq); 172 | return subscriberSocket; 173 | } 174 | } 175 | #endregion 176 | } 177 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following set of attributes. Change these attribute 6 | // values to modify the information associated with an assembly. 7 | [assembly: AssemblyTitle("NetMQ.ReactiveExtensions")] 8 | [assembly: AssemblyDescription("Effortlessly send messages anywhere on the network using Reactive Extensions (RX). Uses NetMQ as the transport layer. See https://github.com/NetMQ/NetMQ.ReactiveExtensions/.")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("NetMQ.ReactiveExtensions")] 12 | [assembly: AssemblyCopyright("Copyright ©Shane Tolmie and Contributors 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("53c7e7c2-0c41-4123-a555-27ec4aa7f275")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.9.4.7")] 35 | [assembly: AssemblyFileVersion("0.9.4.7")] 36 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/PublisherNetMq.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using NetMQ.Sockets; 7 | 8 | namespace NetMQ.ReactiveExtensions 9 | { 10 | /// 11 | /// Intent: Publisher. 12 | /// 13 | public class PublisherNetMq : ISubjectNetMQ, IObserver, IPublisherNetMq 14 | { 15 | private readonly CancellationTokenSource _cancellationTokenSource; 16 | private readonly Action _loggerDelegate; 17 | private readonly WhenToCreateNetworkConnection _whenToCreateNetworkConnection; 18 | 19 | public string SubscriberFilterName { get; set; } 20 | 21 | public string AddressZeroMq { get; set; } 22 | 23 | /// 24 | /// Intent: Create a new publisher, using NetMQ as the transport layer. 25 | /// 26 | /// ZeroMq address to bind to, e.g. "tcp://localhost:56001 27 | /// Filter name on receiver. If you do not set this, it will default to the 28 | /// type name of T, and everything will just work. 29 | /// 30 | /// 31 | /// 32 | /// 33 | public PublisherNetMq(string addressZeroMq, string subscriberFilterName = null, WhenToCreateNetworkConnection whenToCreateNetworkConnection = WhenToCreateNetworkConnection.SetupPublisherTransportNow, CancellationTokenSource cancellationTokenSource = default(CancellationTokenSource), Action loggerDelegate = null) 34 | { 35 | _cancellationTokenSource = cancellationTokenSource; 36 | _loggerDelegate = loggerDelegate; 37 | _whenToCreateNetworkConnection = whenToCreateNetworkConnection; 38 | _cancellationTokenSource = cancellationTokenSource; 39 | 40 | if (subscriberFilterName == null) 41 | { 42 | // Unfortunately, the subscriber never scans more than the first 32 characters of the filter, so we must 43 | // trim to less than this length, but also ensure that it's unique. This ensures that if we get two 44 | // classnames of 50 characters that only differ by the last character, everything will still work and we 45 | // won't get crossed subscriptions. 46 | SubscriberFilterName = typeof(T).Name; 47 | 48 | if (SubscriberFilterName.Length > 32) 49 | { 50 | string uniqueHashCode = string.Format("{0:X8}", SubscriberFilterName.GetHashCode()); 51 | 52 | SubscriberFilterName = SubscriberFilterName.Substring(0, Math.Min(SubscriberFilterName.Length, 24)); 53 | SubscriberFilterName += uniqueHashCode; 54 | } 55 | if (SubscriberFilterName.Length > 32) 56 | { 57 | throw new Exception("Error E38742. Internal error. Logically, at this point subscription length could never be longer than 32 characters."); 58 | } 59 | } 60 | 61 | if (string.IsNullOrEmpty(Thread.CurrentThread.Name) == true) 62 | { 63 | // Cannot set the thread name twice. 64 | Thread.CurrentThread.Name = subscriberFilterName; 65 | } 66 | 67 | AddressZeroMq = addressZeroMq; 68 | 69 | if (string.IsNullOrEmpty(AddressZeroMq)) 70 | { 71 | throw new Exception("Error E76244. Must define the endpoint address for ZeroMQ to connect (or bind) to."); 72 | } 73 | 74 | switch (whenToCreateNetworkConnection) 75 | { 76 | case WhenToCreateNetworkConnection.LazyConnectOnFirstUse: 77 | // (default) Do nothing; will be instantiated on first use. 78 | break; 79 | case WhenToCreateNetworkConnection.SetupPublisherTransportNow: 80 | InitializePublisherOnFirstUse(addressZeroMq); 81 | break; 82 | case WhenToCreateNetworkConnection.SetupSubscriberTransportNow: 83 | throw new Exception("Error E34875. In the publisher, cannot initialize the subscriber on demand."); 84 | default: 85 | throw new Exception("Error E38745. Internal error; at least one publisher or subscriber must be instantiated."); 86 | } 87 | } 88 | 89 | #region Initialize publisher on demand. 90 | private PublisherSocket _publisherSocket; 91 | private volatile bool _initializePublisherDone = false; 92 | private readonly object _initializePublisherLock = new object(); 93 | private void InitializePublisherOnFirstUse(string addressZeroMq) 94 | { 95 | if (_initializePublisherDone == false) // Double checked locking. 96 | { 97 | lock (_initializePublisherLock) 98 | { 99 | if (_initializePublisherDone == false) 100 | { 101 | _loggerDelegate?.Invoke(string.Format("Publisher socket binding to: {0}\n", AddressZeroMq)); 102 | _publisherSocket = NetMqTransportShared.Instance(_loggerDelegate).GetSharedPublisherSocket(addressZeroMq); 103 | _initializePublisherDone = true; 104 | } 105 | } 106 | } 107 | } 108 | #endregion 109 | 110 | #region Implement IObserver (i.e. the publisher). 111 | public void OnNext(T message) 112 | { 113 | try 114 | { 115 | InitializePublisherOnFirstUse(this.AddressZeroMq); 116 | 117 | byte[] serialized = message.SerializeProtoBuf(); 118 | 119 | // Publish message using ZeroMQ as the transport mechanism. 120 | _publisherSocket.SendMoreFrame(SubscriberFilterName) 121 | .SendMoreFrame("N") // "N", "E" or "C" for "OnNext", "OnError" or "OnCompleted". 122 | .SendFrame(serialized); 123 | 124 | // Comment in the remaining code for the standard pub/sub pattern. 125 | 126 | //if (this.HasObservers == false) 127 | //{ 128 | //throw new QxNoSubscribers("Error E23444. As there are no subscribers to this publisher, this event will be lost."); 129 | //} 130 | 131 | //lock (_subscribersLock) 132 | //{ 133 | //this._subscribers.ForEach(msg => msg.OnNext(message)); 134 | //} 135 | } 136 | catch (InvalidOperationException ex) 137 | { 138 | if (ex.Source.ToLower().Contains("protobuf-net")) 139 | { 140 | var exWithDocs = new InvalidOperationException("Error: Message must be serializable by Protobuf-Net. To fix, annotate message with [ProtoContract] and [ProtoMember(N)]. See help on web."); 141 | this.OnError(exWithDocs); 142 | throw exWithDocs; 143 | } 144 | } 145 | catch (Exception ex) 146 | { 147 | _loggerDelegate?.Invoke(string.Format("Exception: {0}.", ex.Message)); 148 | this.OnError(ex); 149 | throw; 150 | } 151 | } 152 | 153 | public void OnError(Exception exception) 154 | { 155 | InitializePublisherOnFirstUse(this.AddressZeroMq); 156 | 157 | string exceptionAsXml = new ExceptionXElement(exception).ToString(); 158 | _publisherSocket.SendMoreFrame(SubscriberFilterName) 159 | .SendMoreFrame("E") // "N", "E" or "C" for "OnNext", "OnError" or "OnCompleted". 160 | .SendMoreFrame(exceptionAsXml) // Human readable exception. Added for 100% cross-platform debugging, so we can read 161 | // the error on the wire. Visual Studio has a nice viewer for XML in any variable during 162 | // debugging. 163 | .SendFrame(new byte[0]); // Future expansion for machine readable exception. 164 | 165 | // Comment in the remaining code for the standard pub/sub pattern. 166 | 167 | //if (this.HasObservers == false) 168 | //{ 169 | //throw new QxNoSubscribers("Error E28244. As there are no subscribers to this publisher, this published exception will be lost."); 170 | //} 171 | 172 | //lock (_subscribersLock) 173 | //{ 174 | //this._subscribers.ForEach(msg => msg.OnError(exception)); 175 | //} 176 | } 177 | 178 | public void OnCompleted() 179 | { 180 | InitializePublisherOnFirstUse(this.AddressZeroMq); 181 | 182 | _publisherSocket.SendMoreFrame(SubscriberFilterName) 183 | .SendFrame("C"); // "N", "E" or "C" for "OnNext", "OnError" or "OnCompleted". 184 | } 185 | #endregion 186 | 187 | public void Dispose() 188 | { 189 | _cancellationTokenSource.Cancel(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/SerializeExceptionViaXml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NetMQ.ReactiveExtensions 7 | { 8 | using System; 9 | using System.Collections; 10 | using System.Linq; 11 | using System.Xml.Linq; 12 | 13 | /// 14 | /// Intent: Represent an exception as XML data. In the Visual Studio debugger view for a variable, we can "View as XML" to see a formatted version. 15 | /// https://seattlesoftware.wordpress.com/2008/08/22/serializing-exceptions-to-xml/ 16 | /// 17 | public class ExceptionXElement : XElement 18 | { 19 | /// Create an instance of ExceptionXElement. 20 | /// The Exception to serialize. 21 | public ExceptionXElement(Exception exception) 22 | : this(exception, false) 23 | { } 24 | 25 | /// Create an instance of ExceptionXElement. 26 | /// The Exception to serialize. 27 | /// 28 | /// Whether or not to serialize the Exception.StackTrace member 29 | /// if it's not null. 30 | /// 31 | public ExceptionXElement(Exception exception, bool omitStackTrace) 32 | : base(new Func(() => 33 | { 34 | // Validate arguments. 35 | if (exception == null) 36 | { 37 | throw new ArgumentNullException(nameof(exception)); 38 | } 39 | 40 | // The root element is the Exception's type. 41 | // TODO: When .NET Core 1.1 is available, fix these lines. 42 | string type = nameof(exception).ToString(); // Compatible with .NET Core 1.0. 43 | //string type = exception.GetType().ToString(); // Compatible with .NET Core 1.1+. 44 | XElement root = new XElement(type); 45 | 46 | root.Add(new XElement("Message", exception.Message)); 47 | 48 | // StackTrace can be null, e.g. "new ExceptionAsXml(new Exception())". 49 | if (!omitStackTrace && exception.StackTrace != null) 50 | { 51 | root.Add 52 | ( 53 | new XElement("StackTrace", 54 | from frame in exception.StackTrace.Split('\n') 55 | let prettierFrame = frame.Substring(6).Trim() 56 | select new XElement("Frame", prettierFrame)) 57 | ); 58 | } 59 | 60 | // Data is never null; it's empty if there is no data. 61 | if (exception.Data.Count > 0) 62 | { 63 | root.Add 64 | ( 65 | new XElement("Data", 66 | from entry in 67 | exception.Data.Cast() 68 | let key = entry.Key.ToString() 69 | let value = entry.Value?.ToString() ?? "null" 70 | select new XElement(key, value)) 71 | ); 72 | } 73 | 74 | // Recursively add any InnerExceptions if they exist. 75 | if (exception.InnerException != null) 76 | { 77 | root.Add 78 | ( 79 | new ExceptionXElement 80 | (exception.InnerException, omitStackTrace) 81 | ); 82 | } 83 | 84 | return root; 85 | })()) 86 | { } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/SerializeViaProtoBuf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using ProtoBuf; 4 | 5 | namespace NetMQ.ReactiveExtensions 6 | { 7 | /// 8 | /// Intent: Allow us to serialize using ProtoBuf. 9 | /// 10 | public static class SerializeViaProtoBuf 11 | { 12 | public static byte[] SerializeProtoBuf(this T message) 13 | { 14 | byte[] result; 15 | using (var stream = new MemoryStream()) 16 | { 17 | Serializer.Serialize(stream, message); 18 | result = stream.ToArray(); 19 | } 20 | return result; 21 | } 22 | 23 | public static T DeserializeProtoBuf(this byte[] bytes) 24 | { 25 | T result; 26 | using (var stream = new MemoryStream(bytes)) 27 | { 28 | result = Serializer.Deserialize(stream); 29 | } 30 | return result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/SubjectNetMQ.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using NetMQ.Monitoring; 6 | using NetMQ.Sockets; 7 | // ReSharper disable SuggestVarOrType_SimpleTypes 8 | // ReSharper disable InvertIf 9 | #pragma warning disable 649 10 | 11 | namespace NetMQ.ReactiveExtensions 12 | { 13 | public class SubjectNetMQ : IDisposable, ISubjectNetMQ, IObservable, IObserver 14 | { 15 | private readonly PublisherNetMq _publisher; 16 | private readonly SubscriberNetMq _subscriber; 17 | 18 | public SubjectNetMQ(string addressZeroMq, string subscriberFilterName = null, WhenToCreateNetworkConnection whenToCreateNetworkConnection = WhenToCreateNetworkConnection.LazyConnectOnFirstUse, CancellationTokenSource cancellationTokenSource = default(CancellationTokenSource), Action loggerDelegate = null) 19 | { 20 | _publisher = new PublisherNetMq(addressZeroMq, subscriberFilterName, WhenToCreateNetworkConnection.LazyConnectOnFirstUse, cancellationTokenSource, loggerDelegate); 21 | _subscriber = new SubscriberNetMq(addressZeroMq, subscriberFilterName, WhenToCreateNetworkConnection.LazyConnectOnFirstUse, cancellationTokenSource, loggerDelegate); 22 | } 23 | 24 | public string SubscriberFilterName 25 | { 26 | get { return _publisher.SubscriberFilterName; } 27 | } 28 | 29 | public string AddressZeroMq 30 | { 31 | get { return _publisher.SubscriberFilterName; } 32 | } 33 | 34 | public IDisposable Subscribe(IObserver observer) 35 | { 36 | return _subscriber.Subscribe(observer); 37 | } 38 | 39 | public void OnNext(T value) 40 | { 41 | _publisher.OnNext(value); 42 | } 43 | 44 | public void OnError(Exception error) 45 | { 46 | _publisher.OnError(error); 47 | } 48 | 49 | public void OnCompleted() 50 | { 51 | _publisher.OnCompleted(); 52 | } 53 | 54 | public void Dispose() 55 | { 56 | _publisher.Dispose(); 57 | _subscriber.Dispose(); 58 | } 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/SubscriberNetMq.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using NetMQ.Sockets; 5 | 6 | namespace NetMQ.ReactiveExtensions 7 | { 8 | /// 9 | /// Intent: Create a new subscriber of type T, using NetMQ as the transport layer. 10 | /// 11 | /// Type we want to create to. 12 | public class SubscriberNetMq : ISubjectNetMQ, IObservable 13 | { 14 | private CancellationTokenSource _cancellationTokenSource; 15 | private readonly Action _loggerDelegate; 16 | private readonly List> _subscribers = new List>(); 17 | 18 | #region Public properties. 19 | 20 | public string SubscriberFilterName { get; private set; } 21 | public string AddressZeroMq { get; private set; } 22 | 23 | #endregion 24 | 25 | /// 26 | /// Intent: Create a new subscriber, using NetMQ as the transport layer. 27 | /// 28 | /// ZeroMq address to bind to, e.g. "tcp://localhost:56001". 29 | /// Filter name on receiver. If you do not set this, it will default to the 30 | /// type name of T, and everything will just work. 31 | /// When to create the network connection. 32 | /// Allows graceful termination of all internal threads associated with this subject. 33 | /// (optional) If we want to look at messages generated within this class, specify a logger here. 34 | public SubscriberNetMq(string addressZeroMq, string subscriberFilterName = null, 35 | WhenToCreateNetworkConnection whenToCreateNetworkConnection = 36 | WhenToCreateNetworkConnection.SetupSubscriberTransportNow, 37 | CancellationTokenSource cancellationTokenSource = default(CancellationTokenSource), 38 | Action loggerDelegate = null) 39 | { 40 | AddressZeroMq = addressZeroMq; 41 | _cancellationTokenSource = cancellationTokenSource; 42 | _loggerDelegate = loggerDelegate; 43 | 44 | if (subscriberFilterName == null) 45 | { 46 | // Unfortunately, the subscriber never scans more than the first 32 characters of the filter, so we must 47 | // trim to less than this length, but also ensure that it's unique. This ensures that if we get two 48 | // classnames of 50 characters that only differ by the last character, everything will still work and we 49 | // won't get crossed subscriptions. 50 | SubscriberFilterName = typeof(T).Name; 51 | 52 | if (SubscriberFilterName.Length > 32) 53 | { 54 | string uniqueHashCode = string.Format("{0:X8}", SubscriberFilterName.GetHashCode()); 55 | 56 | SubscriberFilterName = SubscriberFilterName.Substring(0, Math.Min(SubscriberFilterName.Length, 24)); 57 | SubscriberFilterName += uniqueHashCode; 58 | } 59 | if (SubscriberFilterName.Length > 32) 60 | { 61 | throw new Exception( 62 | "Error E38742. Internal error; subscription length can never be longer than 32 characters."); 63 | } 64 | } 65 | 66 | if (string.IsNullOrEmpty(Thread.CurrentThread.Name) == true) 67 | { 68 | // Cannot set the thread name twice. 69 | Thread.CurrentThread.Name = subscriberFilterName; 70 | } 71 | 72 | AddressZeroMq = addressZeroMq; 73 | 74 | if (string.IsNullOrEmpty(AddressZeroMq)) 75 | { 76 | throw new Exception("Error E76244. Must define the endpoint address for ZeroMQ to connect (or bind) to."); 77 | } 78 | 79 | switch (whenToCreateNetworkConnection) 80 | { 81 | case WhenToCreateNetworkConnection.LazyConnectOnFirstUse: 82 | // (default) Do nothing; will be instantiated on first use. 83 | break; 84 | case WhenToCreateNetworkConnection.SetupPublisherTransportNow: 85 | throw new Exception( 86 | "Error E77238. Internal error. Cannot instantate the publisher in the subscriber."); 87 | case WhenToCreateNetworkConnection.SetupSubscriberTransportNow: 88 | InitializeSubscriberOnFirstUse(addressZeroMq); 89 | break; 90 | default: 91 | throw new Exception( 92 | "Error E38745. Internal error. At least one publisher or subscriber must be instantiated."); 93 | } 94 | 95 | } 96 | 97 | #region Initialize subscriber on demand. 98 | 99 | private SubscriberSocket _subscriberSocket; 100 | private readonly object _subscribersLock = new object(); 101 | private volatile bool _initializeSubscriberDone = false; 102 | private Thread _thread; 103 | private ManualResetEvent _threadWaitExitHandle = new ManualResetEvent(false); 104 | 105 | private void InitializeSubscriberOnFirstUse(string addressZeroMq) 106 | { 107 | if (_initializeSubscriberDone == false) // Double checked locking. 108 | { 109 | lock (_subscribersLock) 110 | { 111 | if (_initializeSubscriberDone == false) 112 | { 113 | if (_cancellationTokenSource == null) 114 | { 115 | _cancellationTokenSource = new CancellationTokenSource(); 116 | } 117 | 118 | _subscriberSocket = 119 | NetMqTransportShared.Instance(_loggerDelegate).GetSharedSubscriberSocket(addressZeroMq); 120 | 121 | ManualResetEvent threadReadySignal = new ManualResetEvent(false); 122 | 123 | _thread = new Thread(() => 124 | { 125 | try 126 | { 127 | _loggerDelegate?.Invoke(string.Format("Thread initialized.\n")); 128 | threadReadySignal.Set(); 129 | while (_cancellationTokenSource.IsCancellationRequested == false) 130 | { 131 | //_loggerDelegate?.Invoke(string.Format("Received message for {0}.\n", typeof(T))); 132 | 133 | string messageTopicReceived = _subscriberSocket.ReceiveFrameString(); 134 | if (messageTopicReceived != SubscriberFilterName) 135 | { 136 | // This message is for another subscriber. This should never occur. 137 | #if DEBUG 138 | throw new Exception( 139 | "Error E38444. Internal exception, this should never occur, as the ZeroMQ lib automaticlaly filters by subject name."); 140 | #else 141 | return; 142 | #endif 143 | } 144 | var type = _subscriberSocket.ReceiveFrameString(); 145 | switch (type) 146 | { 147 | // Originated from "OnNext". 148 | case "N": 149 | T messageReceived = 150 | _subscriberSocket.ReceiveFrameBytes().DeserializeProtoBuf(); 151 | lock (_subscribersLock) 152 | { 153 | try 154 | { 155 | _subscribers.ForEach(o => o.OnNext(messageReceived)); 156 | } 157 | catch (Exception ex) 158 | { 159 | // If an unhandled exception is thrown inside the clients OnNext() event, we don't want this thread to die! 160 | _loggerDelegate?.Invoke(string.Format("Exception thrown on Subscription OnNext handler. Suggest adding an exception handler. Ignoring. Exception: {0}\n", ex)); 161 | } 162 | } 163 | break; 164 | // Originated from "OnCompleted". 165 | case "C": 166 | lock (_subscribersLock) 167 | { 168 | try 169 | { 170 | _subscribers.ForEach(o => o.OnCompleted()); 171 | } 172 | catch (Exception ex) 173 | { 174 | // If an unhandled exception is thrown inside the clients OnCompleted() event, we don't want this thread to die! 175 | _loggerDelegate?.Invoke(string.Format("Exception thrown on Subscription OnCompleted handler. Suggest adding an exception handler. Ignoring. Exception: {0}\n", ex)); 176 | } 177 | 178 | // We are done! We don't want to send any more messages to subscribers, and we 179 | // want to close the listening socket. 180 | _cancellationTokenSource.Cancel(); 181 | } 182 | break; 183 | // Originated from "OnException". 184 | case "E": 185 | Exception exception = null; 186 | try 187 | { 188 | // Not used, but useful for cross-platform debugging: we can read the error straight off the wire. 189 | string exceptionAsXml = _subscriberSocket.ReceiveFrameString(); 190 | exception = new Exception(exceptionAsXml); 191 | _subscriberSocket.ReceiveFrameBytes(); // For future expansion for a machine readable exception. 192 | } 193 | catch (Exception ex) 194 | { 195 | // If we had trouble deserializing the exception (probably due to a 196 | // different version of .NET), then do the next best thing: (1) The 197 | // inner exception is the error we got when deserializing, and (2) the 198 | // main exception is the human-readable "exception.ToString()" that we 199 | // originally captured. 200 | exception = new Exception("Error deserializing exception. To fix, recompile the remote sender with the same version as this subscriber.", ex); 201 | } 202 | 203 | lock (_subscribersLock) 204 | { 205 | try 206 | { 207 | _subscribers.ForEach(o => o.OnError(exception)); 208 | } 209 | catch (Exception ex) 210 | { 211 | // If an unhandled exception is thrown inside the clients OnCompleted() event, we don't want this thread to die! 212 | _loggerDelegate?.Invoke(string.Format("Exception thrown in Subscription OnError handler. Suggest adding an exception handler. Ignoring. Exception: {0}\n", ex)); 213 | } 214 | } 215 | break; 216 | // Originated from a "Ping" request. 217 | case "P": 218 | // Do nothing, this is a ping command used to wait until sockets are initialized properly. 219 | _loggerDelegate?.Invoke(string.Format("Received ping.\n")); 220 | break; 221 | default: 222 | throw new Exception(string.Format("Error E28734. Something is wrong - received '{0}' when we expected \"N\", \"C\" or \"E\" - are we out of sync?", type)); 223 | } 224 | } 225 | } 226 | catch (Exception ex) 227 | { 228 | _loggerDelegate?.Invoke(string.Format("Error E23844. Exception in threadName \"{0}\". Thread exiting. Exception: \"{1}\".\n", SubscriberFilterName, ex.Message)); 229 | lock (_subscribersLock) 230 | { 231 | this._subscribers.ForEach((ob) => ob.OnError(ex)); 232 | } 233 | } 234 | finally 235 | { 236 | lock (_subscribersLock) 237 | { 238 | _subscribers.Clear(); 239 | } 240 | _cancellationTokenSource.Dispose(); 241 | 242 | // Disconnect from the socket. 243 | _subscriberSocket.Dispose(); 244 | 245 | _threadWaitExitHandle.Set(); 246 | } 247 | }) 248 | { 249 | Name = this.SubscriberFilterName, 250 | IsBackground = true // Have to set it to background, or else it will not exit when the program exits. 251 | }; 252 | _thread.Start(); 253 | 254 | // Wait for subscriber thread to properly spin up. 255 | threadReadySignal.WaitOne(TimeSpan.FromMilliseconds(3000)); 256 | 257 | // Intent: Now connect to the socket. 258 | { 259 | _loggerDelegate?.Invoke(string.Format("Subscriber socket connecting to: {0}\n", addressZeroMq)); 260 | 261 | // this.SubscriberFilterName is set to the type T of the incoming class by default, so we can 262 | // have many types on the same transport. 263 | _subscriberSocket.Subscribe(this.SubscriberFilterName); 264 | } 265 | 266 | _loggerDelegate?.Invoke(string.Format("Subscriber: finished setup.\n")); 267 | 268 | _initializeSubscriberDone = true; 269 | } 270 | } // lock 271 | Thread.Sleep(500); // Otherwise, the first item we subscribe to may get missed by the subscriber. 272 | } 273 | } 274 | #endregion 275 | 276 | #region IObservable (i.e. the subscriber) 277 | /// 278 | /// Intent: Exactly the same as "Subscribe" in Reactive Extensions (RX). 279 | /// 280 | /// 281 | /// 282 | public IDisposable Subscribe(IObserver observer) 283 | { 284 | lock (_subscribersLock) 285 | { 286 | this._subscribers.Add(observer); 287 | } 288 | 289 | InitializeSubscriberOnFirstUse(this.AddressZeroMq); 290 | 291 | // Could return ".this", but this would introduce an issue: if one subscriber unsubscribed, it would 292 | // unsubscribe all subscribers. 293 | return new AnonymousDisposable(() => 294 | { 295 | lock (_subscribersLock) 296 | { 297 | this._subscribers.Remove(observer); 298 | } 299 | }); 300 | } 301 | #endregion 302 | 303 | public void Dispose() 304 | { 305 | lock (_subscribersLock) 306 | { 307 | _subscribers.Clear(); 308 | } 309 | _cancellationTokenSource.Cancel(); 310 | 311 | // Wait until the thread has exited. 312 | bool threadExitedProperly = _threadWaitExitHandle.WaitOne(TimeSpan.FromSeconds(30)); 313 | if (threadExitedProperly == false) 314 | { 315 | throw new Exception("Error E62724. Thread did not exit when requested."); 316 | } 317 | } 318 | } 319 | } -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/packages.config: -------------------------------------------------------------------------------- 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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /NetMQ.ReactiveExtensions/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "dependencies": { 4 | "NetMQ": "4.0.0-rc5", 5 | "protobuf-net": "2.1.0", 6 | "System.Reactive.Core": "3.1.0" 7 | }, 8 | "runtimes": { 9 | "win7-x64": {} 10 | }, 11 | "frameworks": { 12 | "netstandard1.6": {}, 13 | "net45": { 14 | "dependencies": { 15 | "System.Xml.Linq": "3.5.*" 16 | } 17 | }, 18 | "net46": { 19 | "dependencies": { 20 | 21 | }, 22 | "frameworkAssemblies": { 23 | "System.Xml.Linq": "4.0.*" 24 | } 25 | } 26 | }, 27 | "buildOptions": { 28 | "xmlDoc": true 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /NuGet/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | /NuGet.Build.bat 3 | -------------------------------------------------------------------------------- /NuGet/NetMQ.ReactiveExtensions.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NetMQ.ReactiveExtensions 5 | $version$ 6 | NetMQ Reactive Extensions 7 | NetMQ 8 | NetMQ 9 | https://github.com/NetMQ/NetMQ.ReactiveExtensions/blob/master/LICENSE 10 | https://github.com/NetMQ/NetMQ.ReactiveExtensions 11 | https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/img/NetMQ.ico 12 | false 13 | Reactive Extensions (RX) with NetMQ as the transport layer. Send messages anywhere on the network! 14 | 15 | Now compatible with .NET Core 1.0. Feedback welcome! 16 | 17 | Copyright 2016 18 | Reactive Extensions RX .NET C# ZeroMQ 0MQ CLRZMQ NetMQ Messaging ZMQ transport distributed 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /NuGet/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetMQ.ReactiveExtensions 2 | 3 | [![Build](https://img.shields.io/appveyor/ci/drewnoakes/netmq-reactiveextensions.svg)](https://ci.appveyor.com/project/drewnoakes/netmq-reactiveextensions) [![NuGet](https://img.shields.io/nuget/v/NetMQ.ReactiveExtensions.svg)](https://www.nuget.org/packages/NetMQ.ReactiveExtensions/) [![NuGet prerelease](https://img.shields.io/nuget/vpre/NetMQ.ReactiveExtensions.svg)](https://www.nuget.org/packages/NetMQ.ReactiveExtensions/) 4 | 5 | Effortlessly send messages anywhere on the network using Reactive Extensions (RX). Uses NetMQ as the transport layer. 6 | 7 | Fast! Runs at >120,000 messages per second on localhost (by comparison, Tibco runs at 100,000 on the same machine). 8 | 9 | ## Sample Code 10 | 11 | The API is a drop-in replacement for `Subject` from Reactive Extensions (RX). 12 | 13 | As a refresher, to use `Subject` in Reactive Extensions (RX): 14 | 15 | ```csharp 16 | var subject = new Subject(); 17 | subject.Subscribe(message => 18 | { 19 | // If we get an error "Cannot convert lambda ... not a delegate type", install Reactive Extensions from NuGet. 20 | Console.Write(message); // Prints "42". 21 | }); 22 | subject.OnNext(42); 23 | ``` 24 | 25 | The new API starts with a drop-in replacement for `Subject`: 26 | 27 | ```csharp 28 | var subject = new SubjectNetMQ("tcp://127.0.0.1:56001"); 29 | subject.Subscribe(message => 30 | { 31 | Console.Write(message); // Prints "42". 32 | }); 33 | subject.OnNext(42); // Sends 42. 34 | ``` 35 | 36 | This is great for a demo, but is not recommended for any real life application. 37 | 38 | For those of us familiar with Reactive Extensions (RX), `Subject` is a combination of a publisher and a subscriber. If we are running a real-life application, we should separate out the publisher and the subscriber, because this means we can create the connection earlier which makes the transport setup more deterministic: 39 | 40 | ```csharp 41 | var publisher = new PublisherNetMq("tcp://127.0.0.1:56001"); 42 | var subscriber = new SubscriberNetMq("tcp://127.0.0.1:56001"); 43 | subscriber.Subscribe(message => 44 | { 45 | Console.Write(message); // Prints "42". 46 | }); 47 | publisher.OnNext(42); // Sends 42. 48 | ``` 49 | 50 | If we want to run in separate applications: 51 | 52 | ```csharp 53 | // Application 1 (subscriber) 54 | var subscriber1 = new SubscriberNetMq("tcp://127.0.0.1:56001"); 55 | subscriber1.Subscribe(message => 56 | { 57 | Console.Write(message); // Prints "42". 58 | }); 59 | 60 | // Application 2 (subscriber) 61 | var subscriber2 = new SubscriberNetMq("tcp://127.0.0.1:56001"); 62 | subscriber2.Subscribe(message => 63 | { 64 | Console.Write(message); // Prints "42". 65 | }); 66 | 67 | // Application 3 (publisher) 68 | var publisher = new PublisherNetMq("tcp://127.0.0.1:56001"); 69 | publisher.OnNext(42); // Sends 42. 70 | ``` 71 | 72 | Currently, serialization is performed using [ProtoBuf](https://github.com/mgravell/protobuf-net "ProtoBuf"). It will handle simple types such as `int` without annotation, but if we want to send more complex classes, we have to annotate like this: 73 | 74 | ```csharp 75 | // For Protobuf support, include NuGet package protobuf-net from Marc Gravell. 76 | [ProtoContract] 77 | public struct MyMessage 78 | { 79 | [ProtoMember(1)] 80 | public int Num { get; set; } 81 | [ProtoMember(2)] 82 | public string Name { get; set; } 83 | } 84 | 85 | var publisher = new PublisherNetMq("tcp://127.0.0.1:56001"); 86 | var subscriber = new SubscriberNetMq("tcp://127.0.0.1:56001"); 87 | subscriber.Subscribe(message => 88 | { 89 | Console.Write(message.Num); // Prints "42". 90 | Console.Write(message.Name); // Prints "Bill". 91 | }); 92 | publisher.OnNext(new MyMessage(42, "Bill"); 93 | ``` 94 | 95 | ## NuGet Package 96 | 97 | [![Build](https://img.shields.io/appveyor/ci/drewnoakes/netmq-reactiveextensions.svg)](https://ci.appveyor.com/project/drewnoakes/netmq-reactiveextensions) [![NuGet](https://img.shields.io/nuget/v/NetMQ.ReactiveExtensions.svg)](https://www.nuget.org/packages/NetMQ.ReactiveExtensions/) [![NuGet prerelease](https://img.shields.io/nuget/vpre/NetMQ.ReactiveExtensions.svg)](https://www.nuget.org/packages/NetMQ.ReactiveExtensions/) 98 | 99 | See [NetMQ.ReactiveExtensions](https://www.nuget.org/packages/NetMQ.ReactiveExtensions/). 100 | 101 | The NuGet package 0.9.3 is designed for .NET 4. It depends on Reactive Extensions v2.2.5 (this is difficult to find, can download the packages manually from NuGet). 102 | 103 | The NuGet package 0.9.4-rc7 is designed .NET Core 1.1, .NET 4.5, and .NET Standard 1.6. If you want to build it for other platforms, please let me know. If you have trouble loading this, load the Git branch for the 0.9.3 release. 104 | 105 | ## .NET Core 1.1 Ready 106 | 107 | As of v0.9.4-rc7, this package will build for: 108 | - .NET 4.5 and up 109 | - [.NET Core 1.1](https://www.microsoft.com/net/download/core) 110 | - .NET Standard 1.6 and up 111 | 112 | As this library supports .NET Standard 1.6 (which is a subset of .NET Core 1.1), this library should be compatible with: 113 | - Windows 114 | - Linux 115 | - Mac 116 | 117 | This library is tested on Window and Linux. If it passes it's unit tests on any given platform, then it should perform nicely on different architectures such as Mac. 118 | 119 | ## Compiling from source 120 | 121 | - Install [Visual Studio 2015 Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2015-update3-vs). 122 | - Install "[.NET Core 1.1 SDK - Installer](https://www.microsoft.com/net/download/core)" from https://www.microsoft.com/net/download/core. 123 | - NuGet Restore. It may not compile until a manual "[nuget restore](https://docs.nuget.org/ndocs/consume-packages/package-restore)" is performed for each project (this also rebuilds the `project.lock.json` file). You can either do this from the command line, or by right clicking on the solution and choosing `Restore NuGet packages`. 124 | - If the project does not compile on your machine, raise an issue here on GitHub. 125 | 126 | NOTE: Not compatible with .NET Core 1.0 or .NET Core 1.0.1. Must install .NET Core 1.1 and above to avoid potential compile errors. 127 | 128 | ## Demos 129 | 130 | To check out the demos, see: 131 | - Publisher: Project `NetMQ.ReactiveExtensions.SamplePublisher` 132 | - Subscriber: Project `NetMQ.ReactiveExtensions.SampleSubscriber` 133 | - Sample unit tests: Project `NetMQ.ReactiveExtensions.Tests` 134 | 135 | ## Performance 136 | 137 | - Runs at >120,000 messages per second on localhost. 138 | 139 | ## 100% compatible with Reactive Extensions (RX) 140 | 141 | - Compatible with all existing Reactive Extensions code, as it implements IObservable and IObserver from Microsoft. 142 | - Can use `.Where()`, `.Select()`, `.Buffer()`, `.Throttle()`, etc. 143 | - Supports `.OnNext()`, `.OnException()`, and `.OnCompleted()`. 144 | - Properly passes exceptions across the wire. 145 | 146 | ## Unit tests 147 | 148 | - Supported by a full suite of unit tests. 149 | 150 | ## Projects like this one that do messaging 151 | 152 | - See [Obvs](https://github.com/inter8ection/Obvs), an fantastic RX wrapper which supports many transport layers including NetMQ, RabbitMQ and Azure, and many serialization methods including ProtoBuf and MsgPack. 153 | - See [Obvs.NetMQ](https://github.com/inter8ection/Obvs.Netmq), the RX wrapper with NetMQ as the transport layer. 154 | - Search for [all packages on NuGet that depend on RX](http://nugetmusthaves.com/Dependencies/Rx-Linq), and pick out the ones that are related to message buses. 155 | - Check out Kafka. It provides many-to-many messaging, with persistance, and multi-node redundancy. And its blindingly fast. 156 | 157 | ## Wiki 158 | 159 | See the [Wiki with more documentation](https://github.com/NetMQ/NetMQ.ReactiveExtensions/wiki). 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /img/NetMQ.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQ.ico -------------------------------------------------------------------------------- /img/NetMQLogo-128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQLogo-128px.png -------------------------------------------------------------------------------- /img/NetMQLogo-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQLogo-32px.png -------------------------------------------------------------------------------- /img/NetMQLogo-400px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQLogo-400px.png -------------------------------------------------------------------------------- /img/NetMQLogo-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQLogo-48px.png -------------------------------------------------------------------------------- /img/NetMQLogo-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetMQ/NetMQ.ReactiveExtensions/db9004a40ee465d166ebb619cfee97306c75fd81/img/NetMQLogo-64px.png -------------------------------------------------------------------------------- /img/NetMQLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 23 | --------------------------------------------------------------------------------