├── .gitignore ├── README.md ├── channel.go ├── client.go ├── event.go ├── server.go └── socket.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zerorpc 2 | ======= 3 | 4 | Golang ZeroRPC client/server library - http://zerorpc.dotcloud.com 5 | 6 | [![GoDoc](https://godoc.org/github.com/bsphere/zerorpc?status.png)](https://godoc.org/github.com/bsphere/zerorpc) 7 | 8 | 9 | THE LIBRARY IS IN A VERY EARLY STAGE!!! 10 | 11 | 12 | Usage 13 | ----- 14 | Installation: `go get github.com/bsphere/zerorpc` 15 | 16 | ```go 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/bsphere/zerorpc" 22 | ) 23 | 24 | func main() { 25 | c, err := zerorpc.NewClient("tcp://0.0.0.0:4242") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | defer c.Close() 31 | 32 | response, err := c.Invoke("hello", "John") 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | fmt.Println(response) 38 | } 39 | ``` 40 | 41 | 42 | 43 | It supports streaming responses: 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "fmt" 50 | "github.com/bsphere/zerorpc" 51 | ) 52 | 53 | func main() { 54 | c, err := zerorpc.NewClient("tcp://0.0.0.0:4242") 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | defer c.Close() 60 | 61 | response, err := c.InvokeStream("streaming_range", 10, 20, 2) 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | } 65 | 66 | for _, r := range response { 67 | fmt.Println(r) 68 | } 69 | } 70 | ``` 71 | 72 | It also supports first class exceptions, in case of an exception, 73 | the error returned from Invoke() or InvokeStream() is the exception name 74 | and the args of the returned event are the exception description and traceback. 75 | 76 | The client sends heartbeat events every 5 seconds, if twp heartbeat events are missed, 77 | the remote is considered as lost and an ErrLostRemote is returned. 78 | 79 | 80 | Server: 81 | 82 | ```go 83 | package main 84 | 85 | import ( 86 | "errors" 87 | "fmt" 88 | "github.com/bsphere/zerorpc" 89 | "time" 90 | ) 91 | 92 | func main() { 93 | s, err := zerorpc.NewServer("tcp://0.0.0.0:4242") 94 | if err != nil { 95 | panic(err) 96 | } 97 | 98 | defer s.Close() 99 | 100 | h := func(v []interface{}) (interface{}, error) { 101 | time.Sleep(10 * time.Second) 102 | return "Hello, " + v[0].(string), nil 103 | } 104 | 105 | s.RegisterTask("hello", &h) 106 | 107 | s.Listen() 108 | } 109 | ``` 110 | 111 | It also supports first class exceptions, in case of the handler function returns an error, 112 | the args of the event passed to the client is an array which is [err.Error(), nil, nil] -------------------------------------------------------------------------------- /channel.go: -------------------------------------------------------------------------------- 1 | package zerorpc 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const ( 11 | // Heartbeat frequency, 12 | // default is 5 seconds 13 | HeartbeatFrequency = 5 * time.Second 14 | 15 | // Channel local buffer size, 16 | // default is 100 17 | bufferSize = 100 18 | ) 19 | 20 | type state int 21 | 22 | const ( 23 | open = iota 24 | closed 25 | ) 26 | 27 | var ( 28 | errClosedChannel = errors.New("zerorpc/channel closed channel") 29 | ErrLostRemote = errors.New("zerorpc/channel lost remote") 30 | ) 31 | 32 | // Channel representation 33 | type channel struct { 34 | Id string 35 | state state 36 | socket *socket 37 | socketInput chan *Event 38 | channelOutput chan *Event 39 | channelErrors chan error 40 | lastHeartbeat time.Time 41 | identity string 42 | mu sync.Mutex 43 | } 44 | 45 | // Returns a pointer to a new channel, 46 | // it adds the channel to the socket's array of channels 47 | // 48 | // it initiates sending of heartbeats event on the channel as long as 49 | // the channel is open 50 | func (s *socket) newChannel(id string) *channel { 51 | s.mu.Lock() 52 | defer s.mu.Unlock() 53 | 54 | c := channel{ 55 | Id: id, 56 | state: open, 57 | socket: s, 58 | socketInput: make(chan *Event, bufferSize), 59 | channelOutput: make(chan *Event), 60 | channelErrors: make(chan error), 61 | lastHeartbeat: time.Now(), 62 | } 63 | 64 | go c.listen() 65 | go c.handleHeartbeats() 66 | 67 | s.Channels = append(s.Channels, &c) 68 | 69 | log.Printf("ZeroRPC socket created new channel %s", c.Id) 70 | 71 | return &c 72 | } 73 | 74 | // Close the channel, 75 | // set it's state to closed and removes it from the socket's array of channels 76 | func (ch *channel) close() { 77 | ch.mu.Lock() 78 | defer ch.mu.Unlock() 79 | 80 | if ch.state == closed { 81 | return 82 | } 83 | 84 | ch.state = closed 85 | 86 | ch.socket.removeChannel(ch) 87 | 88 | close(ch.socketInput) 89 | close(ch.channelOutput) 90 | close(ch.channelErrors) 91 | 92 | log.Printf("Channel %s closed", ch.Id) 93 | } 94 | 95 | // Sends an event on the channel, 96 | // it sets the event response_to header to the channel's id 97 | // and sends the event on the socket the channel belongs to 98 | func (ch *channel) sendEvent(e *Event) error { 99 | ch.mu.Lock() 100 | defer ch.mu.Unlock() 101 | 102 | if ch.state == closed { 103 | return errClosedChannel 104 | } 105 | 106 | if ch.Id != "" { 107 | e.Header["response_to"] = ch.Id 108 | } else { 109 | ch.Id = e.Header["message_id"].(string) 110 | 111 | go ch.sendHeartbeats() 112 | } 113 | 114 | log.Printf("Channel %s sending event %s", ch.Id, e.Header["message_id"].(string)) 115 | 116 | identity := ch.identity 117 | 118 | return ch.socket.sendEvent(e, identity) 119 | } 120 | 121 | // Returns a pointer to a new "buffer size" event, 122 | // it also returns the available space on the channel input buffer 123 | func (ch *channel) newBufferSizeEvent() (*Event, int, error) { 124 | availSpace := ch.getFreeBufferSize() 125 | 126 | ev, err := newEvent("_zpc_more", []int{availSpace}) 127 | if err != nil { 128 | return nil, 0, err 129 | } 130 | 131 | return ev, availSpace, nil 132 | } 133 | 134 | // Sends heartbeat events on the channel as long as the channel is open, 135 | // the heartbeats interval is defined in HeartbeatFrequency, 136 | // default is 5 seconds 137 | func (ch *channel) sendHeartbeats() { 138 | for { 139 | time.Sleep(HeartbeatFrequency) 140 | 141 | if ch.state == closed { 142 | return 143 | } 144 | 145 | ev, err := newHeartbeatEvent() 146 | if err != nil { 147 | log.Printf(err.Error()) 148 | 149 | return 150 | } 151 | 152 | if err := ch.sendEvent(ev); err != nil { 153 | log.Printf(err.Error()) 154 | 155 | return 156 | } 157 | 158 | log.Printf("Channel %s sent heartbeat", ch.Id) 159 | } 160 | } 161 | 162 | func (ch *channel) listen() { 163 | streamCounter := 0 164 | 165 | for { 166 | if ch.state == closed { 167 | return 168 | } 169 | 170 | ev := <-ch.socketInput 171 | 172 | if ev == nil { 173 | continue 174 | } 175 | 176 | switch ev.Name { 177 | case "OK": 178 | ch.channelOutput <- ev 179 | 180 | case "ERR": 181 | ch.channelOutput <- ev 182 | 183 | case "STREAM": 184 | ch.channelOutput <- ev 185 | 186 | if streamCounter == 0 { 187 | me, free, err := ch.newBufferSizeEvent() 188 | if err == nil { 189 | streamCounter = free 190 | 191 | ch.sendEvent(me) 192 | 193 | streamCounter-- 194 | } 195 | } else { 196 | streamCounter-- 197 | } 198 | 199 | case "STREAM_DONE": 200 | ch.channelOutput <- ev 201 | 202 | streamCounter = 0 203 | 204 | case "_zpc_hb": 205 | log.Printf("Channel %s received heartbeat", ch.Id) 206 | ch.lastHeartbeat = time.Now() 207 | 208 | default: 209 | if ch.socket.server == nil { 210 | continue 211 | } 212 | 213 | log.Printf("Channel %s handling task %s with args %s", ch.Id, ev.Name, ev.Args) 214 | go func(ch *channel, ev *Event) { 215 | defer ch.close() 216 | defer ch.socket.removeChannel(ch) 217 | 218 | r, err := ch.socket.server.handleTask(ev) 219 | if err == nil { 220 | if e, err := newEvent("OK", []interface{}{r}); err != nil { 221 | log.Printf("zerorpc/channel %s", err.Error()) 222 | } else { 223 | e := ch.sendEvent(e) 224 | if e != nil { 225 | ch.socket.socketErrors <- e 226 | } 227 | } 228 | } else { 229 | if e, err2 := newEvent("ERR", []interface{}{err.Error(), nil, nil}); err2 == nil { 230 | e := ch.sendEvent(e) 231 | if e != nil { 232 | ch.socket.socketErrors <- e 233 | } 234 | } else { 235 | log.Printf("zerorpc/channel %s", err.Error()) 236 | } 237 | } 238 | }(ch, ev) 239 | } 240 | } 241 | } 242 | 243 | func (ch *channel) handleHeartbeats() { 244 | for { 245 | if ch.state == closed { 246 | return 247 | } 248 | 249 | if time.Since(ch.lastHeartbeat) > 2*HeartbeatFrequency { 250 | log.Printf("ZeroRPC channel %s lost two heartbeat events", ch.Id) 251 | 252 | select { 253 | case ch.channelErrors <- ErrLostRemote: 254 | default: 255 | ch.close() 256 | return 257 | } 258 | } 259 | 260 | time.Sleep(time.Second) 261 | } 262 | } 263 | 264 | func (ch *channel) getFreeBufferSize() int { 265 | return cap(ch.socketInput) - len(ch.socketInput) 266 | } 267 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package zerorpc 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | ) 7 | 8 | // ZeroRPC client representation, 9 | // it holds a pointer to the ZeroMQ socket 10 | type Client struct { 11 | socket *socket 12 | } 13 | 14 | // Connects to a ZeroRPC endpoint and returns a pointer to the new client 15 | func NewClient(endpoint string) (*Client, error) { 16 | s, err := connect(endpoint) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | c := Client{ 22 | socket: s, 23 | } 24 | 25 | return &c, nil 26 | } 27 | 28 | // Closes the ZeroMQ socket 29 | func (c *Client) Close() error { 30 | return c.socket.close() 31 | } 32 | 33 | /* 34 | Invokes a ZeroRPC method, 35 | name is the method name, 36 | args are the method arguments 37 | 38 | it returns the ZeroRPC response event on success 39 | 40 | if the ZeroRPC server raised an exception, 41 | it's name is returned as the err string along with the response event, 42 | the additional exception text and traceback can be found in the response event args 43 | 44 | it returns ErrLostRemote if the channel misses 2 heartbeat events, 45 | default is 10 seconds 46 | 47 | Usage example: 48 | 49 | package main 50 | 51 | import ( 52 | "fmt" 53 | "github.com/bsphere/zerorpc" 54 | ) 55 | 56 | func main() { 57 | c, err := zerorpc.NewClient("tcp://0.0.0.0:4242") 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | defer c.Close() 63 | 64 | response, err := c.Invoke("hello", "John") 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | fmt.Println(response) 70 | } 71 | 72 | It also supports first class exceptions, in case of an exception, 73 | the error returned from Invoke() or InvokeStream() is the exception name 74 | and the args of the returned event are the exception description and traceback. 75 | 76 | The client sends heartbeat events every 5 seconds, if twp heartbeat events are missed, 77 | the remote is considered as lost and an ErrLostRemote is returned. 78 | */ 79 | func (c *Client) Invoke(name string, args ...interface{}) (*Event, error) { 80 | log.Printf("ZeroRPC client invoked %s with args %s", name, args) 81 | 82 | ev, err := newEvent(name, args) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | ch := c.socket.newChannel("") 88 | defer ch.close() 89 | 90 | err = ch.sendEvent(ev) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | for { 96 | select { 97 | case response := <-ch.channelOutput: 98 | if response.Name == "ERR" { 99 | return response, errors.New(response.Args[0].(string)) 100 | } else { 101 | return response, nil 102 | } 103 | 104 | case err := <-ch.channelErrors: 105 | return nil, err 106 | } 107 | } 108 | } 109 | 110 | /* 111 | Invokes a streaming ZeroRPC method, 112 | name is the method name, 113 | args are the method arguments 114 | 115 | it returns an array of ZeroRPC response events on success 116 | 117 | if the ZeroRPC server raised an exception, 118 | it's name is returned as the err string along with the response event, 119 | the additional exception text and traceback can be found in the response event args 120 | 121 | it returns ErrLostRemote if the channel misses 2 heartbeat events, 122 | default is 10 seconds 123 | 124 | Usage example: 125 | 126 | package main 127 | 128 | import ( 129 | "fmt" 130 | "github.com/bsphere/zerorpc" 131 | ) 132 | 133 | func main() { 134 | c, err := zerorpc.NewClient("tcp://0.0.0.0:4242") 135 | if err != nil { 136 | panic(err) 137 | } 138 | 139 | defer c.Close() 140 | 141 | response, err := c.InvokeStream("streaming_range", 10, 20, 2) 142 | if err != nil { 143 | fmt.Println(err.Error()) 144 | } 145 | 146 | for _, r := range response { 147 | fmt.Println(r) 148 | } 149 | } 150 | 151 | It also supports first class exceptions, in case of an exception, 152 | the error returned from Invoke() or InvokeStream() is the exception name 153 | and the args of the returned event are the exception description and traceback. 154 | 155 | The client sends heartbeat events every 5 seconds, if twp heartbeat events are missed, 156 | the remote is considered as lost and an ErrLostRemote is returned. 157 | */ 158 | func (c *Client) InvokeStream(name string, args ...interface{}) (chan *Event, error) { 159 | log.Printf("ZeroRPC client invoked %s with args %s in streaming mode", name, args) 160 | 161 | ev, err := newEvent(name, args) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | ch := c.socket.newChannel("") 167 | 168 | err = ch.sendEvent(ev) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | out := make(chan *Event) 174 | go func(out chan *Event, ch *channel) { 175 | defer close(out) 176 | defer ch.close() 177 | for { 178 | select { 179 | case response := <-ch.channelOutput: 180 | out <- response 181 | if response.Name != "STREAM" { 182 | return 183 | } 184 | case _ = <-ch.channelErrors: 185 | return 186 | } 187 | } 188 | return 189 | }(out, ch) 190 | return out, nil 191 | } 192 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package zerorpc 2 | 3 | import ( 4 | "errors" 5 | uuid "github.com/nu7hatch/gouuid" 6 | "github.com/ugorji/go/codec" 7 | ) 8 | 9 | // ZeroRPC protocol version 10 | const ProtocolVersion = 3 11 | 12 | // Event representation 13 | type Event struct { 14 | Header map[string]interface{} 15 | Name string 16 | Args []interface{} 17 | } 18 | 19 | // Returns a pointer to a new event, 20 | // a UUID V4 message_id is generated 21 | func newEvent(name string, args ...interface{}) (*Event, error) { 22 | id, err := uuid.NewV4() 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | header := make(map[string]interface{}) 28 | header["message_id"] = id.String() 29 | header["v"] = ProtocolVersion 30 | 31 | e := Event{ 32 | Header: header, 33 | Name: name, 34 | Args: args, 35 | } 36 | 37 | return &e, nil 38 | } 39 | 40 | // Packs an event into MsgPack bytes 41 | func (e *Event) packBytes() ([]byte, error) { 42 | data := make([]interface{}, 2) 43 | data[0] = e.Header 44 | data[1] = e.Name 45 | 46 | for _, a := range e.Args { 47 | data = append(data, a) 48 | } 49 | 50 | var buf []byte 51 | 52 | enc := codec.NewEncoderBytes(&buf, &codec.MsgpackHandle{}) 53 | if err := enc.Encode(data); err != nil { 54 | return nil, err 55 | } 56 | 57 | return buf, nil 58 | } 59 | 60 | // Unpacks an event fom MsgPack bytes 61 | func unPackBytes(b []byte) (*Event, error) { 62 | var mh codec.MsgpackHandle 63 | var v interface{} 64 | 65 | dec := codec.NewDecoderBytes(b, &mh) 66 | 67 | err := dec.Decode(&v) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | // get the event headers 73 | h, ok := v.([]interface{})[0].(map[interface{}]interface{}) 74 | if !ok { 75 | return nil, errors.New("zerorpc/event interface conversion error") 76 | } 77 | 78 | header := make(map[string]interface{}) 79 | 80 | for k, v := range h { 81 | switch t := v.(type) { 82 | case []byte: 83 | header[k.(string)] = string(t) 84 | 85 | default: 86 | header[k.(string)] = t 87 | } 88 | } 89 | 90 | // get the event name 91 | n, ok := v.([]interface{})[1].([]byte) 92 | if !ok { 93 | return nil, errors.New("zerorpc/event interface conversion error") 94 | } 95 | 96 | // get the event args 97 | args := make([]interface{}, 0) 98 | 99 | for i := 2; i < len(v.([]interface{})); i++ { 100 | t := v.([]interface{})[i] 101 | 102 | switch t.(type) { 103 | case []interface{}: 104 | for _, a := range t.([]interface{}) { 105 | args = append(args, convertValue(a)) 106 | } 107 | 108 | default: 109 | args = append(args, convertValue(t)) 110 | } 111 | } 112 | 113 | e := Event{ 114 | Header: header, 115 | Name: string(n), 116 | Args: args, 117 | } 118 | 119 | return &e, nil 120 | } 121 | 122 | // Returns a pointer to a new heartbeat event 123 | func newHeartbeatEvent() (*Event, error) { 124 | ev, err := newEvent("_zpc_hb", nil) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | return ev, nil 130 | } 131 | 132 | // converts an interface{} to a type 133 | func convertValue(v interface{}) interface{} { 134 | var out interface{} 135 | 136 | switch t := v.(type) { 137 | case []byte: 138 | out = string(t) 139 | 140 | case []interface{}: 141 | for i, x := range t { 142 | t[i] = convertValue(x) 143 | } 144 | 145 | out = t 146 | 147 | case map[interface{}]interface{}: 148 | for key, val := range v.(map[interface{}]interface{}) { 149 | t[key] = convertValue(val) 150 | } 151 | 152 | out = t 153 | 154 | default: 155 | out = t 156 | } 157 | 158 | return out 159 | } 160 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package zerorpc 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | ) 7 | 8 | // ZeroRPC server representation, 9 | // it holds a pointer to the ZeroMQ socket 10 | type Server struct { 11 | socket *socket 12 | handlers []*taskHandler 13 | } 14 | 15 | // Task handler representation 16 | type taskHandler struct { 17 | TaskName string 18 | HandlerFunc *func(args []interface{}) (interface{}, error) 19 | } 20 | 21 | var ( 22 | ErrDuplicateHandler = errors.New("zerorpc/server duplicate task handler") 23 | ErrNoTaskHandler = errors.New("zerorpc/server no handler for task") 24 | ) 25 | 26 | /* 27 | Binds to a ZeroRPC endpoint and returns a pointer to the new server 28 | 29 | Usage example: 30 | 31 | package main 32 | 33 | import ( 34 | "errors" 35 | "fmt" 36 | "github.com/bsphere/zerorpc" 37 | "time" 38 | ) 39 | 40 | func main() { 41 | s, err := zerorpc.NewServer("tcp://0.0.0.0:4242") 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | defer s.Close() 47 | 48 | h := func(v []interface{}) (interface{}, error) { 49 | time.Sleep(10 * time.Second) 50 | return "Hello, " + v[0].(string), nil 51 | } 52 | 53 | s.RegisterTask("hello", &h) 54 | 55 | s.Listen() 56 | } 57 | 58 | It also supports first class exceptions, in case of the handler function returns an error, 59 | the args of the event passed to the client is an array which is [err.Error(), nil, nil] 60 | */ 61 | func NewServer(endpoint string) (*Server, error) { 62 | s, err := bind(endpoint) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | server := Server{ 68 | socket: s, 69 | handlers: make([]*taskHandler, 0), 70 | } 71 | 72 | server.socket.server = &server 73 | 74 | return &server, nil 75 | } 76 | 77 | // Closes the ZeroMQ socket 78 | func (s *Server) Close() error { 79 | return s.socket.close() 80 | } 81 | 82 | // Register a task handler, 83 | // tasks are invoked in new goroutines 84 | // 85 | // it returns ErrDuplicateHandler if an handler was already registered for the task 86 | func (s *Server) RegisterTask(name string, handlerFunc *func(args []interface{}) (interface{}, error)) error { 87 | for _, h := range s.handlers { 88 | if h.TaskName == name { 89 | return ErrDuplicateHandler 90 | } 91 | } 92 | 93 | s.handlers = append(s.handlers, &taskHandler{TaskName: name, HandlerFunc: handlerFunc}) 94 | 95 | log.Printf("ZeroRPC server registered handler for task %s", name) 96 | 97 | return nil 98 | } 99 | 100 | // Invoke the handler for a task event, 101 | // it returns ErrNoTaskHandler if no handler is registered for the task 102 | func (s *Server) handleTask(ev *Event) (interface{}, error) { 103 | for _, h := range s.handlers { 104 | if h.TaskName == ev.Name { 105 | log.Printf("ZeroRPC server handling task %s with args %s", ev.Name, ev.Args) 106 | 107 | return (*h.HandlerFunc)(ev.Args) 108 | } 109 | } 110 | 111 | return nil, ErrNoTaskHandler 112 | } 113 | 114 | // Listen for incoming requests, 115 | // it is a blocking function 116 | func (s *Server) Listen() { 117 | for { 118 | err := <-s.socket.socketErrors 119 | if err != nil { 120 | log.Printf("ZeroRPC server socket error %s", err.Error()) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /socket.go: -------------------------------------------------------------------------------- 1 | // Package zerorpc provides a client/server Golang library for the ZeroRPC protocol, 2 | // 3 | // for additional info see http://zerorpc.dotcloud.com 4 | package zerorpc 5 | 6 | import ( 7 | zmq "github.com/pebbe/zmq4" 8 | "log" 9 | "sync" 10 | ) 11 | 12 | // ZeroRPC socket representation 13 | type socket struct { 14 | zmqSocket *zmq.Socket 15 | Channels []*channel 16 | server *Server 17 | socketErrors chan error 18 | mu sync.Mutex 19 | } 20 | 21 | // Connects to a ZeroMQ endpoint and returns a pointer to a new znq.DEALER socket, 22 | // a listener for incoming messages is invoked on the new socket 23 | func connect(endpoint string) (*socket, error) { 24 | zmqSocket, err := zmq.NewSocket(zmq.DEALER) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | s := socket{ 30 | zmqSocket: zmqSocket, 31 | Channels: make([]*channel, 0), 32 | socketErrors: make(chan error), 33 | } 34 | 35 | if err := s.zmqSocket.Connect(endpoint); err != nil { 36 | return nil, err 37 | } 38 | 39 | log.Printf("ZeroRPC socket connected to %s", endpoint) 40 | 41 | go s.listen() 42 | 43 | return &s, nil 44 | } 45 | 46 | // Binds to a ZeroMQ endpoint and returns a pointer to a new znq.ROUTER socket, 47 | // a listener for incoming messages is invoked on the new socket 48 | func bind(endpoint string) (*socket, error) { 49 | zmqSocket, err := zmq.NewSocket(zmq.ROUTER) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | s := socket{ 55 | zmqSocket: zmqSocket, 56 | Channels: make([]*channel, 0), 57 | } 58 | 59 | if err := s.zmqSocket.Bind(endpoint); err != nil { 60 | return nil, err 61 | } 62 | 63 | log.Printf("ZeroRPC socket bound to %s", endpoint) 64 | 65 | go s.listen() 66 | 67 | return &s, nil 68 | } 69 | 70 | // Close the socket, 71 | // it closes all the channels first 72 | func (s *socket) close() error { 73 | for _, c := range s.Channels { 74 | c.close() 75 | s.removeChannel(c) 76 | } 77 | 78 | log.Printf("ZeroRPC socket closed") 79 | return s.zmqSocket.Close() 80 | } 81 | 82 | // Removes a channel from the socket's array of channels 83 | func (s *socket) removeChannel(c *channel) { 84 | s.mu.Lock() 85 | defer s.mu.Unlock() 86 | 87 | channels := make([]*channel, 0) 88 | 89 | for _, t := range s.Channels { 90 | if t != c { 91 | channels = append(channels, t) 92 | } 93 | } 94 | 95 | s.Channels = channels 96 | } 97 | 98 | // Sends an event on the ZeroMQ socket 99 | func (s *socket) sendEvent(e *Event, identity string) error { 100 | b, err := e.packBytes() 101 | if err != nil { 102 | return err 103 | } 104 | 105 | log.Printf("ZeroRPC socket sent event %s", e.Header["message_id"].(string)) 106 | 107 | i, err := s.zmqSocket.SendMessage(identity, "", b) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | log.Printf("ZeroRPC socket sent %d bytes", i) 113 | 114 | return nil 115 | } 116 | 117 | func (s *socket) listen() { 118 | log.Printf("ZeroRPC socket listening for incoming data") 119 | 120 | for { 121 | barr, err := s.zmqSocket.RecvMessageBytes(0) 122 | if err != nil { 123 | s.socketErrors <- err 124 | } 125 | 126 | t := 0 127 | for _, k := range barr { 128 | t += len(k) 129 | } 130 | 131 | log.Printf("ZeroRPC socket received %d bytes", t) 132 | 133 | ev, err := unPackBytes(barr[len(barr)-1]) 134 | if err != nil { 135 | s.socketErrors <- err 136 | } 137 | 138 | log.Printf("ZeroRPC socket recieved event %s", ev.Header["message_id"].(string)) 139 | 140 | var ch *channel 141 | if _, ok := ev.Header["response_to"]; !ok { 142 | ch = s.newChannel(ev.Header["message_id"].(string)) 143 | go ch.sendHeartbeats() 144 | 145 | if len(barr) > 1 { 146 | ch.identity = string(barr[0]) 147 | } 148 | } else { 149 | for _, c := range s.Channels { 150 | if c.Id == ev.Header["response_to"].(string) { 151 | ch = c 152 | } 153 | } 154 | } 155 | 156 | if ch != nil && ch.state == open { 157 | log.Printf("ZeroRPC socket routing event %s to channel %s", ev.Header["message_id"].(string), ch.Id) 158 | 159 | ch.socketInput <- ev 160 | } 161 | } 162 | } 163 | --------------------------------------------------------------------------------