├── LICENSE.txt ├── README.md ├── auth.go ├── auth_test.go ├── ctxoptions_unix.go ├── ctxoptions_windows.go ├── doc.go ├── draft ├── TODO.md ├── auth.go ├── auth_test.go ├── ctxoptions_unix.go ├── ctxoptions_windows.go ├── doc.go ├── dummy.c ├── errors.go ├── polling.go ├── reactor.go ├── socketevent_test.go ├── socketget.go ├── socketget_unix.go ├── socketget_windows.go ├── socketset.go ├── utils.go ├── wrappers_unix.go ├── wrappers_windows.go ├── zmq4.go ├── zmq4.h ├── zmq41_test.go ├── zmq42draft.go ├── zmq42draft.h ├── zmq42draft_test.go ├── zmq43draft.h └── zmq4_test.go ├── dummy.c ├── errors.go ├── examples ├── Build.sh ├── README.md ├── asyncsrv.go ├── bstar │ └── bstar.go ├── bstarcli.go ├── bstarsrv.go ├── bstarsrv2.go ├── clone │ └── clone.go ├── clonecli1.go ├── clonecli2.go ├── clonecli3.go ├── clonecli4.go ├── clonecli5.go ├── clonecli6.go ├── clonesrv1.go ├── clonesrv2.go ├── clonesrv3.go ├── clonesrv4.go ├── clonesrv5.go ├── clonesrv6.go ├── eagain.go ├── espresso.go ├── fileio1.go ├── fileio2.go ├── fileio3.go ├── flcliapi │ └── flcliapi.go ├── flclient1.go ├── flclient2.go ├── flclient3.go ├── flserver1.go ├── flserver2.go ├── flserver3.go ├── go.mod ├── go.sum ├── hwclient.go ├── hwserver.go ├── identity.go ├── interrupt.go ├── intface │ └── intface.go ├── kvmsg │ ├── kvmsg.go │ └── kvmsg_test.go ├── kvsimple │ ├── kvsimple.go │ └── kvsimple_test.go ├── lbbroker.go ├── lbbroker2.go ├── lbbroker3.go ├── lpclient.go ├── lpserver.go ├── lvcache.go ├── mdapi │ ├── const.go │ ├── mdcliapi.go │ ├── mdcliapi2.go │ └── mdwrkapi.go ├── mdbroker.go ├── mdclient.go ├── mdclient2.go ├── mdworker.go ├── mmiecho.go ├── msgqueue.go ├── mspoller.go ├── msreader.go ├── mtrelay.go ├── mtserver.go ├── pathopub.go ├── pathosub.go ├── peering1.go ├── peering2.go ├── peering3.go ├── ppqueue.go ├── ppworker.go ├── psenvpub.go ├── psenvsub.go ├── rrbroker.go ├── rrclient.go ├── rrworker.go ├── rtdealer.go ├── rtreq.go ├── spqueue.go ├── spworker.go ├── suisnail.go ├── sync.sh ├── syncpub.go ├── syncsub.go ├── tasksink.go ├── tasksink2.go ├── taskvent.go ├── taskwork.go ├── taskwork2.go ├── ticlient.go ├── titanic.go ├── tripping.go ├── udpping1.go ├── udpping2.go ├── udpping3.go ├── version.go ├── wuclient.go ├── wuproxy.go └── wuserver.go ├── examples_security ├── Makefile ├── README.md ├── grasslands.go ├── ironhouse.go ├── stonehouse.go ├── strawhouse.go └── woodhouse.go ├── go.mod ├── polling.go ├── reactor.go ├── socketevent_test.go ├── socketget.go ├── socketget_unix.go ├── socketget_windows.go ├── socketset.go ├── utils.go ├── wrappers_unix.go ├── wrappers_windows.go ├── zmq4.go ├── zmq4.h ├── zmq41_test.go └── zmq4_test.go /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2018, Peter Kleiweg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 16 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Go interface to [ZeroMQ](http://www.zeromq.org/) version 4. 2 | 3 | 4 | ---------------------------------------------------------------- 5 | 6 | ## Warning 7 | 8 | Starting with Go 1.14, on Unix-like systems, you will get a lot of 9 | interrupted signal calls. See the top of a package documentation 10 | for a fix. 11 | 12 | ---------------------------------------------------------------- 13 | 14 | 15 | [![Go Report Card](https://goreportcard.com/badge/github.com/pebbe/zmq4)](https://goreportcard.com/report/github.com/pebbe/zmq4) 16 | [![GoDoc](https://godoc.org/github.com/pebbe/zmq4?status.svg)](https://godoc.org/github.com/pebbe/zmq4) 17 | 18 | This requires ZeroMQ version 4.0.1 or above. To use CURVE security in 19 | versions prior to 4.2, ZeroMQ must be installed with 20 | [libsodium](https://github.com/jedisct1/libsodium) enabled. 21 | 22 | Partial support for ZeroMQ 4.2 DRAFT is available in the alternate 23 | version of zmq4 `draft`. The API pertaining to this is subject to 24 | change. To use this: 25 | 26 | import ( 27 | zmq "github.com/pebbe/zmq4/draft" 28 | ) 29 | 30 | For ZeroMQ version 3, see: http://github.com/pebbe/zmq3 31 | 32 | For ZeroMQ version 2, see: http://github.com/pebbe/zmq2 33 | 34 | Including all examples of [ØMQ - The Guide](http://zguide.zeromq.org/). 35 | 36 | Keywords: zmq, zeromq, 0mq, networks, distributed computing, message passing, fanout, pubsub, pipeline, request-reply 37 | 38 | ### See also 39 | 40 | * [go-zeromq/zmq4](https://github.com/go-zeromq/zmq4) — A pure-Go implementation of ØMQ (ZeroMQ), version 4 41 | * [goczmq](https://github.com/zeromq/goczmq) — A Go interface to CZMQ 42 | * [Awesome Go: Messaging] — Libraries that implement messaging systems 43 | 44 | 45 | ## Requirements 46 | 47 | zmq4 is just a wrapper for the ZeroMQ library. It doesn't include the 48 | library itself. So you need to have ZeroMQ installed, including its 49 | development files. On Linux and Darwin you can check this with (`$` is 50 | the command prompt): 51 | 52 | ``` 53 | $ pkg-config --modversion libzmq 54 | 4.3.1 55 | ``` 56 | 57 | The Go compiler must be able to compile C code. You can check this 58 | with: 59 | ``` 60 | $ go env CGO_ENABLED 61 | 1 62 | ``` 63 | 64 | You can't do cross-compilation. That would disable C. 65 | 66 | ### Windows 67 | 68 | Build with `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables, for example: 69 | 70 | ``` 71 | $env:CGO_CFLAGS='-ID:/dev/vcpkg/installed/x64-windows/include' 72 | $env:CGO_LDFLAGS='-LD:/dev/vcpkg/installed/x64-windows/lib -l:libzmq-mt-4_3_4.lib' 73 | ``` 74 | > Deploy result program with `libzmq-mt-4_3_4.dll` 75 | 76 | ## Install 77 | 78 | go get github.com/pebbe/zmq4 79 | 80 | ## Docs 81 | 82 | * [package help](http://godoc.org/github.com/pebbe/zmq4) 83 | * [wiki](https://github.com/pebbe/zmq4/wiki) 84 | 85 | ## API change 86 | 87 | There has been an API change in commit 88 | 0bc5ab465849847b0556295d9a2023295c4d169e of 2014-06-27, 10:17:55 UTC 89 | in the functions `AuthAllow` and `AuthDeny`. 90 | 91 | Old: 92 | 93 | func AuthAllow(addresses ...string) 94 | func AuthDeny(addresses ...string) 95 | 96 | New: 97 | 98 | func AuthAllow(domain string, addresses ...string) 99 | func AuthDeny(domain string, addresses ...string) 100 | 101 | If `domain` can be parsed as an IP address, it will be interpreted as 102 | such, and it and all remaining addresses are added to all domains. 103 | 104 | So this should still work as before: 105 | 106 | zmq.AuthAllow("127.0.0.1", "123.123.123.123") 107 | 108 | But this won't compile: 109 | 110 | a := []string{"127.0.0.1", "123.123.123.123"} 111 | zmq.AuthAllow(a...) 112 | 113 | And needs to be rewritten as: 114 | 115 | a := []string{"127.0.0.1", "123.123.123.123"} 116 | zmq.AuthAllow("*", a...) 117 | 118 | Furthermore, an address can now be a single IP address, as well as an IP 119 | address and mask in CIDR notation, e.g. "123.123.123.0/24". 120 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestAuthCurvePublic(t *testing.T) { 10 | if _, minor, _ := zmq.Version(); minor < 2 { 11 | t.Skip("CurvePublic not available in ZeroMQ versions prior to 4.2.0") 12 | } 13 | expected := "Yne@$w-vo= 1 && !zmq.HasCurve() { 30 | t.Skip("Curve not available") 31 | } 32 | 33 | type Meta struct { 34 | key string 35 | value string 36 | ok bool 37 | } 38 | 39 | zmq.AuthSetVerbose(false) 40 | 41 | // Start authentication engine 42 | err := zmq.AuthStart() 43 | if err != nil { 44 | t.Fatal("AuthStart:", err) 45 | } 46 | defer zmq.AuthStop() 47 | 48 | zmq.AuthSetMetadataHandler( 49 | func(version, request_id, domain, address, identity, mechanism string, credentials ...string) (metadata map[string]string) { 50 | return map[string]string{ 51 | "Identity": identity, 52 | "User-Id": "anonymous", 53 | "Hello": "World!", 54 | "Foo": "Bar", 55 | } 56 | }) 57 | 58 | zmq.AuthAllow("domain1", "127.0.0.1") 59 | 60 | // We need two certificates, one for the client and one for 61 | // the server. The client must know the server's public key 62 | // to make a CURVE connection. 63 | client_public, client_secret, err := zmq.NewCurveKeypair() 64 | if err != nil { 65 | t.Fatal("NewCurveKeypair:", err) 66 | } 67 | server_public, server_secret, err := zmq.NewCurveKeypair() 68 | if err != nil { 69 | t.Fatal("NewCurveKeypair:", err) 70 | } 71 | 72 | // Tell authenticator to use this public client key 73 | zmq.AuthCurveAdd("domain1", client_public) 74 | 75 | // Create and bind server socket 76 | server, err := zmq.NewSocket(zmq.DEALER) 77 | if err != nil { 78 | t.Fatal("NewSocket:", err) 79 | } 80 | defer func() { 81 | server.SetLinger(0) 82 | server.Close() 83 | }() 84 | server.SetIdentity("Server1") 85 | server.ServerAuthCurve("domain1", server_secret) 86 | err = server.Bind("tcp://*:9000") 87 | if err != nil { 88 | t.Fatal("server.Bind:", err) 89 | } 90 | 91 | // Create and connect client socket 92 | client, err := zmq.NewSocket(zmq.DEALER) 93 | if err != nil { 94 | t.Fatal("NewSocket:", err) 95 | } 96 | defer func() { 97 | client.SetLinger(0) 98 | client.Close() 99 | }() 100 | server.SetIdentity("Client1") 101 | client.ClientAuthCurve(server_public, client_public, client_secret) 102 | err = client.Connect("tcp://127.0.0.1:9000") 103 | if err != nil { 104 | t.Fatal("client.Connect:", err) 105 | } 106 | 107 | // Send a message from client to server 108 | msg := []string{"Greetings", "Earthlings!"} 109 | _, err = client.SendMessage(msg[0], msg[1]) 110 | if err != nil { 111 | t.Fatal("client.SendMessage:", err) 112 | } 113 | 114 | // Receive message and metadata on the server 115 | tests := []Meta{ 116 | {"Identity", "Server1", true}, 117 | {"User-Id", "anonymous", true}, 118 | {"Socket-Type", "DEALER", true}, 119 | {"Hello", "World!", true}, 120 | {"Foo", "Bar", true}, 121 | {"Fuz", "", false}, 122 | } 123 | keys := make([]string, len(tests)) 124 | for i, test := range tests { 125 | keys[i] = test.key 126 | } 127 | message, metadata, err := server.RecvMessageWithMetadata(0, keys...) 128 | if err != nil { 129 | t.Fatal("server.RecvMessageWithMetadata:", err) 130 | } 131 | if !arrayEqual(message, msg) { 132 | t.Errorf("Received message was %q, expected %q", message, msg) 133 | } 134 | if _, minor, _ := zmq.Version(); minor < 1 { 135 | t.Log("Metadata not avalable in ZeroMQ versions prior to 4.1.0") 136 | } else { 137 | for _, test := range tests { 138 | value, ok := metadata[test.key] 139 | if value != test.value || ok != test.ok { 140 | t.Errorf("Metadata %s, expected %q %v, got %q %v", test.key, test.value, test.ok, value, ok) 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /ctxoptions_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | #include "zmq4.h" 8 | */ 9 | import "C" 10 | 11 | /* 12 | Sets the scheduling policy for internal context’s thread pool. 13 | 14 | This option requires ZeroMQ version 4.1, and is not available on Windows. 15 | 16 | Supported values for this option can be found in sched.h file, or at 17 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 18 | 19 | This option only applies before creating any sockets on the context. 20 | 21 | Default value: -1 22 | 23 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 24 | 25 | Returns ErrorNotImplementedWindows on Windows 26 | */ 27 | func (ctx *Context) SetThreadSchedPolicy(n int) error { 28 | if minor < 1 { 29 | return ErrorNotImplemented41 30 | } 31 | return setOption(ctx, C.ZMQ_THREAD_SCHED_POLICY, n) 32 | } 33 | 34 | /* 35 | Sets scheduling priority for internal context’s thread pool. 36 | 37 | This option requires ZeroMQ version 4.1, and is not available on Windows. 38 | 39 | Supported values for this option depend on chosen scheduling policy. 40 | Details can be found in sched.h file, or at 41 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 42 | 43 | This option only applies before creating any sockets on the context. 44 | 45 | Default value: -1 46 | 47 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 48 | 49 | Returns ErrorNotImplementedWindows on Windows 50 | */ 51 | func (ctx *Context) SetThreadPriority(n int) error { 52 | if minor < 1 { 53 | return ErrorNotImplemented41 54 | } 55 | return setOption(ctx, C.ZMQ_THREAD_PRIORITY, n) 56 | } 57 | -------------------------------------------------------------------------------- /ctxoptions_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package zmq4 4 | 5 | /* 6 | Sets the scheduling policy for internal context’s thread pool. 7 | 8 | This option requires ZeroMQ version 4.1, and is not available on Windows. 9 | 10 | Supported values for this option can be found in sched.h file, or at 11 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 12 | 13 | This option only applies before creating any sockets on the context. 14 | 15 | Default value: -1 16 | 17 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 18 | 19 | Returns ErrorNotImplementedWindows on Windows 20 | */ 21 | func (ctx *Context) SetThreadSchedPolicy(n int) error { 22 | return ErrorNotImplementedWindows 23 | } 24 | 25 | /* 26 | Sets scheduling priority for internal context’s thread pool. 27 | 28 | This option requires ZeroMQ version 4.1, and is not available on Windows. 29 | 30 | Supported values for this option depend on chosen scheduling policy. 31 | Details can be found in sched.h file, or at 32 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 33 | 34 | This option only applies before creating any sockets on the context. 35 | 36 | Default value: -1 37 | 38 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 39 | 40 | Returns ErrorNotImplementedWindows on Windows 41 | */ 42 | func (ctx *Context) SetThreadPriority(n int) error { 43 | return ErrorNotImplementedWindows 44 | } 45 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A Go interface to ZeroMQ (zmq, 0mq) version 4. 3 | 4 | For ZeroMQ version 3, see: http://github.com/pebbe/zmq3 5 | 6 | For ZeroMQ version 2, see: http://github.com/pebbe/zmq2 7 | 8 | http://www.zeromq.org/ 9 | 10 | See also the wiki: https://github.com/pebbe/zmq4/wiki 11 | 12 | ---- 13 | 14 | A note on the use of a context: 15 | 16 | This package provides a default context. This is what will be used by 17 | the functions without a context receiver, that create a socket or 18 | manipulate the context. Package developers that import this package 19 | should probably not use the default context with its associated 20 | functions, but create their own context(s). See: type Context. 21 | 22 | ---- 23 | 24 | Since Go 1.14 you will get a lot of interrupted system calls. 25 | 26 | See: https://golang.org/doc/go1.14#runtime 27 | 28 | There are two options to prevent this. 29 | 30 | The first option is to build your program with the environment variable: 31 | 32 | GODEBUG=asyncpreemptoff=1 33 | 34 | The second option is to let the program retry after an interrupted system call. 35 | 36 | Initially, this is set to true, for the global context, and for contexts 37 | created with NewContext(). 38 | 39 | When you install a signal handler, for instance to handle Ctrl-C, you should 40 | probably clear this option in your signal handler. For example: 41 | 42 | zctx, _ := zmq.NewContext() 43 | 44 | ctx, cancel := context.WithCancel(context.Background()) 45 | 46 | go func() { 47 | chSignal := make(chan os.Signal, 1) 48 | signal.Notify(chSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) 49 | <-chSignal 50 | zmq4.SetRetryAfterEINTR(false) 51 | zctx.SetRetryAfterEINTR(false) 52 | cancel() 53 | }() 54 | 55 | ---- 56 | 57 | */ 58 | package zmq4 59 | -------------------------------------------------------------------------------- /draft/TODO.md: -------------------------------------------------------------------------------- 1 | ## TODO for ZMQ 4.5 DRAFT 2 | 3 | nothing done yet 4 | 5 | ## TODO for ZMQ 4.4 DRAFT 6 | 7 | nothing done yet 8 | 9 | ## TODO for ZMQ 4.3 DRAFT 10 | 11 | nothing done yet 12 | 13 | ## TODO for ZMQ 4.2 DRAFT 14 | 15 | see: https://github.com/zeromq/libzmq/releases/tag/v4.2.0 16 | 17 | ### New poller mechanism and APIs have been introduced in DRAFT state: 18 | 19 | zmq_poller_new 20 | zmq_poller_destroy 21 | zmq_poller_add 22 | zmq_poller_modify 23 | zmq_poller_remove 24 | zmq_poller_wait 25 | zmq_poller_wait_all 26 | zmq_poller_add_fd 27 | zmq_poller_modify_fd 28 | zmq_poller_remove_fd 29 | 30 | and a new supporting struct typedef 31 | 32 | zmq_poller_event_t 33 | 34 | They support existing socket type, new thread-safe socket types and file 35 | descriptors (cross-platform). 36 | 37 | Documentation will be made available in the future before these APIs are 38 | declared stable. 39 | 40 | **Status in Go**: Not implemented because there is no documentation. 41 | 42 | ### New cross-platform timers helper functions have been introduced in DRAFT state: 43 | 44 | zmq_timers_new 45 | zmq_timers_destroy 46 | zmq_timers_add 47 | zmq_timers_cancel 48 | zmq_timers_set_interval 49 | zmq_timers_reset 50 | zmq_timers_timeout 51 | zmq_timers_execute 52 | 53 | and a new supporting callback typedef: 54 | 55 | zmq_timer_fn 56 | 57 | **Status in Go:** Not implemented because there is no documentation. 58 | Do we need this in Go? 59 | -------------------------------------------------------------------------------- /draft/auth_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4/draft" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestAuthCurvePublic(t *testing.T) { 10 | if _, minor, _ := zmq.Version(); minor < 2 { 11 | t.Skip("CurvePublic not available in ZeroMQ versions prior to 4.2.0") 12 | } 13 | expected := "Yne@$w-vo= 1 && !zmq.HasCurve() { 30 | t.Skip("Curve not available") 31 | } 32 | 33 | type Meta struct { 34 | key string 35 | value string 36 | ok bool 37 | } 38 | 39 | zmq.AuthSetVerbose(false) 40 | 41 | // Start authentication engine 42 | err := zmq.AuthStart() 43 | if err != nil { 44 | t.Fatal("AuthStart:", err) 45 | } 46 | defer zmq.AuthStop() 47 | 48 | zmq.AuthSetMetadataHandler( 49 | func(version, request_id, domain, address, identity, mechanism string, credentials ...string) (metadata map[string]string) { 50 | return map[string]string{ 51 | "Identity": identity, 52 | "User-Id": "anonymous", 53 | "Hello": "World!", 54 | "Foo": "Bar", 55 | } 56 | }) 57 | 58 | zmq.AuthAllow("domain1", "127.0.0.1") 59 | 60 | // We need two certificates, one for the client and one for 61 | // the server. The client must know the server's public key 62 | // to make a CURVE connection. 63 | client_public, client_secret, err := zmq.NewCurveKeypair() 64 | if err != nil { 65 | t.Fatal("NewCurveKeypair:", err) 66 | } 67 | server_public, server_secret, err := zmq.NewCurveKeypair() 68 | if err != nil { 69 | t.Fatal("NewCurveKeypair:", err) 70 | } 71 | 72 | // Tell authenticator to use this public client key 73 | zmq.AuthCurveAdd("domain1", client_public) 74 | 75 | // Create and bind server socket 76 | server, err := zmq.NewSocket(zmq.DEALER) 77 | if err != nil { 78 | t.Fatal("NewSocket:", err) 79 | } 80 | defer func() { 81 | server.SetLinger(0) 82 | server.Close() 83 | }() 84 | server.SetIdentity("Server1") 85 | server.ServerAuthCurve("domain1", server_secret) 86 | err = server.Bind("tcp://*:9000") 87 | if err != nil { 88 | t.Fatal("server.Bind:", err) 89 | } 90 | 91 | // Create and connect client socket 92 | client, err := zmq.NewSocket(zmq.DEALER) 93 | if err != nil { 94 | t.Fatal("NewSocket:", err) 95 | } 96 | defer func() { 97 | client.SetLinger(0) 98 | client.Close() 99 | }() 100 | server.SetIdentity("Client1") 101 | client.ClientAuthCurve(server_public, client_public, client_secret) 102 | err = client.Connect("tcp://127.0.0.1:9000") 103 | if err != nil { 104 | t.Fatal("client.Connect:", err) 105 | } 106 | 107 | // Send a message from client to server 108 | msg := []string{"Greetings", "Earthlings!"} 109 | _, err = client.SendMessage(msg[0], msg[1]) 110 | if err != nil { 111 | t.Fatal("client.SendMessage:", err) 112 | } 113 | 114 | // Receive message and metadata on the server 115 | tests := []Meta{ 116 | {"Identity", "Server1", true}, 117 | {"User-Id", "anonymous", true}, 118 | {"Socket-Type", "DEALER", true}, 119 | {"Hello", "World!", true}, 120 | {"Foo", "Bar", true}, 121 | {"Fuz", "", false}, 122 | } 123 | keys := make([]string, len(tests)) 124 | for i, test := range tests { 125 | keys[i] = test.key 126 | } 127 | message, metadata, err := server.RecvMessageWithMetadata(0, keys...) 128 | if err != nil { 129 | t.Fatal("server.RecvMessageWithMetadata:", err) 130 | } 131 | if !arrayEqual(message, msg) { 132 | t.Errorf("Received message was %q, expected %q", message, msg) 133 | } 134 | if _, minor, _ := zmq.Version(); minor < 1 { 135 | t.Log("Metadata not avalable in ZeroMQ versions prior to 4.1.0") 136 | } else { 137 | for _, test := range tests { 138 | value, ok := metadata[test.key] 139 | if value != test.value || ok != test.ok { 140 | t.Errorf("Metadata %s, expected %q %v, got %q %v", test.key, test.value, test.ok, value, ok) 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /draft/ctxoptions_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | #include "zmq4.h" 8 | */ 9 | import "C" 10 | 11 | /* 12 | Sets the scheduling policy for internal context’s thread pool. 13 | 14 | This option requires ZeroMQ version 4.1, and is not available on Windows. 15 | 16 | Supported values for this option can be found in sched.h file, or at 17 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 18 | 19 | This option only applies before creating any sockets on the context. 20 | 21 | Default value: -1 22 | 23 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 24 | 25 | Returns ErrorNotImplementedWindows on Windows 26 | */ 27 | func (ctx *Context) SetThreadSchedPolicy(n int) error { 28 | if minor < 1 { 29 | return ErrorNotImplemented41 30 | } 31 | return setOption(ctx, C.ZMQ_THREAD_SCHED_POLICY, n) 32 | } 33 | 34 | /* 35 | Sets scheduling priority for internal context’s thread pool. 36 | 37 | This option requires ZeroMQ version 4.1, and is not available on Windows. 38 | 39 | Supported values for this option depend on chosen scheduling policy. 40 | Details can be found in sched.h file, or at 41 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 42 | 43 | This option only applies before creating any sockets on the context. 44 | 45 | Default value: -1 46 | 47 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 48 | 49 | Returns ErrorNotImplementedWindows on Windows 50 | */ 51 | func (ctx *Context) SetThreadPriority(n int) error { 52 | if minor < 1 { 53 | return ErrorNotImplemented41 54 | } 55 | return setOption(ctx, C.ZMQ_THREAD_PRIORITY, n) 56 | } 57 | -------------------------------------------------------------------------------- /draft/ctxoptions_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package zmq4 4 | 5 | /* 6 | Sets the scheduling policy for internal context’s thread pool. 7 | 8 | This option requires ZeroMQ version 4.1, and is not available on Windows. 9 | 10 | Supported values for this option can be found in sched.h file, or at 11 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 12 | 13 | This option only applies before creating any sockets on the context. 14 | 15 | Default value: -1 16 | 17 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 18 | 19 | Returns ErrorNotImplementedWindows on Windows 20 | */ 21 | func (ctx *Context) SetThreadSchedPolicy(n int) error { 22 | return ErrorNotImplementedWindows 23 | } 24 | 25 | /* 26 | Sets scheduling priority for internal context’s thread pool. 27 | 28 | This option requires ZeroMQ version 4.1, and is not available on Windows. 29 | 30 | Supported values for this option depend on chosen scheduling policy. 31 | Details can be found in sched.h file, or at 32 | http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html 33 | 34 | This option only applies before creating any sockets on the context. 35 | 36 | Default value: -1 37 | 38 | Returns ErrorNotImplemented41 with ZeroMQ version < 4.1 39 | 40 | Returns ErrorNotImplementedWindows on Windows 41 | */ 42 | func (ctx *Context) SetThreadPriority(n int) error { 43 | return ErrorNotImplementedWindows 44 | } 45 | -------------------------------------------------------------------------------- /draft/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A Go interface to ZeroMQ (zmq, 0mq) version 4. 3 | 4 | This includes partial support for ZeroMQ 4.2 DRAFT. The API pertaining 5 | to this support is subject to change. 6 | 7 | For ZeroMQ version 3, see: http://github.com/pebbe/zmq3 8 | 9 | For ZeroMQ version 2, see: http://github.com/pebbe/zmq2 10 | 11 | http://www.zeromq.org/ 12 | 13 | See also the wiki: https://github.com/pebbe/zmq4/wiki 14 | 15 | ---- 16 | 17 | A note on the use of a context: 18 | 19 | This package provides a default context. This is what will be used by 20 | the functions without a context receiver, that create a socket or 21 | manipulate the context. Package developers that import this package 22 | should probably not use the default context with its associated 23 | functions, but create their own context(s). See: type Context. 24 | 25 | ---- 26 | 27 | Since Go 1.14 you will get a lot of interrupted system calls. 28 | 29 | See: https://golang.org/doc/go1.14#runtime 30 | 31 | There are two options to prevent this. 32 | 33 | The first option is to build your program with the environment variable: 34 | 35 | GODEBUG=asyncpreemptoff=1 36 | 37 | The second option is to let the program retry after an interrupted system call. 38 | 39 | Initially, this is set to true, for the global context, and for contexts 40 | created with NewContext(). 41 | 42 | When you install a signal handler, for instance to handle Ctrl-C, you should 43 | probably clear this option in your signal handler. For example: 44 | 45 | zctx, _ := zmq.NewContext() 46 | 47 | ctx, cancel := context.WithCancel(context.Background()) 48 | 49 | go func() { 50 | chSignal := make(chan os.Signal, 1) 51 | signal.Notify(chSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) 52 | <-chSignal 53 | zmq4.SetRetryAfterEINTR(false) 54 | zctx.SetRetryAfterEINTR(false) 55 | cancel() 56 | }() 57 | 58 | ---- 59 | 60 | */ 61 | package zmq4 62 | -------------------------------------------------------------------------------- /draft/dummy.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | You need CGO_ENABLED=1 to build this package 4 | 5 | */ 6 | -------------------------------------------------------------------------------- /draft/errors.go: -------------------------------------------------------------------------------- 1 | package zmq4 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "syscall" 10 | ) 11 | 12 | // An Errno is an unsigned number describing an error condition as returned by a call to ZeroMQ. 13 | // It implements the error interface. 14 | // The number is either a standard system error, or an error defined by the C library of ZeroMQ. 15 | type Errno uintptr 16 | 17 | const ( 18 | // Error conditions defined by the C library of ZeroMQ. 19 | 20 | // On Windows platform some of the standard POSIX errnos are not defined. 21 | EADDRINUSE = Errno(C.EADDRINUSE) 22 | EADDRNOTAVAIL = Errno(C.EADDRNOTAVAIL) 23 | EAFNOSUPPORT = Errno(C.EAFNOSUPPORT) 24 | ECONNABORTED = Errno(C.ECONNABORTED) 25 | ECONNREFUSED = Errno(C.ECONNREFUSED) 26 | ECONNRESET = Errno(C.ECONNRESET) 27 | EHOSTUNREACH = Errno(C.EHOSTUNREACH) 28 | EINPROGRESS = Errno(C.EINPROGRESS) 29 | EMSGSIZE = Errno(C.EMSGSIZE) 30 | ENETDOWN = Errno(C.ENETDOWN) 31 | ENETRESET = Errno(C.ENETRESET) 32 | ENETUNREACH = Errno(C.ENETUNREACH) 33 | ENOBUFS = Errno(C.ENOBUFS) 34 | ENOTCONN = Errno(C.ENOTCONN) 35 | ENOTSOCK = Errno(C.ENOTSOCK) 36 | ENOTSUP = Errno(C.ENOTSUP) 37 | EPROTONOSUPPORT = Errno(C.EPROTONOSUPPORT) 38 | ETIMEDOUT = Errno(C.ETIMEDOUT) 39 | 40 | // Native 0MQ error codes. 41 | EFSM = Errno(C.EFSM) 42 | EMTHREAD = Errno(C.EMTHREAD) 43 | ENOCOMPATPROTO = Errno(C.ENOCOMPATPROTO) 44 | ETERM = Errno(C.ETERM) 45 | ) 46 | 47 | func errget(err error) error { 48 | eno, ok := err.(syscall.Errno) 49 | if ok { 50 | return Errno(eno) 51 | } 52 | return err 53 | } 54 | 55 | // Return Errno as string. 56 | func (errno Errno) Error() string { 57 | if errno >= C.ZMQ_HAUSNUMERO { 58 | return C.GoString(C.zmq_strerror(C.int(errno))) 59 | } 60 | return syscall.Errno(errno).Error() 61 | } 62 | 63 | /* 64 | Convert error to Errno. 65 | 66 | Example usage: 67 | 68 | switch AsErrno(err) { 69 | 70 | case zmq.Errno(syscall.EINTR): 71 | // standard system error 72 | 73 | // call was interrupted 74 | 75 | case zmq.ETERM: 76 | // error defined by ZeroMQ 77 | 78 | // context was terminated 79 | 80 | } 81 | 82 | See also: examples/interrupt.go 83 | */ 84 | func AsErrno(err error) Errno { 85 | if eno, ok := err.(Errno); ok { 86 | return eno 87 | } 88 | if eno, ok := err.(syscall.Errno); ok { 89 | return Errno(eno) 90 | } 91 | return Errno(0) 92 | } 93 | 94 | func (ctx *Context) retry(err error) bool { 95 | if !ctx.retryEINTR || err == nil { 96 | return false 97 | } 98 | eno, ok := err.(syscall.Errno) 99 | if !ok { 100 | return false 101 | } 102 | return eno == syscall.EINTR 103 | } 104 | -------------------------------------------------------------------------------- /draft/socketevent_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4/draft" 5 | 6 | "fmt" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func rep_socket_monitor(addr string, chMsg chan<- string) { 12 | 13 | defer close(chMsg) 14 | 15 | s, err := zmq.NewSocket(zmq.PAIR) 16 | if err != nil { 17 | chMsg <- fmt.Sprint("NewSocket:", err) 18 | return 19 | } 20 | defer func() { 21 | s.SetLinger(0) 22 | s.Close() 23 | }() 24 | 25 | err = s.Connect(addr) 26 | if err != nil { 27 | chMsg <- fmt.Sprint("s.Connect:", err) 28 | return 29 | } 30 | 31 | for { 32 | a, b, _, err := s.RecvEvent(0) 33 | if err != nil { 34 | chMsg <- fmt.Sprint("s.RecvEvent:", err) 35 | return 36 | } 37 | chMsg <- fmt.Sprint(a, " ", b) 38 | if a == zmq.EVENT_CLOSED { 39 | break 40 | } 41 | } 42 | chMsg <- "Done" 43 | } 44 | 45 | func TestSocketEvent(t *testing.T) { 46 | 47 | var rep *zmq.Socket 48 | defer func() { 49 | if rep != nil { 50 | rep.SetLinger(0) 51 | rep.Close() 52 | } 53 | }() 54 | 55 | // REP socket 56 | rep, err := zmq.NewSocket(zmq.REP) 57 | if err != nil { 58 | t.Fatal("NewSocket:", err) 59 | } 60 | 61 | // REP socket monitor, all events 62 | err = rep.Monitor("inproc://monitor.rep", zmq.EVENT_ALL) 63 | if err != nil { 64 | t.Fatal("rep.Monitor:", err) 65 | } 66 | chMsg := make(chan string, 10) 67 | go rep_socket_monitor("inproc://monitor.rep", chMsg) 68 | time.Sleep(time.Second) 69 | 70 | // Generate an event 71 | err = rep.Bind("tcp://*:9689") 72 | if err != nil { 73 | t.Fatal("rep.Bind:", err) 74 | } 75 | 76 | rep.Close() 77 | rep = nil 78 | 79 | expect := []string{ 80 | "EVENT_LISTENING tcp://0.0.0.0:9689", 81 | "EVENT_CLOSED tcp://0.0.0.0:9689", 82 | "Done", 83 | } 84 | i := 0 85 | for msg := range chMsg { 86 | if i < len(expect) { 87 | if msg != expect[i] { 88 | t.Errorf("Expected message %q, got %q", expect[i], msg) 89 | } 90 | i++ 91 | } else { 92 | t.Errorf("Unexpected message: %q", msg) 93 | } 94 | } 95 | for ; i < len(expect); i++ { 96 | t.Errorf("Expected message %q, got nothing", expect[i]) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /draft/socketget_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | */ 8 | import "C" 9 | 10 | // ZMQ_FD: Retrieve file descriptor associated with the socket 11 | // 12 | // See: http://api.zeromq.org/4-1:zmq-getsockopt#toc9 13 | func (soc *Socket) GetFd() (int, error) { 14 | return soc.getInt(C.ZMQ_FD) 15 | } 16 | -------------------------------------------------------------------------------- /draft/socketget_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | #include 8 | #include "zmq4.h" 9 | */ 10 | import "C" 11 | 12 | // winsock2.h needed for ZeroMQ version 4.3.3 13 | 14 | import ( 15 | "unsafe" 16 | ) 17 | 18 | /* 19 | ZMQ_FD: Retrieve file descriptor associated with the socket 20 | 21 | See: http://api.zeromq.org/4-1:zmq-getsockopt#toc9 22 | */ 23 | func (soc *Socket) GetFd() (uintptr, error) { 24 | value := C.SOCKET(0) 25 | size := C.size_t(unsafe.Sizeof(value)) 26 | var i C.int 27 | var err error 28 | for { 29 | i, err = C.zmq4_getsockopt(soc.soc, C.ZMQ_FD, unsafe.Pointer(&value), &size) 30 | // not really necessary because Windows doesn't have EINTR 31 | if i == 0 || !soc.ctx.retry(err) { 32 | break 33 | } 34 | } 35 | if i != 0 { 36 | return uintptr(0), errget(err) 37 | } 38 | return uintptr(value), nil 39 | } 40 | -------------------------------------------------------------------------------- /draft/wrappers_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | 7 | #include 8 | #include 9 | 10 | #if ZMQ_VERSION_MINOR < 2 11 | // Version < 4.2.x 12 | #include 13 | int zmq_curve_public (char *z85_public_key, const char *z85_secret_key); 14 | #endif // Version < 4.2.x 15 | 16 | #if ZMQ_VERSION_MINOR < 1 17 | const char *zmq_msg_gets (zmq_msg_t *msg, const char *property); 18 | #if ZMQ_VERSION_PATCH < 5 19 | // Version < 4.0.5 20 | int zmq_proxy_steerable (const void *frontend, const void *backend, const void *capture, const void *control); 21 | #endif // Version < 4.0.5 22 | #endif // Version == 4.0.x 23 | 24 | int zmq4_bind (void *socket, const char *endpoint) 25 | { 26 | return zmq_bind(socket, endpoint); 27 | } 28 | 29 | int zmq4_close (void *socket) 30 | { 31 | return zmq_close(socket); 32 | } 33 | 34 | int zmq4_connect (void *socket, const char *endpoint) 35 | { 36 | return zmq_connect(socket, endpoint); 37 | } 38 | 39 | int zmq4_ctx_get (void *context, int option_name) 40 | { 41 | return zmq_ctx_get(context, option_name); 42 | } 43 | 44 | void *zmq4_ctx_new () 45 | { 46 | return zmq_ctx_new(); 47 | } 48 | 49 | int zmq4_ctx_set (void *context, int option_name, int option_value) 50 | { 51 | return zmq_ctx_set(context, option_name, option_value); 52 | } 53 | 54 | int zmq4_ctx_term (void *context) 55 | { 56 | return zmq_ctx_term(context); 57 | } 58 | 59 | int zmq4_curve_keypair (char *z85_public_key, char *z85_secret_key) 60 | { 61 | return zmq_curve_keypair(z85_public_key, z85_secret_key); 62 | } 63 | 64 | int zmq4_curve_public (char *z85_public_key, char *z85_secret_key) 65 | { 66 | return zmq_curve_public(z85_public_key, z85_secret_key); 67 | } 68 | 69 | int zmq4_disconnect (void *socket, const char *endpoint) 70 | { 71 | return zmq_disconnect(socket, endpoint); 72 | } 73 | 74 | int zmq4_getsockopt (void *socket, int option_name, void *option_value, size_t *option_len) 75 | { 76 | return zmq_getsockopt(socket, option_name, option_value, option_len); 77 | } 78 | 79 | const char *zmq4_msg_gets (zmq_msg_t *message, const char *property) 80 | { 81 | return zmq_msg_gets(message, property); 82 | } 83 | 84 | int zmq4_msg_recv (zmq_msg_t *msg, void *socket, int flags) 85 | { 86 | return zmq_msg_recv(msg, socket, flags); 87 | } 88 | 89 | int zmq4_poll (zmq_pollitem_t *items, int nitems, long timeout) 90 | { 91 | return zmq_poll(items, nitems, timeout); 92 | } 93 | 94 | int zmq4_proxy (void *frontend, void *backend, void *capture) 95 | { 96 | return zmq_proxy(frontend, backend, capture); 97 | } 98 | 99 | int zmq4_proxy_steerable (void *frontend, void *backend, void *capture, void *control) 100 | { 101 | return zmq_proxy_steerable(frontend, backend, capture, control); 102 | } 103 | 104 | int zmq4_send (void *socket, void *buf, size_t len, int flags) 105 | { 106 | return zmq_send(socket, buf, len, flags); 107 | } 108 | 109 | int zmq4_setsockopt (void *socket, int option_name, const void *option_value, size_t option_len) 110 | { 111 | return zmq_setsockopt(socket, option_name, option_value, option_len); 112 | } 113 | 114 | void *zmq4_socket (void *context, int type) 115 | { 116 | return zmq_socket(context, type); 117 | } 118 | 119 | int zmq4_socket_monitor (void *socket, char *endpoint, int events) 120 | { 121 | return zmq_socket_monitor(socket, endpoint, events); 122 | } 123 | 124 | int zmq4_unbind (void *socket, const char *endpoint) 125 | { 126 | return zmq_unbind(socket, endpoint); 127 | } 128 | 129 | // DRAFT 130 | 131 | int zmq4_msg_init_size (zmq_msg_t *msg, size_t size) 132 | { 133 | return zmq_msg_init_size(msg, size); 134 | } 135 | 136 | int zmq4_msg_send (zmq_msg_t *msg, void *socket, int flags) 137 | { 138 | return zmq_msg_send(msg, socket, flags); 139 | } 140 | 141 | // not documented, so just assuming about return value... 142 | int zmq4_msg_set_group (zmq_msg_t *msg, const char *group) 143 | { 144 | return zmq_msg_set_group(msg, group); 145 | } 146 | 147 | int zmq4_msg_set_routing_id (zmq_msg_t *message, uint32_t routing_id) 148 | { 149 | return zmq_msg_set_routing_id(message, routing_id); 150 | } 151 | 152 | // not documented, so just assuming about return value... 153 | int zmq4_join (void *s, const char *group) 154 | { 155 | return zmq_join(s, group); 156 | } 157 | 158 | // not documented, so just assuming about return value... 159 | int zmq4_leave (void *s, const char *group) 160 | { 161 | return zmq_leave(s, group); 162 | } 163 | 164 | */ 165 | import "C" 166 | -------------------------------------------------------------------------------- /draft/zmq4.h: -------------------------------------------------------------------------------- 1 | #if ZMQ_VERSION_MAJOR != 4 2 | 3 | #error "You need ZeroMQ version 4 to build this" 4 | 5 | #endif 6 | 7 | #if ZMQ_VERSION_MINOR < 1 8 | 9 | #define ZMQ_CONNECT_RID -1 10 | #define ZMQ_GSSAPI -1 11 | #define ZMQ_GSSAPI_PLAINTEXT -1 12 | #define ZMQ_GSSAPI_PRINCIPAL -1 13 | #define ZMQ_GSSAPI_SERVER -1 14 | #define ZMQ_GSSAPI_SERVICE_PRINCIPAL -1 15 | #define ZMQ_HANDSHAKE_IVL -1 16 | #define ZMQ_IPC_FILTER_GID -1 17 | #define ZMQ_IPC_FILTER_PID -1 18 | #define ZMQ_IPC_FILTER_UID -1 19 | #define ZMQ_ROUTER_HANDOVER -1 20 | #define ZMQ_SOCKS_PROXY -1 21 | #define ZMQ_THREAD_PRIORITY -1 22 | #define ZMQ_THREAD_SCHED_POLICY -1 23 | #define ZMQ_TOS -1 24 | #define ZMQ_XPUB_NODROP -1 25 | 26 | #endif 27 | 28 | #if ZMQ_VERSION_MINOR < 2 29 | 30 | #define ZMQ_MAX_MSGSZ -1 31 | 32 | #define ZMQ_BLOCKY -1 33 | #define ZMQ_XPUB_MANUAL -1 34 | #define ZMQ_XPUB_WELCOME_MSG -1 35 | #define ZMQ_STREAM_NOTIFY -1 36 | #define ZMQ_INVERT_MATCHING -1 37 | #define ZMQ_HEARTBEAT_IVL -1 38 | #define ZMQ_HEARTBEAT_TTL -1 39 | #define ZMQ_HEARTBEAT_TIMEOUT -1 40 | #define ZMQ_XPUB_VERBOSER -1 41 | #define ZMQ_CONNECT_TIMEOUT -1 42 | #define ZMQ_TCP_MAXRT -1 43 | #define ZMQ_THREAD_SAFE -1 44 | #define ZMQ_MULTICAST_MAXTPDU -1 45 | #define ZMQ_VMCI_BUFFER_SIZE -1 46 | #define ZMQ_VMCI_BUFFER_MIN_SIZE -1 47 | #define ZMQ_VMCI_BUFFER_MAX_SIZE -1 48 | #define ZMQ_VMCI_CONNECT_TIMEOUT -1 49 | #define ZMQ_USE_FD -1 50 | 51 | #define ZMQ_GROUP_MAX_LENGTH -1 52 | 53 | #define ZMQ_POLLPRI -1 54 | 55 | #endif 56 | 57 | #if ZMQ_VERSION_MINOR == 2 && ZMQ_VERSION_PATCH < 3 58 | 59 | #define ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL -1 60 | #define ZMQ_EVENT_HANDSHAKE_SUCCEEDED -1 61 | #define ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL -1 62 | #define ZMQ_EVENT_HANDSHAKE_FAILED_AUTH -1 63 | 64 | #endif 65 | 66 | #ifndef ZMQ_ROUTING_ID 67 | #define ZMQ_ROUTING_ID ZMQ_IDENTITY 68 | #endif 69 | #ifndef ZMQ_CONNECT_ROUTING_ID 70 | #define ZMQ_CONNECT_ROUTING_ID ZMQ_CONNECT_RID 71 | #endif 72 | 73 | int zmq4_bind(void *socket, const char *endpoint); 74 | int zmq4_close(void *socket); 75 | int zmq4_connect(void *socket, const char *endpoint); 76 | int zmq4_ctx_get(void *context, int option_name); 77 | void *zmq4_ctx_new(void); 78 | int zmq4_ctx_set(void *context, int option_name, int option_value); 79 | int zmq4_ctx_term(void *context); 80 | int zmq4_curve_keypair(char *z85_public_key, char *z85_secret_key); 81 | int zmq4_curve_public(char *z85_public_key, char *z85_secret_key); 82 | int zmq4_disconnect(void *socket, const char *endpoint); 83 | int zmq4_getsockopt(void *socket, int option_name, void *option_value, 84 | size_t *option_len); 85 | const char *zmq4_msg_gets(zmq_msg_t *message, const char *property); 86 | int zmq4_msg_recv(zmq_msg_t *msg, void *socket, int flags); 87 | int zmq4_poll(zmq_pollitem_t *items, int nitems, long timeout); 88 | int zmq4_proxy(const void *frontend, const void *backend, const void *capture); 89 | int zmq4_proxy_steerable(const void *frontend, const void *backend, 90 | const void *capture, const void *control); 91 | int zmq4_send(void *socket, void *buf, size_t len, int flags); 92 | int zmq4_setsockopt(void *socket, int option_name, const void *option_value, 93 | size_t option_len); 94 | void *zmq4_socket(void *context, int type); 95 | int zmq4_socket_monitor(void *socket, char *endpoint, int events); 96 | int zmq4_unbind(void *socket, const char *endpoint); 97 | 98 | /* DRAFT */ 99 | 100 | int zmq4_msg_init_size(zmq_msg_t *msg, size_t size); 101 | int zmq4_msg_send(zmq_msg_t *msg, void *socket, int flags); 102 | int zmq4_msg_set_group(zmq_msg_t *msg, const char *group); 103 | int zmq4_msg_set_routing_id(zmq_msg_t *message, uint32_t routing_id); 104 | int zmq4_join(void *s, const char *group); 105 | int zmq4_leave(void *s, const char *group); 106 | -------------------------------------------------------------------------------- /draft/zmq41_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4/draft" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestRemoteEndpoint(t *testing.T) { 10 | zmq.SetRetryAfterEINTR(true) 11 | 12 | if _, minor, _ := zmq.Version(); minor < 1 { 13 | t.Skip("RemoteEndpoint not avalable in ZeroMQ versions prior to 4.1.0") 14 | } 15 | 16 | addr := "tcp://127.0.0.1:9560" 17 | peer := "127.0.0.1" 18 | 19 | var rep, req *zmq.Socket 20 | defer func() { 21 | for _, s := range []*zmq.Socket{rep, req} { 22 | if s != nil { 23 | s.SetLinger(0) 24 | s.Close() 25 | } 26 | } 27 | }() 28 | 29 | rep, err := zmq.NewSocket(zmq.REP) 30 | if err != nil { 31 | t.Fatal("NewSocket:", err) 32 | } 33 | req, err = zmq.NewSocket(zmq.REQ) 34 | if err != nil { 35 | t.Fatal("NewSocket:", err) 36 | } 37 | 38 | if err = rep.Bind(addr); err != nil { 39 | t.Fatal("rep.Bind:", err) 40 | } 41 | if err = req.Connect(addr); err != nil { 42 | t.Fatal("req.Connect:", err) 43 | } 44 | 45 | tmp := "test" 46 | if _, err = req.Send(tmp, 0); err != nil { 47 | t.Fatal("req.Send:", err) 48 | } 49 | 50 | // get message with peer address (remote endpoint) 51 | msg, props, err := rep.RecvWithMetadata(0, "Peer-Address") 52 | if err != nil { 53 | t.Fatal("rep.RecvWithMetadata:", err) 54 | return 55 | } 56 | if msg != tmp { 57 | t.Errorf("rep.RecvWithMetadata: expected %q, got %q", tmp, msg) 58 | } 59 | 60 | if p := props["Peer-Address"]; p != peer { 61 | t.Errorf("rep.RecvWithMetadata: expected Peer-Address == %q, got %q", peer, p) 62 | } 63 | 64 | err = rep.Close() 65 | rep = nil 66 | if err != nil { 67 | t.Fatal("rep.Close:", err) 68 | } 69 | 70 | err = req.Close() 71 | req = nil 72 | if err != nil { 73 | t.Fatal("req.Close:", err) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /draft/zmq42draft.go: -------------------------------------------------------------------------------- 1 | package zmq4 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include "zmq4.h" 8 | #include "zmq42draft.h" 9 | 10 | #ifdef ZMQ42HASDRAFT 11 | int zmq4has42draft = 1; 12 | #else 13 | int zmq4has42draft = 0; 14 | // Version >= 4.2.0 with draft 15 | 16 | int zmq_join (void *s, const char *group) { return 0; } 17 | int zmq_leave (void *s, const char *group) { return 0; } 18 | int zmq_msg_set_routing_id(zmq_msg_t *msg, uint32_t routing_id) { return 0; } 19 | uint32_t zmq_msg_routing_id(zmq_msg_t *msg) { return 0; } 20 | int zmq_msg_set_group(zmq_msg_t *msg, const char *group) { return 0; } 21 | const char *zmq_msg_group(zmq_msg_t *msg) { return NULL; } 22 | 23 | #endif // ZMQ42HASDRAFT 24 | 25 | */ 26 | import "C" 27 | 28 | import ( 29 | "unsafe" 30 | ) 31 | 32 | type OptRoutingId uint32 33 | type OptGroup string 34 | 35 | var ( 36 | has42draft bool 37 | ) 38 | 39 | func init() { 40 | has42draft = (C.zmq4has42draft != 0) 41 | } 42 | 43 | func (soc *Socket) Join(group string) error { 44 | if !has42draft { 45 | return ErrorNotImplemented42draft 46 | } 47 | cs := C.CString(group) 48 | defer C.free(unsafe.Pointer(cs)) 49 | n, err := C.zmq4_join(soc.soc, cs) 50 | if n != 0 { 51 | return errget(err) 52 | } 53 | return nil 54 | } 55 | 56 | func (soc *Socket) Leave(group string) error { 57 | if !has42draft { 58 | return ErrorNotImplemented42draft 59 | } 60 | cs := C.CString(group) 61 | defer C.free(unsafe.Pointer(cs)) 62 | n, err := C.zmq4_leave(soc.soc, cs) 63 | if n != 0 { 64 | return errget(err) 65 | } 66 | return nil 67 | } 68 | 69 | func HasDraft() bool { 70 | return has42draft 71 | } 72 | -------------------------------------------------------------------------------- /draft/zmq42draft.h: -------------------------------------------------------------------------------- 1 | #if ZMQ_VERSION_MINOR >= 2 2 | #ifdef ZMQ_BUILD_DRAFT_API 3 | #define ZMQ42HASDRAFT 4 | #endif 5 | #endif 6 | 7 | #ifndef ZMQ42HASDRAFT 8 | #define ZMQ_SERVER -12 9 | #define ZMQ_CLIENT -13 10 | #define ZMQ_RADIO -14 11 | #define ZMQ_DISH -15 12 | #define ZMQ_GATHER -16 13 | #define ZMQ_SCATTER -17 14 | #define ZMQ_DGRAM -18 15 | #endif 16 | -------------------------------------------------------------------------------- /draft/zmq42draft_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4/draft" 5 | 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestUdp(t *testing.T) { 11 | zmq.SetRetryAfterEINTR(true) 12 | 13 | if _, minor, _ := zmq.Version(); minor < 2 { 14 | t.Skip("Sockets RADIO and DISH need ZeroMQ 4.2 with draft enabled") 15 | } 16 | 17 | ctx, err := zmq.NewContext() 18 | if err != nil { 19 | t.Fatal("NewContext:", err) 20 | } 21 | defer ctx.Term() 22 | ctx.SetRetryAfterEINTR(true) 23 | 24 | radio, err := ctx.NewSocket(zmq.RADIO) 25 | if err != nil { 26 | t.Fatal("NewSocket RADIO:", err) 27 | } 28 | defer radio.Close() 29 | dish, err := ctx.NewSocket(zmq.DISH) 30 | if err != nil { 31 | t.Fatal("NewSocket DISH:", err) 32 | } 33 | defer dish.Close() 34 | 35 | // Connecting dish should fail 36 | err = dish.Connect("udp://127.0.0.1:5556") 37 | if err == nil { 38 | t.Fatal("Expected fail on dish.Connect") 39 | } 40 | 41 | err = dish.Bind("udp://*:5556") 42 | if err != nil { 43 | t.Fatal("dish.Bind:", err) 44 | } 45 | 46 | // Bind radio should fail 47 | err = radio.Bind("udp://*:5556") 48 | if err == nil { 49 | t.Fatal("Expected fail on radio.Bind") 50 | } 51 | 52 | err = radio.Connect("udp://127.0.0.1:5556") 53 | if err != nil { 54 | t.Fatal("radio.Connect:", err) 55 | } 56 | 57 | time.Sleep(300 * time.Millisecond) 58 | 59 | err = dish.Join("TV") 60 | if err != nil { 61 | t.Fatal("dish.Join:", err) 62 | } 63 | 64 | _, err = radio.Send("Friends", 0, zmq.OptGroup("TV")) 65 | if err != nil { 66 | t.Fatal("radio.SendMessage:", err) 67 | } 68 | 69 | msg, opt, err := dish.RecvWithOpts(0, zmq.OptGroup("")) 70 | if err != nil { 71 | t.Fatal("dish.RecvWithOpt:", err) 72 | } 73 | if len(opt) != 1 { 74 | t.Fatal("dish.RecvWithOpt: wrong number off options") 75 | } 76 | if string(opt[0].(zmq.OptGroup)) != "TV" { 77 | t.Fatalf("dish.RecvWithOpt: wrong group: %v", string(opt[0].(zmq.OptGroup))) 78 | } 79 | if msg != "Friends" { 80 | t.Fatalf("dish.RecvWithOpt: wrong message: %q", msg) 81 | } 82 | } 83 | 84 | func TestClientServer(t *testing.T) { 85 | zmq.SetRetryAfterEINTR(true) 86 | 87 | if _, minor, _ := zmq.Version(); minor < 2 { 88 | t.Skip("Sockets CLIENT and SERVER need ZeroMQ 4.2 with draft enabled") 89 | } 90 | 91 | ctx, err := zmq.NewContext() 92 | if err != nil { 93 | t.Fatal("NewContext:", err) 94 | } 95 | defer ctx.Term() 96 | ctx.SetRetryAfterEINTR(true) 97 | 98 | server, err := ctx.NewSocket(zmq.SERVER) 99 | if err != nil { 100 | t.Fatal("NewSocket SERVER:", err) 101 | } 102 | defer server.Close() 103 | client, err := ctx.NewSocket(zmq.CLIENT) 104 | if err != nil { 105 | t.Fatal("NewSocket CLIENT:", err) 106 | } 107 | defer client.Close() 108 | 109 | addr := "tcp://127.0.0.1:9797" 110 | err = server.Bind(addr) 111 | if err != nil { 112 | t.Fatal("server.Bind:", err) 113 | } 114 | err = client.Connect(addr) 115 | if err != nil { 116 | t.Fatal("client.Connect:", err) 117 | } 118 | 119 | content := "12345678ABCDEFGH12345678abcdefgh" 120 | rc, err := client.Send(content, zmq.DONTWAIT) 121 | if err != nil { 122 | t.Fatal("client.Send DONTWAIT: ", err) 123 | } 124 | if rc != 32 { 125 | t.Fatalf("client.Send DONTWAIT: %v (%v)", err32, rc) 126 | } 127 | 128 | msg, opts, err := server.RecvWithOpts(0, zmq.OptRoutingId(0)) 129 | if err != nil { 130 | t.Fatal("server.Recv: ", err) 131 | } 132 | // Check that message is still the same 133 | if msg != content { 134 | t.Fatalf("server.Recv: %q != %q", msg, content) 135 | } 136 | 137 | rc, err = server.Send(content, 0, opts[0]) 138 | if err != nil { 139 | t.Fatal("server.Send:", err) 140 | } 141 | if rc != 32 { 142 | t.Fatalf("server.Send: %v (%v)", err32, rc) 143 | } 144 | 145 | // Receive message at client side 146 | msg, err = client.Recv(0) 147 | if err != nil { 148 | t.Fatal("client.Recv: ", err) 149 | } 150 | 151 | // Check that message is still the same 152 | if msg != content { 153 | t.Fatalf("client.Recv: %q != %q", msg, content) 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /draft/zmq43draft.h: -------------------------------------------------------------------------------- 1 | #if ZMQ_VERSION_MINOR < 3 2 | // Version < 4.3 3 | #define ZMQ_ROUTER_NOTIFY -1 4 | #define ZMQ_NOTIFY_CONNECT 1 5 | #define ZMQ_NOTIFY_DISCONNECT 2 6 | #endif 7 | 8 | #if ZMQ_VERSION_MINOR < 3 || (ZMQ_VERSION_MINOR == 3 && ZMQ_VERSION_PATCH < 3) 9 | // Version < 4.3.3 10 | #define ZMQ_HELLO_MSG -1 11 | #define ZMQ_DISCONNECT_MSG -1 12 | #endif 13 | 14 | #if ZMQ_VERSION_MINOR < 3 || (ZMQ_VERSION_MINOR == 3 && ZMQ_VERSION_PATCH < 5) 15 | // Version < 4.3.5 16 | #define ZMQ_HICCUP_MSG -1 17 | #endif 18 | -------------------------------------------------------------------------------- /dummy.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | You need CGO_ENABLED=1 to build this package 4 | 5 | */ 6 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package zmq4 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "syscall" 10 | ) 11 | 12 | // An Errno is an unsigned number describing an error condition as returned by a call to ZeroMQ. 13 | // It implements the error interface. 14 | // The number is either a standard system error, or an error defined by the C library of ZeroMQ. 15 | type Errno uintptr 16 | 17 | const ( 18 | // Error conditions defined by the C library of ZeroMQ. 19 | 20 | // On Windows platform some of the standard POSIX errnos are not defined. 21 | EADDRINUSE = Errno(C.EADDRINUSE) 22 | EADDRNOTAVAIL = Errno(C.EADDRNOTAVAIL) 23 | EAFNOSUPPORT = Errno(C.EAFNOSUPPORT) 24 | ECONNABORTED = Errno(C.ECONNABORTED) 25 | ECONNREFUSED = Errno(C.ECONNREFUSED) 26 | ECONNRESET = Errno(C.ECONNRESET) 27 | EHOSTUNREACH = Errno(C.EHOSTUNREACH) 28 | EINPROGRESS = Errno(C.EINPROGRESS) 29 | EMSGSIZE = Errno(C.EMSGSIZE) 30 | ENETDOWN = Errno(C.ENETDOWN) 31 | ENETRESET = Errno(C.ENETRESET) 32 | ENETUNREACH = Errno(C.ENETUNREACH) 33 | ENOBUFS = Errno(C.ENOBUFS) 34 | ENOTCONN = Errno(C.ENOTCONN) 35 | ENOTSOCK = Errno(C.ENOTSOCK) 36 | ENOTSUP = Errno(C.ENOTSUP) 37 | EPROTONOSUPPORT = Errno(C.EPROTONOSUPPORT) 38 | ETIMEDOUT = Errno(C.ETIMEDOUT) 39 | 40 | // Native 0MQ error codes. 41 | EFSM = Errno(C.EFSM) 42 | EMTHREAD = Errno(C.EMTHREAD) 43 | ENOCOMPATPROTO = Errno(C.ENOCOMPATPROTO) 44 | ETERM = Errno(C.ETERM) 45 | ) 46 | 47 | func errget(err error) error { 48 | eno, ok := err.(syscall.Errno) 49 | if ok { 50 | return Errno(eno) 51 | } 52 | return err 53 | } 54 | 55 | // Return Errno as string. 56 | func (errno Errno) Error() string { 57 | if errno >= C.ZMQ_HAUSNUMERO { 58 | return C.GoString(C.zmq_strerror(C.int(errno))) 59 | } 60 | return syscall.Errno(errno).Error() 61 | } 62 | 63 | /* 64 | Convert error to Errno. 65 | 66 | Example usage: 67 | 68 | switch AsErrno(err) { 69 | 70 | case zmq.Errno(syscall.EINTR): 71 | // standard system error 72 | 73 | // call was interrupted 74 | 75 | case zmq.ETERM: 76 | // error defined by ZeroMQ 77 | 78 | // context was terminated 79 | 80 | } 81 | 82 | See also: examples/interrupt.go 83 | */ 84 | func AsErrno(err error) Errno { 85 | if eno, ok := err.(Errno); ok { 86 | return eno 87 | } 88 | if eno, ok := err.(syscall.Errno); ok { 89 | return Errno(eno) 90 | } 91 | return Errno(0) 92 | } 93 | 94 | func (ctx *Context) retry(err error) bool { 95 | if ctx == nil || !ctx.retryEINTR || err == nil { 96 | return false 97 | } 98 | eno, ok := err.(syscall.Errno) 99 | if !ok { 100 | return false 101 | } 102 | return eno == syscall.EINTR 103 | } 104 | -------------------------------------------------------------------------------- /examples/Build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #go get github.com/pborman/uuid 4 | 5 | #for i in bstar mdapi flcliapi kvsimple kvmsg clone intface 6 | #do 7 | # go install github.com/pebbe/zmq4/examples/$i 8 | #done 9 | 10 | cd `dirname $0` 11 | 12 | goos=`go env GOOS` 13 | gobin=`go env GOBIN` 14 | if [ "$gobin" = "" ] 15 | then 16 | gobin=`go env GOPATH` 17 | if [ "$gobin" = "" ] 18 | then 19 | gobin=`go env GOROOT` 20 | fi 21 | gobin=`echo $gobin | sed -e 's/:.*//'` 22 | gobin=$gobin/bin 23 | fi 24 | 25 | dir=$gobin/zmq4-examples 26 | 27 | echo Installing examples in $dir 28 | 29 | mkdir -p $dir 30 | 31 | for i in *.sh 32 | do 33 | if [ $i != Build.sh ] 34 | then 35 | cp -u $i $dir 36 | fi 37 | done 38 | 39 | src='' 40 | for i in *.go 41 | do 42 | if [ $i = interrupt.go ] 43 | then 44 | if [ $goos = windows -o $goos = plan9 ] 45 | then 46 | continue 47 | fi 48 | fi 49 | bin=$dir/`basename $i .go` 50 | if [ ! -f $bin -o $i -nt $bin ] 51 | then 52 | src="$src $i" 53 | fi 54 | done 55 | 56 | libs=`pkg-config --libs-only-L libzmq` 57 | if [ "$libs" = "" ] 58 | then 59 | for i in $src 60 | do 61 | go build -o $dir/`basename $i .go` $i 62 | done 63 | else 64 | libs="-r `echo $libs | sed -e 's/-L//; s/ *-L/:/g'`" 65 | for i in $src 66 | do 67 | go build -ldflags="$libs" -o $dir/`basename $i .go` $i 68 | done 69 | fi 70 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | These are examples from [ØMQ - The Guide](http://zguide.zeromq.org/page:all), 2 | re-implemented for the current Go package. 3 | -------------------------------------------------------------------------------- /examples/asyncsrv.go: -------------------------------------------------------------------------------- 1 | // 2 | // Asynchronous client-to-server (DEALER to ROUTER). 3 | // 4 | // While this example runs in a single process, that is just to make 5 | // it easier to start and stop the example. Each task has its own 6 | // context and conceptually acts as a separate process. 7 | 8 | package main 9 | 10 | import ( 11 | zmq "github.com/pebbe/zmq4" 12 | 13 | "fmt" 14 | "log" 15 | "math/rand" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | // --------------------------------------------------------------------- 21 | // This is our client task 22 | // It connects to the server, and then sends a request once per second 23 | // It collects responses as they arrive, and it prints them out. We will 24 | // run several client tasks in parallel, each with a different random ID. 25 | 26 | func client_task() { 27 | var mu sync.Mutex 28 | 29 | client, _ := zmq.NewSocket(zmq.DEALER) 30 | defer client.Close() 31 | 32 | // Set random identity to make tracing easier 33 | set_id(client) 34 | client.Connect("tcp://localhost:5570") 35 | 36 | go func() { 37 | for request_nbr := 1; true; request_nbr++ { 38 | time.Sleep(time.Second) 39 | mu.Lock() 40 | client.SendMessage(fmt.Sprintf("request #%d", request_nbr)) 41 | mu.Unlock() 42 | } 43 | }() 44 | 45 | for { 46 | time.Sleep(10 * time.Millisecond) 47 | mu.Lock() 48 | msg, err := client.RecvMessage(zmq.DONTWAIT) 49 | if err == nil { 50 | id, _ := client.GetIdentity() 51 | fmt.Println(msg[0], id) 52 | } 53 | mu.Unlock() 54 | } 55 | } 56 | 57 | // This is our server task. 58 | // It uses the multithreaded server model to deal requests out to a pool 59 | // of workers and route replies back to clients. One worker can handle 60 | // one request at a time but one client can talk to multiple workers at 61 | // once. 62 | 63 | func server_task() { 64 | 65 | // Frontend socket talks to clients over TCP 66 | frontend, _ := zmq.NewSocket(zmq.ROUTER) 67 | defer frontend.Close() 68 | frontend.Bind("tcp://*:5570") 69 | 70 | // Backend socket talks to workers over inproc 71 | backend, _ := zmq.NewSocket(zmq.DEALER) 72 | defer backend.Close() 73 | backend.Bind("inproc://backend") 74 | 75 | // Launch pool of worker threads, precise number is not critical 76 | for i := 0; i < 5; i++ { 77 | go server_worker() 78 | } 79 | 80 | // Connect backend to frontend via a proxy 81 | err := zmq.Proxy(frontend, backend, nil) 82 | log.Fatalln("Proxy interrupted:", err) 83 | } 84 | 85 | // Each worker task works on one request at a time and sends a random number 86 | // of replies back, with random delays between replies: 87 | 88 | func server_worker() { 89 | 90 | worker, _ := zmq.NewSocket(zmq.DEALER) 91 | defer worker.Close() 92 | worker.Connect("inproc://backend") 93 | 94 | for { 95 | // The DEALER socket gives us the reply envelope and message 96 | msg, _ := worker.RecvMessage(0) 97 | identity, content := pop(msg) 98 | 99 | // Send 0..4 replies back 100 | replies := rand.Intn(5) 101 | for reply := 0; reply < replies; reply++ { 102 | // Sleep for some fraction of a second 103 | time.Sleep(time.Duration(rand.Intn(1000)+1) * time.Millisecond) 104 | worker.SendMessage(identity, content) 105 | } 106 | } 107 | } 108 | 109 | // The main thread simply starts several clients, and a server, and then 110 | // waits for the server to finish. 111 | 112 | func main() { 113 | rand.Seed(time.Now().UnixNano()) 114 | 115 | go client_task() 116 | go client_task() 117 | go client_task() 118 | go server_task() 119 | 120 | // Run for 5 seconds then quit 121 | time.Sleep(5 * time.Second) 122 | } 123 | 124 | func set_id(soc *zmq.Socket) { 125 | identity := fmt.Sprintf("%04X-%04X", rand.Intn(0x10000), rand.Intn(0x10000)) 126 | soc.SetIdentity(identity) 127 | } 128 | 129 | func pop(msg []string) (head, tail []string) { 130 | if msg[1] == "" { 131 | head = msg[:2] 132 | tail = msg[2:] 133 | } else { 134 | head = msg[:1] 135 | tail = msg[1:] 136 | } 137 | return 138 | } 139 | -------------------------------------------------------------------------------- /examples/bstarcli.go: -------------------------------------------------------------------------------- 1 | // 2 | // Binary Star client proof-of-concept implementation. This client does no 3 | // real work; it just demonstrates the Binary Star failover model. 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | const ( 16 | REQUEST_TIMEOUT = 1000 * time.Millisecond // msecs 17 | SETTLE_DELAY = 2000 * time.Millisecond // Before failing over 18 | ) 19 | 20 | func main() { 21 | 22 | server := []string{"tcp://localhost:5001", "tcp://localhost:5002"} 23 | server_nbr := 0 24 | 25 | fmt.Printf("I: connecting to server at %s...\n", server[server_nbr]) 26 | client, _ := zmq.NewSocket(zmq.REQ) 27 | client.Connect(server[server_nbr]) 28 | 29 | poller := zmq.NewPoller() 30 | poller.Add(client, zmq.POLLIN) 31 | 32 | sequence := 0 33 | LOOP: 34 | for { 35 | // We send a request, then we work to get a reply 36 | sequence++ 37 | client.SendMessage(sequence) 38 | 39 | for expect_reply := true; expect_reply; { 40 | // Poll socket for a reply, with timeout 41 | polled, err := poller.Poll(REQUEST_TIMEOUT) 42 | if err != nil { 43 | break LOOP // Interrupted 44 | } 45 | 46 | // We use a Lazy Pirate strategy in the client. If there's no 47 | // reply within our timeout, we close the socket and try again. 48 | // In Binary Star, it's the client vote which decides which 49 | // server is primary; the client must therefore try to connect 50 | // to each server in turn: 51 | 52 | if len(polled) == 1 { 53 | // We got a reply from the server, must match sequence 54 | reply, _ := client.RecvMessage(0) 55 | seq, _ := strconv.Atoi(reply[0]) 56 | if seq == sequence { 57 | fmt.Printf("I: server replied OK (%s)\n", reply[0]) 58 | expect_reply = false 59 | time.Sleep(time.Second) // One request per second 60 | } else { 61 | fmt.Printf("E: bad reply from server: %q\n", reply) 62 | } 63 | 64 | } else { 65 | fmt.Println("W: no response from server, failing over") 66 | 67 | // Old socket is confused; close it and open a new one 68 | client.Close() 69 | server_nbr = 1 - server_nbr 70 | time.Sleep(SETTLE_DELAY) 71 | fmt.Printf("I: connecting to server at %s...\n", server[server_nbr]) 72 | client, _ = zmq.NewSocket(zmq.REQ) 73 | client.Connect(server[server_nbr]) 74 | 75 | poller = zmq.NewPoller() 76 | poller.Add(client, zmq.POLLIN) 77 | 78 | // Send request again, on new socket 79 | client.SendMessage(sequence) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/bstarsrv2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Binary Star server, using bstar reactor. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/bstar" 10 | 11 | "fmt" 12 | "os" 13 | ) 14 | 15 | // Echo service 16 | func echo(socket *zmq.Socket) (err error) { 17 | msg, err := socket.RecvMessage(0) 18 | if err != nil { 19 | return 20 | } 21 | _, err = socket.SendMessage(msg) 22 | return 23 | } 24 | 25 | func main() { 26 | // Arguments can be either of: 27 | // -p primary server, at tcp://localhost:5001 28 | // -b backup server, at tcp://localhost:5002 29 | var bst *bstar.Bstar 30 | if len(os.Args) == 2 && os.Args[1] == "-p" { 31 | fmt.Println("I: Primary active, waiting for backup (passive)") 32 | bst, _ = bstar.New(bstar.PRIMARY, "tcp://*:5003", "tcp://localhost:5004") 33 | bst.Voter("tcp://*:5001", zmq.ROUTER, echo) 34 | } else if len(os.Args) == 2 && os.Args[1] == "-b" { 35 | fmt.Println("I: Backup passive, waiting for primary (active)") 36 | bst, _ = bstar.New(bstar.BACKUP, "tcp://*:5004", "tcp://localhost:5003") 37 | bst.Voter("tcp://*:5002", zmq.ROUTER, echo) 38 | } else { 39 | fmt.Println("Usage: bstarsrvs { -p | -b }") 40 | return 41 | } 42 | bst.Start() 43 | } 44 | -------------------------------------------------------------------------------- /examples/clonecli1.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model One 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | ) 13 | 14 | func main() { 15 | // Prepare our context and updates socket 16 | updates, _ := zmq.NewSocket(zmq.SUB) 17 | updates.SetSubscribe("") 18 | updates.Connect("tcp://localhost:5556") 19 | 20 | kvmap := make(map[string]*kvsimple.Kvmsg) 21 | 22 | sequence := int64(0) 23 | for ; true; sequence++ { 24 | kvmsg, err := kvsimple.RecvKvmsg(updates) 25 | if err != nil { 26 | break // Interrupted 27 | } 28 | kvmsg.Store(kvmap) 29 | } 30 | fmt.Printf("Interrupted\n%d messages in\n", sequence) 31 | } 32 | -------------------------------------------------------------------------------- /examples/clonecli2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model Two 3 | // 4 | // In the original C example, the client misses updates between snapshot 5 | // and further updates. Sometimes, it even misses the END message of 6 | // the snapshot, so it waits for it forever. 7 | // This Go implementation has some modifications to improve this, but it 8 | // is still not fully reliable. 9 | 10 | package main 11 | 12 | import ( 13 | zmq "github.com/pebbe/zmq4" 14 | "github.com/pebbe/zmq4/examples/kvsimple" 15 | 16 | "fmt" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | snapshot, _ := zmq.NewSocket(zmq.DEALER) 22 | snapshot.Connect("tcp://localhost:5556") 23 | 24 | subscriber, _ := zmq.NewSocket(zmq.SUB) 25 | subscriber.SetRcvhwm(100000) // or messages between snapshot and next are lost 26 | subscriber.SetSubscribe("") 27 | subscriber.Connect("tcp://localhost:5557") 28 | 29 | time.Sleep(time.Second) // or messages between snapshot and next are lost 30 | 31 | kvmap := make(map[string]*kvsimple.Kvmsg) 32 | 33 | // Get state snapshot 34 | sequence := int64(0) 35 | snapshot.SendMessage("ICANHAZ?") 36 | for { 37 | kvmsg, err := kvsimple.RecvKvmsg(snapshot) 38 | if err != nil { 39 | fmt.Println(err) 40 | break // Interrupted 41 | } 42 | if key, _ := kvmsg.GetKey(); key == "KTHXBAI" { 43 | sequence, _ = kvmsg.GetSequence() 44 | fmt.Printf("Received snapshot=%d\n", sequence) 45 | break // Done 46 | } 47 | kvmsg.Store(kvmap) 48 | } 49 | snapshot.Close() 50 | 51 | first := true 52 | // Now apply pending updates, discard out-of-sequence messages 53 | for { 54 | kvmsg, err := kvsimple.RecvKvmsg(subscriber) 55 | if err != nil { 56 | fmt.Println(err) 57 | break // Interrupted 58 | } 59 | if seq, _ := kvmsg.GetSequence(); seq > sequence { 60 | sequence, _ = kvmsg.GetSequence() 61 | kvmsg.Store(kvmap) 62 | if first { 63 | // Show what the first regular update is after the snapshot, 64 | // to see if we missed updates. 65 | first = false 66 | fmt.Println("Next:", sequence) 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/clonecli3.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model Three 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | snapshot, _ := zmq.NewSocket(zmq.DEALER) 18 | snapshot.Connect("tcp://localhost:5556") 19 | subscriber, _ := zmq.NewSocket(zmq.SUB) 20 | subscriber.SetSubscribe("") 21 | subscriber.Connect("tcp://localhost:5557") 22 | publisher, _ := zmq.NewSocket(zmq.PUSH) 23 | publisher.Connect("tcp://localhost:5558") 24 | 25 | kvmap := make(map[string]*kvsimple.Kvmsg) 26 | rand.Seed(time.Now().UnixNano()) 27 | 28 | // We first request a state snapshot: 29 | sequence := int64(0) 30 | snapshot.SendMessage("ICANHAZ?") 31 | for { 32 | kvmsg, err := kvsimple.RecvKvmsg(snapshot) 33 | if err != nil { 34 | break // Interrupted 35 | } 36 | if key, _ := kvmsg.GetKey(); key == "KTHXBAI" { 37 | sequence, _ := kvmsg.GetSequence() 38 | fmt.Println("I: received snapshot =", sequence) 39 | break // Done 40 | } 41 | kvmsg.Store(kvmap) 42 | } 43 | snapshot.Close() 44 | 45 | // Now we wait for updates from the server, and every so often, we 46 | // send a random key-value update to the server: 47 | 48 | poller := zmq.NewPoller() 49 | poller.Add(subscriber, zmq.POLLIN) 50 | alarm := time.Now().Add(1000 * time.Millisecond) 51 | for { 52 | tickless := alarm.Sub(time.Now()) 53 | if tickless < 0 { 54 | tickless = 0 55 | } 56 | polled, err := poller.Poll(tickless) 57 | if err != nil { 58 | break // Context has been shut down 59 | } 60 | if len(polled) == 1 { 61 | kvmsg, err := kvsimple.RecvKvmsg(subscriber) 62 | if err != nil { 63 | break // Interrupted 64 | } 65 | 66 | // Discard out-of-sequence kvmsgs, incl. heartbeats 67 | if seq, _ := kvmsg.GetSequence(); seq > sequence { 68 | sequence = seq 69 | kvmsg.Store(kvmap) 70 | fmt.Println("I: received update =", sequence) 71 | } 72 | } 73 | // If we timed-out, generate a random kvmsg 74 | if time.Now().After(alarm) { 75 | kvmsg := kvsimple.NewKvmsg(0) 76 | kvmsg.SetKey(fmt.Sprint(rand.Intn(10000))) 77 | kvmsg.SetBody(fmt.Sprint(rand.Intn(1000000))) 78 | kvmsg.Send(publisher) 79 | alarm = time.Now().Add(1000 * time.Millisecond) 80 | } 81 | } 82 | fmt.Printf("Interrupted\n%d messages in\n", sequence) 83 | } 84 | -------------------------------------------------------------------------------- /examples/clonecli4.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model Four 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | const ( 17 | SUBTREE = "/client/" 18 | ) 19 | 20 | func main() { 21 | snapshot, _ := zmq.NewSocket(zmq.DEALER) 22 | snapshot.Connect("tcp://localhost:5556") 23 | subscriber, _ := zmq.NewSocket(zmq.SUB) 24 | subscriber.SetSubscribe(SUBTREE) 25 | subscriber.Connect("tcp://localhost:5557") 26 | publisher, _ := zmq.NewSocket(zmq.PUSH) 27 | publisher.Connect("tcp://localhost:5558") 28 | 29 | kvmap := make(map[string]*kvsimple.Kvmsg) 30 | rand.Seed(time.Now().UnixNano()) 31 | 32 | // We first request a state snapshot: 33 | sequence := int64(0) 34 | snapshot.SendMessage("ICANHAZ?", SUBTREE) 35 | for { 36 | kvmsg, err := kvsimple.RecvKvmsg(snapshot) 37 | if err != nil { 38 | break // Interrupted 39 | } 40 | if key, _ := kvmsg.GetKey(); key == "KTHXBAI" { 41 | sequence, _ := kvmsg.GetSequence() 42 | fmt.Println("I: received snapshot =", sequence) 43 | break // Done 44 | } 45 | kvmsg.Store(kvmap) 46 | } 47 | snapshot.Close() 48 | 49 | poller := zmq.NewPoller() 50 | poller.Add(subscriber, zmq.POLLIN) 51 | alarm := time.Now().Add(1000 * time.Millisecond) 52 | for { 53 | tickless := alarm.Sub(time.Now()) 54 | if tickless < 0 { 55 | tickless = 0 56 | } 57 | polled, err := poller.Poll(tickless) 58 | if err != nil { 59 | break // Context has been shut down 60 | } 61 | if len(polled) == 1 { 62 | kvmsg, err := kvsimple.RecvKvmsg(subscriber) 63 | if err != nil { 64 | break // Interrupted 65 | } 66 | 67 | // Discard out-of-sequence kvmsgs, incl. heartbeats 68 | if seq, _ := kvmsg.GetSequence(); seq > sequence { 69 | sequence = seq 70 | kvmsg.Store(kvmap) 71 | fmt.Println("I: received update =", sequence) 72 | } 73 | } 74 | // If we timed-out, generate a random kvmsg 75 | if time.Now().After(alarm) { 76 | kvmsg := kvsimple.NewKvmsg(0) 77 | kvmsg.SetKey(fmt.Sprintf("%s%d", SUBTREE, rand.Intn(10000))) 78 | kvmsg.SetBody(fmt.Sprint(rand.Intn(1000000))) 79 | kvmsg.Send(publisher) 80 | alarm = time.Now().Add(1000 * time.Millisecond) 81 | } 82 | } 83 | fmt.Printf("Interrupted\n%d messages in\n", sequence) 84 | } 85 | -------------------------------------------------------------------------------- /examples/clonecli5.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model Five 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvmsg" 10 | 11 | "fmt" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | const ( 17 | SUBTREE = "/client/" 18 | ) 19 | 20 | func main() { 21 | snapshot, _ := zmq.NewSocket(zmq.DEALER) 22 | snapshot.Connect("tcp://localhost:5556") 23 | subscriber, _ := zmq.NewSocket(zmq.SUB) 24 | subscriber.SetSubscribe(SUBTREE) 25 | subscriber.Connect("tcp://localhost:5557") 26 | publisher, _ := zmq.NewSocket(zmq.PUSH) 27 | publisher.Connect("tcp://localhost:5558") 28 | 29 | kvmap := make(map[string]*kvmsg.Kvmsg) 30 | rand.Seed(time.Now().UnixNano()) 31 | 32 | // We first request a state snapshot: 33 | sequence := int64(0) 34 | snapshot.SendMessage("ICANHAZ?", SUBTREE) 35 | for { 36 | kvmsg, err := kvmsg.RecvKvmsg(snapshot) 37 | if err != nil { 38 | break // Interrupted 39 | } 40 | if key, _ := kvmsg.GetKey(); key == "KTHXBAI" { 41 | sequence, _ := kvmsg.GetSequence() 42 | fmt.Println("I: received snapshot =", sequence) 43 | break // Done 44 | } 45 | kvmsg.Store(kvmap) 46 | } 47 | snapshot.Close() 48 | 49 | poller := zmq.NewPoller() 50 | poller.Add(subscriber, zmq.POLLIN) 51 | alarm := time.Now().Add(1000 * time.Millisecond) 52 | for { 53 | tickless := alarm.Sub(time.Now()) 54 | if tickless < 0 { 55 | tickless = 0 56 | } 57 | polled, err := poller.Poll(tickless) 58 | if err != nil { 59 | break // Context has been shut down 60 | } 61 | if len(polled) == 1 { 62 | kvmsg, err := kvmsg.RecvKvmsg(subscriber) 63 | if err != nil { 64 | break // Interrupted 65 | } 66 | 67 | // Discard out-of-sequence kvmsgs, incl. heartbeats 68 | if seq, _ := kvmsg.GetSequence(); seq > sequence { 69 | sequence = seq 70 | kvmsg.Store(kvmap) 71 | fmt.Println("I: received update =", sequence) 72 | } 73 | } 74 | // If we timed-out, generate a random kvmsg 75 | if time.Now().After(alarm) { 76 | kvmsg := kvmsg.NewKvmsg(0) 77 | kvmsg.SetKey(fmt.Sprintf("%s%d", SUBTREE, rand.Intn(10000))) 78 | kvmsg.SetBody(fmt.Sprint(rand.Intn(1000000))) 79 | kvmsg.SetProp("ttl", fmt.Sprintf("%d", rand.Intn((30)))) // seconds 80 | kvmsg.Send(publisher) 81 | alarm = time.Now().Add(1000 * time.Millisecond) 82 | } 83 | } 84 | fmt.Printf("Interrupted\n%d messages in\n", sequence) 85 | } 86 | -------------------------------------------------------------------------------- /examples/clonecli6.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone client Model Six 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/pebbe/zmq4/examples/clone" 9 | 10 | "fmt" 11 | "log" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | const ( 17 | SUBTREE = "/client/" 18 | ) 19 | 20 | func main() { 21 | // Create distributed hash instance 22 | clone := clone.New() 23 | 24 | // Specify configuration 25 | clone.Subtree(SUBTREE) 26 | clone.Connect("tcp://localhost", "5556") 27 | clone.Connect("tcp://localhost", "5566") 28 | 29 | // Set random tuples into the distributed hash 30 | for { 31 | // Set random value, check it was stored 32 | key := fmt.Sprintf("%s%d", SUBTREE, rand.Intn(10000)) 33 | value := fmt.Sprint(rand.Intn(1000000)) 34 | clone.Set(key, value, rand.Intn(30)) 35 | v, _ := clone.Get(key) 36 | if v != value { 37 | log.Fatalf("Set: %v - Get: %v - Equal: %v\n", value, v, value == v) 38 | } 39 | time.Sleep(time.Second) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/clonesrv1.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone server Model One 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | // Prepare our context and publisher socket 18 | publisher, _ := zmq.NewSocket(zmq.PUB) 19 | publisher.Bind("tcp://*:5556") 20 | time.Sleep(200 * time.Millisecond) 21 | 22 | kvmap := make(map[string]*kvsimple.Kvmsg) 23 | rand.Seed(time.Now().UnixNano()) 24 | 25 | sequence := int64(1) 26 | for ; true; sequence++ { 27 | // Distribute as key-value message 28 | kvmsg := kvsimple.NewKvmsg(sequence) 29 | kvmsg.SetKey(fmt.Sprint(rand.Intn(10000))) 30 | kvmsg.SetBody(fmt.Sprint(rand.Intn(1000000))) 31 | err := kvmsg.Send(publisher) 32 | kvmsg.Store(kvmap) 33 | if err != nil { 34 | break 35 | } 36 | } 37 | fmt.Printf("Interrupted\n%d messages out\n", sequence) 38 | } 39 | -------------------------------------------------------------------------------- /examples/clonesrv2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone server Model Two 3 | // 4 | // In the original C example, the client misses updates between snapshot 5 | // and further updates. Sometimes, it even misses the END message of 6 | // the snapshot, so it waits for it forever. 7 | // This Go implementation has some modifications to improve this, but it 8 | // is still not fully reliable. 9 | 10 | package main 11 | 12 | import ( 13 | zmq "github.com/pebbe/zmq4" 14 | "github.com/pebbe/zmq4/examples/kvsimple" 15 | 16 | "fmt" 17 | "math/rand" 18 | "time" 19 | ) 20 | 21 | func main() { 22 | // Prepare our context and sockets 23 | publisher, _ := zmq.NewSocket(zmq.PUB) 24 | publisher.Bind("tcp://*:5557") 25 | 26 | sequence := int64(0) 27 | rand.Seed(time.Now().UnixNano()) 28 | 29 | // Start state manager and wait for synchronization signal 30 | updates, _ := zmq.NewSocket(zmq.PAIR) 31 | updates.Bind("inproc://pipe") 32 | go state_manager() 33 | updates.RecvMessage(0) // "READY" 34 | 35 | for { 36 | // Distribute as key-value message 37 | sequence++ 38 | kvmsg := kvsimple.NewKvmsg(sequence) 39 | kvmsg.SetKey(fmt.Sprint(rand.Intn(10000))) 40 | kvmsg.SetBody(fmt.Sprint(rand.Intn(1000000))) 41 | if kvmsg.Send(publisher) != nil { 42 | break 43 | } 44 | if kvmsg.Send(updates) != nil { 45 | break 46 | } 47 | } 48 | fmt.Printf("Interrupted\n%d messages out\n", sequence) 49 | } 50 | 51 | // The state manager task maintains the state and handles requests from 52 | // clients for snapshots: 53 | 54 | func state_manager() { 55 | kvmap := make(map[string]*kvsimple.Kvmsg) 56 | 57 | pipe, _ := zmq.NewSocket(zmq.PAIR) 58 | pipe.Connect("inproc://pipe") 59 | pipe.SendMessage("READY") 60 | snapshot, _ := zmq.NewSocket(zmq.ROUTER) 61 | snapshot.Bind("tcp://*:5556") 62 | 63 | poller := zmq.NewPoller() 64 | poller.Add(pipe, zmq.POLLIN) 65 | poller.Add(snapshot, zmq.POLLIN) 66 | sequence := int64(0) // Current snapshot version number 67 | LOOP: 68 | for { 69 | polled, err := poller.Poll(-1) 70 | if err != nil { 71 | break // Context has been shut down 72 | } 73 | for _, item := range polled { 74 | switch socket := item.Socket; socket { 75 | case pipe: 76 | // Apply state update from main thread 77 | kvmsg, err := kvsimple.RecvKvmsg(pipe) 78 | if err != nil { 79 | break LOOP // Interrupted 80 | } 81 | sequence, _ = kvmsg.GetSequence() 82 | kvmsg.Store(kvmap) 83 | case snapshot: 84 | // Execute state snapshot request 85 | msg, err := snapshot.RecvMessage(0) 86 | if err != nil { 87 | break LOOP // Interrupted 88 | } 89 | identity := msg[0] 90 | // Request is in second frame of message 91 | request := msg[1] 92 | if request != "ICANHAZ?" { 93 | fmt.Println("E: bad request, aborting") 94 | break LOOP 95 | } 96 | // Send state snapshot to client 97 | 98 | // For each entry in kvmap, send kvmsg to client 99 | for _, kvmsg := range kvmap { 100 | snapshot.Send(identity, zmq.SNDMORE) 101 | kvmsg.Send(snapshot) 102 | } 103 | 104 | // Give client some time to deal with it. 105 | // This reduces the risk that the client won't see 106 | // the END message, but it doesn't eliminate the risk. 107 | time.Sleep(100 * time.Millisecond) 108 | 109 | // Now send END message with sequence number 110 | fmt.Printf("Sending state shapshot=%d\n", sequence) 111 | snapshot.Send(identity, zmq.SNDMORE) 112 | kvmsg := kvsimple.NewKvmsg(sequence) 113 | kvmsg.SetKey("KTHXBAI") 114 | kvmsg.SetBody("") 115 | kvmsg.Send(snapshot) 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/clonesrv3.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone server Model Three 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | snapshot, _ := zmq.NewSocket(zmq.ROUTER) 17 | snapshot.Bind("tcp://*:5556") 18 | publisher, _ := zmq.NewSocket(zmq.PUB) 19 | publisher.Bind("tcp://*:5557") 20 | collector, _ := zmq.NewSocket(zmq.PULL) 21 | collector.Bind("tcp://*:5558") 22 | 23 | // The body of the main task collects updates from clients and 24 | // publishes them back out to clients: 25 | 26 | sequence := int64(0) 27 | kvmap := make(map[string]*kvsimple.Kvmsg) 28 | 29 | poller := zmq.NewPoller() 30 | poller.Add(collector, zmq.POLLIN) 31 | poller.Add(snapshot, zmq.POLLIN) 32 | LOOP: 33 | for { 34 | polled, err := poller.Poll(1000 * time.Millisecond) 35 | if err != nil { 36 | break 37 | } 38 | for _, item := range polled { 39 | switch socket := item.Socket; socket { 40 | case collector: 41 | // Apply state update sent from client 42 | kvmsg, err := kvsimple.RecvKvmsg(collector) 43 | if err != nil { 44 | break LOOP // Interrupted 45 | } 46 | sequence++ 47 | kvmsg.SetSequence(sequence) 48 | kvmsg.Send(publisher) 49 | kvmsg.Store(kvmap) 50 | fmt.Println("I: publishing update", sequence) 51 | case snapshot: 52 | // Execute state snapshot request 53 | msg, err := snapshot.RecvMessage(0) 54 | if err != nil { 55 | break LOOP 56 | } 57 | identity := msg[0] 58 | 59 | // Request is in second frame of message 60 | request := msg[1] 61 | if request != "ICANHAZ?" { 62 | fmt.Println("E: bad request, aborting") 63 | break LOOP 64 | } 65 | // Send state snapshot to client 66 | 67 | // For each entry in kvmap, send kvmsg to client 68 | for _, kvmsg := range kvmap { 69 | snapshot.Send(identity, zmq.SNDMORE) 70 | kvmsg.Send(snapshot) 71 | } 72 | 73 | // Now send END message with sequence number 74 | fmt.Println("I: sending shapshot =", sequence) 75 | snapshot.Send(identity, zmq.SNDMORE) 76 | kvmsg := kvsimple.NewKvmsg(sequence) 77 | kvmsg.SetKey("KTHXBAI") 78 | kvmsg.SetBody("") 79 | kvmsg.Send(snapshot) 80 | } 81 | } 82 | } 83 | fmt.Printf("Interrupted\n%d messages handled\n", sequence) 84 | } 85 | -------------------------------------------------------------------------------- /examples/clonesrv4.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone server Model Four 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvsimple" 10 | 11 | "fmt" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // The main task is identical to clonesrv3 except for where it 17 | // handles subtrees. 18 | 19 | func main() { 20 | snapshot, _ := zmq.NewSocket(zmq.ROUTER) 21 | snapshot.Bind("tcp://*:5556") 22 | publisher, _ := zmq.NewSocket(zmq.PUB) 23 | publisher.Bind("tcp://*:5557") 24 | collector, _ := zmq.NewSocket(zmq.PULL) 25 | collector.Bind("tcp://*:5558") 26 | 27 | // The body of the main task collects updates from clients and 28 | // publishes them back out to clients: 29 | 30 | sequence := int64(0) 31 | kvmap := make(map[string]*kvsimple.Kvmsg) 32 | 33 | poller := zmq.NewPoller() 34 | poller.Add(collector, zmq.POLLIN) 35 | poller.Add(snapshot, zmq.POLLIN) 36 | LOOP: 37 | for { 38 | polled, err := poller.Poll(1000 * time.Millisecond) 39 | if err != nil { 40 | break 41 | } 42 | for _, item := range polled { 43 | switch socket := item.Socket; socket { 44 | case collector: 45 | // Apply state update sent from client 46 | kvmsg, err := kvsimple.RecvKvmsg(collector) 47 | if err != nil { 48 | break LOOP // Interrupted 49 | } 50 | sequence++ 51 | kvmsg.SetSequence(sequence) 52 | kvmsg.Send(publisher) 53 | kvmsg.Store(kvmap) 54 | fmt.Println("I: publishing update", sequence) 55 | case snapshot: 56 | // Execute state snapshot request 57 | msg, err := snapshot.RecvMessage(0) 58 | if err != nil { 59 | break LOOP 60 | } 61 | identity := msg[0] 62 | 63 | // Request is in second frame of message 64 | request := msg[1] 65 | if request != "ICANHAZ?" { 66 | fmt.Println("E: bad request, aborting") 67 | break LOOP 68 | } 69 | subtree := msg[2] 70 | // Send state snapshot to client 71 | 72 | // For each entry in kvmap, send kvmsg to client 73 | for _, kvmsg := range kvmap { 74 | if key, _ := kvmsg.GetKey(); strings.HasPrefix(key, subtree) { 75 | snapshot.Send(identity, zmq.SNDMORE) 76 | kvmsg.Send(snapshot) 77 | } 78 | } 79 | 80 | // Now send END message with sequence number 81 | fmt.Println("I: sending shapshot =", sequence) 82 | snapshot.Send(identity, zmq.SNDMORE) 83 | kvmsg := kvsimple.NewKvmsg(sequence) 84 | kvmsg.SetKey("KTHXBAI") 85 | kvmsg.SetBody(subtree) 86 | kvmsg.Send(snapshot) 87 | } 88 | } 89 | } 90 | fmt.Printf("Interrupted\n%d messages handled\n", sequence) 91 | } 92 | -------------------------------------------------------------------------------- /examples/clonesrv5.go: -------------------------------------------------------------------------------- 1 | // 2 | // Clone server Model Five 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | "github.com/pebbe/zmq4/examples/kvmsg" 10 | 11 | "errors" 12 | "fmt" 13 | "log" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | // Our server is defined by these properties 20 | type clonesrv_t struct { 21 | kvmap map[string]*kvmsg.Kvmsg // Key-value store 22 | port int // Main port we're working on 23 | sequence int64 // How many updates we're at 24 | snapshot *zmq.Socket // Handle snapshot requests 25 | publisher *zmq.Socket // Publish updates to clients 26 | collector *zmq.Socket // Collect updates from clients 27 | } 28 | 29 | func main() { 30 | 31 | srv := &clonesrv_t{ 32 | port: 5556, 33 | kvmap: make(map[string]*kvmsg.Kvmsg), 34 | } 35 | 36 | // Set up our clone server sockets 37 | srv.snapshot, _ = zmq.NewSocket(zmq.ROUTER) 38 | srv.snapshot.Bind(fmt.Sprint("tcp://*:", srv.port)) 39 | srv.publisher, _ = zmq.NewSocket(zmq.PUB) 40 | srv.publisher.Bind(fmt.Sprint("tcp://*:", srv.port+1)) 41 | srv.collector, _ = zmq.NewSocket(zmq.PULL) 42 | srv.collector.Bind(fmt.Sprint("tcp://*:", srv.port+2)) 43 | 44 | // Register our handlers with reactor 45 | reactor := zmq.NewReactor() 46 | reactor.AddSocket(srv.snapshot, zmq.POLLIN, 47 | func(e zmq.State) error { return snapshots(srv) }) 48 | reactor.AddSocket(srv.collector, zmq.POLLIN, 49 | func(e zmq.State) error { return collector(srv) }) 50 | reactor.AddChannelTime(time.Tick(1000*time.Millisecond), 1, 51 | func(v interface{}) error { return flush_ttl(srv) }) 52 | 53 | log.Println(reactor.Run(100 * time.Millisecond)) // precision: .1 seconds 54 | } 55 | 56 | // This is the reactor handler for the snapshot socket; it accepts 57 | // just the ICANHAZ? request and replies with a state snapshot ending 58 | // with a KTHXBAI message: 59 | 60 | func snapshots(srv *clonesrv_t) (err error) { 61 | 62 | msg, err := srv.snapshot.RecvMessage(0) 63 | if err != nil { 64 | return 65 | } 66 | identity := msg[0] 67 | 68 | // Request is in second frame of message 69 | request := msg[1] 70 | if request != "ICANHAZ?" { 71 | err = errors.New("E: bad request, aborting") 72 | return 73 | } 74 | subtree := msg[2] 75 | 76 | // Send state socket to client 77 | for _, kvmsg := range srv.kvmap { 78 | if key, _ := kvmsg.GetKey(); strings.HasPrefix(key, subtree) { 79 | srv.snapshot.Send(identity, zmq.SNDMORE) 80 | kvmsg.Send(srv.snapshot) 81 | } 82 | } 83 | 84 | // Now send END message with sequence number 85 | log.Println("I: sending shapshot =", srv.sequence) 86 | srv.snapshot.Send(identity, zmq.SNDMORE) 87 | kvmsg := kvmsg.NewKvmsg(srv.sequence) 88 | kvmsg.SetKey("KTHXBAI") 89 | kvmsg.SetBody(subtree) 90 | kvmsg.Send(srv.snapshot) 91 | 92 | return 93 | } 94 | 95 | // We store each update with a new sequence number, and if necessary, a 96 | // time-to-live. We publish updates immediately on our publisher socket: 97 | 98 | func collector(srv *clonesrv_t) (err error) { 99 | kvmsg, err := kvmsg.RecvKvmsg(srv.collector) 100 | if err != nil { 101 | return 102 | } 103 | 104 | srv.sequence++ 105 | kvmsg.SetSequence(srv.sequence) 106 | kvmsg.Send(srv.publisher) 107 | if ttls, e := kvmsg.GetProp("ttl"); e == nil { 108 | // change duration into specific time, using the same property: ugly! 109 | ttl, e := strconv.ParseInt(ttls, 10, 64) 110 | if e != nil { 111 | err = e 112 | return 113 | } 114 | kvmsg.SetProp("ttl", fmt.Sprint(time.Now().Add(time.Duration(ttl)*time.Second).Unix())) 115 | } 116 | kvmsg.Store(srv.kvmap) 117 | log.Println("I: publishing update =", srv.sequence) 118 | 119 | return 120 | } 121 | 122 | // At regular intervals we flush ephemeral values that have expired. This 123 | // could be slow on very large data sets: 124 | 125 | func flush_ttl(srv *clonesrv_t) (err error) { 126 | 127 | for _, kvmsg := range srv.kvmap { 128 | 129 | // If key-value pair has expired, delete it and publish the 130 | // fact to listening clients. 131 | 132 | if ttls, e := kvmsg.GetProp("ttl"); e == nil { 133 | ttl, e := strconv.ParseInt(ttls, 10, 64) 134 | if e != nil { 135 | err = e 136 | continue 137 | } 138 | if time.Now().After(time.Unix(ttl, 0)) { 139 | srv.sequence++ 140 | kvmsg.SetSequence(srv.sequence) 141 | kvmsg.SetBody("") 142 | e = kvmsg.Send(srv.publisher) 143 | if e != nil { 144 | err = e 145 | } 146 | kvmsg.Store(srv.kvmap) 147 | log.Println("I: publishing delete =", srv.sequence) 148 | } 149 | } 150 | } 151 | return 152 | } 153 | -------------------------------------------------------------------------------- /examples/eagain.go: -------------------------------------------------------------------------------- 1 | // 2 | // Shows how to provoke EAGAIN when reaching HWM 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | ) 12 | 13 | func main() { 14 | 15 | mailbox, _ := zmq.NewSocket(zmq.DEALER) 16 | mailbox.SetSndhwm(4) 17 | mailbox.SetSndtimeo(0) 18 | mailbox.Connect("tcp://localhost:9876") 19 | 20 | for count := 0; count < 10; count++ { 21 | fmt.Println("Sending message", count) 22 | _, err := mailbox.SendMessage(fmt.Sprint("message ", count)) 23 | if err != nil { 24 | fmt.Println(err) 25 | break 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/espresso.go: -------------------------------------------------------------------------------- 1 | // 2 | // Espresso Pattern 3 | // This shows how to capture data using a pub-sub proxy 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | // The subscriber thread requests messages starting with 17 | // A and B, then reads and counts incoming messages. 18 | 19 | func subscriber_thread() { 20 | // Subscribe to "A" and "B" 21 | subscriber, _ := zmq.NewSocket(zmq.SUB) 22 | subscriber.Connect("tcp://localhost:6001") 23 | subscriber.SetSubscribe("A") 24 | subscriber.SetSubscribe("B") 25 | defer subscriber.Close() // cancel subscribe 26 | 27 | for count := 0; count < 5; count++ { 28 | _, err := subscriber.RecvMessage(0) 29 | if err != nil { 30 | break // Interrupted 31 | } 32 | } 33 | } 34 | 35 | // The publisher sends random messages starting with A-J: 36 | 37 | func publisher_thread() { 38 | publisher, _ := zmq.NewSocket(zmq.PUB) 39 | publisher.Bind("tcp://*:6000") 40 | 41 | for { 42 | s := fmt.Sprintf("%c-%05d", rand.Intn(10)+'A', rand.Intn(100000)) 43 | _, err := publisher.SendMessage(s) 44 | if err != nil { 45 | break // Interrupted 46 | } 47 | time.Sleep(100 * time.Millisecond) // Wait for 1/10th second 48 | } 49 | } 50 | 51 | // The listener receives all messages flowing through the proxy, on its 52 | // pipe. In CZMQ, the pipe is a pair of ZMQ_PAIR sockets that connects 53 | // attached child threads. In other languages your mileage may vary: 54 | 55 | func listener_thread() { 56 | pipe, _ := zmq.NewSocket(zmq.PAIR) 57 | pipe.Bind("inproc://pipe") 58 | 59 | // Print everything that arrives on pipe 60 | for { 61 | msg, err := pipe.RecvMessage(0) 62 | if err != nil { 63 | break // Interrupted 64 | } 65 | fmt.Printf("%q\n", msg) 66 | } 67 | } 68 | 69 | // The main task starts the subscriber and publisher, and then sets 70 | // itself up as a listening proxy. The listener runs as a child thread: 71 | 72 | func main() { 73 | // Start child threads 74 | go publisher_thread() 75 | go subscriber_thread() 76 | go listener_thread() 77 | 78 | time.Sleep(100 * time.Millisecond) 79 | 80 | subscriber, _ := zmq.NewSocket(zmq.XSUB) 81 | subscriber.Connect("tcp://localhost:6000") 82 | publisher, _ := zmq.NewSocket(zmq.XPUB) 83 | publisher.Bind("tcp://*:6001") 84 | listener, _ := zmq.NewSocket(zmq.PAIR) 85 | listener.Connect("inproc://pipe") 86 | zmq.Proxy(subscriber, publisher, listener) 87 | 88 | fmt.Println("interrupted") 89 | } 90 | -------------------------------------------------------------------------------- /examples/fileio1.go: -------------------------------------------------------------------------------- 1 | // File Transfer model #1 2 | // 3 | // In which the server sends the entire file to the client in 4 | // large chunks with no attempt at flow control. 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "io" 13 | "os" 14 | ) 15 | 16 | const ( 17 | CHUNK_SIZE = 250000 18 | ) 19 | 20 | func client_thread(pipe chan<- string) { 21 | dealer, _ := zmq.NewSocket(zmq.DEALER) 22 | dealer.Connect("tcp://127.0.0.1:6000") 23 | 24 | dealer.Send("fetch", 0) 25 | total := 0 // Total bytes received 26 | chunks := 0 // Total chunks received 27 | 28 | for { 29 | frame, err := dealer.RecvBytes(0) 30 | if err != nil { 31 | break // Shutting down, quit 32 | } 33 | chunks++ 34 | size := len(frame) 35 | total += size 36 | if size == 0 { 37 | break // Whole file received 38 | } 39 | } 40 | fmt.Printf("%v chunks received, %v bytes\n", chunks, total) 41 | pipe <- "OK" 42 | } 43 | 44 | // The server thread reads the file from disk in chunks, and sends 45 | // each chunk to the client as a separate message. We only have one 46 | // test file, so open that once and then serve it out as needed: 47 | 48 | func server_thread() { 49 | file, err := os.Open("testdata") 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | router, _ := zmq.NewSocket(zmq.ROUTER) 55 | // Default HWM is 1000, which will drop messages here 56 | // since we send more than 1,000 chunks of test data, 57 | // so set an infinite HWM as a simple, stupid solution: 58 | router.SetRcvhwm(0) 59 | router.SetSndhwm(0) 60 | router.Bind("tcp://*:6000") 61 | for { 62 | // First frame in each message is the sender identity 63 | identity, err := router.Recv(0) 64 | if err != nil { 65 | break // Shutting down, quit 66 | } 67 | 68 | // Second frame is "fetch" command 69 | command, _ := router.Recv(0) 70 | if command != "fetch" { 71 | panic("command != \"fetch\"") 72 | } 73 | 74 | chunk := make([]byte, CHUNK_SIZE) 75 | for { 76 | n, _ := io.ReadFull(file, chunk) 77 | router.SendMessage(identity, chunk[:n]) 78 | if n == 0 { 79 | break // Always end with a zero-size frame 80 | } 81 | } 82 | } 83 | file.Close() 84 | } 85 | 86 | // The main task starts the client and server threads; it's easier 87 | // to test this as a single process with threads, than as multiple 88 | // processes: 89 | 90 | func main() { 91 | pipe := make(chan string) 92 | 93 | // Start child threads 94 | go server_thread() 95 | go client_thread(pipe) 96 | // Loop until client tells us it's done 97 | <-pipe 98 | } 99 | -------------------------------------------------------------------------------- /examples/fileio2.go: -------------------------------------------------------------------------------- 1 | // File Transfer model #2 2 | // 3 | // In which the client requests each chunk individually, thus 4 | // eliminating server queue overflows, but at a cost in speed. 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "os" 13 | "strconv" 14 | ) 15 | 16 | const ( 17 | CHUNK_SIZE = 250000 18 | ) 19 | 20 | func client_thread(pipe chan<- string) { 21 | dealer, _ := zmq.NewSocket(zmq.DEALER) 22 | dealer.Connect("tcp://127.0.0.1:6000") 23 | 24 | total := 0 // Total bytes received 25 | chunks := 0 // Total chunks received 26 | 27 | for { 28 | // Ask for next chunk 29 | dealer.SendMessage("fetch", total, CHUNK_SIZE) 30 | 31 | chunk, err := dealer.RecvBytes(0) 32 | if err != nil { 33 | break // Shutting down, quit 34 | } 35 | chunks++ 36 | size := len(chunk) 37 | total += size 38 | if size < CHUNK_SIZE { 39 | break // Last chunk received; exit 40 | } 41 | } 42 | fmt.Printf("%v chunks received, %v bytes\n", chunks, total) 43 | pipe <- "OK" 44 | } 45 | 46 | // The server thread waits for a chunk request from a client, 47 | // reads that chunk and sends it back to the client: 48 | 49 | func server_thread() { 50 | file, err := os.Open("testdata") 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | router, _ := zmq.NewSocket(zmq.ROUTER) 56 | router.SetRcvhwm(1) 57 | router.SetSndhwm(1) 58 | router.Bind("tcp://*:6000") 59 | for { 60 | msg, err := router.RecvMessage(0) 61 | if err != nil { 62 | break // Shutting down, quit 63 | } 64 | // First frame in each message is the sender identity 65 | identity := msg[0] 66 | 67 | // Second frame is "fetch" command 68 | if msg[1] != "fetch" { 69 | panic("command != \"fetch\"") 70 | } 71 | 72 | // Third frame is chunk offset in file 73 | offset, _ := strconv.ParseInt(msg[2], 10, 64) 74 | 75 | // Fourth frame is maximum chunk size 76 | chunksz, _ := strconv.Atoi(msg[3]) 77 | 78 | // Read chunk of data from file 79 | chunk := make([]byte, chunksz) 80 | n, _ := file.ReadAt(chunk, offset) 81 | 82 | // Send resulting chunk to client 83 | router.SendMessage(identity, chunk[:n]) 84 | } 85 | file.Close() 86 | } 87 | 88 | // The main task is just the same as in the first model. 89 | 90 | func main() { 91 | pipe := make(chan string) 92 | 93 | // Start child threads 94 | go server_thread() 95 | go client_thread(pipe) 96 | // Loop until client tells us it's done 97 | <-pipe 98 | } 99 | -------------------------------------------------------------------------------- /examples/fileio3.go: -------------------------------------------------------------------------------- 1 | // File Transfer model #3 2 | // 3 | // In which the client requests each chunk individually, using 4 | // command pipelining to give us a credit-based flow control. 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "os" 13 | "strconv" 14 | ) 15 | 16 | const ( 17 | CHUNK_SIZE = 250000 18 | PIPELINE = 10 19 | ) 20 | 21 | func client_thread(pipe chan<- string) { 22 | dealer, _ := zmq.NewSocket(zmq.DEALER) 23 | dealer.Connect("tcp://127.0.0.1:6000") 24 | 25 | // Up to this many chunks in transit 26 | credit := PIPELINE 27 | 28 | total := 0 // Total bytes received 29 | chunks := 0 // Total chunks received 30 | offset := 0 // Offset of next chunk request 31 | 32 | for { 33 | for credit > 0 { 34 | // Ask for next chunk 35 | dealer.SendMessage("fetch", offset, CHUNK_SIZE) 36 | offset += CHUNK_SIZE 37 | credit-- 38 | } 39 | chunk, err := dealer.RecvBytes(0) 40 | if err != nil { 41 | break // Shutting down, quit 42 | } 43 | chunks++ 44 | credit++ 45 | size := len(chunk) 46 | total += size 47 | if size < CHUNK_SIZE { 48 | break // Last chunk received; exit 49 | } 50 | } 51 | fmt.Printf("%v chunks received, %v bytes\n", chunks, total) 52 | pipe <- "OK" 53 | } 54 | 55 | // The rest of the code is exactly the same as in model 2, except 56 | // that we set the HWM on the server's ROUTER socket to PIPELINE 57 | // to act as a sanity check. 58 | 59 | // The server thread waits for a chunk request from a client, 60 | // reads that chunk and sends it back to the client: 61 | 62 | func server_thread() { 63 | file, err := os.Open("testdata") 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | router, _ := zmq.NewSocket(zmq.ROUTER) 69 | router.SetRcvhwm(PIPELINE * 2) 70 | router.SetSndhwm(PIPELINE * 2) 71 | router.Bind("tcp://*:6000") 72 | for { 73 | msg, err := router.RecvMessage(0) 74 | if err != nil { 75 | break // Shutting down, quit 76 | } 77 | // First frame in each message is the sender identity 78 | identity := msg[0] 79 | 80 | // Second frame is "fetch" command 81 | if msg[1] != "fetch" { 82 | panic("command != \"fetch\"") 83 | } 84 | 85 | // Third frame is chunk offset in file 86 | offset, _ := strconv.ParseInt(msg[2], 10, 64) 87 | 88 | // Fourth frame is maximum chunk size 89 | chunksz, _ := strconv.Atoi(msg[3]) 90 | 91 | // Read chunk of data from file 92 | chunk := make([]byte, chunksz) 93 | n, _ := file.ReadAt(chunk, offset) 94 | 95 | // Send resulting chunk to client 96 | router.SendMessage(identity, chunk[:n]) 97 | } 98 | file.Close() 99 | } 100 | 101 | // The main task is just the same as in the first model. 102 | 103 | func main() { 104 | pipe := make(chan string) 105 | 106 | // Start child threads 107 | go server_thread() 108 | go client_thread(pipe) 109 | // Loop until client tells us it's done 110 | <-pipe 111 | } 112 | -------------------------------------------------------------------------------- /examples/flclient1.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance client - Model 1. 3 | // Uses REQ socket to query one or more services 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "errors" 12 | "fmt" 13 | "os" 14 | "time" 15 | ) 16 | 17 | const ( 18 | REQUEST_TIMEOUT = 1000 * time.Millisecond 19 | MAX_RETRIES = 3 // Before we abandon 20 | ) 21 | 22 | func try_request(endpoint string, request []string) (reply []string, err error) { 23 | fmt.Printf("I: trying echo service at %s...\n", endpoint) 24 | client, _ := zmq.NewSocket(zmq.REQ) 25 | client.Connect(endpoint) 26 | 27 | // Send request, wait safely for reply 28 | client.SendMessage(request) 29 | poller := zmq.NewPoller() 30 | poller.Add(client, zmq.POLLIN) 31 | polled, err := poller.Poll(REQUEST_TIMEOUT) 32 | reply = []string{} 33 | if len(polled) == 1 { 34 | reply, err = client.RecvMessage(0) 35 | } else { 36 | err = errors.New("Time out") 37 | } 38 | return 39 | } 40 | 41 | // The client uses a Lazy Pirate strategy if it only has one server to talk 42 | // to. If it has 2 or more servers to talk to, it will try each server just 43 | // once: 44 | 45 | func main() { 46 | request := []string{"Hello world"} 47 | reply := []string{} 48 | var err error 49 | 50 | endpoints := len(os.Args) - 1 51 | if endpoints == 0 { 52 | fmt.Printf("I: syntax: %s ...\n", os.Args[0]) 53 | } else if endpoints == 1 { 54 | // For one endpoint, we retry N times 55 | for retries := 0; retries < MAX_RETRIES; retries++ { 56 | endpoint := os.Args[1] 57 | reply, err = try_request(endpoint, request) 58 | if err == nil { 59 | break // Successful 60 | } 61 | fmt.Printf("W: no response from %s, retrying...\n", endpoint) 62 | } 63 | } else { 64 | // For multiple endpoints, try each at most once 65 | for endpoint_nbr := 0; endpoint_nbr < endpoints; endpoint_nbr++ { 66 | endpoint := os.Args[endpoint_nbr+1] 67 | reply, err = try_request(endpoint, request) 68 | if err == nil { 69 | break // Successful 70 | } 71 | fmt.Println("W: no response from", endpoint) 72 | } 73 | } 74 | if len(reply) > 0 { 75 | fmt.Printf("Service is running OK: %q\n", reply) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/flclient2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance client - Model 2. 3 | // Uses DEALER socket to blast one or more services 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "errors" 12 | "fmt" 13 | "os" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | const ( 19 | 20 | // If not a single service replies within this time, give up 21 | GLOBAL_TIMEOUT = 2500 * time.Millisecond 22 | ) 23 | 24 | func main() { 25 | if len(os.Args) == 1 { 26 | fmt.Printf("I: syntax: %s ...\n", os.Args[0]) 27 | return 28 | } 29 | // Create new freelance client object 30 | client := new_flclient() 31 | 32 | // Connect to each endpoint 33 | for argn := 1; argn < len(os.Args); argn++ { 34 | client.connect(os.Args[argn]) 35 | } 36 | 37 | // Send a bunch of name resolution 'requests', measure time 38 | start := time.Now() 39 | for requests := 10000; requests > 0; requests-- { 40 | _, err := client.request("random name") 41 | if err != nil { 42 | fmt.Println("E: name service not available, aborting") 43 | break 44 | } 45 | } 46 | fmt.Println("Average round trip cost:", time.Now().Sub(start)) 47 | } 48 | 49 | // Here is the flclient class implementation. Each instance has 50 | // a DEALER socket it uses to talk to the servers, a counter of how many 51 | // servers it's connected to, and a request sequence number: 52 | 53 | type flclient_t struct { 54 | socket *zmq.Socket // DEALER socket talking to servers 55 | servers int // How many servers we have connected to 56 | sequence int // Number of requests ever sent 57 | } 58 | 59 | // -------------------------------------------------------------------- 60 | // Constructor 61 | 62 | func new_flclient() (client *flclient_t) { 63 | client = &flclient_t{} 64 | 65 | client.socket, _ = zmq.NewSocket(zmq.DEALER) 66 | return 67 | } 68 | 69 | // -------------------------------------------------------------------- 70 | // Connect to new server endpoint 71 | 72 | func (client *flclient_t) connect(endpoint string) { 73 | client.socket.Connect(endpoint) 74 | client.servers++ 75 | } 76 | 77 | // The request method does the hard work. It sends a request to all 78 | // connected servers in parallel (for this to work, all connections 79 | // have to be successful and completed by this time). It then waits 80 | // for a single successful reply, and returns that to the caller. 81 | // Any other replies are just dropped: 82 | 83 | func (client *flclient_t) request(request ...string) (reply []string, err error) { 84 | reply = []string{} 85 | 86 | // Prefix request with sequence number and empty envelope 87 | client.sequence++ 88 | 89 | // Blast the request to all connected servers 90 | for server := 0; server < client.servers; server++ { 91 | client.socket.SendMessage("", client.sequence, request) 92 | } 93 | // Wait for a matching reply to arrive from anywhere 94 | // Since we can poll several times, calculate each one 95 | endtime := time.Now().Add(GLOBAL_TIMEOUT) 96 | poller := zmq.NewPoller() 97 | poller.Add(client.socket, zmq.POLLIN) 98 | for time.Now().Before(endtime) { 99 | polled, err := poller.Poll(endtime.Sub(time.Now())) 100 | if err == nil && len(polled) > 0 { 101 | // Reply is [empty][sequence][OK] 102 | reply, _ = client.socket.RecvMessage(0) 103 | if len(reply) != 3 { 104 | panic("len(reply) != 3") 105 | } 106 | sequence := reply[1] 107 | reply = reply[2:] 108 | sequence_nbr, _ := strconv.Atoi(sequence) 109 | if sequence_nbr == client.sequence { 110 | break 111 | } 112 | } 113 | } 114 | if len(reply) == 0 { 115 | err = errors.New("No reply") 116 | } 117 | return 118 | } 119 | -------------------------------------------------------------------------------- /examples/flclient3.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance client - Model 3. 3 | // Uses flcliapi class to encapsulate Freelance pattern 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/pebbe/zmq4/examples/flcliapi" 10 | 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | // Create new freelance client object 17 | client := flcliapi.New() 18 | 19 | // Connect to several endpoints 20 | client.Connect("tcp://localhost:5555") 21 | client.Connect("tcp://localhost:5556") 22 | client.Connect("tcp://localhost:5557") 23 | 24 | // Send a bunch of name resolution 'requests', measure time 25 | start := time.Now() 26 | req := []string{"random name"} 27 | for requests := 1000; requests > 0; requests-- { 28 | _, err := client.Request(req) 29 | if err != nil { 30 | fmt.Println("E: name service not available, aborting") 31 | break 32 | } 33 | } 34 | fmt.Println("Average round trip cost:", time.Now().Sub(start)/1000) 35 | } 36 | -------------------------------------------------------------------------------- /examples/flserver1.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance server - Model 1. 3 | // Trivial echo service 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) < 2 { 17 | fmt.Printf("I: syntax: %s \n", os.Args[0]) 18 | return 19 | } 20 | server, _ := zmq.NewSocket(zmq.REP) 21 | server.Bind(os.Args[1]) 22 | 23 | fmt.Println("I: echo service is ready at", os.Args[1]) 24 | for { 25 | msg, err := server.RecvMessage(0) 26 | if err != nil { 27 | break // Interrupted 28 | } 29 | server.SendMessage(msg) 30 | } 31 | fmt.Println("W: interrupted") 32 | } 33 | -------------------------------------------------------------------------------- /examples/flserver2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance server - Model 2. 3 | // Does some work, replies OK, with message sequencing 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) < 2 { 17 | fmt.Printf("I: syntax: %s \n", os.Args[0]) 18 | return 19 | } 20 | server, _ := zmq.NewSocket(zmq.REP) 21 | server.Bind(os.Args[1]) 22 | 23 | fmt.Println("I: service is ready at", os.Args[1]) 24 | for { 25 | request, err := server.RecvMessage(0) 26 | if err != nil { 27 | break // Interrupted 28 | } 29 | // Fail nastily if run against wrong client 30 | if len(request) != 2 { 31 | panic("len(request) != 2") 32 | } 33 | 34 | identity := request[0] 35 | 36 | server.SendMessage(identity, "OK") 37 | } 38 | fmt.Println("W: interrupted") 39 | } 40 | -------------------------------------------------------------------------------- /examples/flserver3.go: -------------------------------------------------------------------------------- 1 | // 2 | // Freelance server - Model 3. 3 | // Uses an ROUTER/ROUTER socket but just one thread 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | var verbose bool 17 | if len(os.Args) > 1 && os.Args[1] == "-v" { 18 | verbose = true 19 | } 20 | 21 | // Prepare server socket with predictable identity 22 | bind_endpoint := "tcp://*:5555" 23 | connect_endpoint := "tcp://localhost:5555" 24 | server, _ := zmq.NewSocket(zmq.ROUTER) 25 | server.SetIdentity(connect_endpoint) 26 | server.Bind(bind_endpoint) 27 | fmt.Println("I: service is ready at", bind_endpoint) 28 | 29 | for { 30 | request, err := server.RecvMessage(0) 31 | if err != nil { 32 | break 33 | } 34 | if verbose { 35 | fmt.Printf("%q\n", request) 36 | } 37 | 38 | // Frame 0: identity of client 39 | // Frame 1: PING, or client control frame 40 | // Frame 2: request body 41 | identity := request[0] 42 | control := request[1] 43 | reply := make([]string, 1, 3) 44 | if control == "PING" { 45 | reply = append(reply, "PONG") 46 | } else { 47 | reply = append(reply, control) 48 | reply = append(reply, "OK") 49 | } 50 | reply[0] = identity 51 | if verbose { 52 | fmt.Printf("%q\n", reply) 53 | } 54 | server.SendMessage(reply) 55 | } 56 | fmt.Println("W: interrupted") 57 | } 58 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pebbe/zmq4/examples 2 | 3 | go 1.16 4 | 5 | replace github.com/pebbe/zmq4 => ../ 6 | 7 | require ( 8 | github.com/google/uuid v1.0.0 9 | github.com/pebbe/zmq4 v0.0.0-00010101000000-000000000000 10 | ) 11 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 2 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 | -------------------------------------------------------------------------------- /examples/hwclient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Hello World client. 3 | // Connects REQ socket to tcp://localhost:5555 4 | // Sends "Hello" to server, expects "World" back 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | ) 14 | 15 | func main() { 16 | // Socket to talk to server 17 | fmt.Println("Connecting to hello world server...") 18 | requester, _ := zmq.NewSocket(zmq.REQ) 19 | defer requester.Close() 20 | requester.Connect("tcp://localhost:5555") 21 | 22 | for request_nbr := 0; request_nbr != 10; request_nbr++ { 23 | // send hello 24 | msg := fmt.Sprintf("Hello %d", request_nbr) 25 | fmt.Println("Sending ", msg) 26 | requester.Send(msg, 0) 27 | 28 | // Wait for reply: 29 | reply, _ := requester.Recv(0) 30 | fmt.Println("Received ", reply) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/hwserver.go: -------------------------------------------------------------------------------- 1 | // 2 | // Hello World server. 3 | // Binds REP socket to tcp://*:5555 4 | // Expects "Hello" from client, replies with "World" 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | // Socket to talk to clients 18 | responder, _ := zmq.NewSocket(zmq.REP) 19 | defer responder.Close() 20 | responder.Bind("tcp://*:5555") 21 | 22 | for { 23 | // Wait for next request from client 24 | msg, _ := responder.Recv(0) 25 | fmt.Println("Received ", msg) 26 | 27 | // Do some 'work' 28 | time.Sleep(time.Second) 29 | 30 | // Send reply back to client 31 | reply := "World" 32 | responder.Send(reply, 0) 33 | fmt.Println("Sent ", reply) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/identity.go: -------------------------------------------------------------------------------- 1 | // 2 | // Demonstrate identities as used by the request-reply pattern. 3 | // Run this program by itself. 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "regexp" 13 | ) 14 | 15 | var ( 16 | all_char = regexp.MustCompile("^[^[:cntrl:]]*$") 17 | ) 18 | 19 | func main() { 20 | sink, _ := zmq.NewSocket(zmq.ROUTER) 21 | defer sink.Close() 22 | sink.Bind("inproc://example") 23 | 24 | // First allow 0MQ to set the identity 25 | anonymous, _ := zmq.NewSocket(zmq.REQ) 26 | defer anonymous.Close() 27 | anonymous.Connect("inproc://example") 28 | anonymous.Send("ROUTER uses a generated UUID", 0) 29 | dump(sink) 30 | 31 | // Then set the identity ourselves 32 | identified, _ := zmq.NewSocket(zmq.REQ) 33 | defer identified.Close() 34 | identified.SetIdentity("PEER2") 35 | identified.Connect("inproc://example") 36 | identified.Send("ROUTER socket uses REQ's socket identity", 0) 37 | dump(sink) 38 | } 39 | 40 | func dump(soc *zmq.Socket) { 41 | fmt.Println("----------------------------------------") 42 | for { 43 | // Process all parts of the message 44 | message, _ := soc.Recv(0) 45 | 46 | // Dump the message as text or binary 47 | fmt.Printf("[%03d] ", len(message)) 48 | if all_char.MatchString(message) { 49 | fmt.Print(message) 50 | } else { 51 | for i := 0; i < len(message); i++ { 52 | fmt.Printf("%02X ", message[i]) 53 | } 54 | } 55 | fmt.Println() 56 | 57 | more, _ := soc.GetRcvmore() 58 | if !more { 59 | break 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/interrupt.go: -------------------------------------------------------------------------------- 1 | // WARNING: This won't build on Windows and Plan9. 2 | 3 | // 4 | // Handling Ctrl-C cleanly in C. 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "log" 14 | "os" 15 | "os/signal" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | // Socket to talk to server 22 | fmt.Println("Connecting to hello world server...") 23 | client, _ := zmq.NewSocket(zmq.REQ) 24 | defer client.Close() 25 | client.Connect("tcp://localhost:5555") 26 | 27 | // Without signal handling, Go will exit on signal, even if the signal was caught by ZeroMQ 28 | chSignal := make(chan os.Signal, 1) 29 | signal.Notify(chSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) 30 | 31 | zmq.SetRetryAfterEINTR(false) 32 | 33 | LOOP: 34 | for { 35 | client.Send("HELLO", 0) 36 | fmt.Println("Sent: HELLO") 37 | reply, err := client.Recv(0) 38 | if err != nil { 39 | if zmq.AsErrno(err) == zmq.Errno(syscall.EINTR) { 40 | // signal was caught by 0MQ 41 | log.Println("Client Recv:", err) 42 | break 43 | } else { 44 | // some error occurred 45 | log.Panicln(err) 46 | } 47 | } 48 | 49 | fmt.Println("Received:", reply) 50 | time.Sleep(time.Second) 51 | 52 | select { 53 | case sig := <-chSignal: 54 | // signal was caught by Go 55 | log.Println("Signal:", sig) 56 | break LOOP 57 | default: 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/kvmsg/kvmsg_test.go: -------------------------------------------------------------------------------- 1 | package kvmsg 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4" 5 | 6 | "os" 7 | "testing" 8 | ) 9 | 10 | // The test is the same as in kvsimple with added support 11 | // for the uuid and property features of kvmsg 12 | 13 | func TestKvmsg(t *testing.T) { 14 | 15 | // Prepare our context and sockets 16 | output, err := zmq.NewSocket(zmq.DEALER) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | err = output.Bind("ipc://kvmsg_selftest.ipc") 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | 26 | input, err := zmq.NewSocket(zmq.DEALER) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | 31 | err = input.Connect("ipc://kvmsg_selftest.ipc") 32 | if err != nil { 33 | t.Error(err) 34 | } 35 | 36 | kvmap := make(map[string]*Kvmsg) 37 | 38 | // Test send and receive of simple message 39 | kvmsg := NewKvmsg(1) 40 | kvmsg.SetKey("key") 41 | kvmsg.SetUuid() 42 | kvmsg.SetBody("body") 43 | kvmsg.Dump() 44 | err = kvmsg.Send(output) 45 | 46 | kvmsg.Store(kvmap) 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | 51 | kvmsg, err = RecvKvmsg(input) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | kvmsg.Dump() 56 | key, err := kvmsg.GetKey() 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | if key != "key" { 61 | t.Error("Expected \"key\", got \"" + key + "\"") 62 | } 63 | kvmsg.Store(kvmap) 64 | 65 | // Test send and receive of message with properties 66 | kvmsg = NewKvmsg(2) 67 | err = kvmsg.SetProp("prop1", "value1") 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | kvmsg.SetProp("prop2", "value1") 72 | kvmsg.SetProp("prop2", "value2") 73 | kvmsg.SetKey("key") 74 | kvmsg.SetUuid() 75 | kvmsg.SetBody("body") 76 | if val, err := kvmsg.GetProp("prop2"); err != nil || val != "value2" { 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | t.Error("Expected \"prop2\" = \"value2\", got \"" + val + "\"") 81 | } 82 | kvmsg.Dump() 83 | err = kvmsg.Send(output) 84 | 85 | kvmsg, err = RecvKvmsg(input) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | kvmsg.Dump() 90 | key, err = kvmsg.GetKey() 91 | if err != nil { 92 | t.Error(err) 93 | } 94 | if key != "key" { 95 | t.Error("Expected \"key\", got \"" + key + "\"") 96 | } 97 | prop, err := kvmsg.GetProp("prop2") 98 | if err != nil { 99 | t.Error(err) 100 | } 101 | if prop != "value2" { 102 | t.Error("Expected property \"value2\", got \"" + key + "\"") 103 | } 104 | 105 | input.Close() 106 | output.Close() 107 | os.Remove("kvmsg_selftest.ipc") 108 | } 109 | -------------------------------------------------------------------------------- /examples/kvsimple/kvsimple.go: -------------------------------------------------------------------------------- 1 | // kvsimple - simple key-value message class for example applications. 2 | // 3 | // This is a very much unlike typical Go. 4 | package kvsimple 5 | 6 | import ( 7 | zmq "github.com/pebbe/zmq4" 8 | 9 | "errors" 10 | "fmt" 11 | "os" 12 | ) 13 | 14 | const ( 15 | frame_KEY = 0 16 | frame_SEQ = 1 17 | frame_BODY = 2 18 | kvmsg_FRAMES = 3 19 | ) 20 | 21 | // The Kvmsg type holds a single key-value message consisting of a 22 | // list of 0 or more frames. 23 | type Kvmsg struct { 24 | // Presence indicators for each frame 25 | present []bool 26 | // Corresponding 0MQ message frames, if any 27 | frame []string 28 | } 29 | 30 | // Constructor, takes a sequence number for the new Kvmsg instance. 31 | func NewKvmsg(sequence int64) (kvmsg *Kvmsg) { 32 | kvmsg = &Kvmsg{ 33 | present: make([]bool, kvmsg_FRAMES), 34 | frame: make([]string, kvmsg_FRAMES), 35 | } 36 | kvmsg.SetSequence(sequence) 37 | return 38 | } 39 | 40 | // The RecvKvmsg function reads a key-value message from socket, and returns a new 41 | // Kvmsg instance. 42 | func RecvKvmsg(socket *zmq.Socket) (kvmsg *Kvmsg, err error) { 43 | kvmsg = &Kvmsg{ 44 | present: make([]bool, kvmsg_FRAMES), 45 | frame: make([]string, kvmsg_FRAMES), 46 | } 47 | msg, err := socket.RecvMessage(0) 48 | if err != nil { 49 | return 50 | } 51 | //fmt.Printf("Recv from %s: %q\n", socket, msg) 52 | for i := 0; i < kvmsg_FRAMES && i < len(msg); i++ { 53 | kvmsg.frame[i] = msg[i] 54 | kvmsg.present[i] = true 55 | } 56 | return 57 | } 58 | 59 | // The send method sends a multi-frame key-value message to a socket. 60 | func (kvmsg *Kvmsg) Send(socket *zmq.Socket) (err error) { 61 | //fmt.Printf("Send to %s: %q\n", socket, kvmsg.frame) 62 | _, err = socket.SendMessage(kvmsg.frame) 63 | return 64 | } 65 | 66 | func (kvmsg *Kvmsg) GetKey() (key string, err error) { 67 | if !kvmsg.present[frame_KEY] { 68 | err = errors.New("Key not set") 69 | return 70 | } 71 | key = kvmsg.frame[frame_KEY] 72 | return 73 | } 74 | 75 | func (kvmsg *Kvmsg) SetKey(key string) { 76 | kvmsg.frame[frame_KEY] = key 77 | kvmsg.present[frame_KEY] = true 78 | } 79 | 80 | func (kvmsg *Kvmsg) GetSequence() (sequence int64, err error) { 81 | if !kvmsg.present[frame_SEQ] { 82 | err = errors.New("Sequence not set") 83 | return 84 | } 85 | source := kvmsg.frame[frame_SEQ] 86 | sequence = int64(source[0])<<56 + 87 | int64(source[1])<<48 + 88 | int64(source[2])<<40 + 89 | int64(source[3])<<32 + 90 | int64(source[4])<<24 + 91 | int64(source[5])<<16 + 92 | int64(source[6])<<8 + 93 | int64(source[7]) 94 | return 95 | } 96 | 97 | func (kvmsg *Kvmsg) SetSequence(sequence int64) { 98 | 99 | source := make([]byte, 8) 100 | source[0] = byte((sequence >> 56) & 255) 101 | source[1] = byte((sequence >> 48) & 255) 102 | source[2] = byte((sequence >> 40) & 255) 103 | source[3] = byte((sequence >> 32) & 255) 104 | source[4] = byte((sequence >> 24) & 255) 105 | source[5] = byte((sequence >> 16) & 255) 106 | source[6] = byte((sequence >> 8) & 255) 107 | source[7] = byte((sequence) & 255) 108 | 109 | kvmsg.frame[frame_SEQ] = string(source) 110 | kvmsg.present[frame_SEQ] = true 111 | } 112 | 113 | func (kvmsg *Kvmsg) GetBody() (body string, err error) { 114 | if !kvmsg.present[frame_BODY] { 115 | err = errors.New("Body not set") 116 | return 117 | } 118 | body = kvmsg.frame[frame_BODY] 119 | return 120 | } 121 | 122 | func (kvmsg *Kvmsg) SetBody(body string) { 123 | kvmsg.frame[frame_BODY] = body 124 | kvmsg.present[frame_BODY] = true 125 | } 126 | 127 | // The size method returns the body size of the last-read message, if any. 128 | func (kvmsg *Kvmsg) Size() int { 129 | if kvmsg.present[frame_BODY] { 130 | return len(kvmsg.frame[frame_BODY]) 131 | } 132 | return 0 133 | } 134 | 135 | // The store method stores the key-value message into a hash map, unless 136 | // the key is nil. 137 | func (kvmsg *Kvmsg) Store(kvmap map[string]*Kvmsg) { 138 | if kvmsg.present[frame_KEY] { 139 | kvmap[kvmsg.frame[frame_KEY]] = kvmsg 140 | } 141 | } 142 | 143 | // The dump method prints the key-value message to stderr, 144 | // for debugging and tracing. 145 | func (kvmsg *Kvmsg) Dump() { 146 | size := kvmsg.Size() 147 | body, _ := kvmsg.GetBody() 148 | seq, _ := kvmsg.GetSequence() 149 | key, _ := kvmsg.GetKey() 150 | fmt.Fprintf(os.Stderr, "[seq:%v][key:%v][size:%v]", seq, key, size) 151 | for char_nbr := 0; char_nbr < size; char_nbr++ { 152 | fmt.Fprintf(os.Stderr, "%02X", body[char_nbr]) 153 | } 154 | fmt.Fprintln(os.Stderr) 155 | } 156 | 157 | // The test function is in kvsimple_test.go 158 | -------------------------------------------------------------------------------- /examples/kvsimple/kvsimple_test.go: -------------------------------------------------------------------------------- 1 | package kvsimple 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4" 5 | 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestKvmsg(t *testing.T) { 11 | 12 | // Prepare our context and sockets 13 | output, err := zmq.NewSocket(zmq.DEALER) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | 18 | err = output.Bind("ipc://kvmsg_selftest.ipc") 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | 23 | input, err := zmq.NewSocket(zmq.DEALER) 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | err = input.Connect("ipc://kvmsg_selftest.ipc") 29 | if err != nil { 30 | t.Error(err) 31 | } 32 | 33 | kvmap := make(map[string]*Kvmsg) 34 | 35 | // Test send and receive of simple message 36 | kvmsg := NewKvmsg(1) 37 | kvmsg.SetKey("key") 38 | kvmsg.SetBody("body") 39 | kvmsg.Dump() 40 | err = kvmsg.Send(output) 41 | 42 | kvmsg.Store(kvmap) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | 47 | kvmsg, err = RecvKvmsg(input) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | kvmsg.Dump() 52 | key, err := kvmsg.GetKey() 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | if key != "key" { 57 | t.Error("Expected \"key\", got \"" + key + "\"") 58 | } 59 | kvmsg.Store(kvmap) 60 | 61 | input.Close() 62 | output.Close() 63 | os.Remove("kvmsg_selftest.ipc") 64 | } 65 | -------------------------------------------------------------------------------- /examples/lbbroker2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Load-balancing broker. 3 | // Demonstrates use of higher level functions. 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | NBR_CLIENTS = 10 18 | NBR_WORKERS = 3 19 | WORKER_READY = "\001" // Signals worker is ready 20 | ) 21 | 22 | // Basic request-reply client using REQ socket 23 | // 24 | func client_task() { 25 | client, _ := zmq.NewSocket(zmq.REQ) 26 | defer client.Close() 27 | client.Connect("ipc://frontend.ipc") 28 | 29 | // Send request, get reply 30 | for { 31 | client.SendMessage("HELLO") 32 | reply, _ := client.RecvMessage(0) 33 | if len(reply) == 0 { 34 | break 35 | } 36 | fmt.Println("Client:", strings.Join(reply, "\n\t")) 37 | time.Sleep(time.Second) 38 | } 39 | } 40 | 41 | // Worker using REQ socket to do load-balancing 42 | // 43 | func worker_task() { 44 | worker, _ := zmq.NewSocket(zmq.REQ) 45 | defer worker.Close() 46 | worker.Connect("ipc://backend.ipc") 47 | 48 | // Tell broker we're ready for work 49 | worker.SendMessage(WORKER_READY) 50 | 51 | // Process messages as they arrive 52 | for { 53 | msg, e := worker.RecvMessage(0) 54 | if e != nil { 55 | break // Interrupted ?? 56 | } 57 | msg[len(msg)-1] = "OK" 58 | worker.SendMessage(msg) 59 | } 60 | } 61 | 62 | // Now we come to the main task. This has the identical functionality to 63 | // the previous lbbroker example but uses higher level functions to read 64 | // and send messages: 65 | 66 | func main() { 67 | // Prepare our sockets 68 | frontend, _ := zmq.NewSocket(zmq.ROUTER) 69 | backend, _ := zmq.NewSocket(zmq.ROUTER) 70 | defer frontend.Close() 71 | defer backend.Close() 72 | frontend.Bind("ipc://frontend.ipc") 73 | backend.Bind("ipc://backend.ipc") 74 | 75 | for client_nbr := 0; client_nbr < NBR_CLIENTS; client_nbr++ { 76 | go client_task() 77 | } 78 | for worker_nbr := 0; worker_nbr < NBR_WORKERS; worker_nbr++ { 79 | go worker_task() 80 | } 81 | 82 | // Queue of available workers 83 | workers := make([]string, 0, 10) 84 | 85 | poller1 := zmq.NewPoller() 86 | poller1.Add(backend, zmq.POLLIN) 87 | poller2 := zmq.NewPoller() 88 | poller2.Add(backend, zmq.POLLIN) 89 | poller2.Add(frontend, zmq.POLLIN) 90 | 91 | LOOP: 92 | for { 93 | // Poll frontend only if we have available workers 94 | var sockets []zmq.Polled 95 | var err error 96 | if len(workers) > 0 { 97 | sockets, err = poller2.Poll(-1) 98 | } else { 99 | sockets, err = poller1.Poll(-1) 100 | } 101 | if err != nil { 102 | break // Interrupted 103 | } 104 | for _, socket := range sockets { 105 | switch socket.Socket { 106 | case backend: 107 | // Handle worker activity on backend 108 | 109 | // Use worker identity for load-balancing 110 | msg, err := backend.RecvMessage(0) 111 | if err != nil { 112 | break LOOP // Interrupted 113 | } 114 | identity, msg := unwrap(msg) 115 | workers = append(workers, identity) 116 | 117 | // Forward message to client if it's not a READY 118 | if msg[0] != WORKER_READY { 119 | frontend.SendMessage(msg) 120 | } 121 | 122 | case frontend: 123 | // Get client request, route to first available worker 124 | msg, err := frontend.RecvMessage(0) 125 | if err == nil { 126 | backend.SendMessage(workers[0], "", msg) 127 | workers = workers[1:] 128 | } 129 | } 130 | } 131 | } 132 | 133 | time.Sleep(100 * time.Millisecond) 134 | } 135 | 136 | // Pops frame off front of message and returns it as 'head' 137 | // If next frame is empty, pops that empty frame. 138 | // Return remaining frames of message as 'tail' 139 | func unwrap(msg []string) (head string, tail []string) { 140 | head = msg[0] 141 | if len(msg) > 1 && msg[1] == "" { 142 | tail = msg[2:] 143 | } else { 144 | tail = msg[1:] 145 | } 146 | return 147 | } 148 | -------------------------------------------------------------------------------- /examples/lbbroker3.go: -------------------------------------------------------------------------------- 1 | // 2 | // Load-balancing broker. 3 | // Demonstrates use of Reactor, and other higher level functions. 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | NBR_CLIENTS = 10 18 | NBR_WORKERS = 3 19 | WORKER_READY = "\001" // Signals worker is ready 20 | ) 21 | 22 | // Basic request-reply client using REQ socket 23 | // 24 | func client_task() { 25 | client, _ := zmq.NewSocket(zmq.REQ) 26 | defer client.Close() 27 | client.Connect("ipc://frontend.ipc") 28 | 29 | // Send request, get reply 30 | for { 31 | client.SendMessage("HELLO") 32 | reply, _ := client.RecvMessage(0) 33 | if len(reply) == 0 { 34 | break 35 | } 36 | fmt.Println("Client:", strings.Join(reply, "\n\t")) 37 | time.Sleep(time.Second) 38 | } 39 | } 40 | 41 | // Worker using REQ socket to do load-balancing 42 | // 43 | func worker_task() { 44 | worker, _ := zmq.NewSocket(zmq.REQ) 45 | defer worker.Close() 46 | worker.Connect("ipc://backend.ipc") 47 | 48 | // Tell broker we're ready for work 49 | worker.SendMessage(WORKER_READY) 50 | 51 | // Process messages as they arrive 52 | for { 53 | msg, e := worker.RecvMessage(0) 54 | if e != nil { 55 | break // Interrupted 56 | } 57 | msg[len(msg)-1] = "OK" 58 | worker.SendMessage(msg) 59 | } 60 | } 61 | 62 | // Our load-balancer structure, passed to reactor handlers 63 | type lbbroker_t struct { 64 | frontend *zmq.Socket // Listen to clients 65 | backend *zmq.Socket // Listen to workers 66 | workers []string // List of ready workers 67 | reactor *zmq.Reactor 68 | } 69 | 70 | // In the reactor design, each time a message arrives on a socket, the 71 | // reactor passes it to a handler function. We have two handlers; one 72 | // for the frontend, one for the backend: 73 | 74 | // Handle input from client, on frontend 75 | func handle_frontend(lbbroker *lbbroker_t) error { 76 | 77 | // Get client request, route to first available worker 78 | msg, err := lbbroker.frontend.RecvMessage(0) 79 | if err != nil { 80 | return err 81 | } 82 | lbbroker.backend.SendMessage(lbbroker.workers[0], "", msg) 83 | lbbroker.workers = lbbroker.workers[1:] 84 | 85 | // Cancel reader on frontend if we went from 1 to 0 workers 86 | if len(lbbroker.workers) == 0 { 87 | lbbroker.reactor.RemoveSocket(lbbroker.frontend) 88 | } 89 | return nil 90 | } 91 | 92 | // Handle input from worker, on backend 93 | func handle_backend(lbbroker *lbbroker_t) error { 94 | // Use worker identity for load-balancing 95 | msg, err := lbbroker.backend.RecvMessage(0) 96 | if err != nil { 97 | return err 98 | } 99 | identity, msg := unwrap(msg) 100 | lbbroker.workers = append(lbbroker.workers, identity) 101 | 102 | // Enable reader on frontend if we went from 0 to 1 workers 103 | if len(lbbroker.workers) == 1 { 104 | lbbroker.reactor.AddSocket(lbbroker.frontend, zmq.POLLIN, 105 | func(e zmq.State) error { return handle_frontend(lbbroker) }) 106 | } 107 | 108 | // Forward message to client if it's not a READY 109 | if msg[0] != WORKER_READY { 110 | lbbroker.frontend.SendMessage(msg) 111 | } 112 | 113 | return nil 114 | } 115 | 116 | // Now we come to the main task. This has the identical functionality to 117 | // the previous lbbroker example but uses higher level functions to read 118 | // and send messages: 119 | 120 | func main() { 121 | lbbroker := &lbbroker_t{} 122 | lbbroker.frontend, _ = zmq.NewSocket(zmq.ROUTER) 123 | lbbroker.backend, _ = zmq.NewSocket(zmq.ROUTER) 124 | defer lbbroker.frontend.Close() 125 | defer lbbroker.backend.Close() 126 | lbbroker.frontend.Bind("ipc://frontend.ipc") 127 | lbbroker.backend.Bind("ipc://backend.ipc") 128 | 129 | for client_nbr := 0; client_nbr < NBR_CLIENTS; client_nbr++ { 130 | go client_task() 131 | } 132 | for worker_nbr := 0; worker_nbr < NBR_WORKERS; worker_nbr++ { 133 | go worker_task() 134 | } 135 | 136 | // Queue of available workers 137 | lbbroker.workers = make([]string, 0, 10) 138 | 139 | // Prepare reactor and fire it up 140 | lbbroker.reactor = zmq.NewReactor() 141 | lbbroker.reactor.AddSocket(lbbroker.backend, zmq.POLLIN, 142 | func(e zmq.State) error { return handle_backend(lbbroker) }) 143 | lbbroker.reactor.Run(-1) 144 | } 145 | 146 | // Pops frame off front of message and returns it as 'head' 147 | // If next frame is empty, pops that empty frame. 148 | // Return remaining frames of message as 'tail' 149 | func unwrap(msg []string) (head string, tail []string) { 150 | head = msg[0] 151 | if len(msg) > 1 && msg[1] == "" { 152 | tail = msg[2:] 153 | } else { 154 | tail = msg[1:] 155 | } 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /examples/lpclient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Lazy Pirate client. 3 | // Use zmq_poll to do a safe request-reply 4 | // To run, start lpserver and then randomly kill/restart it 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | const ( 18 | REQUEST_TIMEOUT = 2500 * time.Millisecond // msecs, (> 1000!) 19 | REQUEST_RETRIES = 3 // Before we abandon 20 | SERVER_ENDPOINT = "tcp://localhost:5555" 21 | ) 22 | 23 | func main() { 24 | fmt.Println("I: connecting to server...") 25 | client, err := zmq.NewSocket(zmq.REQ) 26 | if err != nil { 27 | panic(err) 28 | } 29 | client.Connect(SERVER_ENDPOINT) 30 | 31 | poller := zmq.NewPoller() 32 | poller.Add(client, zmq.POLLIN) 33 | 34 | sequence := 0 35 | retries_left := REQUEST_RETRIES 36 | for retries_left > 0 { 37 | // We send a request, then we work to get a reply 38 | sequence++ 39 | client.SendMessage(sequence) 40 | 41 | for expect_reply := true; expect_reply; { 42 | // Poll socket for a reply, with timeout 43 | sockets, err := poller.Poll(REQUEST_TIMEOUT) 44 | if err != nil { 45 | break // Interrupted 46 | } 47 | 48 | // Here we process a server reply and exit our loop if the 49 | // reply is valid. If we didn't a reply we close the client 50 | // socket and resend the request. We try a number of times 51 | // before finally abandoning: 52 | 53 | if len(sockets) > 0 { 54 | // We got a reply from the server, must match sequence 55 | reply, err := client.RecvMessage(0) 56 | if err != nil { 57 | break // Interrupted 58 | } 59 | seq, _ := strconv.Atoi(reply[0]) 60 | if seq == sequence { 61 | fmt.Printf("I: server replied OK (%s)\n", reply[0]) 62 | retries_left = REQUEST_RETRIES 63 | expect_reply = false 64 | } else { 65 | fmt.Printf("E: malformed reply from server: %s\n", reply) 66 | } 67 | } else { 68 | retries_left-- 69 | if retries_left == 0 { 70 | fmt.Println("E: server seems to be offline, abandoning") 71 | break 72 | } else { 73 | fmt.Println("W: no response from server, retrying...") 74 | // Old socket is confused; close it and open a new one 75 | client.Close() 76 | client, _ = zmq.NewSocket(zmq.REQ) 77 | client.Connect(SERVER_ENDPOINT) 78 | // Recreate poller for new client 79 | poller = zmq.NewPoller() 80 | poller.Add(client, zmq.POLLIN) 81 | // Send request again, on new socket 82 | client.SendMessage(sequence) 83 | } 84 | } 85 | } 86 | } 87 | client.Close() 88 | } 89 | -------------------------------------------------------------------------------- /examples/lpserver.go: -------------------------------------------------------------------------------- 1 | // 2 | // Lazy Pirate server. 3 | // Binds REQ socket to tcp://*:5555 4 | // Like hwserver except: 5 | // - echoes request as-is 6 | // - randomly runs slowly, or exits to simulate a crash. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | zmq "github.com/pebbe/zmq4" 13 | 14 | "fmt" 15 | "math/rand" 16 | "time" 17 | ) 18 | 19 | func main() { 20 | rand.Seed(time.Now().UnixNano()) 21 | 22 | server, _ := zmq.NewSocket(zmq.REP) 23 | defer server.Close() 24 | server.Bind("tcp://*:5555") 25 | 26 | for cycles := 0; true; { 27 | cycles++ 28 | 29 | // Simulate various problems, after a few cycles 30 | if cycles > 3 && rand.Intn(3) == 0 { 31 | fmt.Println("I: simulating a crash") 32 | break 33 | } else if cycles > 3 && rand.Intn(3) == 0 { 34 | fmt.Println("I: simulating CPU overload") 35 | time.Sleep(2 * time.Second) 36 | } 37 | 38 | request, _ := server.RecvMessage(0) 39 | fmt.Printf("I: normal request (%s)\n", request) 40 | time.Sleep(time.Second) // Do some heavy work 41 | server.SendMessage(request) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/lvcache.go: -------------------------------------------------------------------------------- 1 | // 2 | // Last value cache 3 | // Uses XPUB subscription messages to re-send data 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | frontend, _ := zmq.NewSocket(zmq.SUB) 17 | frontend.Bind("tcp://*:5557") 18 | backend, _ := zmq.NewSocket(zmq.XPUB) 19 | backend.Bind("tcp://*:5558") 20 | 21 | // Subscribe to every single topic from publisher 22 | frontend.SetSubscribe("") 23 | 24 | // Store last instance of each topic in a cache 25 | cache := make(map[string]string) 26 | 27 | // We route topic updates from frontend to backend, and 28 | // we handle subscriptions by sending whatever we cached, 29 | // if anything: 30 | poller := zmq.NewPoller() 31 | poller.Add(frontend, zmq.POLLIN) 32 | poller.Add(backend, zmq.POLLIN) 33 | LOOP: 34 | for { 35 | polled, err := poller.Poll(1000 * time.Millisecond) 36 | if err != nil { 37 | break // Interrupted 38 | } 39 | 40 | for _, item := range polled { 41 | switch socket := item.Socket; socket { 42 | case frontend: 43 | // Any new topic data we cache and then forward 44 | msg, err := frontend.RecvMessage(0) 45 | if err != nil { 46 | break LOOP 47 | } 48 | cache[msg[0]] = msg[1] 49 | backend.SendMessage(msg) 50 | case backend: 51 | // When we get a new subscription we pull data from the cache: 52 | msg, err := backend.RecvMessage(0) 53 | if err != nil { 54 | break LOOP 55 | } 56 | frame := msg[0] 57 | // Event is one byte 0=unsub or 1=sub, followed by topic 58 | if frame[0] == 1 { 59 | topic := frame[1:] 60 | fmt.Println("Sending cached topic", topic) 61 | previous, ok := cache[topic] 62 | if ok { 63 | backend.SendMessage(topic, previous) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/mdapi/const.go: -------------------------------------------------------------------------------- 1 | // Majordomo Protocol Client and Worker API. 2 | // Implements the MDP/Worker spec at http://rfc.zeromq.org/spec:7. 3 | package mdapi 4 | 5 | const ( 6 | // This is the version of MDP/Client we implement 7 | MDPC_CLIENT = "MDPC01" 8 | 9 | // This is the version of MDP/Worker we implement 10 | MDPW_WORKER = "MDPW01" 11 | ) 12 | 13 | const ( 14 | // MDP/Server commands, as strings 15 | MDPW_READY = string(iota + 1) 16 | MDPW_REQUEST 17 | MDPW_REPLY 18 | MDPW_HEARTBEAT 19 | MDPW_DISCONNECT 20 | ) 21 | 22 | var ( 23 | MDPS_COMMANDS = map[string]string{ 24 | MDPW_READY: "READY", 25 | MDPW_REQUEST: "REQUEST", 26 | MDPW_REPLY: "REPLY", 27 | MDPW_HEARTBEAT: "HEARTBEAT", 28 | MDPW_DISCONNECT: "DISCONNECT", 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /examples/mdclient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Majordomo Protocol client example. 3 | // Uses the mdcli API to hide all MDP aspects 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/pebbe/zmq4/examples/mdapi" 10 | 11 | "fmt" 12 | "log" 13 | "os" 14 | ) 15 | 16 | func main() { 17 | var verbose bool 18 | if len(os.Args) > 1 && os.Args[1] == "-v" { 19 | verbose = true 20 | } 21 | session, _ := mdapi.NewMdcli("tcp://localhost:5555", verbose) 22 | 23 | count := 0 24 | for ; count < 100000; count++ { 25 | _, err := session.Send("echo", "Hello world") 26 | if err != nil { 27 | log.Println(err) 28 | break // Interrupt or failure 29 | } 30 | } 31 | fmt.Printf("%d requests/replies processed\n", count) 32 | } 33 | -------------------------------------------------------------------------------- /examples/mdclient2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Majordomo Protocol client example - asynchronous. 3 | // Uses the mdcli API to hide all MDP aspects 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/pebbe/zmq4/examples/mdapi" 10 | 11 | "fmt" 12 | "log" 13 | "os" 14 | ) 15 | 16 | func main() { 17 | var verbose bool 18 | if len(os.Args) > 1 && os.Args[1] == "-v" { 19 | verbose = true 20 | } 21 | session, _ := mdapi.NewMdcli2("tcp://localhost:5555", verbose) 22 | 23 | var count int 24 | for count = 0; count < 100000; count++ { 25 | err := session.Send("echo", "Hello world") 26 | if err != nil { 27 | log.Println("Send:", err) 28 | break 29 | } 30 | } 31 | for count = 0; count < 100000; count++ { 32 | _, err := session.Recv() 33 | if err != nil { 34 | log.Println("Recv:", err) 35 | break 36 | } 37 | } 38 | fmt.Printf("%d replies received\n", count) 39 | } 40 | -------------------------------------------------------------------------------- /examples/mdworker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Majordomo Protocol worker example. 3 | // Uses the mdwrk API to hide all MDP aspects 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/pebbe/zmq4/examples/mdapi" 10 | 11 | "log" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | var verbose bool 17 | if len(os.Args) > 1 && os.Args[1] == "-v" { 18 | verbose = true 19 | } 20 | session, _ := mdapi.NewMdwrk("tcp://localhost:5555", "echo", verbose) 21 | 22 | var err error 23 | var request, reply []string 24 | for { 25 | request, err = session.Recv(reply) 26 | if err != nil { 27 | break // Worker was interrupted 28 | } 29 | reply = request // Echo is complex... :-) 30 | } 31 | log.Println(err) 32 | } 33 | -------------------------------------------------------------------------------- /examples/mmiecho.go: -------------------------------------------------------------------------------- 1 | // 2 | // MMI echo query example. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/pebbe/zmq4/examples/mdapi" 9 | 10 | "fmt" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | var verbose bool 16 | if len(os.Args) > 1 && os.Args[1] == "-v" { 17 | verbose = true 18 | } 19 | session, _ := mdapi.NewMdcli("tcp://localhost:5555", verbose) 20 | 21 | // This is the service we want to look up 22 | request := "echo" 23 | 24 | // This is the service we send our request to 25 | reply, err := session.Send("mmi.service", request) 26 | 27 | if err == nil { 28 | fmt.Println("Lookup echo service:", reply[0]) 29 | } else { 30 | fmt.Println("E: no response from broker, make sure it's running") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/msgqueue.go: -------------------------------------------------------------------------------- 1 | // 2 | // Simple message queuing broker. 3 | // Same as request-reply broker but using QUEUE device 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "log" 12 | ) 13 | 14 | func main() { 15 | var err error 16 | 17 | // Socket facing clients 18 | frontend, _ := zmq.NewSocket(zmq.ROUTER) 19 | defer frontend.Close() 20 | err = frontend.Bind("tcp://*:5559") 21 | if err != nil { 22 | log.Fatalln("Binding frontend:", err) 23 | } 24 | 25 | // Socket facing services 26 | backend, _ := zmq.NewSocket(zmq.DEALER) 27 | defer backend.Close() 28 | err = backend.Bind("tcp://*:5560") 29 | if err != nil { 30 | log.Fatalln("Binding backend:", err) 31 | } 32 | 33 | // Start the proxy 34 | err = zmq.Proxy(frontend, backend, nil) 35 | log.Fatalln("Proxy interrupted:", err) 36 | } 37 | -------------------------------------------------------------------------------- /examples/mspoller.go: -------------------------------------------------------------------------------- 1 | // 2 | // Reading from multiple sockets. 3 | // This version uses zmq.Poll() 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | ) 13 | 14 | func main() { 15 | 16 | // Connect to task ventilator 17 | receiver, _ := zmq.NewSocket(zmq.PULL) 18 | defer receiver.Close() 19 | receiver.Connect("tcp://localhost:5557") 20 | 21 | // Connect to weather server 22 | subscriber, _ := zmq.NewSocket(zmq.SUB) 23 | defer subscriber.Close() 24 | subscriber.Connect("tcp://localhost:5556") 25 | subscriber.SetSubscribe("10001 ") 26 | 27 | // Initialize poll set 28 | poller := zmq.NewPoller() 29 | poller.Add(receiver, zmq.POLLIN) 30 | poller.Add(subscriber, zmq.POLLIN) 31 | // Process messages from both sockets 32 | for { 33 | sockets, _ := poller.Poll(-1) 34 | for _, socket := range sockets { 35 | switch s := socket.Socket; s { 36 | case receiver: 37 | task, _ := s.Recv(0) 38 | // Process task 39 | fmt.Println("Got task:", task) 40 | case subscriber: 41 | update, _ := s.Recv(0) 42 | // Process weather update 43 | fmt.Println("Got weather update:", update) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/msreader.go: -------------------------------------------------------------------------------- 1 | // 2 | // Reading from multiple sockets. 3 | // This version uses a simple recv loop 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | 17 | // Connect to task ventilator 18 | receiver, _ := zmq.NewSocket(zmq.PULL) 19 | defer receiver.Close() 20 | receiver.Connect("tcp://localhost:5557") 21 | 22 | // Connect to weather server 23 | subscriber, _ := zmq.NewSocket(zmq.SUB) 24 | defer subscriber.Close() 25 | subscriber.Connect("tcp://localhost:5556") 26 | subscriber.SetSubscribe("10001 ") 27 | 28 | // Process messages from both sockets 29 | // We prioritize traffic from the task ventilator 30 | for { 31 | 32 | // Process any waiting tasks 33 | for { 34 | task, err := receiver.Recv(zmq.DONTWAIT) 35 | if err != nil { 36 | break 37 | } 38 | // process task 39 | fmt.Println("Got task:", task) 40 | } 41 | 42 | // Process any waiting weather updates 43 | for { 44 | udate, err := subscriber.Recv(zmq.DONTWAIT) 45 | if err != nil { 46 | break 47 | } 48 | // process weather update 49 | fmt.Println("Got weather update:", udate) 50 | } 51 | 52 | // No activity, so sleep for 1 msec 53 | time.Sleep(time.Millisecond) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/mtrelay.go: -------------------------------------------------------------------------------- 1 | // 2 | // Multithreaded relay. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | ) 12 | 13 | func step1() { 14 | // Connect to step2 and tell it we're ready 15 | xmitter, _ := zmq.NewSocket(zmq.PAIR) 16 | defer xmitter.Close() 17 | xmitter.Connect("inproc://step2") 18 | fmt.Println("Step 1 ready, signaling step 2") 19 | xmitter.Send("READY", 0) 20 | } 21 | 22 | func step2() { 23 | // Bind inproc socket before starting step1 24 | receiver, _ := zmq.NewSocket(zmq.PAIR) 25 | defer receiver.Close() 26 | receiver.Bind("inproc://step2") 27 | go step1() 28 | 29 | // Wait for signal and pass it on 30 | receiver.Recv(0) 31 | 32 | // Connect to step3 and tell it we're ready 33 | xmitter, _ := zmq.NewSocket(zmq.PAIR) 34 | defer xmitter.Close() 35 | xmitter.Connect("inproc://step3") 36 | fmt.Println("Step 2 ready, signaling step 3") 37 | xmitter.Send("READY", 0) 38 | } 39 | 40 | func main() { 41 | 42 | // Bind inproc socket before starting step2 43 | receiver, _ := zmq.NewSocket(zmq.PAIR) 44 | defer receiver.Close() 45 | receiver.Bind("inproc://step3") 46 | go step2() 47 | 48 | // Wait for signal 49 | receiver.Recv(0) 50 | 51 | fmt.Println("Test successful!") 52 | } 53 | -------------------------------------------------------------------------------- /examples/mtserver.go: -------------------------------------------------------------------------------- 1 | // 2 | // Multithreaded Hello World server. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "log" 12 | "time" 13 | ) 14 | 15 | func worker_routine() { 16 | // Socket to talk to dispatcher 17 | receiver, _ := zmq.NewSocket(zmq.REP) 18 | defer receiver.Close() 19 | receiver.Connect("inproc://workers") 20 | 21 | for { 22 | msg, e := receiver.Recv(0) 23 | if e != nil { 24 | break 25 | } 26 | fmt.Println("Received request: [" + msg + "]") 27 | 28 | // Do some 'work' 29 | time.Sleep(time.Second) 30 | 31 | // Send reply back to client 32 | receiver.Send("World", 0) 33 | } 34 | } 35 | 36 | func main() { 37 | // Socket to talk to clients 38 | clients, _ := zmq.NewSocket(zmq.ROUTER) 39 | defer clients.Close() 40 | clients.Bind("tcp://*:5555") 41 | 42 | // Socket to talk to workers 43 | workers, _ := zmq.NewSocket(zmq.DEALER) 44 | defer workers.Close() 45 | workers.Bind("inproc://workers") 46 | 47 | // Launch pool of worker goroutines 48 | for thread_nbr := 0; thread_nbr < 5; thread_nbr++ { 49 | go worker_routine() 50 | } 51 | // Connect work threads to client threads via a queue proxy 52 | err := zmq.Proxy(clients, workers, nil) 53 | log.Fatalln("Proxy interrupted:", err) 54 | } 55 | -------------------------------------------------------------------------------- /examples/pathopub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Pathological publisher 3 | // Sends out 1,000 topics and then one random update per second 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "math/rand" 13 | "os" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | publisher, _ := zmq.NewSocket(zmq.PUB) 19 | if len(os.Args) == 2 { 20 | publisher.Connect(os.Args[1]) 21 | } else { 22 | publisher.Bind("tcp://*:5556") 23 | } 24 | 25 | // Ensure subscriber connection has time to complete 26 | time.Sleep(time.Second) 27 | 28 | // Send out all 1,000 topic messages 29 | for topic_nbr := 0; topic_nbr < 1000; topic_nbr++ { 30 | _, err := publisher.SendMessage(fmt.Sprintf("%03d", topic_nbr), "Save Roger") 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | } 35 | // Send one random update per second 36 | rand.Seed(time.Now().UnixNano()) 37 | for { 38 | time.Sleep(time.Second) 39 | _, err := publisher.SendMessage(fmt.Sprintf("%03d", rand.Intn(1000)), "Off with his head!") 40 | if err != nil { 41 | fmt.Println(err) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/pathosub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Pathological subscriber 3 | // Subscribes to one random topic and prints received messages 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "math/rand" 13 | "os" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | subscriber, _ := zmq.NewSocket(zmq.SUB) 19 | if len(os.Args) == 2 { 20 | subscriber.Connect(os.Args[1]) 21 | } else { 22 | subscriber.Connect("tcp://localhost:5556") 23 | } 24 | 25 | rand.Seed(time.Now().UnixNano()) 26 | subscription := fmt.Sprintf("%03d", rand.Intn(1000)) 27 | subscriber.SetSubscribe(subscription) 28 | 29 | for { 30 | msg, err := subscriber.RecvMessage(0) 31 | if err != nil { 32 | break 33 | } 34 | topic := msg[0] 35 | data := msg[1] 36 | if topic != subscription { 37 | panic("topic != subscription") 38 | } 39 | fmt.Println(data) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/peering1.go: -------------------------------------------------------------------------------- 1 | // 2 | // Broker peering simulation (part 1). 3 | // Prototypes the state flow 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "math/rand" 13 | "os" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | // First argument is this broker's name 19 | // Other arguments are our peers' names 20 | // 21 | if len(os.Args) < 2 { 22 | fmt.Println("syntax: peering1 me {you}...") 23 | os.Exit(1) 24 | } 25 | self := os.Args[1] 26 | fmt.Printf("I: preparing broker at %s...\n", self) 27 | rand.Seed(time.Now().UnixNano()) 28 | 29 | // Bind state backend to endpoint 30 | statebe, _ := zmq.NewSocket(zmq.PUB) 31 | defer statebe.Close() 32 | statebe.Bind("ipc://" + self + "-state.ipc") 33 | 34 | // Connect statefe to all peers 35 | statefe, _ := zmq.NewSocket(zmq.SUB) 36 | defer statefe.Close() 37 | statefe.SetSubscribe("") 38 | for _, peer := range os.Args[2:] { 39 | fmt.Printf("I: connecting to state backend at '%s'\n", peer) 40 | statefe.Connect("ipc://" + peer + "-state.ipc") 41 | } 42 | 43 | // The main loop sends out status messages to peers, and collects 44 | // status messages back from peers. The zmq_poll timeout defines 45 | // our own heartbeat: 46 | 47 | poller := zmq.NewPoller() 48 | poller.Add(statefe, zmq.POLLIN) 49 | for { 50 | // Poll for activity, or 1 second timeout 51 | sockets, err := poller.Poll(time.Second) 52 | if err != nil { 53 | break 54 | } 55 | 56 | // Handle incoming status messages 57 | if len(sockets) == 1 { 58 | msg, _ := statefe.RecvMessage(0) 59 | peer_name := msg[0] 60 | available := msg[1] 61 | fmt.Printf("%s - %s workers free\n", peer_name, available) 62 | } else { 63 | statebe.SendMessage(self, rand.Intn(10)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/ppworker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Paranoid Pirate worker. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "math/rand" 12 | "time" 13 | ) 14 | 15 | const ( 16 | HEARTBEAT_LIVENESS = 3 // 3-5 is reasonable 17 | HEARTBEAT_INTERVAL = 1000 * time.Millisecond // msecs 18 | INTERVAL_INIT = 1000 * time.Millisecond // Initial reconnect 19 | INTERVAL_MAX = 32000 * time.Millisecond // After exponential backoff 20 | 21 | // Paranoid Pirate Protocol constants 22 | PPP_READY = "\001" // Signals worker is ready 23 | PPP_HEARTBEAT = "\002" // Signals worker heartbeat 24 | ) 25 | 26 | // Helper function that returns a new configured socket 27 | // connected to the Paranoid Pirate queue 28 | 29 | func s_worker_socket() (*zmq.Socket, *zmq.Poller) { 30 | worker, _ := zmq.NewSocket(zmq.DEALER) 31 | worker.Connect("tcp://localhost:5556") 32 | 33 | // Tell queue we're ready for work 34 | fmt.Println("I: worker ready") 35 | worker.Send(PPP_READY, 0) 36 | 37 | poller := zmq.NewPoller() 38 | poller.Add(worker, zmq.POLLIN) 39 | 40 | return worker, poller 41 | } 42 | 43 | // We have a single task, which implements the worker side of the 44 | // Paranoid Pirate Protocol (PPP). The interesting parts here are 45 | // the heartbeating, which lets the worker detect if the queue has 46 | // died, and vice-versa: 47 | 48 | func main() { 49 | worker, poller := s_worker_socket() 50 | 51 | // If liveness hits zero, queue is considered disconnected 52 | liveness := HEARTBEAT_LIVENESS 53 | interval := INTERVAL_INIT 54 | 55 | // Send out heartbeats at regular intervals 56 | heartbeat_at := time.Tick(HEARTBEAT_INTERVAL) 57 | 58 | rand.Seed(time.Now().UnixNano()) 59 | for cycles := 0; true; { 60 | sockets, err := poller.Poll(HEARTBEAT_INTERVAL) 61 | if err != nil { 62 | break // Interrupted 63 | } 64 | 65 | if len(sockets) == 1 { 66 | // Get message 67 | // - 3-part envelope + content -> request 68 | // - 1-part HEARTBEAT -> heartbeat 69 | msg, err := worker.RecvMessage(0) 70 | if err != nil { 71 | break // Interrupted 72 | } 73 | 74 | // To test the robustness of the queue implementation we // 75 | // simulate various typical problems, such as the worker 76 | // crashing, or running very slowly. We do this after a few 77 | // cycles so that the architecture can get up and running 78 | // first: 79 | if len(msg) == 3 { 80 | cycles++ 81 | if cycles > 3 && rand.Intn(5) == 0 { 82 | fmt.Println("I: simulating a crash") 83 | break 84 | } else if cycles > 3 && rand.Intn(5) == 0 { 85 | fmt.Println("I: simulating CPU overload") 86 | time.Sleep(3 * time.Second) 87 | } 88 | fmt.Println("I: normal reply") 89 | worker.SendMessage(msg) 90 | liveness = HEARTBEAT_LIVENESS 91 | time.Sleep(time.Second) // Do some heavy work 92 | } else if len(msg) == 1 { 93 | // When we get a heartbeat message from the queue, it means the 94 | // queue was (recently) alive, so reset our liveness indicator: 95 | if msg[0] == PPP_HEARTBEAT { 96 | liveness = HEARTBEAT_LIVENESS 97 | } else { 98 | fmt.Printf("E: invalid message: %q\n", msg) 99 | } 100 | } else { 101 | fmt.Printf("E: invalid message: %q\n", msg) 102 | } 103 | interval = INTERVAL_INIT 104 | } else { 105 | // If the queue hasn't sent us heartbeats in a while, destroy the 106 | // socket and reconnect. This is the simplest most brutal way of 107 | // discarding any messages we might have sent in the meantime:// 108 | liveness-- 109 | if liveness == 0 { 110 | fmt.Println("W: heartbeat failure, can't reach queue") 111 | fmt.Println("W: reconnecting in", interval) 112 | time.Sleep(interval) 113 | 114 | if interval < INTERVAL_MAX { 115 | interval = 2 * interval 116 | } 117 | worker, poller = s_worker_socket() 118 | liveness = HEARTBEAT_LIVENESS 119 | } 120 | } 121 | 122 | // Send heartbeat to queue if it's time 123 | select { 124 | case <-heartbeat_at: 125 | fmt.Println("I: worker heartbeat") 126 | worker.Send(PPP_HEARTBEAT, 0) 127 | default: 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /examples/psenvpub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Pubsub envelope publisher. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "time" 11 | ) 12 | 13 | func main() { 14 | // Prepare our publisher 15 | publisher, _ := zmq.NewSocket(zmq.PUB) 16 | defer publisher.Close() 17 | publisher.Bind("tcp://*:5563") 18 | 19 | for { 20 | // Write two messages, each with an envelope and content 21 | publisher.Send("A", zmq.SNDMORE) 22 | publisher.Send("We don't want to see this", 0) 23 | publisher.Send("B", zmq.SNDMORE) 24 | publisher.Send("We would like to see this", 0) 25 | time.Sleep(time.Second) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/psenvsub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Pubsub envelope subscriber. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | ) 12 | 13 | func main() { 14 | // Prepare our subscriber 15 | subscriber, _ := zmq.NewSocket(zmq.SUB) 16 | defer subscriber.Close() 17 | subscriber.Connect("tcp://localhost:5563") 18 | subscriber.SetSubscribe("B") 19 | 20 | for { 21 | // Read envelope with address 22 | address, _ := subscriber.Recv(0) 23 | // Read message contents 24 | contents, _ := subscriber.Recv(0) 25 | fmt.Printf("[%s] %s\n", address, contents) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/rrbroker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Simple request-reply broker. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | ) 10 | 11 | func main() { 12 | // Prepare our sockets 13 | frontend, _ := zmq.NewSocket(zmq.ROUTER) 14 | defer frontend.Close() 15 | backend, _ := zmq.NewSocket(zmq.DEALER) 16 | defer backend.Close() 17 | frontend.Bind("tcp://*:5559") 18 | backend.Bind("tcp://*:5560") 19 | 20 | // Initialize poll set 21 | poller := zmq.NewPoller() 22 | poller.Add(frontend, zmq.POLLIN) 23 | poller.Add(backend, zmq.POLLIN) 24 | 25 | // Switch messages between sockets 26 | for { 27 | sockets, _ := poller.Poll(-1) 28 | for _, socket := range sockets { 29 | switch s := socket.Socket; s { 30 | case frontend: 31 | for { 32 | msg, _ := s.Recv(0) 33 | if more, _ := s.GetRcvmore(); more { 34 | backend.Send(msg, zmq.SNDMORE) 35 | } else { 36 | backend.Send(msg, 0) 37 | break 38 | } 39 | } 40 | case backend: 41 | for { 42 | msg, _ := s.Recv(0) 43 | if more, _ := s.GetRcvmore(); more { 44 | frontend.Send(msg, zmq.SNDMORE) 45 | } else { 46 | frontend.Send(msg, 0) 47 | break 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/rrclient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Request-reply client. 3 | // Connects REQ socket to tcp://localhost:5559 4 | // Sends "Hello" to server, expects "World" back 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | ) 14 | 15 | func main() { 16 | requester, _ := zmq.NewSocket(zmq.REQ) 17 | defer requester.Close() 18 | requester.Connect("tcp://localhost:5559") 19 | 20 | for request := 0; request < 10; request++ { 21 | requester.Send("Hello", 0) 22 | reply, _ := requester.Recv(0) 23 | fmt.Printf("Received reply %d [%s]\n", request, reply) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/rrworker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Hello World worker. 3 | // Connects REP socket to tcp://*:5560 4 | // Expects "Hello" from client, replies with "World" 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | // Socket to talk to clients 18 | responder, _ := zmq.NewSocket(zmq.REP) 19 | defer responder.Close() 20 | responder.Connect("tcp://localhost:5560") 21 | 22 | for { 23 | // Wait for next request from client 24 | request, _ := responder.Recv(0) 25 | fmt.Printf("Received request: [%s]\n", request) 26 | 27 | // Do some 'work' 28 | time.Sleep(time.Second) 29 | 30 | // Send reply back to client 31 | responder.Send("World", 0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/rtdealer.go: -------------------------------------------------------------------------------- 1 | // 2 | // ROUTER-to-DEALER example. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "math/rand" 12 | "time" 13 | ) 14 | 15 | const ( 16 | NBR_WORKERS = 10 17 | ) 18 | 19 | func worker_task() { 20 | worker, _ := zmq.NewSocket(zmq.DEALER) 21 | defer worker.Close() 22 | set_id(worker) // Set a printable identity 23 | worker.Connect("tcp://localhost:5671") 24 | 25 | total := 0 26 | for { 27 | // Tell the broker we're ready for work 28 | worker.Send("", zmq.SNDMORE) 29 | worker.Send("Hi Boss", 0) 30 | 31 | // Get workload from broker, until finished 32 | worker.Recv(0) // Envelope delimiter 33 | workload, _ := worker.Recv(0) 34 | if workload == "Fired!" { 35 | fmt.Printf("Completed: %d tasks\n", total) 36 | break 37 | } 38 | total++ 39 | 40 | // Do some random work 41 | time.Sleep(time.Duration(rand.Intn(500)+1) * time.Millisecond) 42 | } 43 | } 44 | 45 | func main() { 46 | broker, _ := zmq.NewSocket(zmq.ROUTER) 47 | defer broker.Close() 48 | 49 | broker.Bind("tcp://*:5671") 50 | rand.Seed(time.Now().UnixNano()) 51 | 52 | for worker_nbr := 0; worker_nbr < NBR_WORKERS; worker_nbr++ { 53 | go worker_task() 54 | } 55 | // Run for five seconds and then tell workers to end 56 | start_time := time.Now() 57 | workers_fired := 0 58 | for { 59 | // Next message gives us least recently used worker 60 | identity, _ := broker.Recv(0) 61 | broker.Send(identity, zmq.SNDMORE) 62 | broker.Recv(0) // Envelope delimiter 63 | broker.Recv(0) // Response from worker 64 | broker.Send("", zmq.SNDMORE) 65 | 66 | // Encourage workers until it's time to fire them 67 | if time.Since(start_time) < 5*time.Second { 68 | broker.Send("Work harder", 0) 69 | } else { 70 | broker.Send("Fired!", 0) 71 | workers_fired++ 72 | if workers_fired == NBR_WORKERS { 73 | break 74 | } 75 | } 76 | } 77 | 78 | time.Sleep(time.Second) 79 | } 80 | 81 | func set_id(soc *zmq.Socket) { 82 | identity := fmt.Sprintf("%04X-%04X", rand.Intn(0x10000), rand.Intn(0x10000)) 83 | soc.SetIdentity(identity) 84 | } 85 | -------------------------------------------------------------------------------- /examples/rtreq.go: -------------------------------------------------------------------------------- 1 | // 2 | // ROUTER-to-REQ example. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "math/rand" 12 | "time" 13 | ) 14 | 15 | const ( 16 | NBR_WORKERS = 10 17 | ) 18 | 19 | func worker_task() { 20 | worker, _ := zmq.NewSocket(zmq.REQ) 21 | defer worker.Close() 22 | set_id(worker) 23 | worker.Connect("tcp://localhost:5671") 24 | 25 | total := 0 26 | for { 27 | // Tell the broker we're ready for work 28 | worker.Send("Hi Boss", 0) 29 | 30 | // Get workload from broker, until finished 31 | workload, _ := worker.Recv(0) 32 | if workload == "Fired!" { 33 | fmt.Printf("Completed: %d tasks\n", total) 34 | break 35 | } 36 | total++ 37 | 38 | // Do some random work 39 | time.Sleep(time.Duration(rand.Intn(500)+1) * time.Millisecond) 40 | } 41 | } 42 | 43 | func main() { 44 | broker, _ := zmq.NewSocket(zmq.ROUTER) 45 | defer broker.Close() 46 | 47 | broker.Bind("tcp://*:5671") 48 | rand.Seed(time.Now().UnixNano()) 49 | 50 | for worker_nbr := 0; worker_nbr < NBR_WORKERS; worker_nbr++ { 51 | go worker_task() 52 | } 53 | // Run for five seconds and then tell workers to end 54 | start_time := time.Now() 55 | workers_fired := 0 56 | for { 57 | // Next message gives us least recently used worker 58 | identity, _ := broker.Recv(0) 59 | broker.Send(identity, zmq.SNDMORE) 60 | broker.Recv(0) // Envelope delimiter 61 | broker.Recv(0) // Response from worker 62 | broker.Send("", zmq.SNDMORE) 63 | 64 | // Encourage workers until it's time to fire them 65 | if time.Since(start_time) < 5*time.Second { 66 | broker.Send("Work harder", 0) 67 | } else { 68 | broker.Send("Fired!", 0) 69 | workers_fired++ 70 | if workers_fired == NBR_WORKERS { 71 | break 72 | } 73 | } 74 | } 75 | 76 | time.Sleep(time.Second) 77 | } 78 | 79 | func set_id(soc *zmq.Socket) { 80 | identity := fmt.Sprintf("%04X-%04X", rand.Intn(0x10000), rand.Intn(0x10000)) 81 | soc.SetIdentity(identity) 82 | } 83 | -------------------------------------------------------------------------------- /examples/spqueue.go: -------------------------------------------------------------------------------- 1 | // 2 | // Simple Pirate broker. 3 | // This is identical to load-balancing pattern, with no reliability 4 | // mechanisms. It depends on the client for recovery. Runs forever. 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | ) 12 | 13 | const ( 14 | WORKER_READY = "\001" // Signals worker is ready 15 | ) 16 | 17 | func main() { 18 | frontend, _ := zmq.NewSocket(zmq.ROUTER) 19 | backend, _ := zmq.NewSocket(zmq.ROUTER) 20 | defer frontend.Close() 21 | defer backend.Close() 22 | frontend.Bind("tcp://*:5555") // For clients 23 | backend.Bind("tcp://*:5556") // For workers 24 | 25 | // Queue of available workers 26 | workers := make([]string, 0) 27 | 28 | poller1 := zmq.NewPoller() 29 | poller1.Add(backend, zmq.POLLIN) 30 | poller2 := zmq.NewPoller() 31 | poller2.Add(backend, zmq.POLLIN) 32 | poller2.Add(frontend, zmq.POLLIN) 33 | 34 | // The body of this example is exactly the same as lbbroker2. 35 | LOOP: 36 | for { 37 | // Poll frontend only if we have available workers 38 | var sockets []zmq.Polled 39 | var err error 40 | if len(workers) > 0 { 41 | sockets, err = poller2.Poll(-1) 42 | } else { 43 | sockets, err = poller1.Poll(-1) 44 | } 45 | if err != nil { 46 | break // Interrupted 47 | } 48 | for _, socket := range sockets { 49 | switch s := socket.Socket; s { 50 | case backend: // Handle worker activity on backend 51 | // Use worker identity for load-balancing 52 | msg, err := s.RecvMessage(0) 53 | if err != nil { 54 | break LOOP // Interrupted 55 | } 56 | var identity string 57 | identity, msg = unwrap(msg) 58 | workers = append(workers, identity) 59 | 60 | // Forward message to client if it's not a READY 61 | if msg[0] != WORKER_READY { 62 | frontend.SendMessage(msg) 63 | } 64 | 65 | case frontend: 66 | // Get client request, route to first available worker 67 | msg, err := s.RecvMessage(0) 68 | if err == nil { 69 | backend.SendMessage(workers[0], "", msg) 70 | workers = workers[1:] 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | // Pops frame off front of message and returns it as 'head' 78 | // If next frame is empty, pops that empty frame. 79 | // Return remaining frames of message as 'tail' 80 | func unwrap(msg []string) (head string, tail []string) { 81 | head = msg[0] 82 | if len(msg) > 1 && msg[1] == "" { 83 | tail = msg[2:] 84 | } else { 85 | tail = msg[1:] 86 | } 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /examples/spworker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Simple Pirate worker. 3 | // Connects REQ socket to tcp://*:5556 4 | // Implements worker part of load-balancing 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "math/rand" 14 | "time" 15 | ) 16 | 17 | const ( 18 | WORKER_READY = "\001" // Signals worker is ready 19 | ) 20 | 21 | func main() { 22 | worker, _ := zmq.NewSocket(zmq.REQ) 23 | defer worker.Close() 24 | 25 | // Set random identity to make tracing easier 26 | rand.Seed(time.Now().UnixNano()) 27 | identity := fmt.Sprintf("%04X-%04X", rand.Intn(0x10000), rand.Intn(0x10000)) 28 | worker.SetIdentity(identity) 29 | worker.Connect("tcp://localhost:5556") 30 | 31 | // Tell broker we're ready for work 32 | fmt.Printf("I: (%s) worker ready\n", identity) 33 | worker.Send(WORKER_READY, 0) 34 | 35 | for cycles := 0; true; { 36 | msg, err := worker.RecvMessage(0) 37 | if err != nil { 38 | break // Interrupted 39 | } 40 | 41 | // Simulate various problems, after a few cycles 42 | cycles++ 43 | if cycles > 3 && rand.Intn(5) == 0 { 44 | fmt.Printf("I: (%s) simulating a crash\n", identity) 45 | break 46 | } else if cycles > 3 && rand.Intn(5) == 0 { 47 | fmt.Printf("I: (%s) simulating CPU overload\n", identity) 48 | time.Sleep(3 * time.Second) 49 | } 50 | 51 | fmt.Printf("I: (%s) normal reply\n", identity) 52 | time.Sleep(time.Second) // Do some heavy work 53 | worker.SendMessage(msg) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/suisnail.go: -------------------------------------------------------------------------------- 1 | // 2 | // Suicidal Snail 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "log" 12 | "math/rand" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | // This is our subscriber. It connects to the publisher and subscribes to 18 | // everything. It sleeps for a short time between messages to simulate doing 19 | // too much work. If a message is more than 1 second late, it croaks: 20 | 21 | const ( 22 | MAX_ALLOWED_DELAY = 1000 * time.Millisecond 23 | ) 24 | 25 | func subscriber(pipe chan<- string) { 26 | // Subscribe to everything 27 | subscriber, _ := zmq.NewSocket(zmq.SUB) 28 | subscriber.SetSubscribe("") 29 | subscriber.Connect("tcp://localhost:5556") 30 | defer subscriber.Close() 31 | 32 | // Get and process messages 33 | for { 34 | msg, _ := subscriber.RecvMessage(0) 35 | i, _ := strconv.Atoi(msg[0]) 36 | clock := time.Unix(int64(i), 0) 37 | fmt.Println(clock) 38 | 39 | // Suicide snail logic 40 | if time.Now().After(clock.Add(MAX_ALLOWED_DELAY)) { 41 | log.Println("E: subscriber cannot keep up, aborting") 42 | break 43 | } 44 | // Work for 1 msec plus some random additional time 45 | time.Sleep(time.Duration(1 + rand.Intn(2))) 46 | } 47 | pipe <- "gone and died" 48 | } 49 | 50 | // This is our publisher task. It publishes a time-stamped message to its 51 | // PUB socket every 1 msec: 52 | 53 | func publisher(pipe <-chan string) { 54 | // Prepare publisher 55 | publisher, _ := zmq.NewSocket(zmq.PUB) 56 | publisher.Bind("tcp://*:5556") 57 | defer publisher.Close() 58 | 59 | LOOP: 60 | for { 61 | // Send current clock (msecs) to subscribers 62 | publisher.SendMessage(time.Now().Unix()) 63 | select { 64 | case <-pipe: 65 | break LOOP 66 | default: 67 | } 68 | time.Sleep(time.Millisecond) 69 | } 70 | } 71 | 72 | // The main task simply starts a client, and a server, and then 73 | // waits for the client to signal that it has died: 74 | 75 | func main() { 76 | pubpipe := make(chan string) 77 | subpipe := make(chan string) 78 | go publisher(pubpipe) 79 | go subscriber(subpipe) 80 | <-subpipe 81 | pubpipe <- "break" 82 | time.Sleep(100 * time.Millisecond) 83 | } 84 | -------------------------------------------------------------------------------- /examples/sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Starting subscribers..." 3 | for i in 1 2 3 4 5 6 7 8 9 10 4 | do 5 | ./syncsub & 6 | done 7 | echo "Starting publisher..." 8 | ./syncpub 9 | # have all subscribers finished? 10 | sleep 1 11 | echo Still running instances of syncsub: 12 | ps | grep syncsub 13 | -------------------------------------------------------------------------------- /examples/syncpub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Synchronized publisher. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | ) 12 | 13 | const ( 14 | // We wait for 10 subscribers 15 | SUBSCRIBERS_EXPECTED = 10 16 | ) 17 | 18 | func main() { 19 | 20 | ctx, _ := zmq.NewContext() 21 | defer ctx.Term() 22 | 23 | // Socket to talk to clients 24 | publisher, _ := ctx.NewSocket(zmq.PUB) 25 | defer publisher.Close() 26 | publisher.SetSndhwm(1100000) 27 | publisher.Bind("tcp://*:5561") 28 | 29 | // Socket to receive signals 30 | syncservice, _ := ctx.NewSocket(zmq.REP) 31 | defer syncservice.Close() 32 | syncservice.Bind("tcp://*:5562") 33 | 34 | // Get synchronization from subscribers 35 | fmt.Println("Waiting for subscribers") 36 | for subscribers := 0; subscribers < SUBSCRIBERS_EXPECTED; subscribers++ { 37 | // - wait for synchronization request 38 | syncservice.Recv(0) 39 | // - send synchronization reply 40 | syncservice.Send("", 0) 41 | } 42 | // Now broadcast exactly 1M updates followed by END 43 | fmt.Println("Broadcasting messages") 44 | for update_nbr := 0; update_nbr < 1000000; update_nbr++ { 45 | publisher.Send("Rhubarb", 0) 46 | } 47 | 48 | publisher.Send("END", 0) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /examples/syncsub.go: -------------------------------------------------------------------------------- 1 | // 2 | // Synchronized subscriber 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "log" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | 17 | // First, connect our subscriber socket 18 | subscriber, _ := zmq.NewSocket(zmq.SUB) 19 | defer subscriber.Close() 20 | subscriber.Connect("tcp://localhost:5561") 21 | subscriber.SetSubscribe("") 22 | 23 | // 0MQ is so fast, we need to wait a while... 24 | time.Sleep(time.Second) 25 | 26 | // Second, synchronize with publisher 27 | syncclient, _ := zmq.NewSocket(zmq.REQ) 28 | defer syncclient.Close() 29 | syncclient.Connect("tcp://localhost:5562") 30 | 31 | // - send a synchronization request 32 | syncclient.Send("", 0) 33 | 34 | // - wait for synchronization reply 35 | syncclient.Recv(0) 36 | 37 | // Third, get our updates and report how many we got 38 | update_nbr := 0 39 | for { 40 | msg, e := subscriber.Recv(0) 41 | if e != nil { 42 | log.Println(e) 43 | break 44 | } 45 | if msg == "END" { 46 | break 47 | } 48 | update_nbr++ 49 | } 50 | fmt.Printf("Received %d updates\n", update_nbr) 51 | } 52 | -------------------------------------------------------------------------------- /examples/tasksink.go: -------------------------------------------------------------------------------- 1 | // 2 | // Task sink. 3 | // Binds PULL socket to tcp://localhost:5558 4 | // Collects results from workers via that socket 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | // Prepare our socket 18 | receiver, _ := zmq.NewSocket(zmq.PULL) 19 | defer receiver.Close() 20 | receiver.Bind("tcp://*:5558") 21 | 22 | // Wait for start of batch 23 | receiver.Recv(0) 24 | 25 | // Start our clock now 26 | start_time := time.Now() 27 | 28 | // Process 100 confirmations 29 | for task_nbr := 0; task_nbr < 100; task_nbr++ { 30 | receiver.Recv(0) 31 | if task_nbr%10 == 0 { 32 | fmt.Print(":") 33 | } else { 34 | fmt.Print(".") 35 | } 36 | } 37 | 38 | // Calculate and report duration of batch 39 | fmt.Println("\nTotal elapsed time:", time.Since(start_time)) 40 | } 41 | -------------------------------------------------------------------------------- /examples/tasksink2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Task sink - design 2. 3 | // Adds pub-sub flow to send kill signal to workers 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | // Socket to receive messages on 17 | receiver, _ := zmq.NewSocket(zmq.PULL) 18 | defer receiver.Close() 19 | receiver.Bind("tcp://*:5558") 20 | 21 | // Socket for worker control 22 | controller, _ := zmq.NewSocket(zmq.PUB) 23 | defer controller.Close() 24 | controller.Bind("tcp://*:5559") 25 | 26 | // Wait for start of batch 27 | receiver.Recv(0) 28 | 29 | // Start our clock now 30 | start_time := time.Now() 31 | 32 | // Process 100 confirmations 33 | for task_nbr := 0; task_nbr < 100; task_nbr++ { 34 | receiver.Recv(0) 35 | if task_nbr%10 == 0 { 36 | fmt.Print(":") 37 | } else { 38 | fmt.Print(".") 39 | } 40 | } 41 | fmt.Println("\nTotal elapsed time:", time.Since(start_time)) 42 | 43 | // Send kill signal to workers 44 | controller.Send("KILL", 0) 45 | 46 | // Finished 47 | time.Sleep(time.Second) // Give 0MQ time to deliver 48 | } 49 | -------------------------------------------------------------------------------- /examples/taskvent.go: -------------------------------------------------------------------------------- 1 | // 2 | // Task ventilator. 3 | // Binds PUSH socket to tcp://localhost:5557 4 | // Sends batch of tasks to workers via that socket 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "math/rand" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | // Socket to send messages on 19 | sender, _ := zmq.NewSocket(zmq.PUSH) 20 | defer sender.Close() 21 | sender.Bind("tcp://*:5557") 22 | 23 | // Socket to send start of batch message on 24 | sink, _ := zmq.NewSocket(zmq.PUSH) 25 | defer sink.Close() 26 | sink.Connect("tcp://localhost:5558") 27 | 28 | fmt.Print("Press Enter when the workers are ready: ") 29 | var line string 30 | fmt.Scanln(&line) 31 | fmt.Println("Sending tasks to workers...") 32 | 33 | // The first message is "0" and signals start of batch 34 | sink.Send("0", 0) 35 | 36 | // Initialize random number generator 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | // Send 100 tasks 40 | total_msec := 0 41 | for task_nbr := 0; task_nbr < 100; task_nbr++ { 42 | // Random workload from 1 to 100msecs 43 | workload := rand.Intn(100) + 1 44 | total_msec += workload 45 | s := fmt.Sprintf("%d", workload) 46 | sender.Send(s, 0) 47 | } 48 | fmt.Println("Total expected cost:", time.Duration(total_msec)*time.Millisecond) 49 | time.Sleep(time.Second) // Give 0MQ time to deliver 50 | 51 | } 52 | -------------------------------------------------------------------------------- /examples/taskwork.go: -------------------------------------------------------------------------------- 1 | // 2 | // Task worker. 3 | // Connects PULL socket to tcp://localhost:5557 4 | // Collects workloads from ventilator via that socket 5 | // Connects PUSH socket to tcp://localhost:5558 6 | // Sends results to sink via that socket 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | zmq "github.com/pebbe/zmq4" 13 | 14 | "fmt" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | func main() { 20 | // Socket to receive messages on 21 | receiver, _ := zmq.NewSocket(zmq.PULL) 22 | defer receiver.Close() 23 | receiver.Connect("tcp://localhost:5557") 24 | 25 | // Socket to send messages to 26 | sender, _ := zmq.NewSocket(zmq.PUSH) 27 | defer sender.Close() 28 | sender.Connect("tcp://localhost:5558") 29 | 30 | // Process tasks forever 31 | for { 32 | s, _ := receiver.Recv(0) 33 | 34 | // Simple progress indicator for the viewer 35 | fmt.Print(s + ".") 36 | 37 | // Do the work 38 | msec, _ := strconv.Atoi(s) 39 | time.Sleep(time.Duration(msec) * time.Millisecond) 40 | 41 | // Send results to sink 42 | sender.Send("", 0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/taskwork2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Task worker - design 2. 3 | // Adds pub-sub flow to receive and respond to kill signal 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | // Socket to receive messages on 18 | receiver, _ := zmq.NewSocket(zmq.PULL) 19 | defer receiver.Close() 20 | receiver.Connect("tcp://localhost:5557") 21 | 22 | // Socket to send messages to 23 | sender, _ := zmq.NewSocket(zmq.PUSH) 24 | defer sender.Close() 25 | sender.Connect("tcp://localhost:5558") 26 | 27 | // Socket for control input 28 | controller, _ := zmq.NewSocket(zmq.SUB) 29 | defer controller.Close() 30 | controller.Connect("tcp://localhost:5559") 31 | controller.SetSubscribe("") 32 | 33 | // Process messages from receiver and controller 34 | poller := zmq.NewPoller() 35 | poller.Add(receiver, zmq.POLLIN) 36 | poller.Add(controller, zmq.POLLIN) 37 | // Process messages from both sockets 38 | LOOP: 39 | for { 40 | sockets, _ := poller.Poll(-1) 41 | for _, socket := range sockets { 42 | switch s := socket.Socket; s { 43 | case receiver: 44 | msg, _ := s.Recv(0) 45 | 46 | // Do the work 47 | t, _ := strconv.Atoi(msg) 48 | time.Sleep(time.Duration(t) * time.Millisecond) 49 | 50 | // Send results to sink 51 | sender.Send(msg, 0) 52 | 53 | // Simple progress indicator for the viewer 54 | fmt.Printf(".") 55 | case controller: 56 | // Any controller command acts as 'KILL' 57 | break LOOP // Exit loop 58 | } 59 | } 60 | } 61 | fmt.Println() 62 | } 63 | -------------------------------------------------------------------------------- /examples/ticlient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Titanic client example. 3 | // Implements client side of http://rfc.zeromq.org/spec:9 4 | 5 | // To use this example: 6 | // 1. start mdbroker 7 | // 2. start mdworker 8 | // 3. start titanic 9 | // 4. run ticlient 10 | 11 | package main 12 | 13 | import ( 14 | "github.com/pebbe/zmq4/examples/mdapi" 15 | 16 | "errors" 17 | "fmt" 18 | "os" 19 | "time" 20 | ) 21 | 22 | // Calls a TSP service 23 | // Returns response if successful (status code 200 OK), else NULL 24 | // 25 | func ServiceCall(session *mdapi.Mdcli, service string, request ...string) (reply []string, err error) { 26 | reply = []string{} 27 | msg, err := session.Send(service, request...) 28 | if err == nil { 29 | switch status := msg[0]; status { 30 | case "200": 31 | reply = msg[1:] 32 | return 33 | case "400": 34 | fmt.Println("E: client fatal error, aborting") 35 | os.Exit(1) 36 | case "500": 37 | fmt.Println("E: server fatal error, aborting") 38 | os.Exit(1) 39 | } 40 | } else { 41 | fmt.Println("E: " + err.Error()) 42 | os.Exit(0) 43 | } 44 | 45 | err = errors.New("Didn't succeed") 46 | return // Didn't succeed, don't care why not 47 | } 48 | 49 | // The main task tests our service call by sending an echo request: 50 | 51 | func main() { 52 | var verbose bool 53 | if len(os.Args) > 1 && os.Args[1] == "-v" { 54 | verbose = true 55 | } 56 | session, _ := mdapi.NewMdcli("tcp://localhost:5555", verbose) 57 | 58 | // 1. Send 'echo' request to Titanic 59 | reply, err := ServiceCall(session, "titanic.request", "echo", "Hello world") 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | 65 | var uuid string 66 | if err == nil { 67 | uuid = reply[0] 68 | fmt.Println("I: request UUID", uuid) 69 | } 70 | 71 | time.Sleep(100 * time.Millisecond) 72 | 73 | // 2. Wait until we get a reply 74 | for { 75 | reply, err := ServiceCall(session, "titanic.reply", uuid) 76 | if err == nil { 77 | fmt.Println("Reply:", reply[0]) 78 | 79 | // 3. Close request 80 | ServiceCall(session, "titanic.close", uuid) 81 | break 82 | } else { 83 | fmt.Println("I: no reply yet, trying again...") 84 | time.Sleep(5 * time.Second) // Try again in 5 seconds 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/tripping.go: -------------------------------------------------------------------------------- 1 | // 2 | // Round-trip demonstrator. 3 | // 4 | // While this example runs in a single process, that is just to make 5 | // it easier to start and stop the example. The client task signals to 6 | // main when it's ready. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | zmq "github.com/pebbe/zmq4" 13 | 14 | "fmt" 15 | "time" 16 | ) 17 | 18 | func ClientTask(pipe chan<- bool) { 19 | client, _ := zmq.NewSocket(zmq.DEALER) 20 | client.Connect("tcp://localhost:5555") 21 | fmt.Println("Setting up test...") 22 | time.Sleep(100 * time.Millisecond) 23 | 24 | fmt.Println("Synchronous round-trip test...") 25 | start := time.Now() 26 | var requests int 27 | for requests = 0; requests < 10000; requests++ { 28 | client.Send("hello", 0) 29 | client.Recv(0) 30 | } 31 | fmt.Println(requests, "calls in", time.Since(start)) 32 | 33 | fmt.Println("Asynchronous round-trip test...") 34 | start = time.Now() 35 | for requests = 0; requests < 100000; requests++ { 36 | client.Send("hello", 0) 37 | } 38 | for requests = 0; requests < 100000; requests++ { 39 | client.Recv(0) 40 | } 41 | fmt.Println(requests, "calls in", time.Since(start)) 42 | pipe <- true 43 | } 44 | 45 | // Here is the worker task. All it does is receive a message, and 46 | // bounce it back the way it came: 47 | 48 | func WorkerTask() { 49 | worker, _ := zmq.NewSocket(zmq.DEALER) 50 | worker.Connect("tcp://localhost:5556") 51 | 52 | for { 53 | msg, _ := worker.RecvMessage(0) 54 | worker.SendMessage(msg) 55 | } 56 | } 57 | 58 | // Here is the broker task. It uses the zmq_proxy function to switch 59 | // messages between frontend and backend: 60 | 61 | func BrokerTask() { 62 | // Prepare our sockets 63 | frontend, _ := zmq.NewSocket(zmq.DEALER) 64 | frontend.Bind("tcp://*:5555") 65 | backend, _ := zmq.NewSocket(zmq.DEALER) 66 | backend.Bind("tcp://*:5556") 67 | zmq.Proxy(frontend, backend, nil) 68 | } 69 | 70 | // Finally, here's the main task, which starts the client, worker, and 71 | // broker, and then runs until the client signals it to stop: 72 | 73 | func main() { 74 | // Create threads 75 | pipe := make(chan bool) 76 | go ClientTask(pipe) 77 | go WorkerTask() 78 | go BrokerTask() 79 | 80 | // Wait for signal on client pipe 81 | <-pipe 82 | } 83 | -------------------------------------------------------------------------------- /examples/udpping1.go: -------------------------------------------------------------------------------- 1 | // 2 | // UDP ping command 3 | // Model 1, does UDP work inline 4 | // 5 | 6 | // this doesn't use ZeroMQ at all 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | const ( 18 | PING_PORT_NUMBER = 9999 19 | PING_MSG_SIZE = 1 20 | PING_INTERVAL = 1000 * time.Millisecond // Once per second 21 | ) 22 | 23 | func main() { 24 | 25 | log.SetFlags(log.Lshortfile) 26 | 27 | // Create UDP socket 28 | fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) 29 | if err != nil { 30 | log.Fatalln(err) 31 | } 32 | 33 | // Ask operating system to let us do broadcasts from socket 34 | if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil { 35 | log.Fatalln(err) 36 | } 37 | 38 | // Bind UDP socket to local port so we can receive pings 39 | if err := syscall.Bind(fd, &syscall.SockaddrInet4{Port: PING_PORT_NUMBER, Addr: [4]byte{0, 0, 0, 0}}); err != nil { 40 | log.Fatalln(err) 41 | } 42 | 43 | buffer := make([]byte, PING_MSG_SIZE) 44 | 45 | // We use syscall.Select to wait for activity on the UDP socket. 46 | // We send a beacon once a second, and we collect and report 47 | // beacons that come in from other nodes: 48 | 49 | rfds := &syscall.FdSet{} 50 | timeout := &syscall.Timeval{} 51 | 52 | // Send first ping right away 53 | ping_at := time.Now() 54 | 55 | bcast := &syscall.SockaddrInet4{Port: PING_PORT_NUMBER, Addr: [4]byte{255, 255, 255, 255}} 56 | for { 57 | dur := int64(ping_at.Sub(time.Now()) / time.Microsecond) 58 | if dur < 0 { 59 | dur = 0 60 | } 61 | timeout.Sec, timeout.Usec = dur/1000000, dur%1000000 62 | FD_ZERO(rfds) 63 | FD_SET(rfds, fd) 64 | _, err := syscall.Select(fd+1, rfds, nil, nil, timeout) 65 | if err != nil { 66 | log.Fatalln(err) 67 | } 68 | 69 | // Someone answered our ping 70 | if FD_ISSET(rfds, fd) { 71 | _, addr, err := syscall.Recvfrom(fd, buffer, 0) 72 | if err != nil { 73 | log.Fatalln(err) 74 | } 75 | a := addr.(*syscall.SockaddrInet4) 76 | fmt.Printf("Found peer %v.%v.%v.%v:%v\n", a.Addr[0], a.Addr[1], a.Addr[2], a.Addr[3], a.Port) 77 | } 78 | if time.Now().After(ping_at) { 79 | // Broadcast our beacon 80 | fmt.Println("Pinging peers...") 81 | buffer[0] = '!' 82 | if err := syscall.Sendto(fd, buffer, 0, bcast); err != nil { 83 | log.Fatalln(err) 84 | } 85 | ping_at = time.Now().Add(PING_INTERVAL) 86 | } 87 | } 88 | 89 | } 90 | 91 | func FD_SET(p *syscall.FdSet, i int) { 92 | p.Bits[i/64] |= 1 << uint(i) % 64 93 | } 94 | 95 | func FD_ISSET(p *syscall.FdSet, i int) bool { 96 | return (p.Bits[i/64] & (1 << uint(i) % 64)) != 0 97 | } 98 | 99 | func FD_ZERO(p *syscall.FdSet) { 100 | for i := range p.Bits { 101 | p.Bits[i] = 0 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/udpping2.go: -------------------------------------------------------------------------------- 1 | // 2 | // UDP ping command 3 | // Model 2, uses the GO net library 4 | // 5 | 6 | // this doesn't use ZeroMQ at all 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "log" 13 | "net" 14 | "time" 15 | ) 16 | 17 | const ( 18 | PING_PORT_NUMBER = 9999 19 | PING_MSG_SIZE = 1 20 | PING_INTERVAL = 1000 * time.Millisecond // Once per second 21 | ) 22 | 23 | func main() { 24 | 25 | log.SetFlags(log.Lshortfile) 26 | 27 | // Create UDP socket 28 | bcast := &net.UDPAddr{Port: PING_PORT_NUMBER, IP: net.IPv4bcast} 29 | conn, err := net.ListenUDP("udp", bcast) 30 | if err != nil { 31 | log.Fatalln(err) 32 | } 33 | 34 | buffer := make([]byte, PING_MSG_SIZE) 35 | 36 | // We send a beacon once a second, and we collect and report 37 | // beacons that come in from other nodes: 38 | 39 | // Send first ping right away 40 | ping_at := time.Now() 41 | 42 | for { 43 | if err := conn.SetReadDeadline(ping_at); err != nil { 44 | log.Fatalln(err) 45 | } 46 | 47 | if _, addr, err := conn.ReadFrom(buffer); err == nil { 48 | // Someone answered our ping 49 | fmt.Println("Found peer", addr) 50 | } 51 | 52 | if time.Now().After(ping_at) { 53 | // Broadcast our beacon 54 | fmt.Println("Pinging peers...") 55 | buffer[0] = '!' 56 | if _, err := conn.WriteTo(buffer, bcast); err != nil { 57 | log.Fatalln(err) 58 | } 59 | ping_at = time.Now().Add(PING_INTERVAL) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/udpping3.go: -------------------------------------------------------------------------------- 1 | // 2 | // UDP ping command 3 | // Model 3, uses abstract network interface 4 | // 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/pebbe/zmq4/examples/intface" 10 | 11 | "fmt" 12 | "log" 13 | ) 14 | 15 | func main() { 16 | log.SetFlags(log.Lshortfile) 17 | iface := intface.New() 18 | for { 19 | msg, err := iface.Recv() 20 | if err != nil { 21 | log.Fatalln(err) 22 | } 23 | fmt.Printf("%q\n", msg) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Report 0MQ version. 3 | // 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | ) 12 | 13 | func main() { 14 | major, minor, patch := zmq.Version() 15 | fmt.Printf("Current 0MQ version is %d.%d.%d\n", major, minor, patch) 16 | } 17 | -------------------------------------------------------------------------------- /examples/wuclient.go: -------------------------------------------------------------------------------- 1 | // 2 | // Weather update client. 3 | // Connects SUB socket to tcp://localhost:5556 4 | // Collects weather updates and finds avg temp in zipcode 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "os" 14 | "strconv" 15 | "strings" 16 | ) 17 | 18 | func main() { 19 | // Socket to talk to server 20 | fmt.Println("Collecting updates from weather server...") 21 | subscriber, _ := zmq.NewSocket(zmq.SUB) 22 | defer subscriber.Close() 23 | subscriber.Connect("tcp://localhost:5556") 24 | 25 | // Subscribe to zipcode, default is NYC, 10001 26 | filter := "10001 " 27 | if len(os.Args) > 1 { 28 | filter = os.Args[1] + " " 29 | } 30 | subscriber.SetSubscribe(filter) 31 | 32 | // Process 100 updates 33 | total_temp := 0 34 | update_nbr := 0 35 | for update_nbr < 100 { 36 | msg, _ := subscriber.Recv(0) 37 | 38 | if msgs := strings.Fields(msg); len(msgs) > 1 { 39 | if temperature, err := strconv.Atoi(msgs[1]); err == nil { 40 | total_temp += temperature 41 | update_nbr++ 42 | } 43 | } 44 | } 45 | fmt.Printf("Average temperature for zipcode '%s' was %dF \n\n", strings.TrimSpace(filter), total_temp/update_nbr) 46 | } 47 | -------------------------------------------------------------------------------- /examples/wuproxy.go: -------------------------------------------------------------------------------- 1 | // 2 | // Weather proxy device. 3 | // 4 | // NOT TESTED 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "log" 13 | ) 14 | 15 | func main() { 16 | // This is where the weather server sits 17 | frontend, _ := zmq.NewSocket(zmq.XSUB) 18 | defer frontend.Close() 19 | frontend.Connect("tcp://192.168.55.210:5556") 20 | 21 | // This is our public endpoint for subscribers 22 | backend, _ := zmq.NewSocket(zmq.XPUB) 23 | defer backend.Close() 24 | backend.Bind("tcp://10.1.1.0:8100") 25 | 26 | // Run the proxy until the user interrupts us 27 | err := zmq.Proxy(frontend, backend, nil) 28 | log.Fatalln("Proxy interrupted:", err) 29 | } 30 | -------------------------------------------------------------------------------- /examples/wuserver.go: -------------------------------------------------------------------------------- 1 | // 2 | // Weather update server. 3 | // Binds PUB socket to tcp://*:5556 4 | // Publishes random weather updates 5 | // 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "math/rand" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | 19 | // Prepare our publisher 20 | publisher, _ := zmq.NewSocket(zmq.PUB) 21 | defer publisher.Close() 22 | publisher.Bind("tcp://*:5556") 23 | publisher.Bind("ipc://weather.ipc") 24 | 25 | // Initialize random number generator 26 | rand.Seed(time.Now().UnixNano()) 27 | 28 | // loop for a while apparently 29 | for { 30 | 31 | // Get values that will fool the boss 32 | zipcode := rand.Intn(100000) 33 | temperature := rand.Intn(215) - 80 34 | relhumidity := rand.Intn(50) + 10 35 | 36 | // Send message to all subscribers 37 | msg := fmt.Sprintf("%05d %d %d", zipcode, temperature, relhumidity) 38 | publisher.Send(msg, 0) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples_security/Makefile: -------------------------------------------------------------------------------- 1 | 2 | % : %.go 3 | go build $< 4 | 5 | all: grasslands strawhouse woodhouse stonehouse ironhouse 6 | -------------------------------------------------------------------------------- /examples_security/README.md: -------------------------------------------------------------------------------- 1 | These are Go versions of the C examples described in 2 | [Using ZeroMQ Security (part 2)](http://hintjens.com/blog:49) by Pieter Hintjens. 3 | Those C examples use the [zauth module](http://czmq.zeromq.org/manual:zauth) 4 | in the [czmq library](http://czmq.zeromq.org). 5 | 6 | There are some differences: 7 | 8 | * The zauth module doesn't handle domains. The Go version does. 9 | * The zauth module handles files with usernames/passwords and directories with certificates. 10 | The Go version just uses maps of usernames/passwords and lists of public user keys. 11 | -------------------------------------------------------------------------------- /examples_security/grasslands.go: -------------------------------------------------------------------------------- 1 | // The Grasslands Pattern 2 | // 3 | // The Classic ZeroMQ model, plain text with no protection at all. 4 | 5 | package main 6 | 7 | import ( 8 | zmq "github.com/pebbe/zmq4" 9 | 10 | "fmt" 11 | "log" 12 | "runtime" 13 | ) 14 | 15 | func main() { 16 | 17 | // Create and bind server socket 18 | server, err := zmq.NewSocket(zmq.PUSH) 19 | checkErr(err) 20 | checkErr(server.Bind("tcp://*:9000")) 21 | 22 | // Create and connect client socket 23 | client, err := zmq.NewSocket(zmq.PULL) 24 | checkErr(err) 25 | checkErr(client.Connect("tcp://127.0.0.1:9000")) 26 | 27 | // Send a single message from server to client 28 | _, err = server.Send("Hello", 0) 29 | checkErr(err) 30 | message, err := client.Recv(0) 31 | checkErr(err) 32 | if message != "Hello" { 33 | log.Fatalln(message, "!= Hello") 34 | } 35 | 36 | fmt.Println("Grasslands test OK") 37 | } 38 | 39 | func checkErr(err error) { 40 | if err != nil { 41 | log.SetFlags(0) 42 | _, filename, lineno, ok := runtime.Caller(1) 43 | if ok { 44 | log.Fatalf("%v:%v: %v", filename, lineno, err) 45 | } else { 46 | log.Fatalln(err) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples_security/ironhouse.go: -------------------------------------------------------------------------------- 1 | // The Ironhouse Pattern 2 | // 3 | // Security doesn't get any stronger than this. An attacker is going to 4 | // have to break into your systems to see data before/after encryption. 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "log" 13 | "runtime" 14 | ) 15 | 16 | func main() { 17 | 18 | // Start authentication engine 19 | zmq.AuthSetVerbose(true) 20 | zmq.AuthStart() 21 | zmq.AuthAllow("domain1", "127.0.0.1/8") 22 | 23 | // We need two certificates, one for the client and one for 24 | // the server. The client must know the server's public key 25 | // to make a CURVE connection. 26 | client_public, client_secret, err := zmq.NewCurveKeypair() 27 | checkErr(err) 28 | server_public, server_secret, err := zmq.NewCurveKeypair() 29 | checkErr(err) 30 | 31 | // Tell authenticator to use this public client key 32 | zmq.AuthCurveAdd("domain1", client_public) 33 | 34 | // Create and bind server socket 35 | server, _ := zmq.NewSocket(zmq.PUSH) 36 | server.ServerAuthCurve("domain1", server_secret) 37 | server.Bind("tcp://*:9000") 38 | 39 | // Create and connect client socket 40 | client, _ := zmq.NewSocket(zmq.PULL) 41 | client.ClientAuthCurve(server_public, client_public, client_secret) 42 | client.Connect("tcp://127.0.0.1:9000") 43 | 44 | // Send a single message from server to client 45 | _, err = server.Send("Hello", 0) 46 | checkErr(err) 47 | message, err := client.Recv(0) 48 | checkErr(err) 49 | if message != "Hello" { 50 | log.Fatalln(message, "!= Hello") 51 | } 52 | 53 | zmq.AuthStop() 54 | 55 | fmt.Println("Ironhouse test OK") 56 | 57 | } 58 | 59 | func checkErr(err error) { 60 | if err != nil { 61 | log.SetFlags(0) 62 | _, filename, lineno, ok := runtime.Caller(1) 63 | if ok { 64 | log.Fatalf("%v:%v: %v", filename, lineno, err) 65 | } else { 66 | log.Fatalln(err) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples_security/stonehouse.go: -------------------------------------------------------------------------------- 1 | // The Stonehouse Pattern 2 | // 3 | // Where we allow any clients to connect, but we promise clients 4 | // that we are who we claim to be, and our conversations won't be 5 | // tampered with or modified, or spied on. 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "log" 14 | "runtime" 15 | ) 16 | 17 | func main() { 18 | 19 | // Start authentication engine 20 | zmq.AuthSetVerbose(true) 21 | zmq.AuthStart() 22 | zmq.AuthAllow("domain1", "127.0.0.1") 23 | 24 | // Tell the authenticator to allow any CURVE requests for this domain 25 | zmq.AuthCurveAdd("domain1", zmq.CURVE_ALLOW_ANY) 26 | 27 | // We need two certificates, one for the client and one for 28 | // the server. The client must know the server's public key 29 | // to make a CURVE connection. 30 | client_public, client_secret, err := zmq.NewCurveKeypair() 31 | checkErr(err) 32 | server_public, server_secret, err := zmq.NewCurveKeypair() 33 | checkErr(err) 34 | 35 | // Create and bind server socket 36 | server, _ := zmq.NewSocket(zmq.PUSH) 37 | server.ServerAuthCurve("domain1", server_secret) 38 | server.Bind("tcp://*:9000") 39 | 40 | // Create and connect client socket 41 | client, _ := zmq.NewSocket(zmq.PULL) 42 | client.ClientAuthCurve(server_public, client_public, client_secret) 43 | client.Connect("tcp://127.0.0.1:9000") 44 | 45 | // Send a single message from server to client 46 | _, err = server.Send("Hello", 0) 47 | checkErr(err) 48 | message, err := client.Recv(0) 49 | checkErr(err) 50 | if message != "Hello" { 51 | log.Fatalln(message, "!= Hello") 52 | } 53 | 54 | zmq.AuthStop() 55 | 56 | fmt.Println("Stonehouse test OK") 57 | 58 | } 59 | 60 | func checkErr(err error) { 61 | if err != nil { 62 | log.SetFlags(0) 63 | _, filename, lineno, ok := runtime.Caller(1) 64 | if ok { 65 | log.Fatalf("%v:%v: %v", filename, lineno, err) 66 | } else { 67 | log.Fatalln(err) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples_security/strawhouse.go: -------------------------------------------------------------------------------- 1 | // The Strawhouse Pattern 2 | // 3 | // We allow or deny clients according to their IP address. It may keep 4 | // spammers and idiots away, but won't stop a real attacker for more 5 | // than a heartbeat. 6 | 7 | package main 8 | 9 | import ( 10 | zmq "github.com/pebbe/zmq4" 11 | 12 | "fmt" 13 | "log" 14 | "runtime" 15 | ) 16 | 17 | func main() { 18 | 19 | // Get some indication of what the authenticator is deciding 20 | zmq.AuthSetVerbose(true) 21 | 22 | // Start the authentication engine. This engine 23 | // allows or denies incoming connections (talking to the libzmq 24 | // core over a protocol called ZAP). 25 | zmq.AuthStart() 26 | 27 | // Whitelist our address; any other address will be rejected 28 | zmq.AuthAllow("domain1", "127.0.0.1") 29 | 30 | // Create and bind server socket 31 | server, err := zmq.NewSocket(zmq.PUSH) 32 | checkErr(err) 33 | server.ServerAuthNull("domain1") 34 | server.Bind("tcp://*:9000") 35 | 36 | // Create and connect client socket 37 | client, err := zmq.NewSocket(zmq.PULL) 38 | checkErr(err) 39 | checkErr(client.Connect("tcp://127.0.0.1:9000")) 40 | 41 | // Send a single message from server to client 42 | _, err = server.Send("Hello", 0) 43 | checkErr(err) 44 | message, err := client.Recv(0) 45 | checkErr(err) 46 | if message != "Hello" { 47 | log.Fatalln(message, "!= Hello") 48 | } 49 | 50 | zmq.AuthStop() 51 | 52 | fmt.Println("Strawhouse test OK") 53 | } 54 | 55 | func checkErr(err error) { 56 | if err != nil { 57 | log.SetFlags(0) 58 | _, filename, lineno, ok := runtime.Caller(1) 59 | if ok { 60 | log.Fatalf("%v:%v: %v", filename, lineno, err) 61 | } else { 62 | log.Fatalln(err) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples_security/woodhouse.go: -------------------------------------------------------------------------------- 1 | // The Woodhouse Pattern 2 | // 3 | // It may keep some malicious people out but all it takes is a bit 4 | // of network sniffing, and they'll be able to fake their way in. 5 | 6 | package main 7 | 8 | import ( 9 | zmq "github.com/pebbe/zmq4" 10 | 11 | "fmt" 12 | "log" 13 | "runtime" 14 | ) 15 | 16 | func main() { 17 | 18 | // Start authentication engine 19 | zmq.AuthSetVerbose(true) 20 | zmq.AuthStart() 21 | zmq.AuthAllow("domain1", "127.0.0.1") 22 | 23 | // Tell the authenticator how to handle PLAIN requests 24 | zmq.AuthPlainAdd("domain1", "admin", "secret") 25 | 26 | // Create and bind server socket 27 | server, _ := zmq.NewSocket(zmq.PUSH) 28 | server.ServerAuthPlain("domain1") 29 | server.Bind("tcp://*:9000") 30 | 31 | // Create and connect client socket 32 | client, _ := zmq.NewSocket(zmq.PULL) 33 | client.SetPlainUsername("admin") 34 | client.SetPlainPassword("secret") 35 | client.Connect("tcp://127.0.0.1:9000") 36 | 37 | // Send a single message from server to client 38 | _, err := server.Send("Hello", 0) 39 | checkErr(err) 40 | message, err := client.Recv(0) 41 | checkErr(err) 42 | if message != "Hello" { 43 | log.Fatalln(message, "!= Hello") 44 | } 45 | 46 | zmq.AuthStop() 47 | 48 | fmt.Println("Woodhouse test OK") 49 | 50 | } 51 | 52 | func checkErr(err error) { 53 | if err != nil { 54 | log.SetFlags(0) 55 | _, filename, lineno, ok := runtime.Caller(1) 56 | if ok { 57 | log.Fatalf("%v:%v: %v", filename, lineno, err) 58 | } else { 59 | log.Fatalln(err) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pebbe/zmq4 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /socketevent_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4" 5 | 6 | "fmt" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func rep_socket_monitor(addr string, chMsg chan<- string) { 12 | 13 | defer close(chMsg) 14 | 15 | s, err := zmq.NewSocket(zmq.PAIR) 16 | if err != nil { 17 | chMsg <- fmt.Sprint("NewSocket:", err) 18 | return 19 | } 20 | defer func() { 21 | s.SetLinger(0) 22 | s.Close() 23 | }() 24 | 25 | err = s.Connect(addr) 26 | if err != nil { 27 | chMsg <- fmt.Sprint("s.Connect:", err) 28 | return 29 | } 30 | 31 | for { 32 | a, b, _, err := s.RecvEvent(0) 33 | if err != nil { 34 | chMsg <- fmt.Sprint("s.RecvEvent:", err) 35 | return 36 | } 37 | chMsg <- fmt.Sprint(a, " ", b) 38 | if a == zmq.EVENT_CLOSED { 39 | break 40 | } 41 | } 42 | chMsg <- "Done" 43 | } 44 | 45 | func TestSocketEvent(t *testing.T) { 46 | 47 | var rep *zmq.Socket 48 | defer func() { 49 | if rep != nil { 50 | rep.SetLinger(0) 51 | rep.Close() 52 | } 53 | }() 54 | 55 | // REP socket 56 | rep, err := zmq.NewSocket(zmq.REP) 57 | if err != nil { 58 | t.Fatal("NewSocket:", err) 59 | } 60 | 61 | // REP socket monitor, all events 62 | err = rep.Monitor("inproc://monitor.rep", zmq.EVENT_ALL) 63 | if err != nil { 64 | t.Fatal("rep.Monitor:", err) 65 | } 66 | chMsg := make(chan string, 10) 67 | go rep_socket_monitor("inproc://monitor.rep", chMsg) 68 | time.Sleep(time.Second) 69 | 70 | // Generate an event 71 | err = rep.Bind("tcp://*:9689") 72 | if err != nil { 73 | t.Fatal("rep.Bind:", err) 74 | } 75 | 76 | rep.Close() 77 | rep = nil 78 | 79 | expect := []string{ 80 | "EVENT_LISTENING tcp://0.0.0.0:9689", 81 | "EVENT_CLOSED tcp://0.0.0.0:9689", 82 | "Done", 83 | } 84 | i := 0 85 | for msg := range chMsg { 86 | if i < len(expect) { 87 | if msg != expect[i] { 88 | t.Errorf("Expected message %q, got %q", expect[i], msg) 89 | } 90 | i++ 91 | } else { 92 | t.Errorf("Unexpected message: %q", msg) 93 | } 94 | } 95 | for ; i < len(expect); i++ { 96 | t.Errorf("Expected message %q, got nothing", expect[i]) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /socketget_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | */ 8 | import "C" 9 | 10 | // ZMQ_FD: Retrieve file descriptor associated with the socket 11 | // 12 | // See: http://api.zeromq.org/4-1:zmq-getsockopt#toc9 13 | func (soc *Socket) GetFd() (int, error) { 14 | return soc.getInt(C.ZMQ_FD) 15 | } 16 | -------------------------------------------------------------------------------- /socketget_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package zmq4 4 | 5 | /* 6 | #include 7 | #include 8 | #include "zmq4.h" 9 | */ 10 | import "C" 11 | 12 | // winsock2.h needed for ZeroMQ version 4.3.3 13 | 14 | import ( 15 | "unsafe" 16 | ) 17 | 18 | /* 19 | ZMQ_FD: Retrieve file descriptor associated with the socket 20 | 21 | See: http://api.zeromq.org/4-1:zmq-getsockopt#toc9 22 | */ 23 | func (soc *Socket) GetFd() (uintptr, error) { 24 | value := C.SOCKET(0) 25 | size := C.size_t(unsafe.Sizeof(value)) 26 | var i C.int 27 | var err error 28 | for { 29 | i, err = C.zmq4_getsockopt(soc.soc, C.ZMQ_FD, unsafe.Pointer(&value), &size) 30 | // not really necessary because Windows doesn't have EINTR 31 | if i == 0 || !soc.ctx.retry(err) { 32 | break 33 | } 34 | } 35 | if i != 0 { 36 | return uintptr(0), errget(err) 37 | } 38 | return uintptr(value), nil 39 | } 40 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package zmq4 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | /* 8 | Send multi-part message on socket. 9 | 10 | Any `[]string' or `[][]byte' is split into separate `string's or `[]byte's 11 | 12 | Any other part that isn't a `string' or `[]byte' is converted 13 | to `string' with `fmt.Sprintf("%v", part)'. 14 | 15 | Returns total bytes sent. 16 | */ 17 | func (soc *Socket) SendMessage(parts ...interface{}) (total int, err error) { 18 | return soc.sendMessage(0, parts...) 19 | } 20 | 21 | /* 22 | Like SendMessage(), but adding the DONTWAIT flag. 23 | */ 24 | func (soc *Socket) SendMessageDontwait(parts ...interface{}) (total int, err error) { 25 | return soc.sendMessage(DONTWAIT, parts...) 26 | } 27 | 28 | func (soc *Socket) sendMessage(dontwait Flag, parts ...interface{}) (total int, err error) { 29 | 30 | var last int 31 | PARTS: 32 | for last = len(parts) - 1; last >= 0; last-- { 33 | switch t := parts[last].(type) { 34 | case []string: 35 | if len(t) > 0 { 36 | break PARTS 37 | } 38 | case [][]byte: 39 | if len(t) > 0 { 40 | break PARTS 41 | } 42 | default: 43 | break PARTS 44 | } 45 | } 46 | 47 | opt := SNDMORE | dontwait 48 | for i := 0; i <= last; i++ { 49 | if i == last { 50 | opt = dontwait 51 | } 52 | switch t := parts[i].(type) { 53 | case []string: 54 | opt = SNDMORE | dontwait 55 | n := len(t) - 1 56 | for j, s := range t { 57 | if j == n && i == last { 58 | opt = dontwait 59 | } 60 | c, e := soc.Send(s, opt) 61 | if e == nil { 62 | total += c 63 | } else { 64 | return -1, e 65 | } 66 | } 67 | case [][]byte: 68 | opt = SNDMORE | dontwait 69 | n := len(t) - 1 70 | for j, b := range t { 71 | if j == n && i == last { 72 | opt = dontwait 73 | } 74 | c, e := soc.SendBytes(b, opt) 75 | if e == nil { 76 | total += c 77 | } else { 78 | return -1, e 79 | } 80 | } 81 | case string: 82 | c, e := soc.Send(t, opt) 83 | if e == nil { 84 | total += c 85 | } else { 86 | return -1, e 87 | } 88 | case []byte: 89 | c, e := soc.SendBytes(t, opt) 90 | if e == nil { 91 | total += c 92 | } else { 93 | return -1, e 94 | } 95 | default: 96 | c, e := soc.Send(fmt.Sprintf("%v", t), opt) 97 | if e == nil { 98 | total += c 99 | } else { 100 | return -1, e 101 | } 102 | } 103 | } 104 | return 105 | } 106 | 107 | /* 108 | Receive parts as message from socket. 109 | 110 | Returns last non-nil error code. 111 | */ 112 | func (soc *Socket) RecvMessage(flags Flag) (msg []string, err error) { 113 | msg = make([]string, 0) 114 | for { 115 | s, e := soc.Recv(flags) 116 | if e == nil { 117 | msg = append(msg, s) 118 | } else { 119 | return msg[0:0], e 120 | } 121 | more, e := soc.GetRcvmore() 122 | if e == nil { 123 | if !more { 124 | break 125 | } 126 | } else { 127 | return msg[0:0], e 128 | } 129 | } 130 | return 131 | } 132 | 133 | /* 134 | Receive parts as message from socket. 135 | 136 | Returns last non-nil error code. 137 | */ 138 | func (soc *Socket) RecvMessageBytes(flags Flag) (msg [][]byte, err error) { 139 | msg = make([][]byte, 0) 140 | for { 141 | b, e := soc.RecvBytes(flags) 142 | if e == nil { 143 | msg = append(msg, b) 144 | } else { 145 | return msg[0:0], e 146 | } 147 | more, e := soc.GetRcvmore() 148 | if e == nil { 149 | if !more { 150 | break 151 | } 152 | } else { 153 | return msg[0:0], e 154 | } 155 | } 156 | return 157 | } 158 | 159 | /* 160 | Receive parts as message from socket, including metadata. 161 | 162 | Metadata is picked from the first message part. 163 | 164 | For details about metadata, see RecvWithMetadata(). 165 | 166 | Returns last non-nil error code. 167 | */ 168 | func (soc *Socket) RecvMessageWithMetadata(flags Flag, properties ...string) (msg []string, metadata map[string]string, err error) { 169 | b, p, err := soc.RecvMessageBytesWithMetadata(flags, properties...) 170 | m := make([]string, len(b)) 171 | for i, bt := range b { 172 | m[i] = string(bt) 173 | } 174 | return m, p, err 175 | } 176 | 177 | /* 178 | Receive parts as message from socket, including metadata. 179 | 180 | Metadata is picked from the first message part. 181 | 182 | For details about metadata, see RecvBytesWithMetadata(). 183 | 184 | Returns last non-nil error code. 185 | */ 186 | func (soc *Socket) RecvMessageBytesWithMetadata(flags Flag, properties ...string) (msg [][]byte, metadata map[string]string, err error) { 187 | bb := make([][]byte, 0) 188 | b, p, err := soc.RecvBytesWithMetadata(flags, properties...) 189 | if err != nil { 190 | return bb, p, err 191 | } 192 | for { 193 | bb = append(bb, b) 194 | 195 | var more bool 196 | more, err = soc.GetRcvmore() 197 | if err != nil || !more { 198 | break 199 | } 200 | b, err = soc.RecvBytes(flags) 201 | if err != nil { 202 | break 203 | } 204 | } 205 | return bb, p, err 206 | } 207 | -------------------------------------------------------------------------------- /wrappers_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package zmq4 4 | 5 | /* 6 | 7 | #include 8 | #include 9 | 10 | #if ZMQ_VERSION_MINOR < 2 11 | // Version < 4.2.x 12 | #include 13 | int zmq_curve_public (char *z85_public_key, const char *z85_secret_key); 14 | #endif // Version < 4.2.x 15 | 16 | #if ZMQ_VERSION_MINOR < 1 17 | const char *zmq_msg_gets (zmq_msg_t *msg, const char *property); 18 | #if ZMQ_VERSION_PATCH < 5 19 | // Version < 4.0.5 20 | int zmq_proxy_steerable (const void *frontend, const void *backend, const void *capture, const void *control); 21 | #endif // Version < 4.0.5 22 | #endif // Version == 4.0.x 23 | 24 | int zmq4_bind (void *socket, const char *endpoint) 25 | { 26 | return zmq_bind(socket, endpoint); 27 | } 28 | 29 | int zmq4_close (void *socket) 30 | { 31 | return zmq_close(socket); 32 | } 33 | 34 | int zmq4_connect (void *socket, const char *endpoint) 35 | { 36 | return zmq_connect(socket, endpoint); 37 | } 38 | 39 | int zmq4_ctx_get (void *context, int option_name) 40 | { 41 | return zmq_ctx_get(context, option_name); 42 | } 43 | 44 | void *zmq4_ctx_new () 45 | { 46 | return zmq_ctx_new(); 47 | } 48 | 49 | int zmq4_ctx_set (void *context, int option_name, int option_value) 50 | { 51 | return zmq_ctx_set(context, option_name, option_value); 52 | } 53 | 54 | int zmq4_ctx_term (void *context) 55 | { 56 | return zmq_ctx_term(context); 57 | } 58 | 59 | int zmq4_curve_keypair (char *z85_public_key, char *z85_secret_key) 60 | { 61 | return zmq_curve_keypair(z85_public_key, z85_secret_key); 62 | } 63 | 64 | int zmq4_curve_public (char *z85_public_key, char *z85_secret_key) 65 | { 66 | return zmq_curve_public(z85_public_key, z85_secret_key); 67 | } 68 | 69 | int zmq4_disconnect (void *socket, const char *endpoint) 70 | { 71 | return zmq_disconnect(socket, endpoint); 72 | } 73 | 74 | int zmq4_getsockopt (void *socket, int option_name, void *option_value, size_t *option_len) 75 | { 76 | return zmq_getsockopt(socket, option_name, option_value, option_len); 77 | } 78 | 79 | const char *zmq4_msg_gets (zmq_msg_t *message, const char *property) 80 | { 81 | return zmq_msg_gets(message, property); 82 | } 83 | 84 | int zmq4_msg_recv (zmq_msg_t *msg, void *socket, int flags) 85 | { 86 | return zmq_msg_recv(msg, socket, flags); 87 | } 88 | 89 | int zmq4_poll (zmq_pollitem_t *items, int nitems, long timeout) 90 | { 91 | return zmq_poll(items, nitems, timeout); 92 | } 93 | 94 | int zmq4_proxy (void *frontend, void *backend, void *capture) 95 | { 96 | return zmq_proxy(frontend, backend, capture); 97 | } 98 | 99 | int zmq4_proxy_steerable (void *frontend, void *backend, void *capture, void *control) 100 | { 101 | return zmq_proxy_steerable(frontend, backend, capture, control); 102 | } 103 | 104 | int zmq4_send (void *socket, void *buf, size_t len, int flags) 105 | { 106 | return zmq_send(socket, buf, len, flags); 107 | } 108 | 109 | int zmq4_setsockopt (void *socket, int option_name, const void *option_value, size_t option_len) 110 | { 111 | return zmq_setsockopt(socket, option_name, option_value, option_len); 112 | } 113 | 114 | void *zmq4_socket (void *context, int type) 115 | { 116 | return zmq_socket(context, type); 117 | } 118 | 119 | int zmq4_socket_monitor (void *socket, char *endpoint, int events) 120 | { 121 | return zmq_socket_monitor(socket, endpoint, events); 122 | } 123 | 124 | int zmq4_unbind (void *socket, const char *endpoint) 125 | { 126 | return zmq_unbind(socket, endpoint); 127 | } 128 | 129 | */ 130 | import "C" 131 | -------------------------------------------------------------------------------- /zmq4.h: -------------------------------------------------------------------------------- 1 | #if ZMQ_VERSION_MAJOR != 4 2 | 3 | #error "You need ZeroMQ version 4 to build this" 4 | 5 | #endif 6 | 7 | #if ZMQ_VERSION_MINOR < 1 8 | 9 | #define ZMQ_CONNECT_RID -1 10 | #define ZMQ_GSSAPI -1 11 | #define ZMQ_GSSAPI_PLAINTEXT -1 12 | #define ZMQ_GSSAPI_PRINCIPAL -1 13 | #define ZMQ_GSSAPI_SERVER -1 14 | #define ZMQ_GSSAPI_SERVICE_PRINCIPAL -1 15 | #define ZMQ_HANDSHAKE_IVL -1 16 | #define ZMQ_IPC_FILTER_GID -1 17 | #define ZMQ_IPC_FILTER_PID -1 18 | #define ZMQ_IPC_FILTER_UID -1 19 | #define ZMQ_ROUTER_HANDOVER -1 20 | #define ZMQ_SOCKS_PROXY -1 21 | #define ZMQ_THREAD_PRIORITY -1 22 | #define ZMQ_THREAD_SCHED_POLICY -1 23 | #define ZMQ_TOS -1 24 | #define ZMQ_XPUB_NODROP -1 25 | 26 | #endif 27 | 28 | #if ZMQ_VERSION_MINOR < 2 29 | 30 | #define ZMQ_MAX_MSGSZ -1 31 | 32 | #define ZMQ_BLOCKY -1 33 | #define ZMQ_XPUB_MANUAL -1 34 | #define ZMQ_XPUB_WELCOME_MSG -1 35 | #define ZMQ_STREAM_NOTIFY -1 36 | #define ZMQ_INVERT_MATCHING -1 37 | #define ZMQ_HEARTBEAT_IVL -1 38 | #define ZMQ_HEARTBEAT_TTL -1 39 | #define ZMQ_HEARTBEAT_TIMEOUT -1 40 | #define ZMQ_XPUB_VERBOSER -1 41 | #define ZMQ_CONNECT_TIMEOUT -1 42 | #define ZMQ_TCP_MAXRT -1 43 | #define ZMQ_THREAD_SAFE -1 44 | #define ZMQ_MULTICAST_MAXTPDU -1 45 | #define ZMQ_VMCI_BUFFER_SIZE -1 46 | #define ZMQ_VMCI_BUFFER_MIN_SIZE -1 47 | #define ZMQ_VMCI_BUFFER_MAX_SIZE -1 48 | #define ZMQ_VMCI_CONNECT_TIMEOUT -1 49 | #define ZMQ_USE_FD -1 50 | 51 | #define ZMQ_GROUP_MAX_LENGTH -1 52 | 53 | #define ZMQ_POLLPRI -1 54 | 55 | #endif 56 | 57 | #if ZMQ_VERSION_MINOR < 3 58 | 59 | #define ZMQ_MSG_T_SIZE -1 60 | #define ZMQ_THREAD_AFFINITY_CPU_ADD -1 61 | #define ZMQ_THREAD_AFFINITY_CPU_REMOVE -1 62 | #define ZMQ_THREAD_NAME_PREFIX -1 63 | 64 | #define ZMQ_GSSAPI_PRINCIPAL_NAMETYPE -1 65 | #define ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE -1 66 | #define ZMQ_BINDTODEVICE -1 67 | 68 | #define ZMQ_GSSAPI_NT_HOSTBASED -1 69 | #define ZMQ_GSSAPI_NT_USER_NAME -1 70 | #define ZMQ_GSSAPI_NT_KRB5_PRINCIPAL -1 71 | 72 | #define ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL -1 73 | #define ZMQ_EVENT_HANDSHAKE_SUCCEEDED -1 74 | #define ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL -1 75 | #define ZMQ_EVENT_HANDSHAKE_FAILED_AUTH -1 76 | #define ZMQ_PROTOCOL_ERROR_ZMTP_UNSPECIFIED -1 77 | #define ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND -1 78 | #define ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE -1 79 | #define ZMQ_PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE -1 80 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED -1 81 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE -1 82 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO -1 83 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE -1 84 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR -1 85 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY -1 86 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME -1 87 | #define ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_METADATA -1 88 | #define ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC -1 89 | #define ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH -1 90 | #define ZMQ_PROTOCOL_ERROR_ZAP_UNSPECIFIED -1 91 | #define ZMQ_PROTOCOL_ERROR_ZAP_MALFORMED_REPLY -1 92 | #define ZMQ_PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID -1 93 | #define ZMQ_PROTOCOL_ERROR_ZAP_BAD_VERSION -1 94 | #define ZMQ_PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE -1 95 | #define ZMQ_PROTOCOL_ERROR_ZAP_INVALID_METADATA -1 96 | 97 | #endif 98 | 99 | #ifndef ZMQ_ROUTING_ID 100 | #define ZMQ_ROUTING_ID ZMQ_IDENTITY 101 | #endif 102 | #ifndef ZMQ_CONNECT_ROUTING_ID 103 | #define ZMQ_CONNECT_ROUTING_ID ZMQ_CONNECT_RID 104 | #endif 105 | 106 | int zmq4_bind (void *socket, const char *endpoint); 107 | int zmq4_close (void *socket); 108 | int zmq4_connect (void *socket, const char *endpoint); 109 | int zmq4_ctx_get (void *context, int option_name); 110 | void *zmq4_ctx_new (void); 111 | int zmq4_ctx_set (void *context, int option_name, int option_value); 112 | int zmq4_ctx_term (void *context); 113 | int zmq4_curve_keypair (char *z85_public_key, char *z85_secret_key); 114 | int zmq4_curve_public (char *z85_public_key, char *z85_secret_key); 115 | int zmq4_disconnect (void *socket, const char *endpoint); 116 | int zmq4_getsockopt (void *socket, int option_name, void *option_value, size_t *option_len); 117 | const char *zmq4_msg_gets (zmq_msg_t *message, const char *property); 118 | int zmq4_msg_recv (zmq_msg_t *msg, void *socket, int flags); 119 | int zmq4_poll (zmq_pollitem_t *items, int nitems, long timeout); 120 | int zmq4_proxy (const void *frontend, const void *backend, const void *capture); 121 | int zmq4_proxy_steerable (const void *frontend, const void *backend, const void *capture, const void *control); 122 | int zmq4_send (void *socket, void *buf, size_t len, int flags); 123 | int zmq4_setsockopt (void *socket, int option_name, const void *option_value, size_t option_len); 124 | void *zmq4_socket (void *context, int type); 125 | int zmq4_socket_monitor (void *socket, char *endpoint, int events); 126 | int zmq4_unbind (void *socket, const char *endpoint); 127 | -------------------------------------------------------------------------------- /zmq41_test.go: -------------------------------------------------------------------------------- 1 | package zmq4_test 2 | 3 | import ( 4 | zmq "github.com/pebbe/zmq4" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestRemoteEndpoint(t *testing.T) { 10 | 11 | if _, minor, _ := zmq.Version(); minor < 1 { 12 | t.Skip("RemoteEndpoint not avalable in ZeroMQ versions prior to 4.1.0") 13 | } 14 | 15 | addr := "tcp://127.0.0.1:9560" 16 | peer := "127.0.0.1" 17 | 18 | var rep, req *zmq.Socket 19 | defer func() { 20 | for _, s := range []*zmq.Socket{rep, req} { 21 | if s != nil { 22 | s.SetLinger(0) 23 | s.Close() 24 | } 25 | } 26 | }() 27 | 28 | rep, err := zmq.NewSocket(zmq.REP) 29 | if err != nil { 30 | t.Fatal("NewSocket:", err) 31 | } 32 | req, err = zmq.NewSocket(zmq.REQ) 33 | if err != nil { 34 | t.Fatal("NewSocket:", err) 35 | } 36 | 37 | if err = rep.Bind(addr); err != nil { 38 | t.Fatal("rep.Bind:", err) 39 | } 40 | if err = req.Connect(addr); err != nil { 41 | t.Fatal("req.Connect:", err) 42 | } 43 | 44 | tmp := "test" 45 | if _, err = req.Send(tmp, 0); err != nil { 46 | t.Fatal("req.Send:", err) 47 | } 48 | 49 | // get message with peer address (remote endpoint) 50 | msg, props, err := rep.RecvWithMetadata(0, "Peer-Address") 51 | if err != nil { 52 | t.Fatal("rep.RecvWithMetadata:", err) 53 | return 54 | } 55 | if msg != tmp { 56 | t.Errorf("rep.RecvWithMetadata: expected %q, got %q", tmp, msg) 57 | } 58 | 59 | if p := props["Peer-Address"]; p != peer { 60 | t.Errorf("rep.RecvWithMetadata: expected Peer-Address == %q, got %q", peer, p) 61 | } 62 | 63 | err = rep.Close() 64 | rep = nil 65 | if err != nil { 66 | t.Fatal("rep.Close:", err) 67 | } 68 | 69 | err = req.Close() 70 | req = nil 71 | if err != nil { 72 | t.Fatal("req.Close:", err) 73 | } 74 | } 75 | --------------------------------------------------------------------------------