From f4bd7e12fd63ca5c4a12b5a4ac22bae92422156e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 May 2011 22:49:55 +0100 Subject: [PATCH] Initial commit Initial commit of hxzmq, covering core 2.1.6 libzmq functionality. Unit tests and guide examples built and tested on following targets: - cpp (Mac64, WindowsXP) - neko (WindowsXP) --- .gitignore | 5 + .tm-autocomplete.hxml | 6 + .tmbuild | 1 + INSTALL.md | 14 + README | 12 - README.md | 153 +++++++ bin/ndll/Mac64/hxzmq.ndll | Bin 0 -> 48816 bytes bin/ndll/Windows/hxzmq.ndll | Bin 0 -> 95232 bytes bin/ndll/hxzmq.ndll | Bin 0 -> 44608 bytes build.bat | 26 ++ build.sh | 24 ++ build.xml | 49 +++ guide/buildMac64.hxml | 11 + guide/buildWindows.hxml | 16 + haxelib.xml | 9 + hxzmq.hxproj | 48 +++ org/zeromq/ZMQ.hx | 516 ++++++++++++++++++++++++ org/zeromq/ZMQContext.hx | 124 ++++++ org/zeromq/ZMQException.hx | 57 +++ org/zeromq/ZMQPoller.hx | 183 +++++++++ org/zeromq/ZMQSocket.hx | 351 ++++++++++++++++ org/zeromq/guide/HelloWorldClient.hx | 63 +++ org/zeromq/guide/HelloWorldServer.hx | 69 ++++ org/zeromq/guide/Interrupt.hx | 70 ++++ org/zeromq/guide/MTServer.hx | 156 +++++++ org/zeromq/guide/Run.hx | 105 +++++ org/zeromq/guide/TaskSink.hx | 73 ++++ org/zeromq/guide/TaskVent.hx | 82 ++++ org/zeromq/guide/TaskWork.hx | 71 ++++ org/zeromq/guide/WUClient.hx | 89 ++++ org/zeromq/guide/WUServer.hx | 58 +++ org/zeromq/test/BaseTest.hx | 160 ++++++++ org/zeromq/test/TestAll.hx | 46 +++ org/zeromq/test/TestContext.hx | 114 ++++++ org/zeromq/test/TestError.hx | 54 +++ org/zeromq/test/TestInterrupt.hx | 55 +++ org/zeromq/test/TestMultiPartMessage.hx | 64 +++ org/zeromq/test/TestPoller.hx | 193 +++++++++ org/zeromq/test/TestPubSub.hx | 91 +++++ org/zeromq/test/TestReqRep.hx | 125 ++++++ org/zeromq/test/TestSocket.hx | 190 +++++++++ org/zeromq/test/TestVersion.hx | 56 +++ src/Context.cpp | 76 ++++ src/Interrupt.cpp | 57 +++ src/Poller.cpp | 101 +++++ src/Socket.cpp | 484 ++++++++++++++++++++++ src/ZMQ.cpp | 383 ++++++++++++++++++ src/include/hxzmq.h | 0 src/socket.h | 24 ++ src/valgrind.supp | 92 +++++ test/buildMac64.hxml | 15 + test/buildWindows.hxml | 14 + test/out-cpp/hxzmq.ndll | Bin 0 -> 44672 bytes 53 files changed, 4823 insertions(+), 12 deletions(-) create mode 100644 .gitignore create mode 100644 .tm-autocomplete.hxml create mode 100644 .tmbuild create mode 100644 INSTALL.md delete mode 100644 README create mode 100644 README.md create mode 100755 bin/ndll/Mac64/hxzmq.ndll create mode 100644 bin/ndll/Windows/hxzmq.ndll create mode 100644 bin/ndll/hxzmq.ndll create mode 100644 build.bat create mode 100755 build.sh create mode 100644 build.xml create mode 100644 guide/buildMac64.hxml create mode 100644 guide/buildWindows.hxml create mode 100644 haxelib.xml create mode 100644 hxzmq.hxproj create mode 100644 org/zeromq/ZMQ.hx create mode 100644 org/zeromq/ZMQContext.hx create mode 100644 org/zeromq/ZMQException.hx create mode 100644 org/zeromq/ZMQPoller.hx create mode 100644 org/zeromq/ZMQSocket.hx create mode 100644 org/zeromq/guide/HelloWorldClient.hx create mode 100644 org/zeromq/guide/HelloWorldServer.hx create mode 100644 org/zeromq/guide/Interrupt.hx create mode 100644 org/zeromq/guide/MTServer.hx create mode 100644 org/zeromq/guide/Run.hx create mode 100644 org/zeromq/guide/TaskSink.hx create mode 100644 org/zeromq/guide/TaskVent.hx create mode 100644 org/zeromq/guide/TaskWork.hx create mode 100644 org/zeromq/guide/WUClient.hx create mode 100644 org/zeromq/guide/WUServer.hx create mode 100644 org/zeromq/test/BaseTest.hx create mode 100644 org/zeromq/test/TestAll.hx create mode 100644 org/zeromq/test/TestContext.hx create mode 100644 org/zeromq/test/TestError.hx create mode 100644 org/zeromq/test/TestInterrupt.hx create mode 100644 org/zeromq/test/TestMultiPartMessage.hx create mode 100644 org/zeromq/test/TestPoller.hx create mode 100644 org/zeromq/test/TestPubSub.hx create mode 100644 org/zeromq/test/TestReqRep.hx create mode 100644 org/zeromq/test/TestSocket.hx create mode 100644 org/zeromq/test/TestVersion.hx create mode 100644 src/Context.cpp create mode 100644 src/Interrupt.cpp create mode 100644 src/Poller.cpp create mode 100644 src/Socket.cpp create mode 100644 src/ZMQ.cpp create mode 100644 src/include/hxzmq.h create mode 100644 src/socket.h create mode 100644 src/valgrind.supp create mode 100644 test/buildMac64.hxml create mode 100644 test/buildWindows.hxml create mode 100755 test/out-cpp/hxzmq.ndll diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0f94fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Git ignore file for hxzmq +lib/ +obj/ +all_objs +vc90.pdb diff --git a/.tm-autocomplete.hxml b/.tm-autocomplete.hxml new file mode 100644 index 0000000..053b99e --- /dev/null +++ b/.tm-autocomplete.hxml @@ -0,0 +1,6 @@ +# Haxe build file +-cp src +-cp test/src +-cpp out +-D HXCPP_M64 +-main TestZMQ diff --git a/.tmbuild b/.tmbuild new file mode 100644 index 0000000..3f14470 --- /dev/null +++ b/.tmbuild @@ -0,0 +1 @@ +/Users/richardsmith/Projects/hxzmq/build.hxml \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..45ee9ae --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,14 @@ +hxzmq Installation Instructions +=============================== + +## haxelib Installation + +To be able to just use hxzmq to build 0MQ messaging into new haXe applications, without modifying the or re-building hxzmq from source, you need: + +1. Install the current hxzmq package using the haXe haxelib tool + haxelib install hxzmq +2. To build your application including the hxzmq project, include the hxzmq library in your hxml haXe compilation file: + -lib hxzmq +3. To run your target executable, your system will need to reach the hxzmq.ndll library file and the libzmq.dll 0MQ library file. Add paths to both to your PATH environment variable, or copy both files into the same folder as your newly-minted haXe executable (e.g. either a neko .n or cpp executable). + + diff --git a/README b/README deleted file mode 100644 index bd59e5f..0000000 --- a/README +++ /dev/null @@ -1,12 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\paperw11900\paperh16840\margl1440\margr1440\vieww9000\viewh8400\viewkind0 -\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural - -\f0\fs24 \cf0 Haxe Bindings for ZeroMQ\ -------------------------------------\ -\ -This repository contains hxzmq, Haxe language bindings for the ZeroMQ messaging library.\ -\ -} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf2a8ed --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +hxzmq - haXe Language Bindings for ZeroMQ +========================================= + +Welcome to hxzmq, [haXe language bindings] [2] for the iMatix [ZeroMQ messaging library] [3]. 0MQ is a lightweight and fast messaging implementation. + +By Richard Smith [RSBA Technology Ltd] [1] + + +## Introduction +This repository provides C++ binding code that wraps the libzmq library API to create a Neko DLL file, hxzmq.ndll. The ndll is then accessed via the hxzmq org.zeromq package to expose the 0MQ API to haXe application code targetted at C++ or nekovm platforms. +### Background & Rationale +haXe enables applications to be written in a single unified programming language that can then be executed on any combination of an ever-growing number of [target language platforms.] [6]. It is quite possible to write back-end server code targetted at php or C++, with a rich internet application Flash or javascript front-end, plus an iPhone application (via the C++ target), all using a single shared haXe codebase. Code written using non-target specific APIs can be automatically re-used on any of these platforms, such as an application's internal domain model or framework code. Conditional compilation, together with many target - specific APIs contained in the [haXe standard library] [7], provides the opportunity to access platform-specific features, giving the best of both worlds. Most of the target platforms also support extending the standard capabilities by use of externs and Foreign Function Interface mechanisms; an ability which has been used to write hxzmq. haXe is an [open source project] [7]. + +0MQ is a relatively recent entrant into the messaging layer software space, conceived as a low-level, cross-platform but highly - performant messaging library that can replace direct use of in-process and tcp sockets etc with a message-orientated API. It implements a number of well-defined message patterns (e.g. Request-Reply, Publish-Subscribe) that allow very complex distributed system architectures to be built from simple parts. 0MQ is maintained as an [open source project] [8] by iMatix. + +hxzmq has been written to allow the author (and anyone else who might be interested) to explore and experiment what improvements in software design practise can be made when a cross-platform language is coupled with a highly performant cross-platform messaging layer. +### haXe Code Example + +Client: + package ; + import haxe.io.Bytes; + import org.zeromq.ZMQ; + import org.zeromq.ZMQContext; + import org.zeromq.ZMQSocket; + + /** + * Hello World client in Haxe. + */ + class HelloWorldClient + { + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + var socket:ZMQSocket = context.socket(ZMQ_REQ); + socket.connect ("tcp://localhost:5556"); + for (i in 0...10) { + var requestString = "Hello "; + socket.sendMsg(Bytes.ofString(requestString)); + var msg:Bytes = socket.recvMsg(); + trace ("Received reply " + i + ": [" + msg.toString() + "]"); + } + socket.close(); + context.term(); + } + } + +Server: + package ; + import haxe.io.Bytes; + import org.zeromq.ZMQ; + import org.zeromq.ZMQContext; + import org.zeromq.ZMQException; + import org.zeromq.ZMQSocket; + + /** + * Hello World server in Haxe + * Binds REP to tcp://*:5556 + * Expects "Hello" from client, replies with "World" + * + */ + class HelloWorldServer + { + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + var responder:ZMQSocket = context.socket(ZMQ_REP); + responder.bind("tcp://*:5556"); + while (true) { + var request:Bytes = responder.recvMsg(); + // Do some work + Sys.sleep(1); + // Send reply back to client + responder.sendMsg(Bytes.ofString("World")); + } + responder.close(); + context.term(); + } + } + +## Contents + +Key files and folders contained in this repository: + +* *build.xml* + + XML compilation configuration file used by the hxcpp cross-platform ndll build tool to compile & build the hxzmq.ndll library. See INSTALL.md for further details. + +* *build.sh* + + Mac/linux build shell script that builds hxzmq.ndll, unit test and guide programs + +* *build.bat* + + Windows script file that builds hxzmq.ndll, unit test and guide programs + +* */src* + + The C++ code that wraps the libzmq C library in [hxcpp CFFI] [10] calls, which exposes it to the haXe layer. Compiles into the hxzmq.ndll library. + +* */org/zeromq* + + The haXe code that invokes the native functions defined in hxzmq.ndll. Provides the core API used by haXe applications. + +* */org/zeromq/test* + + Unit tests for the org.zeromq package. Main program invoked from the TestAll.hx class. + +* */org/zeromq/guide* + + haXe implementations of some code examples included in the [0MQ Guide] [11]. Can be compiled separately, or via the menu class, Run.hx. + +* */test* + + Contains build hxml files for compiling the unit tests on different platforms. + +* */guide* + + Contains build hxml files for compiling the 0MQ Guide code examples on different platforms + +* */bin/ndll* + + Contains pre-built ndll files for different platforms + +## Versions + +The current release of hxzmq is 1.0.0, compatable with libzmq-2.1.4 or any later 2.1.x version. The latest released hxzmq package shall also be available in the [haxelib repository] [4], accessable via the [haxelib tool] [5] which is included in the standard haXe distribution. + +## Building and Installation + +If you are a haXe user who just wants to start using 0MQ and hxzmq in your projects, make sure libzmq.dll is available on your system's PATH setting, and then simply install the latest hxzmq package available from the haXe repository, using the haxelib tool: + + haxelib install hxzmq + +Please refer to the separate INSTALL.md file in this distribution for details on how to build and install hxzmq.ndll from source. + +## Copying + +Free use of this software is granted under the terms of the GNU Lesser General +Public License (LGPL). For details see the files `COPYING.LESSER` +included with the hxzmq distribution. + +[1]: http://rsbatechnology.co.uk "RSBA Technology Ltd" +[2]: http://haxe.org "haXe" +[3]: http://zeromq.org "ZeroMQ" +[4]: http://lib.haxe.org "haXelib repository" +[5]: http://haxe.org/com/haxelib "haXelib" +[6]: http://haxe.org/doc/features "haXe Features" +[7]: http://code.google.com/p/haxe "haXe source code repository" +[8]: https://github.com/zeromq/libzmq "libzmq source code repository" +[9]: http://www.imatix.com/ "iMatix Corporation" +[10]: http://haxe.org/doc/cpp/ffi "C++ FC Foreign Function Interface" +[11]: http://zguide.zeromq.org/ "0MQ Guide" + + + diff --git a/bin/ndll/Mac64/hxzmq.ndll b/bin/ndll/Mac64/hxzmq.ndll new file mode 100755 index 0000000000000000000000000000000000000000..322fd9c20e8e84c970bdc317f88d12e9bb968d91 GIT binary patch literal 48816 zcmeHwdw5jUx%bLtARv-(6%`~ZSWqsB5GvMa0trlxLkN%{D0B$Pgrwv$CKC)+YA}g1 z4x`b7mwL2~Ek!={*k~ySDb-j|QjgWBRMC1gD(a-5Mx{vWCExFT*V=pb+>*V|U*GdR z+0Qe3t+n^>{k@lU*=t|tm4AKu{ZJv|5`+*#@Ed_&w@ru^F$y^$&c<(Aybw-jVeYCz z-W0lO3oQy&2B!cGa}vVo^tc*4l#@$1U+*}%{m>$rQaMp!IY|_a#-h_%x4CM4O{KHW zQ@=hv(!M=wB>G(?MCCXGL6YJlpJ{PAOX@te71bMLF~Z;b*UBAt$*>42Q_QplAqrVw z9}IJryDHtTTI>n;cY2M4-=d7t$~sAezxCBOR8*HbE2_&#G@P&9EhWnpAC*JmrxVMJ z)9EUAmem$lxwLX<{Z1gGu>@wyl*&eb&k$mr+#}c2PPRd})0vZ9m~Gh8rt&E&4J%VS zAzsZ^r?VP$Dy!D*sg21uP0>+%Gsd3kbE>uvsnc0m+#laG$kQ@S#f{2FKCG>u1ph z;AlA)Kax3h@E7A39=-OXX@%pz_MSQOq6>s*Od|mFCLu{eT8`9>livGX&8uhCkpVRh3wUArgS=OjIlKQL5QVM_EB6{;YYe;l_et=CuXnM zT<39B&306*uPv_KJbMv!l^bemH`dKwSW{9{Jd67dZJ52%Ra;k4Q(ZSZTke9q97iuh z8ssJRcrm-au6A~1O-XU(?8=Ju^rHZ#&nn%FY&5xHR5L@BoRp~Zl$Km_$*g(0P!zf0 z@msJJey~0%CtaGgz$gdM;4&D2!3YdSU@!uM5g3fXU<3vuFc^Ws2nvOG zXAvl}dp~;i_Ix26zDXTEA^x1*_hDJ`-C!S7uVN_zr!q^E#Xrj|UbUYTvv}2hx^FY-Z<35pyIK4&iElKE zA1m<%X7R%%KGiJ#42kc()ug|4(HHHZ!z_NZ#J8Ho)4eQe|7P)HBtFe7p0448Uzo+y zC2eZ|EhgiWBJpiz@v1+%&EnPZB+D#bwdWMGcy0X0kZk4n)@~L*UdE@TnZ;i!@xm-VP2#(5F&Q7+Pp1CT zW)`0*@ouyDDH5M$7XN+b@?o$i#Vr0CiSOQO(%)|-zTGVTJBe>JiMM;#9(8zK-41V6 zz~Qa$b$GX&wl|%~%JY72_t}1eTC}%xc+PWpzs~a>wEF<|0qg_V2e5C;Y3jswe@5U6 zAP*n@9&^*cM9O<@Go^6-!Dr$TQ@cO&C2SA;^GA|$JcNG7z{7##w9RY#7BPgCJc{$$C19vd?pxt{o@F8XW4PyiC z$mDqs*}d=5<_?senN79q{Uq>+g2TsUFJ#5Rio=1&Xj|M-`~GUz#+qVx#Vorw<#1LK z-AH{QYlN^jZRr%AQ4a6Nu~v;yjDwVSfP?E@A=&MpC`AhL2%&T zIO$(<%Oi-^{>L~r8AnOTXFL06lw1bN4=)#jC(D7?k>j%fL~JX;VB^!+hKRk0S|%+A zP&~eq&kww7QHL(7!<0Plw|4JWu<}Vr1x7*}L~e!%Rq1$y1EO536Q?+QrBjl)Dt)$l zaCg$~OWJ=CEPE!7&C4Xcfi=2GS;&>OM$yOWeoEk9e#h#PG6B>(Ez{CjVd z@tJ1eUr(Z2bpGO~{AC2{{1?aP3u?5{?O&<*ryBSdkmw|xKQ1c&LIRabes664Q=6sz zlMVc%N%W8NwD>%XT(G~4qn#;|ce>&=^p{JB^q>~O6OZ<;EOiC93%J>|K!L# zOU=9A@g*aQ!Y7I0+YHTtZseklFLFHFN$4x3zQqITd%i&iY@)%z3S!u<>vQY+xW93C zDv z#tQoaJuYxyt`@&G9ad^Lk2+q?L#HDN!I%pgYzQXttcmgwV zIb7b2T=HaHUwHjxYxVaIiGA`9_OrbqSbsVF)L*$$Zyc?663=X1{TNoCBd@<1j%U9l zSc6nIU#W|7JQ$)h7`yLA68#HK1*C&dA{T6*n9I80V-@PLUbWHhv%UYw^B(d3H(IHK zWaFgT{WnN2r+VLVct6PTex}WH)r`pDtDKVJ@Oh?8PzTq|9vN+8-#e2CvvgyoAZNH< z>s=e$9y=TK8AjKD`y_|`d}Yu@cC!|JQWt5_W5oCb&a71ZBWI@m?;){IzLNcynlVN^ z>c9Qge-!m=h-bF0ehjNu^&hQ&Eg^i3)SII8#_V5T*GBq#E)me2Ui$kht(!+CS6U8 z`MOD$vPm(=7ybA!m&9)Sf&-GI3>oP7kQ7Dzw>TUHUcrSMS>3-wPCq_`wJ&*mJx&56 zrQ+MD!6E{&kFWQ*BRzha7}n@2vyhX=S3~>yJz;kDvU_JMEm6j^c~R(ZC(-Y4pG11} zK61hF%y>MK{`{2SS4vHfP?PD`pXY0&KgKaDj~KS=3f;&>__K&GM?YtO<|{2x{GmtQ z>2%R}1UQF8XX^B$V*10!*O7#-k%~I0zTr;n{$DNC8K>_b&H=$Q=&-8)Bd7I$w1-&z z^9_J6KW|jZjR*H#M3b#6pUTP`F?eX>gR%bD-v>$JW~sQ7T0?*S4pl0R z6Qqqq@bbl)zfU2j`DY z=Fo3@%uouAC+Km+@GUL@$QtZME}}g$#xAvNR$8L?^G2og$9TT}_NAbINT=V5oa$eO zc)dfQzzOzZRBZlDir?6so+8mqo&WqWem&hdGR>JSb@;a{ov zjn#WOiB8h_#ujU8e%iT)A2Qby=uUcd_V`B238{|3Qtmih_?(l=D;LzmH~qmL26%cx-K;8Vy&8UN`7ATU9yd7tVi zdVAcpK}OKnUTz`LDqX=cRuCKycsy$&%+sH6)NWMtk>ii_N-xT$ z4T4#^zR?DKhVk?yp&F!`^Oc$?<4{pXPA5}iLijGz0*S%mqyq(4W| zNA{0tQLH~P3zDBhpS1pU#|+Jn(1UP5a!E{VfUy95Gy<9HQuq17IzQ z`~|0^s{JFU+7I3L`Z9S`?rB`?Sy*z0}je)#U2@dyW$6j-n%A}B{FC>76L!1j zlVa%rBC0DmJ`NeY6BPhVH^-VS*O4|mvY;}t(*tn>eC z21Ne{S3qTjK8IXnd`=Q*n&i(j@P{>+y-pdQ3l01)lIRwlzc?!Yp9$3Y9>?c>GBSGo zu2lR+`yU|DNjiUAkU#2pb34I*EH%|AO|i$DkA5s2I^W>XYGQc&TrFU&$VHq_UPGAE z@3KE}vHiJK(Hs4_m_(Q8^y$b&*2feA-7oonPCG)?L|U+Ej*dd8z`W$27;`5~gYUe{g3x*HK&dH(Oue1HHQ|HE;6l8lF6hVf&i z;xxvqmP97$oN+;a4D+E%0+mbNWr{b-eCX5~Y4c=Qq+Op~K%##w~a##jn8)%#Tm-M`n3H971fu*o+h_6LI}?);PLBccr!i2vmM(Iapwfubue%> zQtzMaEr0XOvHLRLqv+y^9lz}_EC2+af5F`_svT@yiv4{lcJEPbbN>-)MsU&dcR2#Fs{ z2|QplasNU#Gou7^Bg}WIIk8P;K>8^e?jNuj$Okg%@gu)&C3YJ_5UgNTzrRl~)F%YS z8S5A2miW;9uCV>T84|j`C^5ABQA%jJB{{5oW@tHML}*wEO{d{;4*U!tG z*jgB%)z59J0qFI!7rTx1Q#MO$zf?cjg)7u!UA~M%5QV}en!@Df{iKh3*)-nRbUMK^ zACP#I49&VkN_f#Zurn7WD7-Fv4c+AAnW*d*l4+9yAH=u<^e3xSB+)qocaNoD2JWKd zPw2Rl0Js+b=<>dK{GPD!DxI1i$p^KC^uPVM76OMd_TW?$&H0y{*jBQ_o~dZPVSM57 z*n_%)hD>Q8Mi@gpCh$fr9$#L=Hfr5`g_Kb7$UrG*VMIL26j;UMO;B;qr+91{!|~Wk zi58DrrLI`nyx*cP$Ef#cz~J;=45hLBv$GO|8La$X(@GqLaE zl_9ZMte~=OOh+kbU0D1j-)E5TOYy33baDv;A)@i8(O^dNs6{FV_H+#FBgrZr*iR}f z4eVL`TpRVhO)9=esX@{}nkxK)a z&FxI@u=_IZ1U*P6NZr&(GBAGG{gX}=!BreO$6z{x12Zz}&{dE94u4e}j-`R;M{xzW zQ9>2$0M&0G$PFI(VVe8gvyNjA+J>*FGjGmW$t_z+QM5;B532s=w>`(vmp$zsmZrdd z4dxojg}~x@n$H4-YzVgB#}a{&C}(>Qwfrr)E%+P9K|3e(PjKm8lfSI44Pl;_8 z0^XO>^C%uS4Qnq=r!VweMLO!Fj%#%tdjW>%br3})bG)DQw975@i6m{{>Uj{`NPMmo zKUWiPzDt59O0YcsvlVcTD`GvDdTN0Sl;LnQVEb{~y<6^=6E>WZ{d755fx~-IXZmYs zLsuhOF!#s`u3luSAF}aK<=b*Ud`XeM$maEZ;2RP>7}$pdtqS|oSQPD{`3*F$?51xoOm3b; z7W-%Z2GPm$twlCtI9O17*?l-$L*&V>+ayyp67-h}0(uW?fa9xWO~^H30Zpes*OvmX zW$|k+!s{Lz+MNWdb_MWst{Hv4b~}T$TCbEP-KP3$N?_!H0D0=xfwRg^RMh8%mD0LPtZ5f9XW2I+{19NF=xXi-NcH z5NXHooV}LL1N8COez>I*J&ew=q^j3J9XvGrCzs%*-R~!y!~dHuO1vN7SWyT^5%M+EZ)$Q8syo3o~+NglYsGOPUgh+27-(Fe3r(Jiy<94AB%DPDbOcrlc|OJZM&I; zJ31e4xGI`>qpXSr09}W&Hrk=#_hqb`0rdi%8>&h4ajg0us3VpR$GET7zo-+jKZ*%8V*lTiQ0#vI-Cg-w?B_uuH1@Ee=LUogu<5!{{+U})Nvaj@ z!&vY=7<^k^;;w$s;h(w^yAjn-&&BZ3H+G8m1%5KLudis|5?>XNoe~MiU&0{SeShs4 z3Vr(%W&P~He!y<##5NClq91S6{Lt_!gfF9&t+1WVLR=jm>ybLSgqUP?Q2TocWkYq$ zlrpM1l4YssPzuje==>jX52*Ne2jl-Tvug3*%_VM_^@fPtuwLlY>;{4FQ8GKix+|1q*YczkhQh?wJ8PDuJ zxhO~PKiy%^BjZ{iotxx&|B65gR0HDTWm4`tFUUY{z?NX}D3I_-268b3(xjQB?Lx)J31c!3zEvlfa~?mMhaiJfpE{8crh_TI+@14t4|q2C>JPvk;K<*;(NNyOn{YZgu3Os#9@|e**>+!= zR5tFrMU1AA?R^o6q6{h3x}ogMsZJ%isz~r5Vz=rzIK2Fn?weYaTa}q(@_1 z_r4AKcFYHTh%n9@m;>jvaKEMFXFtkKKZX}X9sW%6C(k$8?#tbso&4-gwvpg1R=j`W zXWPH-dp*1FP+rsXDLFrK?W}(dZ0(2-1}dn%cMSPG?miBf_XUUdOHzxb(zIo_@C;)Y zhj3NF`lw?U=(grEoz~($K#?yRDVpTwCY&xX)gKV;o_Z?zZC6VvoKYQr5tnJHKODBb z4MU&~T|)Jb3@)hq9lp$G$a25!X2_x$%S(FAfN1w`LcbqF*Gu80t&J+uo7;Uj_XI(; znT&sJH_mvy?>>vl!!t`*$zn1U-B>}?CY@KKQ{B9-<+X>`n|R&8 z>l=A}6R&^9>qcJR!s}*U-^%N4yuO{++j)HluUmP2H?Mc_`d(h&&+7+x{UEP@!Rt0& z|BBZ;dA*C*yLtU6uOH|26TEKc^;5jw!|T0R%cZ=*SyEP3A++~{`4h|bRSOFWoQ|d0 zInJEi1x1TR>dY%sr`8RJ$ikZX%F@*88c%9vMfJv1cV+#Cit1EPO{%-Lro>fOmpZMy zVd0`hd5)UmQdjM)^3tFJEc;KRnL;i%N% zVnd0u&gF4>YTVA6GJNX1xMZWWTiOvS9|f!^ipyM1cTGjLN7)}Krz50Cnd(^^`}?E7^FyIKz+m;l#$l=r$-`!5}q0y*tNez>Blb}7ZgR$8rVL7 zARz|~RW+5a>VD+Hp<&W28O=_k9Av25!)B>csf!wy5TFt)uB|QJ?8KgWSp(7Zk(>b+ zB3W8fzrGTp{mV&Ih#*apu=FnnP>9g_ni^FR{mT(9M9^JdR~|E-LH`IAB1dhtVqiNN z79z+(1N#>uqjd~(`c!ua8XvRYg+icGHXyE24oVC=4KQ8k2HAz2TFZ`B4^qy6)TP6O z`_&u7svwotyMrA#nqKlV2r@`1Q~HZ5$tjo;42~83$w`zFF`*|yhYDmYf4~m}_+?Ph zA?ENQEgIec(FN9Ho{#@n;=UHk4=JZ1bE%I(?gtXcmP7i440 z8DY9?S!GRe%yB{5$q-#Yb1k+WIff`7`yD@m7@fYlUi}kLY<_|OPmQm-zG}TXz(>Oe zAqua18+#noyd_!kH5RY*l2CGtjD<6dj5r_0w7Yf#&)H+)b&xVy`?siAc-pU^I40?* z5i#vnASE&|o)8-UW_R>?21UVxoxtH#sI0i6GOBvDPoiA4wKX(hisl!9yj#;-UR!em z^(9)O;aNX{ATVooU2O^eM+xWO4W3ygZnvnY#{P=ZROzeSzXBHkTyi(FmRE?%8d2k> z%N5SliaNd!!cBesW{<1Ri9Mu*H>(k-rZ!d00ATCoQ%jZRf||-oTpuCDIJNfBYLlzl zQ->!HCiP;fpS_}QYBy4PXA2k6@a9vW%)LW7++7hpuTgF;SB8sTJ#=BLdwJ1Eu!3uwCxJYy+yf8DHovJG|JKK_MY=7Hw>5jdd5=j9Lf!++;qwb%3V&mQ@D>) z?x5TW%B`c^2b8O&+)>JTDfcqv&LQg$QEnLJ_EQeZ1V4CF{s7^2(YAKV(StYTbUC`` zZpyt)xowm?Nx7dPN1}XHaw5{9_+6&TS0IG~?28*UO2m&scGRmPVbrUuY@=RHN~E$7 zZM+tRNaz=5C?}viLM#wtp$W?n{P$q|x>S*vc7cdXxe(M-gvh!`#J64~;@m$FqU#4D zE^8Xf(?mkbbo__ebP-o@u}J8;SctSsgsor(@-u*$3AHmtLh3AGYo8?&(q=>LY-mUW z586-{Qhd`Tp(9=*|`*Wr0X&T_EDS7vL|}7K+5y91-811DQo4J`GoEQ|uxx^=grrQXmrA zmWjly6(X*&P$YI-BWz-|h)-FKeQV(N8X@psi9;yHw8Y(5uw2A7qzw6wH{eamh@nZX z9Hkl9hp>qDU!!ec8B1VV=>EAFnR072mj!$xI#s*Eo2lQqz_(f8 zF9p8Q0{;%+-4^%{0bii-auDdhy`2O;%L4xtwD435_7?)5Vu8OI_}+(Q{hEpY9^ktb z-b{Nq27HGF{;`Q@4;J`yfN!3n*ID2{0(^l5{zMywY76{xflsx-Ukbdi zz`q0dUYaXlF;l;XfbUXxGwtUj@EsQTr(kezv%p^ne5(ciX5ifx`1b(6&I122;Il07 zkHw@T%>sW8@F^DfJ-`bK{5yf~{v|gDQ^(&Uz;`LUnf8;Ig#K=Ue=hKC7WhknZ?wR_ z19-Ou{zJeQSl~Yie3k|NDZ|j;E$|ltpJIW(8Tj5`^zmY*|Ly_4Tj9;r?=j#zEbx!T zjplX>{5in4THyBp-)MnqV1Yje_*M)2 z9^e}-@b3hEody0Qz!zBHrz=Nk7Wn4^pK5`>luk-5@b3V=_W=vzKk!`&Z)*GpzQY3l zl+oz#7WfN+Z?(YR47}R{{~qAiS>Qhge3k|Nv18C4Eb!+5pJIXE1H7=nzZ3ZGpPL^a zjsV}K@Mgw`L^PUq3;c6|Z?nK(3Vfpl{vE)(E$|-#zQ6+iN#L_g^3O$8O~J$FsS4kP zYiqoOA5RnMp1_?6cO?34+lMp_CAl?_+o$TeT1rJ;2TjD}L~KuJ!Ziz8NNxw@dX-!i zZ^bG?U#Q#+Kwg5}G&RmFi6y7D6aHJ^?H2Ge#tTty0lx8!X^&0)Dr`4^&^&E*=NIQ{k<&r(=-Q=9^~a zs6Hm5NnfVssdP_^mxy*K>yPYn0AH@~R`fJOu1(3AiNhY?I~4w>%B;x#fk}YyCu~n_ zA_Mup#0khHsq-*1{!GM6TbC((WlTLIuqU+t7DCSbzmVGwx!p?6ieHBy*Q?|PYS&ax zCxO?lXWSmsudM}j|byR8Y=&q!_tUh-@Br92L8w;)#vxh^GV)-TfcAmk>f>ttr* zKLc_{AXlK|%)}${TpXViesfH}LfaGBcLn4+m7JA$cp#@;KeiGN`UY9Kx{hq6e;tEd zo02mVkFmHZ*!92A2}`L7tZw<2mt9#777M7Wf^&PuOYR z{+s9{2Nv*;1K(f){|@lGE#OB?7UHN$JokU#lYS%pHCxXl=Yd?7lC#wRA-7G*S?Mpw zAlG40j_W^Fh|@~WOnYEJQ{WfENDe$QZZ>HVt z0KUKi{t)nu7Vsy5Z?}M-G6mNkEZ_@)PobyYungocwWDU>vlQO!I7;<=0CEi`^$`99 z@Viao$*zf0aXe6Xv-1qnQwX`#$E5#e+D9|+SqeW;yojH^Y3Ek>N|bqt(O$`qW5Bm7 z{1F9@Y~MIJGF-pFREd}1b79ZWUG9kI!Y+GU`gx|pNARlc92ev#Sim;}e_Gl7UMzk? zKi&h(ZpB|=0)GscfWnszfRBvF6F4i1j7QRcT8oFK-%R`{9-S8AXN-p#eu~F73;gt) zT)72&4)AshczRB5ng#q$;FB!i={dQ8YF}pJL(j=|TENqDa=R_y={dP=3U8+U(sOc} zJp;908VBe(IW0bM@ST^4aU%hnn-aHEf`#P1g7kaIsF z`_bjl%}az_=y*kT?u1-JyOevxg4{ch>wQYf-5N74BcP$lwms1gw2TWqCugVUys_LB zQ!dg@2jtp+FXhr>%F#nYq4m%VxqW-3-0_%lk@^lm&b?2{Wm}Lt3At@gOS!C=a${j< z@H$cCI7tzW94FoTCGSA}j_S1#zXFB-&Sbqt+VMSDBJDVRK-xi9cX$bJXKJ%{{5q=e z1KCaZX8iUkJk<&>{qXMrV7CSSW591yc#07(;r#0OsO=sot#M1l{2G}qROu3xUZc`t zm2OmNy-FWZ>60q`gG%X-18I3hrEjV9LzRB2(yvt-r|dXGrQ=jOS*6oeI!C3MDqXD7 zWh&jQ(k7MqReGOFA6BVLrPrzSA!WzEtJJH~n^j8B-_wF;@p-{>`Mk7e%aoqgr=>fK z6Va&BRF!sICAY6rsZc3B>rV@w`{$)MQ>OGT04-@M#rpxg;9UV;QdHXhBUvs`Y47E- z+^W(vmC}0#w5(Gp-aX)@Ri*Sk0xcaslqud(;05n1@Pc<2uVaLRG~ZT=L_0*k{-G z$?YXI_4womb`)SoX=M$*>_w$jT&mj)f5i3cP@u0vQobDdGJN;4+EYeFHx=<^L&`T$ z{)Sq7eve9xaVSE+t9la^w^6Z*KYv2SRxY}#O5B^Ncs~_`tZh_;j#5_x?cGhqx{3|> zPz*j+L0j5+i>FqJ?^8wjg$?ZQP#fsWHz;<-jm~o3Sam(els0r}8%io`>e$?)`j(pN zYWk`SP~F-#@K)E*mVmYej@99V8u&;CZSK`JS5(U=oz^x~)opO6#%EVu;-xdh$W7<~^l=gKWL#D8 zMwe47JLiZ~adqOGxNDazbBZ1DIZN{kugT6UbUM?-pNFi-&snl`c`g-R9+E3wA5tP7 z97d|0&UEqFFzOXf=cS@?cx}lh%CrqfU5MV{A#0zJLgfe_U zhw?AQRf$*Q+{K=faw>iimmpek^&-CbCpERS;gh(EYMDEMT^;x+h3KUfeUd`7#ZH?9IsO3r`Y?7Knc)T$9b(9<=4>EGSw;`#(mVQ|eC)8t%6tHuy}3=)yIk z#9XmAacNPZ^!Bfb3s;J-60-~0lwCHFu#BYtJCS+25*IENk3qU|Hbs_J-Lz^=!>WT; zy|ik>r#?g%u3N=xZDN~EnRA?kFU9V5T6NQ^bq-dzDh6R%wc$Eh{L0+rtDSi(9ZvCr zEk8eZVWBMak~3-IP1}+cxeLjzcR<%Q7gCKGShdrtn^vv3C`ZV~AxpAX5wK+lbl^)P zqL)@}Kg6nwR`?4_$l+37LS8=M_76cQDD&))+~pkqLy+tyRO@`KI%w5Pt2SImOv=j_ zzZ#k^I))Z3Us|{{f9Z;%f`X;XnV>Z(TilbhNIagjaA|)2^4vv5D{{HkzfW3HXkVU- zPa8wcwqXc7H?RAK!R>6W+dmN9;^nz3xMegAgRM{&zf`<7G`BEk={0QWktAU&gg#>N zB`wKilIN3ha#zxp7n2Zh7Jmi$mlR|dvW2wkZtyk!2p-Ut()h*M((y-pO+8ci27tw{?fwimDzdh#y>&UcqKsX zwCbi+>s46c$}cEr)s}@-R~Dj=1H`k#XjG>j`mbT!)p6{=fw+!{JI)aC_?%IFiN`7a zI*dko8bod#j!I4!fnjPC!={tN5=ARM%#$eEc-_05@{N94xAM9TYY|Vw(H!x>aF5tE zJW;gso(|qaA5-cW&RP)wv5z2I_*h8+! zgAnY_lK2#6;^&ctb8D>+!3_){O}q!x6dIn+zZCpN;fGh&1hv(%_~A7jF&@9O@SA|& z+4!A<-?{ir#BUOQ=ixUQzf}Cr$L|9C=vKiL{OA?~-F~235_G$Q8b5WGA^2g*qTm0E zc9L@SENuIStKiN=Qji(vf2?kO+wU8$j`ssu`k(wBIQi$CoRQ0{n(lK!&ht5InBa4n zXXcy>PU-a`&*k+}D3nTa_O1hX+Ri1OuXBzk>YU>ly2|NOb6J*ia?bN)JmO@rwz#zR S^4YU9=B3Zgn5%C8iT?{2*O#6E literal 0 HcmV?d00001 diff --git a/bin/ndll/Windows/hxzmq.ndll b/bin/ndll/Windows/hxzmq.ndll new file mode 100644 index 0000000000000000000000000000000000000000..bf0a69b8074722860112739b30f17c91fd5d0f2e GIT binary patch literal 95232 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 o9F7vWCprZmE73iqIzh4FZ8@cifV*mgE literal 0 HcmV?d00001 diff --git a/bin/ndll/hxzmq.ndll b/bin/ndll/hxzmq.ndll new file mode 100644 index 0000000000000000000000000000000000000000..1f45492b7dadd9fc576b629cd7ed5b3ae5df92b9 GIT binary patch literal 44608 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 F{{p+%&Rzfj literal 0 HcmV?d00001 diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..24f88f3 --- /dev/null +++ b/build.bat @@ -0,0 +1,26 @@ +echo "** Build hxzmq.ndll on Windows:" +haxelib run hxcpp build.xml + +echo "** Build Haxe Unit Tests:" +cd test +haxe buildWindows.hxml + +echo "** Build Haxe ZeroMQ Guide programs:" +cd ../guide +haxe buildWindows.hxml + +echo "** Copy hxzmq.ndll:" +cd .. +copy /Y out\ndll\Windows\hxzmq.ndll test\out-cpp\Windows +copy /Y out\ndll\Windows\hxzmq.ndll test\out-neko\Windows +copy /Y out\ndll\Windows\hxzmq.ndll guide\out-cpp\Windows +copy /Y out\ndll\Windows\hxzmq.ndll guide\out-neko\Windows + + +rem echo "** Run CPP unit tests:" +rem cd test/out-cpp/Windows +rem TestAll-debug.exe + +rem echo "** Run Neko unit tests:" +rem cd test/out-neko/Windows +rem neko TestAll.n \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..43b7623 --- /dev/null +++ b/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo "** Build hxzmq.ndll:" +haxelib run hxcpp build.xml -DHXCPP_M64 + +echo "** Build Haxe Unit Tests:" +cd test +/usr/bin/haxe buildMac64.hxml + +echo "** Build Haxe ZeroMQ Guide programs:" +cd ../guide +/usr/bin/haxe buildMac64.hxml + +echo "** Copying hxzmq.ndll:" +cd .. +cp bin/ndll/Mac64/hxzmq.ndll test/out-cpp/Mac64 +cp bin/ndll/Mac64/hxzmq.ndll test/out-neko/Mac64 +cp bin/ndll/Mac64/hxzmq.ndll guide/out-cpp/Mac64 + +echo "** Running CPP executables:" +cd test/out-cpp/Mac64 +./TestAll-debug + + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..c20bab7 --- /dev/null +++ b/build.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guide/buildMac64.hxml b/guide/buildMac64.hxml new file mode 100644 index 0000000..9827a5c --- /dev/null +++ b/guide/buildMac64.hxml @@ -0,0 +1,11 @@ +# Haxe build file +# Build CPP guide target +-cp .. +-cpp out-cpp/Mac64 +-debug +-D HXCPP_M64 +-D HXCPP_MULTI_THREADED +--remap neko:cpp +-main org.zeromq.guide.Run + +# todo: Neko Mac0SX 64 bit target diff --git a/guide/buildWindows.hxml b/guide/buildWindows.hxml new file mode 100644 index 0000000..2a08a15 --- /dev/null +++ b/guide/buildWindows.hxml @@ -0,0 +1,16 @@ +# Haxe build file +# Builds Windows HXZMQ Guide code +# Build CPP guide target +-cp .. +-cpp out-cpp/Windows +-debug +-D HXCPP_MULTI_THREADED +--remap neko:cpp +-main org.zeromq.guide.Run +--next +# Build Neko guide target +-cp .. +-neko out-neko/Windows/run-debug.n +-debug +-main org.zeromq.guide.Run + diff --git a/haxelib.xml b/haxelib.xml new file mode 100644 index 0000000..1a55086 --- /dev/null +++ b/haxelib.xml @@ -0,0 +1,9 @@ + + + + + + + Haxe language binding for the ZeroMQ socket library + Initial upload, compatable with libzmq 2.1.6+ + \ No newline at end of file diff --git a/hxzmq.hxproj b/hxzmq.hxproj new file mode 100644 index 0000000..5352d44 --- /dev/null +++ b/hxzmq.hxproj @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org/zeromq/ZMQ.hx b/org/zeromq/ZMQ.hx new file mode 100644 index 0000000..31013fe --- /dev/null +++ b/org/zeromq/ZMQ.hx @@ -0,0 +1,516 @@ +/* + Copyright (c) 2011 Richard J Smith + + This file is part of hxzmq. + + hxzmq is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + hxzmq is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +package org.zeromq; + +import haxe.Int32; +import haxe.io.Bytes; +import org.zeromq.ZMQContext; +import neko.Lib; + +/** + * Enumeration of 0MQ socket types + */ +enum SocketType { + ZMQ_PAIR; + ZMQ_PUB; + ZMQ_SUB; + ZMQ_REQ; + ZMQ_REP; + ZMQ_ROUTER; // Replaces XREQ in 2.1.4 + ZMQ_DEALER; // Replaces XREP in 2.1.4 + ZMQ_PULL; + ZMQ_PUSH; +} + +/** + * Enumeration of 0MQ error types + */ +enum ErrorType { + EINVAL; + ENOTSUP; + EPROTONOSUPPORT; + EAGAIN; + ENOMEM; + ENODEV; + ENOBUFS; + ENETDOWN; + EADDRINUSE; + EADDRNOTAVAIL; + ECONNREFUSED; + EINPROGRESS; + EMTHREAD; + EFSM; + ENOCOMPATPROTO; + ETERM; +} + +/** + * Enumeration of 0MQ send and receive flags + */ +enum SendReceiveFlagType { + DONTWAIT; // ZMQSocket flag to indicate a nonblocking send or recv mode. + // Maps to underlying NOBLOCK in 0MQ 2.1.x + SNDMORE; // ZMQSocket flag to indicate that more message parts are coming. +} + +/** + * Enumeration of 0MQ socket types + * See: http://api.zeromq.org/master:zmq-setsockopt + */ +enum SocketOptionsType { + ZMQ_HWM; // Set high water mark + ZMQ_SWAP; // Set disk offload size + ZMQ_AFFINITY; // Set I/O thread affinity + ZMQ_IDENTITY; // Set socket identity + ZMQ_SUBSCRIBE; // Establish message filter + ZMQ_UNSUBSCRIBE; // Remove message filter + ZMQ_RATE; // Set multicast data rate + ZMQ_RECOVERY_IVL; // Set multicast recovery interval + ZMQ_RECOVERY_IVL_MSEC; // Set multicast recovery interval in milliseconds + ZMQ_MCAST_LOOP; // Control multicast loop-back + ZMQ_SNDBUF; // Set kernel transmit buffer size + ZMQ_RCVBUF; // Set kernel receive buffer size + ZMQ_LINGER; // Set linger period for socket shutdown + ZMQ_RCVMORE; // More message parts to follow flag + ZMQ_RECONNECT_IVL; // Set reconnection interval + ZMQ_RECONNECT_IVL_MAX; // Set maximum reconnection interval + ZMQ_BACKLOG; // Set maximum length of the queue of outstanding connections + ZMQ_FD; // Retrieve file descriptor associated with the socket + ZMQ_EVENTS; // Retrieve socket event state (bitmask use ZMQ_POLLIN and ZMQ_POLLOUT) + ZMQ_TYPE; // Retrieves type of socket +} + +/** + * Used to pass 64 bit ints to setlongsockopt + */ +typedef ZMQInt64Type = { hi:Int, lo:Int }; + +/** + * Core class for 0MQ Haxe bindings + */ +class ZMQ { + + // Values for flags in ZMQSocket's send and recv functions. + public static var bytesSocketOptionTypes:Array = + [ + ZMQ_IDENTITY, + ZMQ_SUBSCRIBE, + ZMQ_UNSUBSCRIBE + ]; + public static var int64SocketOptionTypes:Array = + [ + ZMQ_HWM, + ZMQ_SWAP, + ZMQ_AFFINITY, + ZMQ_RATE, + ZMQ_RECOVERY_IVL, + ZMQ_RECOVERY_IVL_MSEC, + ZMQ_MCAST_LOOP, + ZMQ_SNDBUF, + ZMQ_RCVBUF, + ZMQ_RCVMORE + ]; + + public static var intSocketOptionTypes:Array = + [ + ZMQ_LINGER, + ZMQ_RECONNECT_IVL, + ZMQ_RECONNECT_IVL_MAX, + ZMQ_BACKLOG, + ZMQ_FD, // Only int on POSIX systems + ZMQ_EVENTS, + ZMQ_TYPE + ]; + + + /** + * Flag to specify a STREAMER device. + */ + public static inline var STREAMER = 1; + + /** + * Flag to specify a FORWARDER device. + */ + public static inline var FORWARDER = 2; + + /** + * Flag to specify a QUEUE device. + */ + public static inline var QUEUE = 3; + + // Bitmask flags for ZMQ_EVENTS socket event option query + + public static inline function ZMQ_POLLIN():Int { + return _hx_zmq_ZMQ_POLLIN(); + } + public static inline function ZMQ_POLLOUT():Int { + return _hx_zmq_ZMQ_POLLOUT(); + } + public static inline function ZMQ_POLLERR():Int { + return _hx_zmq_ZMQ_POLLERR(); + } + + /** + * Gets complete 0MQ library version + * @return 0MQ library version in form MMmmpp (MM=major, mm=minor, pp=patch) + */ + public static function version_full():Int + { + return _hx_zmq_version_full(); + } + + /** + * Gets 0MQ library major version + * @return 0MQ major library version (2, 3 etc) + */ + public static function versionMajor():Int + { + return _hx_zmq_version_major(); + } + + /** + * Gets 0MQ library minor version + * @return 0MQ minor library version + */ + public static function versionMinor():Int + { + return _hx_zmq_version_minor(); + } + + /** + * Gets 0MQ library patch version + * @return 0MQ library patch version + */ + public static function versionPatch():Int + { + return _hx_zmq_version_patch(); + } + + /** + * Creates an integer in same form as given by versionFull() + * @param major + * @param minor + * @param patch + * @return + */ + public static function makeVersion(major:Int, minor:Int, patch:Int):Int + { + return _hx_zmq_make_version(major, minor, patch); + } + + /** + * Returns a human-readable description from a ZMQException object errNo number + * @param errNo A valid 0MQ error number. + * Use the errorTypeToErrNo method to convert a ZMQ.ErrorType + * @return A short description of the error + */ + public static function strError(e:Int):String + { + return Lib.nekoToHaxe(_hx_zmq_str_error(e)); + } + + /** + * Converts a SocketType enum into a ZMQ socket type integer value + * @param type + * @return + */ + public static function socketTypeNo(type:SocketType):Int { + return { + switch(type) { + case ZMQ_PUB: + _hx_zmq_ZMQ_PUB(); + case ZMQ_SUB: + _hx_zmq_ZMQ_SUB(); + case ZMQ_PAIR: + _hx_zmq_ZMQ_PAIR(); + case ZMQ_REQ: + _hx_zmq_ZMQ_REQ(); + case ZMQ_REP: + _hx_zmq_ZMQ_REP(); + case ZMQ_ROUTER: + _hx_zmq_ZMQ_ROUTER(); + case ZMQ_DEALER: + _hx_zmq_ZMQ_DEALER(); + case ZMQ_PULL: + _hx_zmq_ZMQ_PULL(); + case ZMQ_PUSH: + _hx_zmq_ZMQ_PUSH(); + default: + null; + } + } + } + + /** + * Converts a SocketOptionsType enum into a ZMQ int + * @param option + * @return + */ + public static function socketOptionTypeNo(option:SocketOptionsType):Int { + return { + switch(option) { + case ZMQ_LINGER: + _hx_zmq_ZMQ_LINGER(); + case ZMQ_HWM: + _hx_zmq_ZMQ_HWM(); + case ZMQ_SWAP: + _hx_zmq_ZMQ_SWAP(); + case ZMQ_AFFINITY: + _hx_zmq_ZMQ_AFFINITY(); + case ZMQ_IDENTITY: + _hx_zmq_ZMQ_IDENTITY(); + case ZMQ_SUBSCRIBE: + _hx_zmq_ZMQ_SUBSCRIBE(); + case ZMQ_UNSUBSCRIBE: + _hx_zmq_ZMQ_UNSUBSCRIBE(); + case ZMQ_RATE: + _hx_zmq_ZMQ_RATE(); + case ZMQ_RECOVERY_IVL: + _hx_zmq_ZMQ_RECOVERY_IVL(); + case ZMQ_RECOVERY_IVL_MSEC: + _hx_zmq_ZMQ_RECOVERY_IVL_MSEC(); + case ZMQ_MCAST_LOOP: + _hx_zmq_ZMQ_MCAST_LOOP(); + case ZMQ_SNDBUF: + _hx_zmq_ZMQ_SNDBUF(); + case ZMQ_RCVBUF: + _hx_zmq_ZMQ_RCVBUF(); + case ZMQ_RECONNECT_IVL: + _hx_zmq_ZMQ_RECONNECT_IVL(); + case ZMQ_RECONNECT_IVL_MAX: + _hx_zmq_ZMQ_RECONNECT_IVL_MAX(); + case ZMQ_BACKLOG: + _hx_zmq_ZMQ_BACKLOG(); + case ZMQ_RCVMORE: + _hx_zmq_ZMQ_RCVMORE(); + case ZMQ_FD: + _hx_zmq_ZMQ_FD(); + case ZMQ_EVENTS: + _hx_zmq_ZMQ_EVENTS(); + case ZMQ_TYPE: + _hx_zmq_ZMQ_TYPE(); + default: + null; + } + } + } + + /** + * Converts a SendReceiveFlagType enum value into the corresponding 0MQ library int value + * @param type + * @return + */ + public static function sendReceiveFlagNo(type:SendReceiveFlagType):Int { + if (type == null) return null; + return { + switch(type) { + case DONTWAIT: + _hx_zmq_DONTWAIT(); + case SNDMORE: + _hx_zmq_SNDMORE(); + default: + null; + } + } + } + + /** + * Converts Haxe ErrorType enum value to ZMQ errNo integer value + * @param e + * @return + */ + public static function errorTypeToErrNo(e:ErrorType):Int { + return { + switch(e) { + case EINVAL: + _hx_zmq_EINVAL(); + case ENOTSUP: + _hx_zmq_ENOTSUP(); + case EPROTONOSUPPORT: + _hx_zmq_EPROTONOSUPPORT(); + case EAGAIN: + _hx_zmq_EAGAIN(); + case ENOMEM: + _hx_zmq_ENOMEM(); + case ENODEV: + _hx_zmq_ENODEV(); + case ENOBUFS: + _hx_zmq_ENOBUFS(); + case ENETDOWN: + _hx_zmq_ENETDOWN(); + case EADDRINUSE: + _hx_zmq_EADDRINUSE(); + case EADDRNOTAVAIL: + _hx_zmq_EADDRNOTAVAIL(); + case ECONNREFUSED: + _hx_zmq_ECONNREFUSED(); + case EINPROGRESS: + _hx_zmq_EINPROGRESS(); + case EMTHREAD: + _hx_zmq_EMTHREAD(); + case EFSM: + _hx_zmq_EFSM(); + case ENOCOMPATPROTO: + _hx_zmq_ENOCOMPATPROTO(); + case ETERM: + _hx_zmq_ETERM(); + default: + 0; + } + } + } + + /** + * Converts ZMQ errNo integer value to Haxe ErrorType enum value + * @param e + * @return + */ + public static function errNoToErrorType(e:Int):ErrorType { + return { + switch (e) { + case _hx_zmq_EINVAL(): + EINVAL; + case _hx_zmq_ENOTSUP(): + ENOTSUP; + case _hx_zmq_EPROTONOSUPPORT(): + EPROTONOSUPPORT; + case _hx_zmq_EAGAIN(): + EAGAIN; + case _hx_zmq_ENOMEM(): + ENOMEM; + case _hx_zmq_ENODEV(): + ENODEV; + case _hx_zmq_ENOBUFS(): + ENOBUFS; + case _hx_zmq_ENETDOWN(): + ENETDOWN; + case _hx_zmq_EADDRINUSE(): + EADDRINUSE; + case _hx_zmq_EADDRNOTAVAIL(): + EADDRNOTAVAIL; + case _hx_zmq_ECONNREFUSED(): + ECONNREFUSED; + case _hx_zmq_EINPROGRESS(): + EINPROGRESS; + case _hx_zmq_EMTHREAD(): + EMTHREAD; + case _hx_zmq_EFSM(): + EFSM; + case _hx_zmq_ENOCOMPATPROTO(): + ENOCOMPATPROTO; + case _hx_zmq_ETERM(): + ETERM; + default: + ENOTSUP; + } + } + } + + /** + * Sets up interrupt signal handling. + * Use isInterrupted() to subsequwnrly test for interruption + */ + public static function catchSignals() { + _hx_zmq_catch_signals(); + } + + /** + * Indicates if 0MQ has been interrupted by a system signal (SIGINT or SIGTERM) + * Use this method to detect interrupt, and exit cleanly (close 0MQ sockets and contexts), + * particularly after recvMsg() and poll() calls. + * See: http://zguide.zeromq.org/page:all#Handling-Interrupt-Signals + * + * @return True if 0MQ has been interrupted + */ + public static function isInterrupted():Bool { + var i:Int = _hx_zmq_interrupted(); + return (i == 1); + } + + #if (neko||cpp) + // Load function references from hxzmq.ndll + private static var _hx_zmq_version_full = Lib.load("hxzmq", "hx_zmq_version_full",0); + private static var _hx_zmq_version_major = Lib.load("hxzmq", "hx_zmq_version_major",0); + private static var _hx_zmq_version_minor = Lib.load("hxzmq", "hx_zmq_version_minor",0); + private static var _hx_zmq_version_patch = Lib.load("hxzmq", "hx_zmq_version_patch",0); + private static var _hx_zmq_make_version = Lib.load("hxzmq", "hx_zmq_make_version", 3); + private static var _hx_zmq_str_error = Lib.load("hxzmq", "hx_zmq_str_error", 1); + private static var _hx_zmq_catch_signals = Lib.load("hxzmq", "hx_zmq_catch_signals", 0); + private static var _hx_zmq_interrupted = Lib.load("hxzmq", "hx_zmq_interrupted", 0); + + private static var _hx_zmq_ZMQ_PUB = Lib.load("hxzmq", "hx_zmq_ZMQ_PUB", 0); + private static var _hx_zmq_ZMQ_SUB = Lib.load("hxzmq", "hx_zmq_ZMQ_SUB", 0); + private static var _hx_zmq_ZMQ_PAIR = Lib.load("hxzmq", "hx_zmq_ZMQ_PAIR", 0); + private static var _hx_zmq_ZMQ_REQ = Lib.load("hxzmq", "hx_zmq_ZMQ_REQ", 0); + private static var _hx_zmq_ZMQ_REP = Lib.load("hxzmq", "hx_zmq_ZMQ_REP", 0); + private static var _hx_zmq_ZMQ_DEALER = Lib.load("hxzmq", "hx_zmq_ZMQ_DEALER", 0); + private static var _hx_zmq_ZMQ_ROUTER = Lib.load("hxzmq", "hx_zmq_ZMQ_ROUTER", 0); + private static var _hx_zmq_ZMQ_PULL = Lib.load("hxzmq", "hx_zmq_ZMQ_PULL", 0); + private static var _hx_zmq_ZMQ_PUSH = Lib.load("hxzmq", "hx_zmq_ZMQ_PUSH", 0); + + private static var _hx_zmq_ZMQ_LINGER = Lib.load("hxzmq", "hx_zmq_ZMQ_LINGER", 0); + private static var _hx_zmq_ZMQ_HWM = Lib.load("hxzmq", "hx_zmq_ZMQ_HWM", 0); + private static var _hx_zmq_ZMQ_RCVMORE = Lib.load("hxzmq", "hx_zmq_ZMQ_RCVMORE", 0); + private static var _hx_zmq_ZMQ_SUBSCRIBE = Lib.load("hxzmq", "hx_zmq_ZMQ_SUBSCRIBE", 0); + private static var _hx_zmq_ZMQ_UNSUBSCRIBE = Lib.load("hxzmq", "hx_zmq_ZMQ_UNSUBSCRIBE", 0); + private static var _hx_zmq_ZMQ_SWAP = Lib.load("hxzmq", "hx_zmq_ZMQ_SWAP", 0); + private static var _hx_zmq_ZMQ_AFFINITY = Lib.load("hxzmq", "hx_zmq_ZMQ_AFFINITY", 0); + private static var _hx_zmq_ZMQ_IDENTITY = Lib.load("hxzmq", "hx_zmq_ZMQ_IDENTITY", 0); + + private static var _hx_zmq_ZMQ_RATE = Lib.load("hxzmq", "hx_zmq_ZMQ_RATE", 0); + private static var _hx_zmq_ZMQ_RECOVERY_IVL = Lib.load("hxzmq", "hx_zmq_ZMQ_RECOVERY_IVL", 0); + private static var _hx_zmq_ZMQ_RECOVERY_IVL_MSEC = Lib.load("hxzmq", "hx_zmq_ZMQ_RECOVERY_IVL_MSEC", 0); + private static var _hx_zmq_ZMQ_MCAST_LOOP = Lib.load("hxzmq", "hx_zmq_ZMQ_MCAST_LOOP", 0); + private static var _hx_zmq_ZMQ_SNDBUF = Lib.load("hxzmq", "hx_zmq_ZMQ_SNDBUF", 0); + private static var _hx_zmq_ZMQ_RCVBUF = Lib.load("hxzmq", "hx_zmq_ZMQ_RCVBUF", 0); + private static var _hx_zmq_ZMQ_RECONNECT_IVL = Lib.load("hxzmq", "hx_zmq_ZMQ_RECONNECT_IVL", 0); + private static var _hx_zmq_ZMQ_RECONNECT_IVL_MAX = Lib.load("hxzmq", "hx_zmq_ZMQ_RECONNECT_IVL_MAX", 0); + private static var _hx_zmq_ZMQ_BACKLOG = Lib.load("hxzmq", "hx_zmq_ZMQ_BACKLOG", 0); + private static var _hx_zmq_ZMQ_FD = Lib.load("hxzmq", "hx_zmq_ZMQ_FD", 0); + private static var _hx_zmq_ZMQ_EVENTS = Lib.load("hxzmq", "hx_zmq_ZMQ_EVENTS", 0); + private static var _hx_zmq_ZMQ_TYPE = Lib.load("hxzmq", "hx_zmq_ZMQ_TYPE", 0); + + private static var _hx_zmq_ZMQ_POLLIN = Lib.load("hxzmq", "hx_zmq_ZMQ_POLLIN", 0); + private static var _hx_zmq_ZMQ_POLLOUT = Lib.load("hxzmq", "hx_zmq_ZMQ_POLLOUT", 0); + private static var _hx_zmq_ZMQ_POLLERR = Lib.load("hxzmq", "hx_zmq_ZMQ_POLLERR", 0); + + private static var _hx_zmq_DONTWAIT = Lib.load("hxzmq", "hx_zmq_DONTWAIT", 0); + private static var _hx_zmq_SNDMORE = Lib.load("hxzmq", "hx_zmq_SNDMORE", 0); + + private static var _hx_zmq_EINVAL = Lib.load("hxzmq", "hx_zmq_EINVAL", 0); + private static var _hx_zmq_ENOTSUP = Lib.load("hxzmq", "hx_zmq_ENOTSUP", 0); + private static var _hx_zmq_EPROTONOSUPPORT = Lib.load("hxzmq", "hx_zmq_EPROTONOSUPPORT", 0); + private static var _hx_zmq_EAGAIN = Lib.load("hxzmq", "hx_zmq_EAGAIN", 0); + private static var _hx_zmq_ENOMEM = Lib.load("hxzmq", "hx_zmq_ENOMEM", 0); + private static var _hx_zmq_ENODEV = Lib.load("hxzmq", "hx_zmq_ENODEV", 0); + private static var _hx_zmq_ENOBUFS = Lib.load("hxzmq", "hx_zmq_ENOBUFS", 0); + private static var _hx_zmq_ENETDOWN = Lib.load("hxzmq", "hx_zmq_ENETDOWN", 0); + private static var _hx_zmq_EADDRINUSE = Lib.load("hxzmq", "hx_zmq_EADDRINUSE", 0); + private static var _hx_zmq_EADDRNOTAVAIL = Lib.load("hxzmq", "hx_zmq_EADDRNOTAVAIL", 0); + private static var _hx_zmq_ECONNREFUSED = Lib.load("hxzmq", "hx_zmq_ECONNREFUSED", 0); + private static var _hx_zmq_EINPROGRESS = Lib.load("hxzmq", "hx_zmq_EINPROGRESS", 0); + private static var _hx_zmq_EMTHREAD = Lib.load("hxzmq", "hx_zmq_EMTHREAD", 0); + private static var _hx_zmq_EFSM = Lib.load("hxzmq", "hx_zmq_EFSM", 0); + private static var _hx_zmq_ENOCOMPATPROTO = Lib.load("hxzmq", "hx_zmq_ENOCOMPATPROTO", 0); + private static var _hx_zmq_ETERM = Lib.load("hxzmq", "hx_zmq_ETERM", 0); + + #end +} + diff --git a/org/zeromq/ZMQContext.hx b/org/zeromq/ZMQContext.hx new file mode 100644 index 0000000..87f6968 --- /dev/null +++ b/org/zeromq/ZMQContext.hx @@ -0,0 +1,124 @@ +/* + Copyright (c) 2011 Richard J Smith + + This file is part of hxzmq. + + hxzmq is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + hxzmq is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +package org.zeromq; + +import neko.Lib; +import org.zeromq.ZMQ; +import org.zeromq.ZMQException; + +class ZMQContext { + + /** Opaque data used by hxzmq driver */ + public var contextHandle(default,null):Dynamic; + + /** Records if context has been terminated */ + public var closed(default,null):Bool; + + private static var _instance:ZMQContext; + private static var _hasInstance:Bool; + + /** + * Creates a ZMQ Context + * + * Can throw EINVAL if invalid number of iothreads requested + * + * See: http://api.zeromq.org/master:zmq-init + */ + private function new(ioThreads:Int) { + closed = true; + + // Initialize the zmq context + try { + contextHandle = _hx_zmq_construct(ioThreads); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } catch (e:Dynamic) { + trace(e); + } + closed = false; + } + + /** + * Close or terminate the context + * + * This can be called to close the context by hand. If this is not + * called, the context will automatically be closed when it is + * garbage collected. + */ + public function term():Void { + if (!closed) { + try { + _hx_zmq_term(contextHandle); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + closed = true; + contextHandle = null; + _hasInstance = false; + } + } + + /** + * Create a Socket associated with this Context + * @param socketType The socket type which can be any of the ZMQ socket types + * @return A ZMQSocket object + */ + public function socket(type:SocketType):ZMQSocket { + if (closed) + throw new ZMQException(ENOTSUP); + + return new ZMQSocket(this, type); + } + + /** + * Convenience method to create a ZMQPoller object. + * Raises a ENOTSUP ZMQException if context is closed + * @return A ZMQPoller object + */ + public function poller():ZMQPoller { + if (closed) + throw new ZMQException(ENOTSUP); + + return new ZMQPoller(); + } + + /** + * Returns a global ZMQContext instance, or null + * + * @param ?ioThreads = 1 + */ + public static function instance(?ioThreads = 1):ZMQContext { + if (!_hasInstance) { + _instance = new ZMQContext (ioThreads); + _hasInstance = true; + } + + return { + if (_hasInstance) _instance else null; + } + } + + + private static var _hx_zmq_construct = neko.Lib.load("hxzmq", "hx_zmq_construct", 1); + private static var _hx_zmq_term = neko.Lib.load("hxzmq", "hx_zmq_term", 1); + + +} \ No newline at end of file diff --git a/org/zeromq/ZMQException.hx b/org/zeromq/ZMQException.hx new file mode 100644 index 0000000..28403f4 --- /dev/null +++ b/org/zeromq/ZMQException.hx @@ -0,0 +1,57 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq; + +import org.zeromq.ZMQ; + +/** + * Encapsulates ZMQ Errors + * Provides the ZMQ - specified errno and a human - readable description + */ +class ZMQException { + + public var err(default, null):ErrorType; + + public var errNo(default,null):Int; + + public function new(e:ErrorType) { + this.err = e; + this.errNo = ZMQ.errorTypeToErrNo(err); + } + + /** + * Returns ZMQ - specified human-readable error description + * @return + */ + public function str():String { + return ZMQ.strError(errNo); + } + + public function toString():String { + var b:StringBuf = new StringBuf(); + + b.add("errNo:" + errNo + ", str:" + str()); + + return b.toString(); + } + + //private static var _hx_zmq_strerror = neko.Lib.load("hxzmq", "hx_zmq_strerror", 1); + +} \ No newline at end of file diff --git a/org/zeromq/ZMQPoller.hx b/org/zeromq/ZMQPoller.hx new file mode 100644 index 0000000..786c042 --- /dev/null +++ b/org/zeromq/ZMQPoller.hx @@ -0,0 +1,183 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq; + +import neko.Lib; +import org.zeromq.ZMQ; +import org.zeromq.ZMQSocket; + +/** + * Encapsulates ZMQ Poller functions. + * + * Statefull class, maintaining a set of sockets to poll, events to poll for + */ +class ZMQPoller +{ + /** + * Provides the last-polled set of rececived events + */ + public var revents(default,null):Array; + + private var pollItems:List; + + /** + * Constructor + */ + public function new() { + + pollItems = new List(); + revents = new Array(); + + } + + public function getSize():Int { + return pollItems.length; + } + + /** + * Adds a socket to the internak list of polled sockets + * @param socket A ZMQScxket object + * @param event Bitmasked Int for polled events (ZMQ_POLLIN, ZMQ_POLLOUT) + */ + public function registerSocket(socket:ZMQSocket, event:Int) + { + + if (socket == null || event == null) { + throw new ZMQException(EINVAL); + return; + } + + pollItems.add( { _socket:socket, _event:event } ); + + } + + /** + * Removes a previously registered socket + * @param socket + * @return + */ + public function unregisterSocket(socket:ZMQSocket):Bool { + + if (socket == null) { + throw new ZMQException(EINVAL); + return null; + } + + // Find first matching socket object, then remove it + for (pi in pollItems) { + if (pi._socket == socket) { + pollItems.remove(pi); + return true; + } + } + + return false; + } + + /** + * Removes all current registered sockets + */ + public function unregisterAllSockets() { + pollItems.clear(); + } + + /** + * Poll a set of 0MQ sockets, + * @param ?timeout Timeout in microseconds, or 0 to return immediately, or -1 to block indefintely (default) + * @return how many objects signalled, or 0 if none, or -1 if failure + */ + public function poll(?timeout:Int = -1):Int + { + + // Split pollItems array into 2 separate arrays to pass to the native layer + var sArray:Array = new Array(); + var eArray:Array = new Array(); + + revents = null; // Clear out revents array ready for next set of results + revents = new Array(); + for (p in pollItems) { + sArray.push(p._socket._socketHandle); + eArray.push(p._event); + revents.push(0); // initialise revents array + } + + try { + var r:PollResult = _hx_zmq_poll(sArray, eArray, timeout); + if (r == null) { + return -1; + } + revents = Lib.nekoToHaxe(r._revents).copy(); + return r._ret; + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + return -1; + } catch (e:Dynamic) { + return -1; + } + } + + /** + * Test if the s'th registered socket has a registered POLLIN event. + * Call this after a poll() method call to test the results. + * + * @param s Valid s parameter range from 1 to revents.length + * @return True if specified registered socket has a current POLLIN event, else False + */ + public function pollin(s: Int):Bool { + if (revents == null || revents.length == 0) return false; + if (s > revents.length) return false; + + return (revents[s - 1] & ZMQ.ZMQ_POLLIN()) == ZMQ.ZMQ_POLLIN(); + } + + /** + * Test if the s'th registered socket has a registered POLLOUT event. + * Call this after a poll() method call to test the results. + * + * @param s Valid s parameter range from 1 to revents.length + * @return True if specified registered socket has a current POLLOUT event, else False + */ + public function pollout(s: Int):Bool { + if (revents == null || revents.length == 0) return false; + if (s > revents.length) return false; + + return (revents[s - 1] & ZMQ.ZMQ_POLLOUT()) == ZMQ.ZMQ_POLLOUT(); + } + + /** + * Test of the s'th registered socket has no received polled events + * @param s Valid s parameter range from 1 to revents.length + * @return True if no received polled events on the socket + */ + public function noevents(s:Int):Bool { + return (!pollin(s) && !pollout(s)); + } + + private static var _hx_zmq_poll = Lib.load("hxzmq", "hx_zmq_poll", 3); +} + +typedef PollSocketEventTuple = { + _socket:ZMQSocket, + _event:Int }; + +typedef PollResult = { + _revents:Array, + _ret:Int +}; \ No newline at end of file diff --git a/org/zeromq/ZMQSocket.hx b/org/zeromq/ZMQSocket.hx new file mode 100644 index 0000000..395f4f3 --- /dev/null +++ b/org/zeromq/ZMQSocket.hx @@ -0,0 +1,351 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq; + +import haxe.io.Bytes; +import haxe.io.BytesData; +import neko.Lib; +import neko.Sys; + +import org.zeromq.ZMQ; + +/** + * A 0MQ socket + * + * These objects will generally be created via the socket() method of a ZMQContext object. + * + * Class based on code from pyzmq project + * See: https://github.com/zeromq/pyzmq/blob/master/zmq/core/socket.pyx + */ +class ZMQSocket +{ + + /** Records if socket has been closed */ + public var closed(default,null):Bool; + + /** + * Hold reference to context associated with socket, to stop it being garbage collected + */ + public var context(default,null):ZMQContext; + + /** Opaque data used by hxzmq driver */ + public var _socketHandle(default,null):Dynamic; + + /** + * Constructor. + * + * Creates a new ZMQ socket + * @param context A ZMQ Context previously created + * @param type A ZMQ socket type + */ + public function new(context:ZMQContext, type:SocketType) + { + closed = true; + this.context = context; + try { + _socketHandle = _hx_zmq_construct_socket(context.contextHandle, ZMQ.socketTypeNo(type)); + + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + closed = false; + } + + /** + * Close the socket + * + * This can be called to close the socket by hand. If this is not + * called, the socket will automatically be closed when it is + * garbage collected. + */ + public function close() + { + if (_socketHandle != null && !closed) { + try { + _hx_zmq_close(_socketHandle); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + closed = true; + _socketHandle = null; + } + } + + /** + * Bind a socket to an address + * + * This causes the socket to listen on a network port. Sockets on the + * other side of this connection will use ``Socket.connect(addr)`` to + * connect to this socket. + * + * @param addr The address string. + * This has the form 'protocol://interface:port', + * for example 'tcp://127.0.0.1:5555'. Protocols supported are + * tcp, upd, pgm, inproc and ipc. If the address is unicode, it is + * encoded to utf-8 first. + */ + public function bind(addr:String) + { + if (closed) + throw new ZMQException(ENOTSUP); + + try { + _hx_zmq_bind(_socketHandle, Lib.haxeToNeko(addr)); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + } + + /** + * Connect to a remote ZMQ socket + * + * @param addr The address string + * This has the form 'protocol://interface:port', + * for example 'tcp://127.0.0.1:5555'. Protocols supported are + * tcp, upd, pgm, inproc and ipc. If the address is unicode, it is + * encoded to utf-8 first. + */ + public function connect(addr:String) + { + if (closed) + throw new ZMQException(ENOTSUP); + + try { + _hx_zmq_connect(_socketHandle, Lib.haxeToNeko(addr)); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + } + + /** + * Set socket options. + * + * See the ZMQ documentation for details on specific options: + * http://api.zeromq.org/master:zmq-setsockopt + * + * + * @param option SocketOptionsType (defined in ZMQ.hx) + * @param optval Either Int or String or Bytes + */ + public function setsockopt(option:SocketOptionsType, optval:Dynamic):Void { + + if (closed) + throw new ZMQException(ENOTSUP); + + // Handle 32 bit int options + if (Lambda.exists(ZMQ.intSocketOptionTypes, + function(so) { return so == option; } )) + { + if (!Std.is(optval,Int)) + throw new String("Expected Int, got " + optval); + + try { + _hx_zmq_setintsockopt(_socketHandle, ZMQ.socketOptionTypeNo(option), optval); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + // Handle 64 bit int options + } else if (Lambda.exists(ZMQ.int64SocketOptionTypes, + function(so) { return so == option; } )) + { + var _hi = Reflect.field(optval, "hi"); + var _lo = Reflect.field(optval, "lo"); + if (_hi == null || _lo == null) { + throw new String("Expected ZMQInt64Type, got " + optval); + return null; + } + + try { + _hx_zmq_setint64sockopt(_socketHandle, ZMQ.socketOptionTypeNo(option), _hi, _lo); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + // Handle bytes options + } else if (Lambda.exists(ZMQ.bytesSocketOptionTypes, + function(so) { return so == option; } )) + { + if (!Std.is(optval, Bytes)) { + throw new String("Expected Bytes, got " + optval); + return null; + } + try { + _hx_zmq_setbytessockopt(_socketHandle, ZMQ.socketOptionTypeNo(option), optval.getData() ); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + } else { + throw new ZMQException(EINVAL); + } + return; + } + + /** + * Return a previously set socket option + * + * @param option + * @return + */ + public function getsockopt(option:SocketOptionsType):Dynamic + { + var _optval:Dynamic; + + if (closed) { + throw new ZMQException(ENOTSUP); + return null; + } + + if (Lambda.exists(ZMQ.intSocketOptionTypes, + function(so) { return so == option; } )) + { + + try { + _optval = Lib.nekoToHaxe(_hx_zmq_getintsockopt(_socketHandle, ZMQ.socketOptionTypeNo(option))); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + return null; + } + if (!Std.is(_optval,Int)) { + throw new String("Expected Int, got " + _optval); + return null; + } else { + return _optval; + } + } else if (Lambda.exists(ZMQ.int64SocketOptionTypes, + function(so) { return so == option; } )) + { + + try { + _optval = Lib.nekoToHaxe(_hx_zmq_getint64sockopt(_socketHandle, ZMQ.socketOptionTypeNo(option))); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + return null; + } + var _hi = Reflect.field(_optval, "hi"); + var _lo = Reflect.field(_optval, "lo"); + if (_hi == null || _lo == null) { + throw new String("Expected ZMQInt64Type, got " + _optval); + return null; + } else { + return {hi:_optval.hi, lo:_optval.lo}; + } + }else if (Lambda.exists(ZMQ.bytesSocketOptionTypes, + function(so) { return so == option; } )) + { + + try { + _optval = _hx_zmq_getbytessockopt(_socketHandle, ZMQ.socketOptionTypeNo(option)); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + return null; + } + return Bytes.ofData(_optval); + + } else { + throw new ZMQException(EINVAL); + return null; + } + return null; + + } + + /** + * Send a message on this socket + * + * This queues the message to be sent by the IO thread at a later time. + * + * @param data The content of the message + * @param ?flags Any supported SocketFlag DONTWAIT, SNDMORE + */ + public function sendMsg(data:Bytes, ?flags:SendReceiveFlagType):Void { + + if (closed) { + throw new ZMQException(ENOTSUP); + } + + try { + _hx_zmq_send(_socketHandle, data.getData(), ZMQ.sendReceiveFlagNo(flags)); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + } + + /** + * Receive a message on this socket + * + * Will return either a message, null (if DONTWAIT was used and there was no data received) or a ZMQException + * + * @param ?flags + * @return + */ + public function recvMsg(?flags:SendReceiveFlagType):Bytes { + + if (closed) + throw new ZMQException(ENOTSUP); + + var bytes:BytesData = null; + + try { + bytes = _hx_zmq_rcv(_socketHandle, ZMQ.sendReceiveFlagNo(flags)); + } catch (e:Int) { + throw new ZMQException(ZMQ.errNoToErrorType(e)); + } + + return { + if (bytes == null) { + null; + } else { + Bytes.ofData(bytes); + }; + } + + } + + /** + * Convenience method to test if socket has more parts of a multipart message to read + * @return + */ + public function hasReceiveMore():Bool { + if (closed) return false; + var r = getsockopt(ZMQ_RCVMORE); + return (r != null && r.lo == 1); + } + + private static var _hx_zmq_construct_socket = neko.Lib.load("hxzmq", "hx_zmq_construct_socket", 2); + private static var _hx_zmq_close = neko.Lib.load("hxzmq", "hx_zmq_close", 1); + private static var _hx_zmq_bind = neko.Lib.load("hxzmq", "hx_zmq_bind", 2); + private static var _hx_zmq_connect = neko.Lib.load("hxzmq", "hx_zmq_connect", 2); + private static var _hx_zmq_send = neko.Lib.load("hxzmq", "hx_zmq_send", 3); + private static var _hx_zmq_rcv = neko.Lib.load("hxzmq", "hx_zmq_rcv", 2); + private static var _hx_zmq_setintsockopt = neko.Lib.load("hxzmq", "hx_zmq_setintsockopt", 3); + private static var _hx_zmq_setint64sockopt = neko.Lib.load("hxzmq", "hx_zmq_setint64sockopt", 4); + private static var _hx_zmq_setbytessockopt = neko.Lib.load("hxzmq", "hx_zmq_setbytessockopt", 3); + private static var _hx_zmq_getintsockopt = neko.Lib.load("hxzmq", "hx_zmq_getintsockopt", 2); + private static var _hx_zmq_getint64sockopt = neko.Lib.load("hxzmq", "hx_zmq_getint64sockopt", 2); + private static var _hx_zmq_getbytessockopt = neko.Lib.load("hxzmq", "hx_zmq_getbytessockopt", 2); + +} diff --git a/org/zeromq/guide/HelloWorldClient.hx b/org/zeromq/guide/HelloWorldClient.hx new file mode 100644 index 0000000..d5ab2b6 --- /dev/null +++ b/org/zeromq/guide/HelloWorldClient.hx @@ -0,0 +1,63 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; +import haxe.io.Bytes; +import neko.Lib; +import neko.Sys; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +/** + * Hello World client in Haxe. + */ +class HelloWorldClient +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + var socket:ZMQSocket = context.socket(ZMQ_REQ); + + Lib.println("** HelloWorldClient (see: http://zguide.zeromq.org/page:all#Ask-and-Ye-Shall-Receive)"); + + trace ("Connecting to hello world server..."); + socket.connect ("tcp://localhost:5556"); + + // Do 10 requests, waiting each time for a response + for (i in 0...10) { + var requestString = "Hello "; + + // Send the message + trace ("Sending request " + i + " ..."); + socket.sendMsg(Bytes.ofString(requestString)); + + // Wait for the reply + var msg:Bytes = socket.recvMsg(); + + trace ("Received reply " + i + ": [" + msg.toString() + "]"); + + } + + // Shut down socket and context + socket.close(); + context.term(); + } +} \ No newline at end of file diff --git a/org/zeromq/guide/HelloWorldServer.hx b/org/zeromq/guide/HelloWorldServer.hx new file mode 100644 index 0000000..1149436 --- /dev/null +++ b/org/zeromq/guide/HelloWorldServer.hx @@ -0,0 +1,69 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; +import haxe.io.Bytes; +import neko.Lib; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQException; +import org.zeromq.ZMQSocket; + +/** + * Hello World server in Haxe + * Binds REP to tcp://*:5556 + * Expects "Hello" from client, replies with "World" + * + */ +class HelloWorldServer +{ + + public static function main() { + + var context:ZMQContext = ZMQContext.instance(); + var responder:ZMQSocket = context.socket(ZMQ_REP); + + Lib.println("** HelloWorldServer (see: http://zguide.zeromq.org/page:all#Ask-and-Ye-Shall-Receive)"); + + responder.setsockopt(ZMQ_LINGER, 0); + responder.bind("tcp://*:5556"); + + try { + while (true) { + // Wait for next request from client + var request:Bytes = responder.recvMsg(); + + trace ("Received request:" + request.toString()); + + // Do some work + Sys.sleep(1); + + // Send reply back to client + responder.sendMsg(Bytes.ofString("World")); + } + } catch (e:ZMQException) { + trace (e.toString()); + } + responder.close(); + context.term(); + + } + +} \ No newline at end of file diff --git a/org/zeromq/guide/Interrupt.hx b/org/zeromq/guide/Interrupt.hx new file mode 100644 index 0000000..5048bf8 --- /dev/null +++ b/org/zeromq/guide/Interrupt.hx @@ -0,0 +1,70 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import haxe.io.Bytes; +import haxe.Stack; +import neko.Lib; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +/** + * Signal Handling + * + * Call + */ +class Interrupt +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + var receiver:ZMQSocket = context.socket(ZMQ_REP); + receiver.bind("tcp://127.0.0.1:5559"); + + Lib.println("** Interrupt (see: http://zguide.zeromq.org/page:all#Handling-Interrupt-Signals)"); + + ZMQ.catchSignals(); + + Lib.println ("\nPress Ctrl+C"); + + while (true) { + // Blocking read, will exit only on an interrupt (Ctrl+C) + + try { + var msg:Bytes = receiver.recvMsg(); + } catch (e:ZMQException) { + if (ZMQ.isInterrupted()) { + trace ("W: interrupt received, killing server ...\n"); + break; + } + + // Handle other errors + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + } + } + // Close up gracefully + receiver.close(); + context.term(); + + + } +} \ No newline at end of file diff --git a/org/zeromq/guide/MTServer.hx b/org/zeromq/guide/MTServer.hx new file mode 100644 index 0000000..993d7fc --- /dev/null +++ b/org/zeromq/guide/MTServer.hx @@ -0,0 +1,156 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import haxe.io.Bytes; +import haxe.Stack; +import neko.Lib; +import neko.Sys; +import neko.vm.Thread; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQPoller; +import org.zeromq.ZMQSocket; + +/** + * Multithreaded Hello World Server + * + * See: http://zguide.zeromq.org/page:all#Multithreading-with-MQ + */ +class MTServer +{ + + static function worker() { + var context:ZMQContext = ZMQContext.instance(); + + // Socket to talk to dispatcher + var responder:ZMQSocket = context.socket(ZMQ_REP); + responder.connect("inproc://workers"); + + ZMQ.catchSignals(); + + while (true) { + + try { + // Wait for next request from client + var request:Bytes = responder.recvMsg(); + + trace ("Received request:" + request.toString()); + + // Do some work + Sys.sleep(1); + + // Send reply back to client + responder.sendMsg(Bytes.ofString("World")); + } catch (e:ZMQException) { + if (ZMQ.isInterrupted()) { + break; + } + trace (e.toString()); + } + } + responder.close(); + return null; + } + + /** + * Implements a reqeust/reply QUEUE broker device + * Returns if poll is interrupted + * @param ctx + * @param frontend + * @param backend + */ + static function queueDevice(ctx:ZMQContext, frontend:ZMQSocket, backend:ZMQSocket) { + + // Initialise pollset + var poller:ZMQPoller = ctx.poller(); + poller.registerSocket(frontend, ZMQ.ZMQ_POLLIN()); + poller.registerSocket(backend, ZMQ.ZMQ_POLLIN()); + + ZMQ.catchSignals(); + + while (true) { + try { + poller.poll(); + if (poller.pollin(1)) { + var more:Bool = true; + while (more) { + // Receive message + var msg = frontend.recvMsg(); + more = frontend.hasReceiveMore(); + + // Broker it + backend.sendMsg(msg, { if (more) SNDMORE else null; } ); + } + } + + if (poller.pollin(2)) { + var more:Bool = true; + while (more) { + // Receive message + var msg = backend.recvMsg(); + more = backend.hasReceiveMore(); + + // Broker it + frontend.sendMsg(msg, { if (more) SNDMORE else null; } ); + } + } + } catch (e:ZMQException) { + if (ZMQ.isInterrupted()) { + break; + } + // Handle other errors + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + + } + + } + + } + public static function main() { + var workerThreads:List = new List(); + + var context:ZMQContext = ZMQContext.instance(); + + Lib.println ("** MTServer (see: http://zguide.zeromq.org/page:all#Multithreading-with-MQ)"); + + // Socket to talk to clients + var clients:ZMQSocket = context.socket(ZMQ_ROUTER); + clients.bind ("tcp://*:5556"); + + // Socket to talk to workers + var workers:ZMQSocket = context.socket(ZMQ_DEALER); + workers.bind ("inproc://workers"); + + // Launch worker thread pool + for (thread_nbr in 0 ... 5) { + workerThreads.add(Thread.create(worker)); + } + + // Invoke request / reply broker (aka QUEUE device) to connect clients to workers + queueDevice(context, clients, workers); + + // Close up shop + clients.close(); + workers.close(); + context.term(); + } +} \ No newline at end of file diff --git a/org/zeromq/guide/Run.hx b/org/zeromq/guide/Run.hx new file mode 100644 index 0000000..c56d945 --- /dev/null +++ b/org/zeromq/guide/Run.hx @@ -0,0 +1,105 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import neko.io.File; +import neko.io.FileInput; +import neko.Lib; +import neko.Sys; + +import org.zeromq.guide.HelloWorldClient; +import org.zeromq.guide.HelloWorldServer; +import org.zeromq.guide.WUClient; +import org.zeromq.guide.WUServer; +import org.zeromq.guide.TaskVent; +import org.zeromq.guide.TaskWork; +import org.zeromq.guide.TaskSink; +import org.zeromq.guide.Interrupt; + +/** + * Main class that allows any of the implemented Haxe guide programs to run + */ +class Run +{ + + public static function main() + { + var selection:Int; + + Lib.println("** HaXe ZeroMQ Guide program launcher **"); + + if (Sys.args().length > 0) { + selection = Std.parseInt(Sys.args()[0]); + } else { + Lib.println(""); + Lib.println("Programs:"); + + Lib.println("1. HelloWorldClient"); + Lib.println("2. HelloWorldServer"); + Lib.println(""); + Lib.println("3. WUClient"); + Lib.println("4. WUServer"); + Lib.println(""); + Lib.println("5. TaskVent"); + Lib.println("6. TaskWork"); + Lib.println("7. TaskSink"); + Lib.println(""); + Lib.println("11. Interrupt (** Doesn't work on Windows!)"); + Lib.println(""); + Lib.println("12. MTServer"); + + do { + Lib.print("Type number followed by Enter key, or q to quit: "); + var f:FileInput = File.stdin(); + var str:String = f.readLine(); + + if (str.toLowerCase() == "q") { + return; + } + + selection = Std.parseInt(str); + } while (selection == null); + } + + switch (selection) { + case 1: + HelloWorldClient.main(); + case 2: + HelloWorldServer.main(); + case 3: + WUClient.main(); + case 4: + WUServer.main(); + case 5: + TaskVent.main(); + case 6: + TaskWork.main(); + case 7: + TaskSink.main(); + case 11: + Interrupt.main(); + case 12: + MTServer.main(); + default: + Lib.println ("Unknown program number ... exiting"); + } + } + +} diff --git a/org/zeromq/guide/TaskSink.hx b/org/zeromq/guide/TaskSink.hx new file mode 100644 index 0000000..3514bf9 --- /dev/null +++ b/org/zeromq/guide/TaskSink.hx @@ -0,0 +1,73 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import haxe.io.Bytes; +import neko.Lib; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +/** + * Task sink in Haxe + * Binds PULL request socket to tcp://localhost:5558 + * Collects results from workers via this socket + * + * See: http://zguide.zeromq.org/page:all#Divide-and-Conquer + * + * Based on http://zguide.zeromq.org/java:tasksink + */ +class TaskSink +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + + Lib.println("** TaskSink (see: http://zguide.zeromq.org/page:all#Divide-and-Conquer)"); + + // Socket to receive messages on + var receiver:ZMQSocket = context.socket(ZMQ_PULL); + receiver.bind("tcp://127.0.0.1:5558"); + + // Wait for start of batch + var msgString = StringTools.trim(receiver.recvMsg().toString()); + + // Start our clock now + var tStart = Sys.time(); + + // Process 100 messages + var task_nbr:Int; + for (task_nbr in 0 ... 100) { + msgString = StringTools.trim(receiver.recvMsg().toString()); + if (task_nbr % 10 == 0) { + Lib.println(":"); // Print a ":" every 10 messages + } else { + Lib.print("."); + } + } + // Calculate and report duation of batch + var tEnd = Sys.time(); + Lib.println("Total elapsed time: " + Math.ceil((tEnd - tStart) * 1000) + " msec"); + + receiver.close(); + context.term(); + } +} \ No newline at end of file diff --git a/org/zeromq/guide/TaskVent.hx b/org/zeromq/guide/TaskVent.hx new file mode 100644 index 0000000..8936d72 --- /dev/null +++ b/org/zeromq/guide/TaskVent.hx @@ -0,0 +1,82 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import haxe.io.Bytes; +import haxe.Stack; +import neko.Lib; +import neko.io.File; +import neko.io.FileInput; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQException; +import org.zeromq.ZMQSocket; + +/** + * Task ventilator in Haxe + * Binds PUSH socket to tcp://localhost:5557 + * Sends batch of tasks to workers via that socket. + * + * Based on code from: http://zguide.zeromq.org/java:taskvent + */ +class TaskVent +{ + + public static function main() { + + try { + var context:ZMQContext = ZMQContext.instance(); + var sender:ZMQSocket = context.socket(ZMQ_PUSH); + + Lib.println("** TaskVent (see: http://zguide.zeromq.org/page:all#Divide-and-Conquer)"); + + sender.bind("tcp://127.0.0.1:5557"); + + Lib.println("Press Enter when the workers are ready: "); + var f:FileInput = File.stdin(); + var str:String = f.readLine(); + Lib.println("Sending tasks to workers ...\n"); + + // The first message is "0" and signals starts of batch + sender.sendMsg(Bytes.ofString("0")); + + // Send 100 tasks + var totalMsec:Int = 0; // Total expected cost in msec + for (task_nbr in 0 ... 100) { + var workload = Std.random(100) + 1; // Generates 1 to 100 msecs + totalMsec += workload; + Lib.print(workload + "."); + sender.sendMsg(Bytes.ofString(Std.string(workload))); + } + Lib.println("Total expected cost: " + totalMsec + " msec"); + + // Give 0MQ time to deliver + Sys.sleep(1); + + sender.close(); + context.term(); + } catch (e:ZMQException) { + trace("ZMQException #:" + ZMQ.errNoToErrorType(e.errNo) + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + + } + } +} \ No newline at end of file diff --git a/org/zeromq/guide/TaskWork.hx b/org/zeromq/guide/TaskWork.hx new file mode 100644 index 0000000..289a4f2 --- /dev/null +++ b/org/zeromq/guide/TaskWork.hx @@ -0,0 +1,71 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; + +import haxe.io.Bytes; +import neko.Lib; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +/** + * Task worker in Haxe + * Connects PULL socket to tcp://localhost:5557 + * Collects workloads from ventilator via that socket + * Connects PUSH socket to tcp://localhost:5558 + * Sends results to sink via that socket + * + * See: http://zguide.zeromq.org/page:all#Divide-and-Conquer + * + * Based on code from: http://zguide.zeromq.org/java:taskwork + */ +class TaskWork +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + + Lib.println("** TaskWork (see: http://zguide.zeromq.org/page:all#Divide-and-Conquer)"); + + // Socket to receive messages on + var receiver:ZMQSocket = context.socket(ZMQ_PULL); + receiver.connect("tcp://127.0.0.1:5557"); + + // Socket to send messages to + var sender:ZMQSocket = context.socket(ZMQ_PUSH); + sender.connect("tcp://127.0.0.1:5558"); + + // Process tasks forever + while (true) { + var msgString = StringTools.trim(receiver.recvMsg().toString()); + var sec:Float = Std.parseFloat(msgString) / 1000.0; + Lib.print(msgString + "."); + + // Do the work + Sys.sleep(sec); + + // Send results to sink + sender.sendMsg(Bytes.ofString("")); + } + + + } +} \ No newline at end of file diff --git a/org/zeromq/guide/WUClient.hx b/org/zeromq/guide/WUClient.hx new file mode 100644 index 0000000..c609648 --- /dev/null +++ b/org/zeromq/guide/WUClient.hx @@ -0,0 +1,89 @@ +/** + * (c) $(CopyrightDate) Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; +import haxe.io.Bytes; +import neko.Lib; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQException; +import org.zeromq.ZMQSocket; + +/** + * Weather update client in Haxe + * Connects SUB socket to tcp://localhost:5556 + * Collects weather updates and finds average temp in zipcode + * + * Use optional argument to specify zip code (in range 1 to 100000) + * + * See: http://zguide.zeromq.org/page:all#Getting-the-Message-Out + */ +class WUClient +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + + Lib.println("** WUClient (see: http://zguide.zeromq.org/page:all#Getting-the-Message-Out)"); + + // Socket to talk to server + trace ("Collecting updates from weather server..."); + var subscriber:ZMQSocket = context.socket(ZMQ_SUB); + subscriber.setsockopt(ZMQ_LINGER, 0); // Don't block when closing socket at end + + subscriber.connect("tcp://localhost:5556"); + + // Subscribe to zipcode, default in NYC, 10001 + var filter:String = + if (Sys.args().length > 0) { + Sys.args()[0]; + } else { + "10001"; + }; + + try { + subscriber.setsockopt(ZMQ_SUBSCRIBE, Bytes.ofString(filter)); + } catch (e:ZMQException) { + trace (e.str()); + } + + // Process 100 updates + var update_nbr = 0; + var total_temp:Int = 0; + for (update_nbr in 0...100) { + var msg:Bytes = subscriber.recvMsg(); + trace (update_nbr+ ". Received: " + msg.toString()); + + var zipcode, temperature, relhumidity; + + var sscanf:Array = msg.toString().split(" "); + zipcode = sscanf[0]; + temperature = sscanf[1]; + relhumidity = sscanf[2]; + total_temp += Std.parseInt(temperature); + + } + trace ("Average temperature for zipcode " + filter + " was " + total_temp / 100); + + // Close gracefully + subscriber.close(); + context.term(); + } +} \ No newline at end of file diff --git a/org/zeromq/guide/WUServer.hx b/org/zeromq/guide/WUServer.hx new file mode 100644 index 0000000..f76d98f --- /dev/null +++ b/org/zeromq/guide/WUServer.hx @@ -0,0 +1,58 @@ +/** + * (c) $(CopyrightDate) Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.guide; +import haxe.io.Bytes; +import neko.Lib; +import neko.Random; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +/** + * Weather update server in Haxe + * Binds PUB socket to tcp://*:5556 + * Publishes random weather updates + * + * See: http://zguide.zeromq.org/page:all#Getting-the-Message-Out + */ +class WUServer +{ + + public static function main() { + var context:ZMQContext = ZMQContext.instance(); + + Lib.println("** WUServer (see: http://zguide.zeromq.org/page:all#Getting-the-Message-Out)"); + + var publisher:ZMQSocket = context.socket(ZMQ_PUB); + publisher.bind("tcp://127.0.0.1:5556"); + + while (true) { + // Get values that will fool the boss + var zipcode, temperature, relhumidity; + zipcode = Std.random(100000) + 1; + temperature = Std.random(215) - 80 + 1; + relhumidity = Std.random(50) + 10 + 1; + + // Send message to all subscribers + var update:String = zipcode + " " + temperature + " " + relhumidity; + publisher.sendMsg(Bytes.ofString(update)); + } + } +} \ No newline at end of file diff --git a/org/zeromq/test/BaseTest.hx b/org/zeromq/test/BaseTest.hx new file mode 100644 index 0000000..dc7777b --- /dev/null +++ b/org/zeromq/test/BaseTest.hx @@ -0,0 +1,160 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.io.Bytes; +import haxe.unit.TestCase; +import haxe.PosInfos; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; +import org.zeromq.ZMQException; + +/** + * Utility methods. + * + * Based on https://github.com/zeromq/pyzmq/blob/master/zmq/tests/__init__.py + */ +class BaseTest extends TestCase +{ + + private var _context:ZMQContext; + private var _sockets:List; + + public override function setup():Void { + _context = ZMQContext.instance(); + _sockets = new List(); + } + + public override function tearDown():Void { + var _contexts:List = new List(); + var s:ZMQSocket; + var _ctx:ZMQContext; + + _contexts.add(_context); + for (s in _sockets) { + _contexts.add(s.context); + s.close(); + } + _sockets = null; + for (_ctx in _contexts) { + _ctx.term(); + } + + } + + /** + * Create a bound ZMQ socket pair using a random port + * @param type1 + * @param type2 + * @param ?interface + */ + public function createBoundPair(type1:SocketType, type2:SocketType, ?iface:String = 'tcp://127.0.0.1'): SocketPair { + if (_context == null || _context.closed) { + assertTrue(false); + return null; + } + + var randomPort:Int = Math.round(Math.random() * 18000) + 2000; + var _s1:ZMQSocket = _context.socket(type1); + _s1.setsockopt(ZMQ_LINGER, 0); + var _p1 = bindToRandomPort(_s1, iface); + var _s2:ZMQSocket = _context.socket(type2); + _s2.setsockopt(ZMQ_LINGER, 0); + _s2.connect(iface + ":" + _p1); + _sockets.add(_s1); + _sockets.add(_s2); + return { s1:_s1, s2:_s2 }; + } + + /** + * Send a message from p.s1 to p.s2, then send it back again from s2 to s1 + * @param p SocketPair + * @param msg original message to ping pong + * @return ping-ponged message + */ + public function ping_pong(p:SocketPair, msg:Bytes):Bytes { + //trace ("s1.sendMsg:"+msg.toString()); + p.s1.sendMsg(msg); + //trace ("s2.recvMsg"); + var msg2:Bytes = p.s2.recvMsg(); + //trace ("s2.sendMsg:"+msg2.toString()); + p.s2.sendMsg(msg2); + //trace ("s1.recvMsg"); + var msg3:Bytes = p.s1.recvMsg(); + //trace ("return"); + return msg3; + } + + /** + * Binds a socket to a random port in range + * @param s + * @param addr + * @param ?minPort = 2000 + * @param ?maxPort = 20000 + * @param ?maxTries = 100 + * @return + */ + private function bindToRandomPort(s:ZMQSocket, addr:String, ?minPort = 2000, ?maxPort = 20000, ?maxTries = 100):Int { + var _iter = new IntIter(1, maxTries); + var _port = minPort; + + for (i in _iter) { + try { + _port = Math.round(Math.random() * (maxPort - minPort)) + minPort; + s.bind(addr + ":" + _port); + return _port; + } catch (e:ZMQException) { + // Ignore error. Go round and try another port + } + } + throw new String("Could not bind socket to random port"); + return null; + } + + function assertRaisesZMQException(fn:Void->Void, err:ErrorType, ?c : PosInfos):Void { + + var res = ""; + + currentTest.done = true; + try { + fn(); + res = "no exception"; + } + catch (e:ZMQException) { + if (e.err == err) { + assertTrue(true); + return; + } + res = "ZMQException errNo:" + e.errNo + ", str:" + e.str(); + } catch (e:Dynamic) { + res = e; + } + + currentTest.success = false; + currentTest.error = "expected "+err+" but got "+res+" instead"; + currentTest.posInfos = c; + throw currentTest; + + } +} + +typedef SocketPair = { s1:ZMQSocket, s2:ZMQSocket }; \ No newline at end of file diff --git a/org/zeromq/test/TestAll.hx b/org/zeromq/test/TestAll.hx new file mode 100644 index 0000000..412ff28 --- /dev/null +++ b/org/zeromq/test/TestAll.hx @@ -0,0 +1,46 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.unit.TestRunner; +import org.zeromq.test.TestContext; +import org.zeromq.test.TestVersion; + +class TestAll +{ + + public static function main() { + + var runner:TestRunner = new TestRunner(); + + // Define tests + runner.add(new TestVersion()); + runner.add(new TestContext()); + runner.add(new TestError()); + runner.add(new TestSocket()); + runner.add(new TestPubSub()); + runner.add(new TestMultiPartMessage()); + runner.add(new TestReqRep()); + runner.add(new TestPoller()); + + // Run + runner.run(); + } +} \ No newline at end of file diff --git a/org/zeromq/test/TestContext.hx b/org/zeromq/test/TestContext.hx new file mode 100644 index 0000000..d6cef3d --- /dev/null +++ b/org/zeromq/test/TestContext.hx @@ -0,0 +1,114 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQException; +import org.zeromq.ZMQPoller; +import org.zeromq.ZMQSocket; + +/** + * ZMQ.ZMQContext class tests + * + * Based on https://github.com/zeromq/pyzmq/blob/master/zmq/tests/test_context.py + */ +class TestContext extends BaseTest +{ + + public function testInit() + { + var c1 = ZMQContext.instance(); + assertTrue(Std.is(c1, ZMQContext)); + c1 = null; + + var c2 = ZMQContext.instance(); + assertTrue(Std.is(c2, ZMQContext)); + c2 = null; + + var c3 = ZMQContext.instance(); + assertTrue(Std.is(c3, ZMQContext)); + c3 = null; + } + + public function testTerm() + { + var c1:ZMQContext = ZMQContext.instance(); + c1.term(); + assertTrue(c1.closed); + } + + public function testFailInit() { + + var c1:ZMQContext; + assertRaisesZMQException(function() c1 = ZMQContext.instance(0),EINVAL); + } + + public function testInstance() { + var ctx = ZMQContext.instance(); + var c2 = ZMQContext.instance(2); + assertTrue(Std.is(c2, ZMQContext)); + assertTrue(c2 == ctx); + c2.term(); + + var c3:ZMQContext = ZMQContext.instance(); + var c4:ZMQContext = ZMQContext.instance(); + assertFalse(c3 == c2); + assertFalse(c3.closed); + assertTrue(c3 == c4); + } + + public function testSocket() { + var ctx:ZMQContext = ZMQContext.instance(); + var s:ZMQSocket = ctx.socket(ZMQ_PUB); + + assertTrue(s != null); + assertFalse(s.closed); + s.close(); + + ctx.term(); + assertRaisesZMQException(function() s = ctx.socket(ZMQ_PUB), ENOTSUP); + try { + s = ctx.socket(ZMQ_PUB); + assertTrue(false); + } catch (e:ZMQException) { + assertTrue(e.err == ENOTSUP); + } + + } + + public function testPoller() { + var ctx:ZMQContext = ZMQContext.instance(); + var p:ZMQPoller = ctx.poller(); + + assertTrue(p != null); + + ctx.term(); + assertRaisesZMQException(function() p = ctx.poller(), ENOTSUP); + } + + public override function setup():Void { + // No setup needed for these tests + } + + public override function tearDown():Void { + // No tearDown needed for these tests + } +} \ No newline at end of file diff --git a/org/zeromq/test/TestError.hx b/org/zeromq/test/TestError.hx new file mode 100644 index 0000000..3a680fc --- /dev/null +++ b/org/zeromq/test/TestError.hx @@ -0,0 +1,54 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQException; + +class TestError extends BaseTest +{ + + public function testStrError() { + + var iter = new IntIter(1, 10); + for (i in iter) { + var s = ZMQ.strError(i); + assertTrue(Std.is(s, String)); + } + } + + public function testZMQException() { + + var e:ZMQException = new ZMQException(EINVAL); + assertTrue(e.errNo == ZMQ.errorTypeToErrNo(EINVAL)); + assertTrue(e.str() == ZMQ.strError(ZMQ.errorTypeToErrNo(EINVAL))); + + assertTrue(ZMQ.errNoToErrorType(ZMQ.errorTypeToErrNo(EINVAL)) == EINVAL); + + } + public override function setup():Void { + // No setup needed for these tests + } + + public override function tearDown():Void { + // No tearDown needed for these tests + } + +} \ No newline at end of file diff --git a/org/zeromq/test/TestInterrupt.hx b/org/zeromq/test/TestInterrupt.hx new file mode 100644 index 0000000..f5c7507 --- /dev/null +++ b/org/zeromq/test/TestInterrupt.hx @@ -0,0 +1,55 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; +import haxe.io.Bytes; +import neko.Sys; +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; + +class TestInterrupt extends BaseTest +{ + + public function testInterrupt() { + + var context:ZMQContext = ZMQContext.instance(); + var receiver:ZMQSocket = context.socket(ZMQ_REP); + receiver.bind("tcp://127.0.0.1:5559"); + trace ("About to call catchSignals"); + ZMQ.catchSignals(); + + print("\nPress Ctrl+C"); + + while (true) { + // Blocking read, will exit only on an interrupt (Ctrl+C) + var msg:Bytes = receiver.recvMsg(); + + if (ZMQ.isInterrupted()) { + assertTrue(true); + break; + } + // Should not get here + assertTrue(false); + } + // Close up gracefully + receiver.close(); + context.term(); + } +} \ No newline at end of file diff --git a/org/zeromq/test/TestMultiPartMessage.hx b/org/zeromq/test/TestMultiPartMessage.hx new file mode 100644 index 0000000..d1b2285 --- /dev/null +++ b/org/zeromq/test/TestMultiPartMessage.hx @@ -0,0 +1,64 @@ +/** + * (c) $(CopyrightDate) Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.io.Bytes; +import haxe.Stack; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQException; +import org.zeromq.test.BaseTest; + +class TestMultiPartMessage extends BaseTest +{ + + public function testMultiPartMessage() + { + var pair:SocketPair = null; + + try { + pair = createBoundPair(ZMQ_REQ, ZMQ_REP); + pair.s1.setsockopt(ZMQ_LINGER, 0); + pair.s2.setsockopt(ZMQ_LINGER, 0); + + var msg1:Bytes = Bytes.ofString("message1"); + var msg2:Bytes = Bytes.ofString("message2"); + var msg3:Bytes = Bytes.ofString("message3"); + pair.s1.sendMsg(msg1, SNDMORE); // 1st part of multipart message + pair.s1.sendMsg(msg2, SNDMORE); // 2nd part of multipart message + pair.s1.sendMsg(msg3); // last part of multipart message + + // Receive multipart message + var i:Int = 1; + do { + var b:Bytes = pair.s2.recvMsg(); // Blocking call + assertEquals(Std.string(i), b.toString().charAt(7)); + i++; + } while (pair.s2.hasReceiveMore()); + assertTrue(i == 4); // 3 parts received + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.toString()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + +} \ No newline at end of file diff --git a/org/zeromq/test/TestPoller.hx b/org/zeromq/test/TestPoller.hx new file mode 100644 index 0000000..a2a1049 --- /dev/null +++ b/org/zeromq/test/TestPoller.hx @@ -0,0 +1,193 @@ +/** + * (c) $(CopyrightDate) Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.io.Bytes; +import neko.Sys; +import haxe.Stack; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQPoller; +import org.zeromq.ZMQException; + +import org.zeromq.test.BaseTest; + +class TestPoller extends BaseTest +{ + + public function testRegistering() { + + var pair:SocketPair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + + // Create Poller object + var poller:ZMQPoller = new ZMQPoller(); + assertTrue(poller != null); + + poller.registerSocket(pair.s2, ZMQ.ZMQ_POLLIN()); + poller.registerSocket(pair.s1, ZMQ.ZMQ_POLLOUT()); + assertEquals(2, poller.getSize()); + + assertEquals(true, poller.unregisterSocket(pair.s2)); + assertEquals(1, poller.getSize()); + + poller.unregisterAllSockets(); + assertEquals(0, poller.getSize()); + } + + public function testPollingPairPair() { + + var pair:SocketPair = createBoundPair(ZMQ_PAIR, ZMQ_PAIR); + var pollinout:Int = ZMQ.ZMQ_POLLIN() | ZMQ.ZMQ_POLLOUT(); + try { + Sys.sleep(0.1); // Allow sockets time to connect + + var poller:ZMQPoller = new ZMQPoller(); + poller.registerSocket(pair.s1, pollinout); + poller.registerSocket(pair.s2, pollinout); + + var numSocks = poller.poll(); + assertEquals(2, numSocks); + assertEquals(2, poller.revents.length); + assertTrue(poller.pollout(1)); // PAIR socket s1 should be ready for writing + assertTrue(poller.pollout(2)); // PAIR socket s2 should be ready for writing + + // Now do a send on both, wait and test for POLLOUT|POLLIN + pair.s1.sendMsg(Bytes.ofString("msg1")); + pair.s2.sendMsg(Bytes.ofString("msg2")); + Sys.sleep(0.1); + + numSocks = poller.poll(); + assertTrue(poller.pollin(1) && poller.pollout(1)); // PAIR socket s1 should be ready for reading & writing + assertTrue(poller.pollin(2) && poller.pollout(2)); // PAIR socket s2 should be reasy for reading & writing + + // Make sure both are in POLLOUT after recv + var msg1 = pair.s1.recvMsg(); + var msg2 = pair.s2.recvMsg(); + numSocks = poller.poll(); + assertTrue(poller.pollout(1)); // PAIR socket s1 should be ready for writing + assertTrue(poller.pollout(2)); // PAIR socket s2 should be ready for writing + + poller.unregisterAllSockets(); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + + public function testPollingReqRep() { + + var pair:SocketPair = createBoundPair(ZMQ_REP, ZMQ_REQ); + var pollinout:Int = ZMQ.ZMQ_POLLIN() | ZMQ.ZMQ_POLLOUT(); + try { + Sys.sleep(0.1); // Allow sockets time to connect + + var poller:ZMQPoller = new ZMQPoller(); + poller.registerSocket(pair.s1, pollinout); + poller.registerSocket(pair.s2, pollinout); + + var numSocks = poller.poll(); + assertEquals(1, numSocks); // Only one revent bitmask with an event + assertEquals(2, poller.getSize()); + assertTrue(poller.noevents(1)); // REP socket s1 no events + assertTrue(poller.pollout(2)); // REQ socket s2 should be ready for writing + + // Make sure s2 REQ socket immediately goes into state 0 after send + pair.s2.sendMsg(Bytes.ofString("msg1")); + numSocks = poller.poll(); + assertTrue(poller.noevents(2)); + + // Make sure that s1 goes into POLLIN state after a sleep() + Sys.sleep(0.1); + numSocks = poller.poll(); + assertTrue(poller.pollin(1)); + + // Make sure s1 REP socket goes into POLLOUT after recv + var msg1 = pair.s1.recvMsg(); + numSocks = poller.poll(); + assertTrue(poller.pollout(1)); + + // Make sure s1 REP socket immediately goes into state 0 after send + pair.s1.sendMsg(Bytes.ofString("msg2")); + numSocks = poller.poll(); + assertTrue(poller.noevents(1)); + + // Make sure that s2 goes into POLLIN state after a sleep() + Sys.sleep(0.1); + numSocks = poller.poll(); + assertTrue(poller.pollin(2)); + + // Make sure s2 REQ socket goes into POLLOUT after recv + var msg1 = pair.s2.recvMsg(); + numSocks = poller.poll(); + assertTrue(poller.pollout(2)); + + poller.unregisterAllSockets(); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + } + + public function testPollingPubSub() { + var pair:SocketPair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + var pollinout:Int = ZMQ.ZMQ_POLLIN() | ZMQ.ZMQ_POLLOUT(); + try { + pair.s2.setsockopt(ZMQ_SUBSCRIBE, Bytes.ofString("")); + Sys.sleep(0.1); // Allow sockets time to connect + + var poller:ZMQPoller = new ZMQPoller(); + poller.registerSocket(pair.s1, pollinout); + poller.registerSocket(pair.s2, ZMQ.ZMQ_POLLIN()); + + // Makes sure only first is send ready + var numSocks = poller.poll(); + assertTrue(poller.pollout(1)); + assertTrue(poller.noevents(2)); + + // Make sure s1 stays in POLLOUT after send + pair.s1.sendMsg(Bytes.ofString("msg1")); + numSocks = poller.poll(); + assertTrue(poller.pollout(1)); + + // Make sure SUB s2 is ready for reading + Sys.sleep(0.1); + numSocks = poller.poll(); + assertTrue(poller.pollin(2)); + + var msg2:Bytes = pair.s2.recvMsg(); + numSocks = poller.poll(); + assertTrue(poller.noevents(2)); + + poller.unregisterAllSockets(); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + + } +} \ No newline at end of file diff --git a/org/zeromq/test/TestPubSub.hx b/org/zeromq/test/TestPubSub.hx new file mode 100644 index 0000000..636117a --- /dev/null +++ b/org/zeromq/test/TestPubSub.hx @@ -0,0 +1,91 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.io.Bytes; +import haxe.Stack; +import neko.Sys; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQSocket; +import org.zeromq.ZMQException; +import org.zeromq.test.BaseTest; + +class TestPubSub extends BaseTest +{ + + public function testBasicPubSub() { + + try { + var pair:SocketPair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + _sockets.add(pair.s1); + _sockets.add(pair.s2); + + // Subscribe to everything + pair.s2.setsockopt(ZMQ_SUBSCRIBE, Bytes.ofString("")); + + Sys.sleep(0.1); + pair.s1.sendMsg(Bytes.ofString("foo")); + var msg:Bytes = pair.s2.recvMsg(); // this is a blocking call + assertTrue(msg.toString() == "foo"); + + var a:Bytes = Bytes.alloc(1); + for (i in 0...10) { + a.set(0, i); + pair.s1.sendMsg(a); + msg = pair.s2.recvMsg(); + assertTrue(msg.length == 1); + assertTrue(msg.get(0) == i); + } + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + + public function testTopic() { + try { + var pair:SocketPair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + _sockets.add(pair.s1); + _sockets.add(pair.s2); + + var b:Bytes = Bytes.ofString("x"); + pair.s2.setsockopt(ZMQ_SUBSCRIBE, b); + Sys.sleep(0.1); // make sure subscriber gets first message published by publisher + + pair.s1.sendMsg(Bytes.ofString("message")); // send message that shouldnt meet the subscribe filter + pair.s1.sendMsg(Bytes.ofString("xmessage")); // send message that should meet the subscribe filter + var msg:Bytes = pair.s2.recvMsg(DONTWAIT); // This is a non-blocking call + assertTrue(msg == null); + + msg = pair.s2.recvMsg(); // this is a blocking call + assertTrue(msg != null); + assertTrue(msg.toString() == "xmessage"); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + } +} \ No newline at end of file diff --git a/org/zeromq/test/TestReqRep.hx b/org/zeromq/test/TestReqRep.hx new file mode 100644 index 0000000..8277402 --- /dev/null +++ b/org/zeromq/test/TestReqRep.hx @@ -0,0 +1,125 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.io.Bytes; +import StringTools; +import haxe.Stack; +import neko.Sys; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQSocket; +import org.zeromq.ZMQException; +import org.zeromq.test.BaseTest; + +/** + * Test class for REQ and REP socket types. + * + * Based on https://github.com/zeromq/pyzmq/blob/master/zmq/tests/test_reqrep.py + */ +class TestReqRep extends BaseTest +{ + + public function TODOtestBasicReqRep() { + var pair:SocketPair; + try { + pair = createBoundPair(ZMQ_REQ, ZMQ_REP); + var msg1:Bytes = Bytes.ofString("message1"); + + var msg2:Bytes = ping_pong(pair, msg1); + assertTrue(StringTools.startsWith(msg2.toString(),msg1.toString())); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + + public function testMultiple() { + var pair:SocketPair; + var msg1,msg2:Bytes; + + try { + pair = createBoundPair(ZMQ_REQ, ZMQ_REP); + for (i in 9...12) { + msg1 = Bytes.ofString(i + " "); + msg2 = ping_pong(pair, msg1); + assertTrue(StringTools.startsWith(msg2.toString(),msg1.toString())); + } + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + } + + public function testBadSendRcv() { + var pair:SocketPair; + try { + pair = createBoundPair(ZMQ_REQ, ZMQ_REP); + + //TODO: Why is this raising a Invalid argument (mutex.hpp:98) ereror at runtime? + assertRaisesZMQException(function() { pair.s1.recvMsg(); }, EFSM); // Try receiving on a REQ socket + assertRaisesZMQException(function() { pair.s2.sendMsg(Bytes.ofString("foo")); }, EFSM); // Try sending on a REP socket before receiving request + + // Added to prevent an abort trap being raised when we run the test + // See: https://github.com/zeromq/pyzmq/blob/master/zmq/tests/test_reqrep.py + var msg1:Bytes = Bytes.ofString("message1"); + var msg2:Bytes = ping_pong(pair, msg1); + assertTrue(StringTools.startsWith(msg2.toString(),msg1.toString())); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + + public function testLargeMessage() { + var pair:SocketPair; + try { + pair = createBoundPair(ZMQ_REQ, ZMQ_REP); + + var b:StringBuf = new StringBuf(); + + //TODO: Fails when set >= 6922 + for (c in 0...1000) { + b.add("x"); + } + + var msg1:Bytes = Bytes.ofString(b.toString()); + for (i in 0...10) { + var msg2:Bytes = ping_pong(pair, msg1); + assertTrue(StringTools.startsWith(msg2.toString(),msg1.toString())); + } + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + } + + +} \ No newline at end of file diff --git a/org/zeromq/test/TestSocket.hx b/org/zeromq/test/TestSocket.hx new file mode 100644 index 0000000..3f4610b --- /dev/null +++ b/org/zeromq/test/TestSocket.hx @@ -0,0 +1,190 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import haxe.Stack; +import haxe.io.Bytes; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQContext; +import org.zeromq.ZMQSocket; +import org.zeromq.ZMQException; +import org.zeromq.test.BaseTest; + +/** + * Haxe test class focused on the ZMQSocket haxe binding class + * + * Assumes a baseline ZMQ version of 2.1.4 (ie. some tests may not work if run against any earlier versions of the libzmq library) + */ +class TestSocket extends BaseTest +{ + + public function testCreate() { + var ctx:ZMQContext = ZMQContext.instance(); + var s:ZMQSocket = ctx.socket(ZMQ_PUB); + + assertTrue(s != null); + assertFalse(s.closed); + + // Test bind to invalid protocol + assertRaisesZMQException(function() s.bind("ftl://a"), EPROTONOSUPPORT); + assertRaisesZMQException(function() s.bind("tcp://"),EINVAL); + assertRaisesZMQException(function() s.connect("ftl://a"),EPROTONOSUPPORT); + + s.close(); + + assertTrue(s.closed); + + } + + public function testClose() { + var ctx:ZMQContext = ZMQContext.instance(); + var s:ZMQSocket = ctx.socket(ZMQ_PUB); + s.close(); + assertTrue(s.closed); + + assertRaisesZMQException(function() s.bind("") , ENOTSUP); + assertRaisesZMQException(function() s.connect(""), ENOTSUP); + } + + public function testBoundPair() { + var pair:SocketPair = null; + + try { + pair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + assertTrue(pair != null); + assertTrue(!pair.s1.closed); + assertTrue(!pair.s2.closed); + + } + + public function testIntSocketOptions() { + var pair:SocketPair = null; + + try { + pair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + pair.s1.setsockopt(ZMQ_LINGER, 0); + assertTrue(pair.s1.getsockopt(ZMQ_LINGER) == 0); + pair.s1.setsockopt(ZMQ_LINGER, -1); + assertTrue(pair.s1.getsockopt(ZMQ_LINGER) == -1); + var r:Int = pair.s1.getsockopt(ZMQ_EVENTS); + assertEquals(ZMQ.ZMQ_POLLOUT(), r); + assertRaisesZMQException(function() { pair.s1.setsockopt(ZMQ_EVENTS, 2 ^ 7 - 1); }, EINVAL); + assertEquals(ZMQ.socketTypeNo(ZMQ_PUB), pair.s1.getsockopt(ZMQ_TYPE)); + assertEquals(ZMQ.socketTypeNo(ZMQ_SUB), pair.s2.getsockopt(ZMQ_TYPE)); + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + + public function testInt64SocketOptions() { + var pair:SocketPair = null; + var r:ZMQInt64Type = null; + + try { + pair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + pair.s1.setsockopt(ZMQ_LINGER, 0); + r = pair.s1.getsockopt(ZMQ_HWM); + assertTrue(r.lo == 0); // Test default HWM is 0 for a new socket + assertTrue(r.hi == 0); + pair.s2.setsockopt(ZMQ_HWM, { hi:128, lo:128 } ); // (128 * 2^32)+128 + var r:ZMQInt64Type = pair.s2.getsockopt(ZMQ_HWM); + assertTrue(r.lo == 128); + assertTrue(r.hi == 128); + + r = pair.s1.getsockopt(ZMQ_AFFINITY); + assertEquals(0, r.lo); + + r = pair.s1.getsockopt(ZMQ_SWAP); + assertTrue(r.lo == 0); + assertTrue(r.hi == 0); + pair.s1.setsockopt(ZMQ_SWAP, { hi:1, lo:255 } ); // (1 * 2^32) + 255 bytes + r = pair.s1.getsockopt(ZMQ_SWAP); + assertTrue(r.lo == 255); + assertTrue(r.hi == 1); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + } + + public function testBytesSocketOptions() { + var pair:SocketPair = null; + + try { + pair = createBoundPair(ZMQ_PUB, ZMQ_SUB); + pair.s2.setsockopt(ZMQ_SUBSCRIBE, Bytes.ofString("foo")); + + // Test that you cannot retrieve a previously sefined SUBSCRIBE option value using getsockopt + // See: http://api.zeromq.org/2-1-3:zmq-getsockopt + assertRaisesZMQException(function() pair.s2.getsockopt(ZMQ_SUBSCRIBE),EINVAL); + + pair.s1.setsockopt(ZMQ_IDENTITY, Bytes.ofString("Socket1")); + var b:Bytes = pair.s1.getsockopt(ZMQ_IDENTITY); + assertEquals("Socket1", b.toString()); + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + } + public function testSendBasic() { + + try { + var pair:SocketPair = createBoundPair(ZMQ_PAIR, ZMQ_PAIR); + pair.s1.setsockopt(ZMQ_LINGER, 0); + pair.s2.setsockopt(ZMQ_LINGER, 0); + pair.s1.sendMsg(Bytes.ofString("foo")); + var msg:Bytes = pair.s2.recvMsg(); + assertTrue(msg.toString() == "foo"); + + + var a:Bytes = Bytes.alloc(1); + for (i in 0...10) { + a.set(0, i); + pair.s1.sendMsg(a); + msg = pair.s2.recvMsg(); + assertTrue(msg.length == 1); + assertTrue(msg.get(0) == i); + } + + } catch (e:ZMQException) { + trace("ZMQException #:" + e.errNo + ", str:" + e.str()); + trace (Stack.toString(Stack.exceptionStack())); + assertTrue(false); + } + + + } + + + +} \ No newline at end of file diff --git a/org/zeromq/test/TestVersion.hx b/org/zeromq/test/TestVersion.hx new file mode 100644 index 0000000..fea3dba --- /dev/null +++ b/org/zeromq/test/TestVersion.hx @@ -0,0 +1,56 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + +package org.zeromq.test; + +import org.zeromq.ZMQ; + +class TestVersion extends BaseTest +{ + + public function testVersionMethods() + { + var _major = ZMQ.versionMajor(); + assertTrue(_major >= 2); + + var _minor = ZMQ.versionMinor(); + assertTrue(_minor >= 0); + + var _patch = ZMQ.versionPatch(); + assertTrue(_patch >= 0); + + var _version = ZMQ.version_full(); + trace ("version_full:" + _version); + assertTrue(_version >= 0); + + } + + public function testMakeVersion() + { + assertTrue(ZMQ.makeVersion(1, 2, 3) == 10203); + } + + public override function setup():Void { + // No setup needed for these tests + } + + public override function tearDown():Void { + // No tearDown needed for these tests + } +} \ No newline at end of file diff --git a/src/Context.cpp b/src/Context.cpp new file mode 100644 index 0000000..bf5dff2 --- /dev/null +++ b/src/Context.cpp @@ -0,0 +1,76 @@ +/* + Copyright (c) Richard Smith 2011 + + This file is part of hxzmq. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +// Define a Kind type name for ZMQ context handles, which are opaque to the Haxe layer +DEFINE_KIND(k_zmq_context_handle); + +// Finalizer for context +void finalize_context( value v) { + gc_enter_blocking(); + int ret = zmq_term( val_data(v)); + gc_exit_blocking(); + if (ret != 0) { + int err = zmq_errno(); + val_throw(alloc_int(err)); + } +} + +value hx_zmq_construct (value io_threads) +{ + if (!val_is_int(io_threads)) + return alloc_null(); + + int _io_threads = val_int(io_threads); + if (_io_threads <= 0) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + void *c = zmq_init (_io_threads); + int err = zmq_errno(); + + if (c == NULL) { + val_throw (alloc_int(err)); + return alloc_null(); + } + + // See: http://nekovm.org/doc/ffi#abstracts_and_kinds + value v = alloc_abstract(k_zmq_context_handle,c); + val_gc(v,finalize_context); // finalize_context is called when the abstract value is garbage collected + return v; + +} + +value hx_zmq_term(value v) +{ + // Ensure v represents a previously created ZMQ context handle + val_check_kind(v,k_zmq_context_handle); + // Remove automatic gc finaliser calback + val_gc(v,0); + // Terminate the context + finalize_context(v); + return alloc_null(); +} + +DEFINE_PRIM( hx_zmq_construct, 1); +DEFINE_PRIM( hx_zmq_term, 1); \ No newline at end of file diff --git a/src/Interrupt.cpp b/src/Interrupt.cpp new file mode 100644 index 0000000..50e2239 --- /dev/null +++ b/src/Interrupt.cpp @@ -0,0 +1,57 @@ +/* + Copyright (c) Richard Smith 2011 + + This file is part of hxzmq. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + + +// Add in functions for handling system interrupts +// See: http://zguide.zeromq.org/page:all#Handling-Interrupt-Signals + +static int s_interrupted = 0; +void s_signal_handler (int signal_value) +{ + s_interrupted = 1; +} + +value hx_zmq_catch_signals () +{ +#if defined (_WIN32) + // Code adapted from: http://suacommunity.com/dictionary/signal-entry.php + void (*prev_handler) (int); + prev_handler = signal (SIGINT, s_signal_handler); + prev_handler = signal (SIGTERM, s_signal_handler); +#else + struct sigaction action; + action.sa_handler = s_signal_handler; + action.sa_flags = 0; + sigemptyset (&action.sa_mask); + sigaction (SIGINT, &action, NULL); + sigaction (SIGTERM, &action, NULL); +#endif + return alloc_null(); +} +DEFINE_PRIM( hx_zmq_catch_signals, 0); + +// Returns 1 if interrupted, else 0 +value hx_zmq_interrupted () +{ + return alloc_int(s_interrupted); +} +DEFINE_PRIM (hx_zmq_interrupted, 0); diff --git a/src/Poller.cpp b/src/Poller.cpp new file mode 100644 index 0000000..6b7ff71 --- /dev/null +++ b/src/Poller.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) Richard Smith 2011 + + This file is part of hxzmq. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + + +#include "socket.h" + +value hx_zmq_poll (value sockets_, value events_, value timeout_) { + + if (!val_is_int(timeout_) || !val_is_array(sockets_) || !val_is_array(events_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + //value *sockets_array; + //value *events_array; + //sockets_array = val_array_ptr( sockets_ ); + //events_array = val_array_ptr ( events_ ); + + int ls = val_array_size(sockets_); + int le = val_array_size(events_); + + if (ls != le) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + zmq_pollitem_t *pitem = new zmq_pollitem_t [ls]; + for (int i = 0; i < ls; i++) { + value socket = val_array_i(sockets_, i); + value event = val_array_i(events_, i); + + // Test that array index values are of the expected value type + val_check_kind(socket, k_zmq_socket_handle); + if (!val_is_int(event)) { + val_throw(alloc_int(EINVAL)); + delete [] pitem; + return alloc_null(); + } + + pitem [i].socket = val_data (socket); + pitem [i].fd = 0; + pitem [i].events = val_int (event); + pitem [i].revents = 0; + } + + gc_enter_blocking(); + + int rc = 0; + int err = 0; + long tout = val_int(timeout_); + + rc = zmq_poll (pitem, ls, tout); + err = zmq_errno(); + + gc_exit_blocking(); + + if (rc == -1) { + val_throw(alloc_int(err)); + delete [] pitem; + return alloc_null(); + } + + // return results + value retObj = alloc_empty_object (); + alloc_field( retObj, val_id("_ret"), alloc_int(rc)); + // build returned _revents int array + value haxe_revents = alloc_array(ls); + for (int i = 0; i < ls; i++) { + val_array_set_i (haxe_revents, i, alloc_int(pitem[i].revents)); + } + alloc_field( retObj, val_id("_revents"), haxe_revents); + + delete [] pitem; + pitem = NULL; + + return retObj; + +} + +DEFINE_PRIM (hx_zmq_poll, 3); \ No newline at end of file diff --git a/src/Socket.cpp b/src/Socket.cpp new file mode 100644 index 0000000..bd656e5 --- /dev/null +++ b/src/Socket.cpp @@ -0,0 +1,484 @@ +/* + Copyright (c) Richard Smith 2011 + + This file is part of hxzmq. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +#ifdef _MSC_VER +// Add stdint.hpp header file from zeromq distro to pick up integer types definitions +// Required by hxcpp build tool on windows if using msvc build tool? +#include +#endif + +#include +#include +#include +#include + +#include "socket.h" + +DEFINE_KIND( k_zmq_socket_handle ); + +DECLARE_KIND( k_zmq_context_handle ); + +// Finalizer for context +void finalize_socket( value v) { + gc_enter_blocking(); + int ret = zmq_close( val_data(v)); + gc_exit_blocking(); + if (ret != 0) { + int err = zmq_errno(); + val_throw(alloc_int(err)); + } +} + +value hx_zmq_construct_socket (value context_handle,value type) +{ + val_is_kind(context_handle,k_zmq_context_handle); + if (!val_is_int(type)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + void *s = zmq_socket (val_data(context_handle),val_int(type)); + int err = zmq_errno(); + + if (s == NULL) { + val_throw (alloc_int(err)); + return alloc_null(); + } + + // See: http://nekovm.org/doc/ffi#abstracts_and_kinds + value v = alloc_abstract(k_zmq_socket_handle,s); + val_gc(v,finalize_socket); // finalize_socket is called when the abstract value is garbage collected + return v; + +} + +value hx_zmq_close(value socket_handle) +{ + // Ensure v represents a previously created ZMQ socket handle + val_check_kind(socket_handle, k_zmq_socket_handle); + + // Remove the automatic gc finaliser callback + val_gc(socket_handle, 0); + + // Close the socket + finalize_socket(socket_handle); + + return alloc_null(); +} + +value hx_zmq_bind(value socket_handle, value addr) +{ + val_check_kind(socket_handle, k_zmq_socket_handle); + if (!val_is_string(addr)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = zmq_bind(val_data(socket_handle),val_string(addr)); + int err = zmq_errno(); + + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_int(rc); + +} + +value hx_zmq_connect(value socket_handle, value addr) +{ + val_check_kind(socket_handle, k_zmq_socket_handle); + if (!val_is_string(addr)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = zmq_connect(val_data(socket_handle),val_string(addr)); + int err = zmq_errno(); + + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + + return alloc_int(rc); + +} + +/* +See Socket.cpp from jzmq library +*/ +value hx_zmq_setintsockopt(value socket_handle_,value option_, value optval_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + if (!val_is_int(optval_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = 0; + int err = 0; + int ival = val_int(optval_); + int option = val_int(option_); + size_t optvallen = sizeof(ival); + + rc = zmq_setsockopt (val_data(socket_handle_), option, &ival, optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_int(rc); +} + + +value hx_zmq_setint64sockopt(value socket_handle_,value option_, value hi_optval_, value lo_optval_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + if (!val_is_int(hi_optval_) || !val_is_int(lo_optval_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + uint64_t _optval64 = val_int(hi_optval_); + _optval64 <<= 32; // Shift the hi int into the top half of the uint64_t value + _optval64 += val_int(lo_optval_); + + int rc = 0; + int err = 0; + int option = val_int(option_); + size_t optvallen = sizeof(_optval64); + + rc = zmq_setsockopt (val_data(socket_handle_), option, &_optval64, optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_int(rc); + +} + +value hx_zmq_setbytessockopt(value socket_handle_, value option_, value bytes_optval_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + printf("option_ is not int"); + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + size_t size = 0; + uint8_t *data = 0; + + // If data from neko + if (val_is_string(bytes_optval_)) + { + size = val_strlen(bytes_optval_); + data = (uint8_t *)val_string(bytes_optval_); + } // else from C++ + else if (val_is_buffer(bytes_optval_)) + { + buffer buf = val_to_buffer(bytes_optval_); + size = buffer_size(buf); + data = (uint8_t *)buffer_data(buf); + } else { + printf("bytes_optval_ not string or buffer"); + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = 0; + int err = 0; + int option = val_int(option_); + + rc = zmq_setsockopt (val_data(socket_handle_), option, data, size); + + err = zmq_errno(); + if (rc != 0) { + printf("err:%d",err); + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_int(rc); + +} + +value hx_zmq_getintsockopt(value socket_handle_,value option_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = 0; + int err = 0; + uint64_t optval = 0; + size_t optvallen = sizeof(optval); + rc = zmq_getsockopt(val_data(socket_handle_),val_int(option_),&optval, &optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_int(0); + } + return alloc_int(optval); +} + +value hx_zmq_getint64sockopt(value socket_handle_,value option_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = 0; + int err = 0; + uint64_t optval = 0; + size_t optvallen = sizeof(optval); + rc = zmq_getsockopt(val_data(socket_handle_),val_int(option_),&optval, &optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_int(0); + } + value ret = alloc_empty_object(); + alloc_field(ret, val_id("hi"),alloc_int(optval >> 32)); + alloc_field(ret, val_id("lo"),alloc_int(optval & 0xFFFFFFFF)); + + return ret; +} + +value hx_zmq_getbytessockopt(value socket_handle_,value option_) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_int(option_)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + int rc = 0; + int err = 0; + char optval [255]; + size_t optvallen = 255; + rc = zmq_getsockopt(val_data(socket_handle_),val_int(option_),&optval, &optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_int(0); + } + + // Return data to Haxe + + // Create a return buffer byte array, by memcopying the message data, then discard the ZMQ message + buffer b = alloc_buffer(NULL); + buffer_append_sub(b,optval,optvallen); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + + return buffer_val(b); +} + + +/** + * Receive data from socket + * Based on code in https://github.com/zeromq/jzmq/blob/master/src/Socket.cpp + */ +value hx_zmq_send(value socket_handle_, value msg_data, value flags) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_null(flags) && !val_is_int(flags)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + size_t size = 0; + uint8_t *data = 0; + + + zmq_msg_t message; + + // Extract byte data from either Neko string or C++ buffer + // see: http://waxe.googlecode.com/svn-history/r32/trunk/src/waxe/HaxeAPI.cpp "Val2ByteData" + if (val_is_string(msg_data)) + { + // Neko + size = val_strlen(msg_data); + data = (uint8_t *)val_string(msg_data); + } + else if (val_is_buffer(msg_data)) + { + // CPP + buffer buf = val_to_buffer(msg_data); + size = buffer_size(buf); + data = (uint8_t *)buffer_data(buf); + } else { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + + // Set up send message buffer by referencing the provided bytes data + //zero copy version: int rc = zmq_msg_init_data (&message, data, size,NULL,NULL); + int rc = zmq_msg_init_size(&message, size); + memcpy (zmq_msg_data(&message), data, size); + + int err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + + gc_enter_blocking(); + // Send + rc = zmq_send (val_data(socket_handle_), &message, val_int(flags)); + err = zmq_errno(); + + gc_exit_blocking(); + + // If NOBLOCK, but cant send message now, close message first before quitting + if (rc != 0 && err == EAGAIN) { + rc = zmq_msg_close (&message); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_null(); + } + + if (rc != 0) { + val_throw(alloc_int(err)); + rc = zmq_msg_close (&message); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_null(); + } + + rc = zmq_msg_close (&message); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_null(); +} + +/** + * Receive data from socket + * Based on code in https://github.com/zeromq/jzmq/blob/master/src/Socket.cpp + */ +value hx_zmq_rcv(value socket_handle_, value flags) { + + val_check_kind(socket_handle_, k_zmq_socket_handle); + + if (!val_is_null(flags) && !val_is_int(flags)) { + val_throw(alloc_int(EINVAL)); + return alloc_null(); + } + + zmq_msg_t message; + + int rc = zmq_msg_init (&message); + int err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + + gc_enter_blocking(); + + rc = zmq_recv (val_data(socket_handle_), &message, val_int(flags)); + + gc_exit_blocking(); + + err = zmq_errno(); + if (rc != 0 && err == EAGAIN) { + rc = zmq_msg_close (&message); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return alloc_null(); + } + + if (rc != 0) { + rc = zmq_msg_close (&message); + int err1 = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err1)); + return alloc_null(); + } + val_throw(alloc_int(err)); + return alloc_null(); + } + + // Return data to Haxe + int sz = zmq_msg_size (&message); + const char* pd = (char *)zmq_msg_data (&message); + + // Create a return buffer byte array, by memcopying the message data, then discard the ZMQ message + buffer b = alloc_buffer(NULL); + buffer_append_sub(b,pd,sz); + rc = zmq_msg_close (&message); + err = zmq_errno(); + if (rc != 0) { + val_throw(alloc_int(err)); + return alloc_null(); + } + return buffer_val(b); +} + +DEFINE_PRIM( hx_zmq_construct_socket, 2); +DEFINE_PRIM( hx_zmq_close, 1); +DEFINE_PRIM( hx_zmq_bind, 2); +DEFINE_PRIM( hx_zmq_connect, 2); +DEFINE_PRIM( hx_zmq_send, 3); +DEFINE_PRIM( hx_zmq_rcv, 2); +DEFINE_PRIM( hx_zmq_setintsockopt,3); +DEFINE_PRIM( hx_zmq_setint64sockopt,4); +DEFINE_PRIM( hx_zmq_setbytessockopt,3); +DEFINE_PRIM( hx_zmq_getintsockopt,2); +DEFINE_PRIM( hx_zmq_getint64sockopt,2); +DEFINE_PRIM( hx_zmq_getbytessockopt,2); diff --git a/src/ZMQ.cpp b/src/ZMQ.cpp new file mode 100644 index 0000000..8907341 --- /dev/null +++ b/src/ZMQ.cpp @@ -0,0 +1,383 @@ +/* + Copyright (c) Richard Smith 2011 + + This file is part of hxzmq. + + 0MQ is free software; you can redistribute it and/or modify it under + the terms of the Lesser GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 0MQ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + Lesser GNU General Public License for more details. + + You should have received a copy of the Lesser GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#define IMPLEMENT_API +#include + +value hx_zmq_version_full() +{ + return alloc_int(ZMQ_VERSION); +} +DEFINE_PRIM( hx_zmq_version_full,0); + +value hx_zmq_version_major() +{ + return alloc_int(ZMQ_VERSION_MAJOR); +} +DEFINE_PRIM( hx_zmq_version_major,0); + +value hx_zmq_version_minor() +{ + return alloc_int(ZMQ_VERSION_MINOR); +} +DEFINE_PRIM( hx_zmq_version_minor,0); + +value hx_zmq_version_patch() +{ + return alloc_int(ZMQ_VERSION_PATCH); +} +DEFINE_PRIM( hx_zmq_version_patch,0); + +value hx_zmq_make_version(value major_, value minor_, value patch_) +{ + if ( !val_is_int(major_) || !val_is_int(minor_) || !val_is_int(patch_) ) + return alloc_null(); + + return alloc_int(ZMQ_MAKE_VERSION(val_int(major_), val_int(minor_), val_int (patch_))); +} +DEFINE_PRIM( hx_zmq_make_version,3); + +value hx_zmq_str_error(value errno_) +{ + if (!val_is_int(errno_)) + return alloc_null(); + + return alloc_string(strerror(val_int(errno_))); +} +DEFINE_PRIM (hx_zmq_str_error,1); + +/* ******* Socket Types ****************/ +value hx_zmq_ZMQ_PUB() +{ + return alloc_int(ZMQ_PUB); +} +DEFINE_PRIM( hx_zmq_ZMQ_PUB,0); + +value hx_zmq_ZMQ_SUB() +{ + return alloc_int(ZMQ_SUB); +} +DEFINE_PRIM( hx_zmq_ZMQ_SUB,0); + +value hx_zmq_ZMQ_PAIR() +{ + return alloc_int(ZMQ_PAIR); +} +DEFINE_PRIM( hx_zmq_ZMQ_PAIR,0); + +value hx_zmq_ZMQ_REQ() +{ + return alloc_int(ZMQ_REQ); +} +DEFINE_PRIM( hx_zmq_ZMQ_REQ,0); + +value hx_zmq_ZMQ_REP() +{ + return alloc_int(ZMQ_REP); +} +DEFINE_PRIM( hx_zmq_ZMQ_REP,0); + +value hx_zmq_ZMQ_ROUTER() +{ + return alloc_int(ZMQ_ROUTER); +} +DEFINE_PRIM( hx_zmq_ZMQ_ROUTER,0); + +value hx_zmq_ZMQ_DEALER() +{ + return alloc_int(ZMQ_DEALER); +} +DEFINE_PRIM( hx_zmq_ZMQ_DEALER,0); + +value hx_zmq_ZMQ_PULL() +{ + return alloc_int(ZMQ_PULL); +} +DEFINE_PRIM( hx_zmq_ZMQ_PULL,0); + +value hx_zmq_ZMQ_PUSH() +{ + return alloc_int(ZMQ_PUSH); +} +DEFINE_PRIM( hx_zmq_ZMQ_PUSH,0); + +/* ******* Socket Option Types **********/ +value hx_zmq_ZMQ_LINGER() +{ + return alloc_int(ZMQ_LINGER); +} +DEFINE_PRIM( hx_zmq_ZMQ_LINGER,0); + +value hx_zmq_ZMQ_HWM() +{ + return alloc_int(ZMQ_HWM); +} + +DEFINE_PRIM( hx_zmq_ZMQ_HWM,0); + +value hx_zmq_ZMQ_RCVMORE() +{ + return alloc_int(ZMQ_RCVMORE); +} +DEFINE_PRIM( hx_zmq_ZMQ_RCVMORE,0); + +value hx_zmq_ZMQ_SUBSCRIBE() +{ + return alloc_int(ZMQ_SUBSCRIBE); +} +DEFINE_PRIM( hx_zmq_ZMQ_SUBSCRIBE,0); + +value hx_zmq_ZMQ_UNSUBSCRIBE() +{ + return alloc_int(ZMQ_UNSUBSCRIBE); +} +DEFINE_PRIM( hx_zmq_ZMQ_UNSUBSCRIBE,0); + +value hx_zmq_ZMQ_IDENTITY() +{ + return alloc_int(ZMQ_IDENTITY); +} +DEFINE_PRIM( hx_zmq_ZMQ_IDENTITY,0); + +value hx_zmq_ZMQ_SWAP() +{ + return alloc_int(ZMQ_SWAP); +} +DEFINE_PRIM( hx_zmq_ZMQ_SWAP,0); + +value hx_zmq_ZMQ_AFFINITY() +{ + return alloc_int(ZMQ_AFFINITY); +} +DEFINE_PRIM( hx_zmq_ZMQ_AFFINITY,0); + +value hx_zmq_ZMQ_RATE() +{ + return alloc_int(ZMQ_RATE); +} +DEFINE_PRIM( hx_zmq_ZMQ_RATE,0); + +value hx_zmq_ZMQ_RECOVERY_IVL() +{ + return alloc_int(ZMQ_RECOVERY_IVL); +} +DEFINE_PRIM( hx_zmq_ZMQ_RECOVERY_IVL,0); + +value hx_zmq_ZMQ_RECOVERY_IVL_MSEC() +{ + return alloc_int(ZMQ_RECOVERY_IVL_MSEC); +} +DEFINE_PRIM( hx_zmq_ZMQ_RECOVERY_IVL_MSEC,0); + +value hx_zmq_ZMQ_MCAST_LOOP() +{ + return alloc_int(ZMQ_MCAST_LOOP); +} +DEFINE_PRIM( hx_zmq_ZMQ_MCAST_LOOP,0); + +value hx_zmq_ZMQ_SNDBUF() +{ + return alloc_int(ZMQ_SNDBUF); +} +DEFINE_PRIM( hx_zmq_ZMQ_SNDBUF,0); + +value hx_zmq_ZMQ_RCVBUF() +{ + return alloc_int(ZMQ_RCVBUF); +} +DEFINE_PRIM( hx_zmq_ZMQ_RCVBUF,0); + +value hx_zmq_ZMQ_RECONNECT_IVL() +{ + return alloc_int(ZMQ_RECONNECT_IVL); +} +DEFINE_PRIM( hx_zmq_ZMQ_RECONNECT_IVL,0); + +value hx_zmq_ZMQ_RECONNECT_IVL_MAX() +{ + return alloc_int(ZMQ_RECONNECT_IVL_MAX); +} +DEFINE_PRIM( hx_zmq_ZMQ_RECONNECT_IVL_MAX,0); + +value hx_zmq_ZMQ_BACKLOG() +{ + return alloc_int(ZMQ_BACKLOG); +} +DEFINE_PRIM( hx_zmq_ZMQ_BACKLOG,0); + +value hx_zmq_ZMQ_FD() +{ + return alloc_int(ZMQ_FD); +} +DEFINE_PRIM( hx_zmq_ZMQ_FD,0); + +value hx_zmq_ZMQ_EVENTS() +{ + return alloc_int(ZMQ_EVENTS); +} +DEFINE_PRIM( hx_zmq_ZMQ_EVENTS,0); + +value hx_zmq_ZMQ_TYPE() +{ + return alloc_int(ZMQ_TYPE); +} +DEFINE_PRIM( hx_zmq_ZMQ_TYPE,0); + +value hx_zmq_ZMQ_POLLIN() +{ + return alloc_int(ZMQ_POLLIN); +} +DEFINE_PRIM( hx_zmq_ZMQ_POLLIN,0); + +value hx_zmq_ZMQ_POLLOUT() +{ + return alloc_int(ZMQ_POLLOUT); +} +DEFINE_PRIM( hx_zmq_ZMQ_POLLOUT,0); + +value hx_zmq_ZMQ_POLLERR() +{ + return alloc_int(ZMQ_POLLERR); +} +DEFINE_PRIM( hx_zmq_ZMQ_POLLERR,0); + +/* ******* Send/Receive Options ********/ +value hx_zmq_DONTWAIT() +{ +#ifndef ZMQ_DONTWAIT + return alloc_int(ZMQ_NOBLOCK); +#else + return alloc_int(ZMQ_DONTWAIT); +#endif +} +DEFINE_PRIM( hx_zmq_DONTWAIT,0); + +value hx_zmq_SNDMORE() +{ + return alloc_int(ZMQ_SNDMORE); +} +DEFINE_PRIM( hx_zmq_SNDMORE,0); + +/* ******* Exception Codes *************/ +value hx_zmq_EINVAL() +{ + return alloc_int(EINVAL); +} +DEFINE_PRIM( hx_zmq_EINVAL,0); + +value hx_zmq_ENOTSUP() +{ + return alloc_int(ENOTSUP); +} +DEFINE_PRIM( hx_zmq_ENOTSUP,0); + +value hx_zmq_EPROTONOSUPPORT() +{ + return alloc_int(EPROTONOSUPPORT); +} +DEFINE_PRIM( hx_zmq_EPROTONOSUPPORT,0); + +value hx_zmq_EAGAIN() +{ + return alloc_int(EAGAIN); +} +DEFINE_PRIM( hx_zmq_EAGAIN,0); + +value hx_zmq_EFAULT() +{ + return alloc_int(EFAULT); +} +DEFINE_PRIM( hx_zmq_EFAULT,0); + +value hx_zmq_ENOMEM() +{ + return alloc_int(ENOMEM); +} +DEFINE_PRIM( hx_zmq_ENOMEM,0); + +value hx_zmq_ENODEV() +{ + return alloc_int(ENODEV); +} +DEFINE_PRIM( hx_zmq_ENODEV,0); + +value hx_zmq_ENOBUFS() +{ + return alloc_int(ENOBUFS); +} +DEFINE_PRIM( hx_zmq_ENOBUFS,0); + +value hx_zmq_ENETDOWN() +{ + return alloc_int(ENETDOWN); +} +DEFINE_PRIM( hx_zmq_ENETDOWN,0); + +value hx_zmq_EADDRINUSE() +{ + return alloc_int(EADDRINUSE); +} +DEFINE_PRIM( hx_zmq_EADDRINUSE,0); + +value hx_zmq_EADDRNOTAVAIL() +{ + return alloc_int(EADDRNOTAVAIL); +} +DEFINE_PRIM( hx_zmq_EADDRNOTAVAIL,0); + +value hx_zmq_ECONNREFUSED() +{ + return alloc_int(ECONNREFUSED); +} +DEFINE_PRIM( hx_zmq_ECONNREFUSED,0); + +value hx_zmq_EINPROGRESS() +{ + return alloc_int(EINPROGRESS); +} +DEFINE_PRIM( hx_zmq_EINPROGRESS,0); + +value hx_zmq_EMTHREAD() +{ + return alloc_int(EMTHREAD); +} +DEFINE_PRIM( hx_zmq_EMTHREAD,0); + +value hx_zmq_EFSM() +{ + return alloc_int(EFSM); +} +DEFINE_PRIM( hx_zmq_EFSM,0); + +value hx_zmq_ENOCOMPATPROTO() +{ + return alloc_int(ENOCOMPATPROTO); +} +DEFINE_PRIM( hx_zmq_ENOCOMPATPROTO,0); + +value hx_zmq_ETERM() +{ + return alloc_int(ETERM); +} +DEFINE_PRIM( hx_zmq_ETERM,0); + diff --git a/src/include/hxzmq.h b/src/include/hxzmq.h new file mode 100644 index 0000000..e69de29 diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..04139d4 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,24 @@ +/** + * (c) 2011 Richard J Smith + * + * This file is part of hxzmq + * + * hxzmq is free software; you can redistribute it and/or modify it under + * the terms of the Lesser GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * hxzmq is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + */ + + +#include + +// Define a Kind type name for ZMQ socket handles, which are opaque to the Haxe layer +DECLARE_KIND(k_zmq_socket_handle); diff --git a/src/valgrind.supp b/src/valgrind.supp new file mode 100644 index 0000000..c71d01c --- /dev/null +++ b/src/valgrind.supp @@ -0,0 +1,92 @@ +{ + + Memcheck:Param + socketcall.sendto(msg) + fun:sendto + ... +} + +{ + + Memcheck:Leak + fun:malloc + fun:__cxa_get_globals + fun:__cxa_allocate_exception + ... +} + +{ + + Memcheck:Leak + fun:malloc + fun:_ZN2hx25InternalCreateConstBufferEPKvi + ... +} + +{ + + Memcheck:Addr1 + fun:memcpy + fun:_ZNK6StringplES_ + ... +} + +{ + + Memcheck:Addr1 + fun:_ZN6String7getCharEi + ... +} + +{ + + Memcheck:Addr2 + fun:memcpy + fun:_ZNK6StringplES_ + ... +} + +{ + + Memcheck:Addr8 + fun:memcpy + fun:_ZNK6StringplES_ + ... +} + +{ + + Memcheck:Addr1 + fun:memcpy + fun:_ZNK6String6substrEi7Dynamic + fun:_Z11FindHaxelib6String + fun:_Z10__loadprim6StringS_i + fun:_ZN3cpp7Lib_obj4loadE6StringS1_i + fun:_ZN3org6zeromq7ZMQ_obj6__bootEv + fun:_Z10__boot_allv + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:_ZN15GlobalAllocator13GetEmptyBlockEv + fun:_ZN15GlobalAllocator16GetRecycledBlockEi + fun:_ZN14LocalAllocator5AllocEib + fun:_ZN2hx11InternalNewEib + ... +} + +{ + + Memcheck:Leak + fun:_Znwm + fun:_ZNSs4_Rep9_S_createEmmRKSaIcE + fun:_ZNSs12_S_constructIPKcEEPcT_S3_RKSaIcESt20forward_iterator_tag + fun:_ZNSsC2EPKcRKSaIcE + fun:_Z19__hxcpp_field_to_idPKc + fun:val_id + ... +} + diff --git a/test/buildMac64.hxml b/test/buildMac64.hxml new file mode 100644 index 0000000..748bb19 --- /dev/null +++ b/test/buildMac64.hxml @@ -0,0 +1,15 @@ +# Haxe build file + +# Build CPP unit test target for Mac64 +-cp .. +-cpp out-cpp/Mac64 +-debug +-D HXCPP_M64 +--remap neko:cpp +-main org.zeromq.test.TestAll +--next +# Build Neko unit test target for Mac64 +-cp .. +-neko out-neko/Mac64/TestAll.n +-debug +-main org.zeromq.test.TestAll diff --git a/test/buildWindows.hxml b/test/buildWindows.hxml new file mode 100644 index 0000000..2df1e25 --- /dev/null +++ b/test/buildWindows.hxml @@ -0,0 +1,14 @@ +# Haxe build file + +# Build CPP unit test target for Windows +-cp .. +-cpp out-cpp/Windows +-debug +--remap neko:cpp +-main org.zeromq.test.TestAll +--next +# Build Neko unit test target for Windows +-cp .. +-neko out-neko/Windows/TestAll.n +-debug +-main org.zeromq.test.TestAll diff --git a/test/out-cpp/hxzmq.ndll b/test/out-cpp/hxzmq.ndll new file mode 100755 index 0000000000000000000000000000000000000000..d77612a4c574adcf14d8988a3fafc8000a205833 GIT binary patch literal 44672 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 literal 0 HcmV?d00001