ffi-rzmq 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/AUTHORS.txt +1 -0
  2. data/History.txt +35 -0
  3. data/README.rdoc +48 -15
  4. data/Rakefile +7 -2
  5. data/examples/README.rdoc +21 -76
  6. data/examples/{local_lat.rb → v2api/local_lat.rb} +27 -12
  7. data/examples/v2api/local_lat_poll.rb +66 -0
  8. data/examples/{local_throughput.rb → v2api/local_throughput.rb} +24 -9
  9. data/examples/v2api/publish_subscribe.rb +82 -0
  10. data/examples/{remote_lat.rb → v2api/remote_lat.rb} +26 -8
  11. data/examples/v2api/remote_throughput.rb +39 -0
  12. data/examples/v2api/reqrep_poll.rb +62 -0
  13. data/examples/v2api/request_response.rb +40 -0
  14. data/examples/v2api/throughput_measurement.rb +138 -0
  15. data/examples/v3api/local_lat.rb +59 -0
  16. data/examples/v3api/local_lat_poll.rb +66 -0
  17. data/examples/v3api/local_throughput.rb +65 -0
  18. data/examples/v3api/publish_subscribe.rb +82 -0
  19. data/examples/v3api/remote_lat.rb +71 -0
  20. data/examples/v3api/remote_throughput.rb +47 -0
  21. data/examples/v3api/reqrep_poll.rb +62 -0
  22. data/examples/v3api/request_response.rb +40 -0
  23. data/examples/v3api/throughput_measurement.rb +166 -0
  24. data/ext/README +5 -0
  25. data/ffi-rzmq.gemspec +4 -4
  26. data/lib/ffi-rzmq.rb +4 -1
  27. data/lib/ffi-rzmq/constants.rb +178 -0
  28. data/lib/ffi-rzmq/context.rb +61 -45
  29. data/lib/ffi-rzmq/device.rb +22 -9
  30. data/lib/ffi-rzmq/exceptions.rb +0 -98
  31. data/lib/ffi-rzmq/libc.rb +19 -0
  32. data/lib/ffi-rzmq/libzmq.rb +188 -0
  33. data/lib/ffi-rzmq/message.rb +33 -40
  34. data/lib/ffi-rzmq/poll.rb +49 -52
  35. data/lib/ffi-rzmq/socket.rb +902 -392
  36. data/lib/ffi-rzmq/util.rb +101 -0
  37. data/spec/context_spec.rb +47 -21
  38. data/spec/device_spec.rb +78 -58
  39. data/spec/message_spec.rb +90 -12
  40. data/spec/multipart_spec.rb +162 -0
  41. data/spec/nonblocking_recv_spec.rb +325 -0
  42. data/spec/pushpull_spec.rb +95 -34
  43. data/spec/reqrep_spec.rb +55 -20
  44. data/spec/socket_spec.rb +353 -204
  45. data/spec/spec_helper.rb +46 -3
  46. data/version.txt +1 -1
  47. metadata +91 -66
  48. data/examples/local_lat_poll.rb +0 -54
  49. data/examples/local_lat_zerocopy.rb +0 -24
  50. data/examples/publish_subscribe.rb +0 -52
  51. data/examples/remote_lat_zerocopy.rb +0 -35
  52. data/examples/remote_throughput.rb +0 -27
  53. data/examples/reqrep_poll.rb +0 -49
  54. data/examples/request_response.rb +0 -23
  55. data/lib/ffi-rzmq/wrapper.rb +0 -121
  56. data/lib/ffi-rzmq/zmq.rb +0 -198
@@ -1,8 +1,6 @@
1
1
 
2
2
  module ZMQ
3
3
 
4
- ZMQ_POLL_STR = 'zmq_poll'.freeze
5
-
6
4
  class Poller
7
5
  include ZMQ::Util
8
6
 
@@ -16,7 +14,7 @@ module ZMQ
16
14
  @writables = []
17
15
  end
18
16
 
19
- # Checks each poll item for selectability based on the poll items'
17
+ # Checks each registered socket for selectability based on the poll items'
20
18
  # registered +events+. Will block for up to +timeout+ milliseconds
21
19
  # A millisecond is 1/1000 of a second, so to block for 1 second
22
20
  # pass the value "1000" to #poll.
@@ -24,34 +22,42 @@ module ZMQ
24
22
  # Pass "-1" or +:blocking+ for +timeout+ for this call to block
25
23
  # indefinitely.
26
24
  #
27
- # May raise a ZMQ::PollError exception. This occurs when one of the
28
- # registered sockets belongs to an application thread in another
29
- # Context.
30
- #
31
25
  # This method will return *immediately* when there are no registered
32
26
  # sockets. In that case, the +timeout+ parameter is not honored. To
33
27
  # prevent a CPU busy-loop, the caller of this method should detect
34
28
  # this possible condition (via #size) and throttle the call
35
29
  # frequency.
36
30
  #
31
+ # Returns 0 when there are no registered sockets that are readable
32
+ # or writable.
33
+ #
34
+ # Return 1 (or greater) to indicate the number of readable or writable
35
+ # sockets. These sockets should be processed using the #readables and
36
+ # #writables accessors.
37
+ #
38
+ # Returns -1 when there is an error. Use ZMQ::Util.errno to get the related
39
+ # error number.
40
+ #
37
41
  def poll timeout = :blocking
38
42
  unless @items.empty?
39
43
  timeout = adjust timeout
40
44
  items_triggered = LibZMQ.zmq_poll @items.address, @items.size, timeout
41
- error_check ZMQ_POLL_STR, items_triggered >= 0 ? 0 : items_triggered
42
- update_selectables
43
- items_hash
45
+
46
+ if Util.resultcode_ok?(items_triggered)
47
+ update_selectables
48
+ end
49
+
50
+ items_triggered
44
51
  else
45
- {}
52
+ 0
46
53
  end
47
54
  end
48
55
 
49
56
  # The non-blocking version of #poll. See the #poll description for
50
57
  # potential exceptions.
51
58
  #
52
- # May raise a ZMQ::PollError exception. This occurs when one of the
53
- # registered sockets belongs to an application thread in another
54
- # Context.
59
+ # May return -1 when an error is encounted. Check ZMQ::Util.errno
60
+ # to determine the underlying cause.
55
61
  #
56
62
  def poll_nonblock
57
63
  poll 0
@@ -62,8 +68,6 @@ module ZMQ
62
68
  # will only get registered at most once. Calling multiple times with
63
69
  # different values for +events+ will OR the event information together.
64
70
  #
65
- # Does not raise any exceptions.
66
- #
67
71
  def register sock, events = ZMQ::POLLIN | ZMQ::POLLOUT, fd = 0
68
72
  return false if (sock.nil? && fd.zero?) || events.zero?
69
73
 
@@ -84,13 +88,12 @@ module ZMQ
84
88
  @raw_to_socket[item[:socket].address] = sock
85
89
  @items << item
86
90
  end
87
-
91
+
88
92
  item[:events] |= events
89
93
  end
90
94
 
91
- # Deregister the +sock+ for +events+.
92
- #
93
- # Does not raise any exceptions.
95
+ # Deregister the +sock+ for +events+. When there are no events left,
96
+ # this also deletes the socket from the poll items.
94
97
  #
95
98
  def deregister sock, events, fd = 0
96
99
  return unless sock || !fd.zero?
@@ -102,6 +105,9 @@ module ZMQ
102
105
  item[:events] ^= events
103
106
 
104
107
  delete sock if item[:events].zero?
108
+ true
109
+ else
110
+ false
105
111
  end
106
112
  end
107
113
 
@@ -129,33 +135,28 @@ module ZMQ
129
135
  deregister sock, ZMQ::POLLOUT, 0
130
136
  end
131
137
 
132
- # Deletes the +sock+ for all subscribed events.
133
- #
134
- # def delete sock
135
- # removed = false
136
- #
137
- # if index = @sockets.index(sock)
138
- # removed = @items.delete_at(index) and @sockets.delete(sock) and @raw_to_socket.delete(sock.socket.address)
139
- # end
140
- #
141
- # removed
142
- # end
138
+ # Deletes the +sock+ for all subscribed events. Called internally
139
+ # when a socket has been deregistered and has no more events
140
+ # registered anywhere.
141
+ #
143
142
  def delete sock
144
- removed = false
145
- deregister_readable sock
146
- deregister_writable sock
147
-
148
- size = @sockets.size
149
- @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
150
- socket_deleted = size != @sockets.size
151
-
152
- item_deleted = @items.delete sock
153
-
154
- size = @raw_to_socket.size
155
- @raw_to_socket.delete(sock.socket.address)
156
- raw_deleted = size != @raw_to_socket.size
157
-
158
- socket_deleted && item_deleted && raw_deleted
143
+ removed_readable = deregister_readable sock
144
+ removed_writable = deregister_writable sock
145
+
146
+ if (size = @sockets.size) > 0
147
+ @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
148
+ socket_deleted = size != @sockets.size
149
+
150
+ item_deleted = @items.delete sock
151
+
152
+ raw_deleted = @raw_to_socket.delete(sock.socket.address)
153
+
154
+ socket_deleted && item_deleted && raw_deleted
155
+
156
+ else
157
+ # return result of deregistration
158
+ removed_readable || removed_writable
159
+ end
159
160
  end
160
161
 
161
162
  def size(); @items.size; end
@@ -169,14 +170,10 @@ module ZMQ
169
170
 
170
171
  private
171
172
 
172
- def items_hash
173
- hsh = {}
174
-
173
+ def items_hash hash
175
174
  @items.each do |poll_item|
176
- hsh[@raw_to_socket[poll_item[:socket].address]] = poll_item
175
+ hash[@raw_to_socket[poll_item[:socket].address]] = poll_item
177
176
  end
178
-
179
- hsh
180
177
  end
181
178
 
182
179
  def update_selectables
@@ -1,16 +1,7 @@
1
1
 
2
2
  module ZMQ
3
3
 
4
- ZMQ_SOCKET_STR = 'zmq_socket'.freeze unless defined? ZMQ_SOCKET_STR
5
- ZMQ_SETSOCKOPT_STR = 'zmq_setsockopt'.freeze
6
- ZMQ_GETSOCKOPT_STR = 'zmq_getsockopt'.freeze
7
- ZMQ_BIND_STR = 'zmq_bind'.freeze
8
- ZMQ_CONNECT_STR = 'zmq_connect'.freeze
9
- ZMQ_CLOSE_STR = 'zmq_close'.freeze
10
- ZMQ_SEND_STR = 'zmq_send'.freeze
11
- ZMQ_RECV_STR = 'zmq_recv'.freeze
12
-
13
- class Socket
4
+ module CommonSocketBehavior
14
5
  include ZMQ::Util
15
6
 
16
7
  attr_reader :socket, :name
@@ -18,7 +9,39 @@ module ZMQ
18
9
  # Allocates a socket of type +type+ for sending and receiving data.
19
10
  #
20
11
  # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
21
- # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH,
12
+ # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
13
+ # ZMQ::DEALER or ZMQ::ROUTER.
14
+ #
15
+ # By default, this class uses ZMQ::Message for manual
16
+ # memory management. For automatic garbage collection of received messages,
17
+ # it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
18
+ #
19
+ # sock = Socket.create(Context.create, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
20
+ #
21
+ # Advanced users may want to replace the receiver class with their
22
+ # own custom class. The custom class must conform to the same public API
23
+ # as ZMQ::Message.
24
+ #
25
+ # Creation of a new Socket object can return nil when socket creation
26
+ # fails.
27
+ #
28
+ # if (socket = Socket.new(context.pointer, ZMQ::REQ))
29
+ # ...
30
+ # else
31
+ # STDERR.puts "Socket creation failed"
32
+ # end
33
+ #
34
+ def self.create context_ptr, type, opts = {:receiver_class => ZMQ::Message}
35
+ new(context_ptr, type, opts) rescue nil
36
+ end
37
+
38
+ # To avoid rescuing exceptions, use the factory method #create for
39
+ # all socket creation.
40
+ #
41
+ # Allocates a socket of type +type+ for sending and receiving data.
42
+ #
43
+ # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
44
+ # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
22
45
  # ZMQ::DEALER or ZMQ::ROUTER.
23
46
  #
24
47
  # By default, this class uses ZMQ::Message for manual
@@ -31,26 +54,32 @@ module ZMQ
31
54
  # own custom class. The custom class must conform to the same public API
32
55
  # as ZMQ::Message.
33
56
  #
34
- # Can raise two kinds of exceptions depending on the error.
35
- # ContextError:: Raised when a socket operation is attempted on a terminated
36
- # #Context. See #ContextError.
37
- # SocketError:: See all of the possibilities in the docs for #SocketError.
57
+ # Creation of a new Socket object can raise an exception. This occurs when the
58
+ # +context_ptr+ is null or when the allocation of the 0mq socket within the
59
+ # context fails.
60
+ #
61
+ # begin
62
+ # socket = Socket.new(context.pointer, ZMQ::REQ)
63
+ # rescue ContextError => e
64
+ # # error handling
65
+ # end
38
66
  #
39
67
  def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message}
40
68
  # users may override the classes used for receiving; class must conform to the
41
69
  # same public API as ZMQ::Message
42
70
  @receiver_klass = opts[:receiver_class]
71
+
72
+ context_ptr = context_ptr.pointer if context_ptr.kind_of?(ZMQ::Context)
43
73
 
44
74
  unless context_ptr.null?
45
75
  @socket = LibZMQ.zmq_socket context_ptr, type
46
- if @socket
47
- error_check ZMQ_SOCKET_STR, @socket.null? ? 1 : 0
76
+ if @socket && !@socket.null?
48
77
  @name = SocketTypeNameMap[type]
49
78
  else
50
- raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Socket pointer was null"
79
+ raise ContextError.new 'zmq_socket', 0, ETERM, "Socket pointer was null"
51
80
  end
52
81
  else
53
- raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Context pointer was null"
82
+ raise ContextError.new 'zmq_socket', 0, ETERM, "Context pointer was null"
54
83
  end
55
84
 
56
85
  @sockopt_cache = {}
@@ -60,416 +89,897 @@ module ZMQ
60
89
 
61
90
  # Set the queue options on this socket.
62
91
  #
63
- # Valid +option_name+ values that take a numeric +option_value+ are:
92
+ # Valid +name+ values that take a numeric +value+ are:
64
93
  # ZMQ::HWM
65
- # ZMQ::SWAP
94
+ # ZMQ::SWAP (version 2 only)
66
95
  # ZMQ::AFFINITY
67
96
  # ZMQ::RATE
68
97
  # ZMQ::RECOVERY_IVL
69
- # ZMQ::MCAST_LOOP
98
+ # ZMQ::MCAST_LOOP (version 2 only)
70
99
  # ZMQ::LINGER
71
100
  # ZMQ::RECONNECT_IVL
72
101
  # ZMQ::BACKLOG
73
- # ZMQ::RECOVER_IVL_MSEC
102
+ # ZMQ::RECOVER_IVL_MSEC (version 2 only)
103
+ # ZMQ::RECONNECT_IVL_MAX (version 3/4 only)
104
+ # ZMQ::MAXMSGSIZE (version 3/4 only)
105
+ # ZMQ::SNDHWM (version 3/4 only)
106
+ # ZMQ::RCVHWM (version 3/4 only)
107
+ # ZMQ::MULTICAST_HOPS (version 3/4 only)
108
+ # ZMQ::RCVTIMEO (version 3/4 only)
109
+ # ZMQ::SNDTIMEO (version 3/4 only)
110
+ # ZMQ::RCVLABEL (version 3/4 only)
74
111
  #
75
- # Valid +option_name+ values that take a string +option_value+ are:
76
- # ZMQ::IDENTITY
112
+ # Valid +name+ values that take a string +value+ are:
113
+ # ZMQ::IDENTITY (version 2/3 only)
77
114
  # ZMQ::SUBSCRIBE
78
115
  # ZMQ::UNSUBSCRIBE
79
116
  #
80
- # Can raise two kinds of exceptions depending on the error.
81
- # ContextError:: Raised when a socket operation is attempted on a terminated
82
- # #Context. See #ContextError.
83
- # SocketError:: See all of the possibilities in the docs for #SocketError.
117
+ # Returns 0 when the operation completed successfully.
118
+ # Returns -1 when this operation failed.
84
119
  #
85
- def setsockopt option_name, option_value, option_len = nil
86
- option_value = sanitize_value option_name, option_value
120
+ # With a -1 return code, the user must check ZMQ.errno to determine the
121
+ # cause.
122
+ #
123
+ # rc = socket.setsockopt(ZMQ::LINGER, 1_000)
124
+ # ZMQ::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
125
+ #
126
+ def setsockopt name, value, length = nil
127
+ if long_long_option?(name)
128
+ length = 8
129
+ pointer = LibC.malloc length
130
+ pointer.write_long_long value
131
+
132
+ elsif int_option?(name)
133
+ length = 4
134
+ pointer = LibC.malloc length
135
+ pointer.write_int value
136
+
137
+ elsif string_option?(name)
138
+ length ||= value.size
139
+
140
+ # note: not checking errno for failed memory allocations :(
141
+ pointer = LibC.malloc length
142
+ pointer.write_string value
143
+ end
87
144
 
88
- begin
89
- case option_name
90
- when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
91
- option_len = 8 # all of these options are defined as int64_t or uint64_t
92
- option_value_ptr = LibC.malloc option_len
93
- option_value_ptr.write_long_long option_value
145
+ rc = LibZMQ.zmq_setsockopt @socket, name, pointer, length
146
+ LibC.free(pointer) unless pointer.nil? || pointer.null?
147
+ rc
148
+ end
94
149
 
95
- when LINGER, RECONNECT_IVL, BACKLOG
96
- option_len = 4 # hard-code "int" length to 4 bytes
97
- option_value_ptr = LibC.malloc option_len
98
- option_value_ptr.write_int option_value
150
+ # Convenience method for checking on additional message parts.
151
+ #
152
+ # Equivalent to calling Socket#getsockopt with ZMQ::RCVMORE.
153
+ #
154
+ # Warning: if the call to #getsockopt fails, this method will return
155
+ # false and swallow the error.
156
+ #
157
+ # message_parts = []
158
+ # message = Message.new
159
+ # rc = socket.recv(message)
160
+ # if ZMQ::Util.resultcode_ok?(rc)
161
+ # message_parts << message
162
+ # while more_parts?
163
+ # message = Message.new
164
+ # rc = socket.recv(message)
165
+ # message_parts.push(message) if resulcode_ok?(rc)
166
+ # end
167
+ # end
168
+ #
169
+ def more_parts?
170
+ array = []
171
+ rc = getsockopt ZMQ::RCVMORE, array
172
+
173
+ Util.resultcode_ok?(rc) ? array.at(0) : false
174
+ end
99
175
 
100
- when IDENTITY, SUBSCRIBE, UNSUBSCRIBE
101
- option_len ||= option_value.size
176
+ # Binds the socket to an +address+.
177
+ #
178
+ # socket.bind("tcp://127.0.0.1:5555")
179
+ #
180
+ def bind address
181
+ LibZMQ.zmq_bind @socket, address
182
+ end
102
183
 
103
- # note: not checking errno for failed memory allocations :(
104
- option_value_ptr = LibC.malloc option_len
105
- option_value_ptr.write_string option_value
184
+ # Connects the socket to an +address+.
185
+ #
186
+ # socket.connect("tcp://127.0.0.1:5555")
187
+ #
188
+ def connect address
189
+ rc = LibZMQ.zmq_connect @socket, address
190
+ end
106
191
 
107
- else
108
- # we didn't understand the passed option argument
109
- # will force a raise due to EINVAL being non-zero
110
- error_check ZMQ_SETSOCKOPT_STR, EINVAL
192
+ # Closes the socket. Any unprocessed messages in queue are sent or dropped
193
+ # depending upon the value of the socket option ZMQ::LINGER.
194
+ #
195
+ # Returns 0 upon success *or* when the socket has already been closed.
196
+ # Returns -1 when the operation fails. Check ZMQ.errno for the error code.
197
+ #
198
+ # rc = socket.close
199
+ # puts("Given socket was invalid!") unless 0 == rc
200
+ #
201
+ def close
202
+ if @socket
203
+ remove_finalizer
204
+ rc = LibZMQ.zmq_close @socket
205
+ @socket = nil
206
+ release_cache
207
+ rc
208
+ else
209
+ 0
210
+ end
211
+ end
212
+
213
+
214
+ private
215
+
216
+ def __getsockopt__ name, array
217
+ value, length = sockopt_buffers name
218
+
219
+ rc = LibZMQ.zmq_getsockopt @socket, name, value, length
220
+
221
+ if Util.resultcode_ok?(rc)
222
+ result = if int_option?(name)
223
+ value.read_int
224
+ elsif long_long_option?(name)
225
+ value.read_long_long
226
+ elsif string_option?(name)
227
+ value.read_string(length.read_int)
111
228
  end
112
229
 
113
- result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len
114
- error_check ZMQ_SETSOCKOPT_STR, result_code
115
- ensure
116
- LibC.free option_value_ptr unless option_value_ptr.nil? || option_value_ptr.null?
230
+ array << result
117
231
  end
118
- end
119
232
 
120
- # Get the options set on this socket. Returns a value dependent upon
121
- # the +option_name+ requested.
122
- #
123
- # Valid +option_name+ values and their return types:
124
- # ZMQ::RCVMORE - boolean
125
- # ZMQ::HWM - integer
126
- # ZMQ::SWAP - integer
127
- # ZMQ::AFFINITY - bitmap in an integer
128
- # ZMQ::IDENTITY - string
129
- # ZMQ::RATE - integer
130
- # ZMQ::RECOVERY_IVL - integer
131
- # ZMQ::MCAST_LOOP - boolean
132
- # ZMQ::SNDBUF - integer
133
- # ZMQ::RCVBUF - integer
134
- # ZMQ::FD - fd in an integer
135
- # ZMQ::EVENTS - bitmap integer
136
- # ZMQ::LINGER - integer measured in milliseconds
137
- # ZMQ::RECONNECT_IVL - integer measured in milliseconds
138
- # ZMQ::BACKLOG - integer
139
- # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
140
- #
141
- # Can raise two kinds of exceptions depending on the error.
142
- # ContextError:: Raised when a socket operation is attempted on a terminated
143
- # #Context. See #ContextError.
144
- # SocketError:: See all of the possibilities in the docs for #SocketError.
145
- #
146
- def getsockopt option_name
147
- begin
148
- option_value = FFI::MemoryPointer.new :pointer
149
- option_length = FFI::MemoryPointer.new(:size_t) rescue FFI::MemoryPointer.new(:ulong)
150
-
151
- unless [
152
- TYPE, RCVMORE, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP, IDENTITY,
153
- SNDBUF, RCVBUF, FD, EVENTS, LINGER, RECONNECT_IVL, BACKLOG, RECOVERY_IVL_MSEC
154
- ].include? option_name
155
- # we didn't understand the passed option argument
156
- # will force a raise
157
- error_check ZMQ_SETSOCKOPT_STR, -1
158
- end
159
-
160
- option_value, option_length = alloc_temp_sockopt_buffers option_name
161
-
162
- result_code = LibZMQ.zmq_getsockopt @socket, option_name, option_value, option_length
163
- error_check ZMQ_GETSOCKOPT_STR, result_code
164
- ret = 0
165
-
166
- case option_name
167
- when RCVMORE, MCAST_LOOP
168
- # boolean return
169
- ret = option_value.read_long_long != 0
170
- when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
171
- ret = option_value.read_long_long
172
- when TYPE, LINGER, RECONNECT_IVL, BACKLOG, FD, EVENTS
173
- ret = option_value.read_int
174
- when IDENTITY
175
- ret = option_value.read_string(option_length.read_long_long)
176
- end
177
-
178
- ret
233
+ rc
179
234
  end
180
- end
181
-
182
- # Convenience method for checking on additional message parts.
183
- #
184
- # Equivalent to Socket#getsockopt ZMQ::RCVMORE
185
- #
186
- def more_parts?
187
- getsockopt ZMQ::RCVMORE
188
- end
189
-
190
- # Convenience method for getting the value of the socket IDENTITY.
191
- #
192
- def identity
193
- getsockopt ZMQ::IDENTITY
194
- end
195
-
196
- # Convenience method for setting the value of the socket IDENTITY.
197
- #
198
- def identity= value
199
- setsockopt ZMQ::IDENTITY, value.to_s
200
- end
201
-
202
- # Can raise two kinds of exceptions depending on the error.
203
- # ContextError:: Raised when a socket operation is attempted on a terminated
204
- # #Context. See #ContextError.
205
- # SocketError:: See all of the possibilities in the docs for #SocketError.
206
- #
207
- def bind address
208
- result_code = LibZMQ.zmq_bind @socket, address
209
- error_check ZMQ_BIND_STR, result_code
210
- end
211
-
212
- # Can raise two kinds of exceptions depending on the error.
213
- # ContextError:: Raised when a socket operation is attempted on a terminated
214
- # #Context. See #ContextError.
215
- # SocketError:: See all of the possibilities in the docs for #SocketError.
216
- #
217
- def connect address
218
- result_code = LibZMQ.zmq_connect @socket, address
219
- error_check ZMQ_CONNECT_STR, result_code
220
- end
221
-
222
- # Closes the socket. Any unprocessed messages in queue are sent or dropped
223
- # depending upon the value of the socket option ZMQ::LINGER.
224
- #
225
- def close
226
- if @socket
227
- remove_finalizer
228
- result_code = LibZMQ.zmq_close @socket
229
- error_check ZMQ_CLOSE_STR, result_code
230
- @socket = nil
231
- release_cache
235
+
236
+ # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
237
+ # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
238
+ # Cannot save the buffer for the IDENTITY.
239
+ def sockopt_buffers name
240
+ if long_long_option?(name)
241
+ # int64_t or uint64_t
242
+ unless @sockopt_cache[:int64]
243
+ length = FFI::MemoryPointer.new :size_t
244
+ length.write_int 8
245
+ @sockopt_cache[:int64] = [FFI::MemoryPointer.new(:int64), length]
246
+ end
247
+
248
+ @sockopt_cache[:int64]
249
+
250
+ elsif int_option?(name)
251
+ # int, 0mq assumes int is 4-bytes
252
+ unless @sockopt_cache[:int32]
253
+ length = FFI::MemoryPointer.new :size_t
254
+ length.write_int 4
255
+ @sockopt_cache[:int32] = [FFI::MemoryPointer.new(:int32), length]
256
+ end
257
+
258
+ @sockopt_cache[:int32]
259
+
260
+ elsif string_option?(name)
261
+ length = FFI::MemoryPointer.new :size_t
262
+ # could be a string of up to 255 bytes
263
+ length.write_int 255
264
+ [FFI::MemoryPointer.new(255), length]
265
+
266
+ else
267
+ # uh oh, someone passed in an unknown option; use a slop buffer
268
+ unless @sockopt_cache[:unknown]
269
+ length = FFI::MemoryPointer.new :size_t
270
+ length.write_int 4
271
+ @sockopt_cache[:unknown] = [FFI::MemoryPointer.new(:int32), length]
272
+ end
273
+
274
+ @sockopt_cache[:unknown]
275
+ end
232
276
  end
233
- end
234
-
235
- # Queues the message for transmission. Message is assumed to conform to the
236
- # same public API as #Message.
237
- #
238
- # +flags+ may take two values:
239
- # * 0 (default) - blocking operation
240
- # * ZMQ::NOBLOCK - non-blocking operation
241
- # * ZMQ::SNDMORE - this message is part of a multi-part message
242
- #
243
- # Returns true when the message was successfully enqueued.
244
- # Returns false under two conditions.
245
- # 1. The message could not be enqueued
246
- # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
247
- #
248
- # The application code is responsible for handling the +message+ object
249
- # lifecycle when #send returns.
250
- #
251
- # Can raise two kinds of exceptions depending on the error.
252
- # ContextError:: Raised when a socket operation is attempted on a terminated
253
- # #Context. See #ContextError.
254
- # SocketError:: See all of the possibilities in the docs for #SocketError.
255
- #
256
- def send message, flags = 0
257
- begin
258
- result_code = LibZMQ.zmq_send @socket, message.address, flags
259
-
260
- # when the flag isn't set, do a normal error check
261
- # when set, check to see if the message was successfully queued
262
- queued = noblock?(flags) ? error_check_nonblock(result_code) : error_check(ZMQ_SEND_STR, result_code)
277
+
278
+ def supported_option? name
279
+ int_option?(name) || long_long_option?(name) || string_option?(name)
263
280
  end
264
281
 
265
- # true if sent, false if failed/EAGAIN
266
- queued
267
- end
268
-
269
- # Helper method to make a new #Message instance out of the +message_string+ passed
270
- # in for transmission.
271
- #
272
- # +flags+ may be ZMQ::NOBLOCK.
273
- #
274
- # Can raise two kinds of exceptions depending on the error.
275
- # ContextError:: Raised when a socket operation is attempted on a terminated
276
- # #Context. See #ContextError.
277
- # SocketError:: See all of the possibilities in the docs for #SocketError.
278
- #
279
- def send_string message_string, flags = 0
280
- message = Message.new message_string
281
- result_code = send_and_close message, flags
282
-
283
- result_code
284
- end
285
-
286
- # Send a sequence of strings as a multipart message out of the +parts+
287
- # passed in for transmission. Every element of +parts+ should be
288
- # a String.
289
- #
290
- # +flags+ may be ZMQ::NOBLOCK.
291
- #
292
- # Raises the same exceptions as Socket#send.
293
- #
294
- def send_strings parts, flags = 0
295
- return false if !parts || parts.empty?
296
-
297
- parts[0...-1].each do |part|
298
- return false unless send_string part, flags | ZMQ::SNDMORE
282
+ def int_option? name
283
+ EVENTS == name ||
284
+ LINGER == name ||
285
+ RECONNECT_IVL == name ||
286
+ FD == name ||
287
+ TYPE == name ||
288
+ BACKLOG == name
299
289
  end
300
290
 
301
- send_string parts[-1], flags
302
- end
303
-
304
- # Sends a message. This will automatically close the +message+ for both successful
305
- # and failed sends.
306
- #
307
- # Raises the same exceptions as Socket#send
308
- #
309
- def send_and_close message, flags = 0
310
- begin
311
- result_code = send message, flags
312
- ensure
313
- message.close
291
+ def string_option? name
292
+ SUBSCRIBE == name ||
293
+ UNSUBSCRIBE == name
314
294
  end
315
- result_code
316
- end
317
-
318
- # Dequeues a message from the underlying queue. By default, this is a blocking operation.
319
- #
320
- # +flags+ may take two values:
321
- # 0 (default) - blocking operation
322
- # ZMQ::NOBLOCK - non-blocking operation
323
- #
324
- # Returns a true when it successfully dequeues one from the queue. Also, the +message+
325
- # object is populated by the library with a data buffer containing the received
326
- # data.
327
- #
328
- # Returns nil when a message could not be dequeued *and* +flags+ is set
329
- # with ZMQ::NOBLOCK. The +message+ object is not modified in this situation.
330
- #
331
- # The application code is *not* responsible for handling the +message+ object lifecycle
332
- # when #recv raises an exception. The #recv method takes ownership of the
333
- # +message+ and its associated buffers. A failed call will
334
- # release the data buffers assigned to the +message+.
335
- #
336
- # Can raise two kinds of exceptions depending on the error.
337
- # ContextError:: Raised when a socket operation is attempted on a terminated
338
- # #Context. See #ContextError.
339
- # SocketError:: See all of the possibilities in the docs for #SocketError.
340
- #
341
- def recv message, flags = 0
342
- begin
343
- dequeued = _recv message, flags
344
- rescue ZeroMQError
345
- message.close
346
- raise
295
+
296
+ def long_long_option? name
297
+ RCVMORE == name ||
298
+ AFFINITY == name
347
299
  end
348
300
 
349
- dequeued ? true : nil
350
- end
351
-
352
- # Helper method to make a new #Message instance and convert its payload
353
- # to a string.
354
- #
355
- # +flags+ may be ZMQ::NOBLOCK.
356
- #
357
- # Can raise two kinds of exceptions depending on the error.
358
- # ContextError:: Raised when a socket operation is attempted on a terminated
359
- # #Context. See #ContextError.
360
- # SocketError:: See all of the possibilities in the docs for #SocketError.
361
- #
362
- def recv_string flags = 0
363
- message = @receiver_klass.new
364
-
365
- begin
366
- dequeued = _recv message, flags
367
-
368
- if dequeued
369
- message.copy_out_string
370
- else
371
- nil
372
- end
373
- ensure
374
- message.close
301
+ def unsupported_setsock_option? name
302
+ RCVMORE == name
375
303
  end
376
- end
377
-
378
- # Receive a multipart message as a list of strings.
379
- #
380
- # +flags+ may be ZMQ::NOBLOCK.
381
- #
382
- # Raises the same exceptions as Socket#recv.
383
- #
384
- def recv_strings flags = 0
385
- parts = []
386
- parts << recv_string(flags)
387
- parts << recv_string(flags) while more_parts?
388
- parts
389
- end
390
-
391
- private
392
-
393
- def noblock? flag
394
- (NOBLOCK & flag) == NOBLOCK
395
- end
396
-
397
- def _recv message, flags = 0
398
- result_code = LibZMQ.zmq_recv @socket, message.address, flags
399
-
400
- if noblock?(flags)
401
- error_check_nonblock(result_code)
402
- else
403
- error_check(ZMQ_RECV_STR, result_code)
304
+
305
+ def unsupported_getsock_option? name
306
+ UNSUBSCRIBE == name ||
307
+ SUBSCRIBE == name
404
308
  end
405
- end
406
-
407
- # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
408
- # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
409
- # Cannot save the buffer for the IDENTITY.
410
- def alloc_temp_sockopt_buffers option_name
411
- case option_name
412
- when RCVMORE, MCAST_LOOP, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
413
- # int64_t
414
- unless @sockopt_cache[:int64]
415
- length = FFI::MemoryPointer.new :int64
416
- length.write_long_long 8
417
- @sockopt_cache[:int64] = [FFI::MemoryPointer.new(:int64), length]
418
- end
419
- @sockopt_cache[:int64]
420
-
421
- when TYPE, LINGER, RECONNECT_IVL, BACKLOG, FD, EVENTS
422
- # int, 0mq assumes int is 4-bytes
423
- unless @sockopt_cache[:int32]
424
- length = FFI::MemoryPointer.new :int32
425
- length.write_int 4
426
- @sockopt_cache[:int32] = [FFI::MemoryPointer.new(:int32), length]
427
- end
428
- @sockopt_cache[:int32]
429
-
430
- when IDENTITY
431
- length = FFI::MemoryPointer.new :int64
432
- # could be a string of up to 255 bytes
433
- length.write_long_long 255
434
- [FFI::MemoryPointer.new(255), length]
309
+
310
+ def release_cache
311
+ @sockopt_cache.clear
435
312
  end
436
- end
437
-
438
- def sanitize_value option_name, option_value
439
- case option_name
440
- when HWM, AFFINITY, SNDBUF, RCVBUF
441
- option_value.abs
442
- when MCAST_LOOP
443
- option_value ? 1 : 0
444
- else
445
- option_value
313
+ end # module CommonSocketBehavior
314
+
315
+
316
+ module IdentitySupport
317
+
318
+ # Convenience method for getting the value of the socket IDENTITY.
319
+ #
320
+ def identity
321
+ array = []
322
+ getsockopt IDENTITY, array
323
+ array.at(0)
446
324
  end
447
- end
448
-
449
- def release_cache
450
- @sockopt_cache.clear
451
- end
452
-
453
- # require a minimum of 0mq 2.1.0 to support socket finalizers; it contains important
454
- # fixes for sockets and threads so that a garbage collector thread can successfully
455
- # reap this resource without crashing
456
- if Util.minimum_api?([2, 1, 0])
457
- def define_finalizer
458
- ObjectSpace.define_finalizer(self, self.class.close(@socket))
325
+
326
+ # Convenience method for setting the value of the socket IDENTITY.
327
+ #
328
+ def identity=(value)
329
+ setsockopt IDENTITY, value.to_s
459
330
  end
460
- else
461
- def define_finalizer
462
- # no op
331
+
332
+
333
+ private
334
+
335
+ def string_option? name
336
+ super ||
337
+ IDENTITY == name
463
338
  end
464
- end
339
+ end # module IdentitySupport
340
+
341
+
342
+ if LibZMQ.version2?
343
+
344
+ class Socket
345
+ # Inclusion order is *important* since later modules may have a call
346
+ # to #super. We want those calls to go up the chain in a particular
347
+ # order
348
+ include CommonSocketBehavior
349
+ include IdentitySupport
350
+
351
+ # Get the options set on this socket.
352
+ #
353
+ # +name+ determines the socket option to request
354
+ # +array+ should be an empty array; a result of the proper type
355
+ # (numeric, string, boolean) will be inserted into
356
+ # the first position.
357
+ #
358
+ # Valid +option_name+ values:
359
+ # ZMQ::RCVMORE - true or false
360
+ # ZMQ::HWM - integer
361
+ # ZMQ::SWAP - integer
362
+ # ZMQ::AFFINITY - bitmap in an integer
363
+ # ZMQ::IDENTITY - string
364
+ # ZMQ::RATE - integer
365
+ # ZMQ::RECOVERY_IVL - integer
366
+ # ZMQ::MCAST_LOOP - true or false
367
+ # ZMQ::SNDBUF - integer
368
+ # ZMQ::RCVBUF - integer
369
+ # ZMQ::FD - fd in an integer
370
+ # ZMQ::EVENTS - bitmap integer
371
+ # ZMQ::LINGER - integer measured in milliseconds
372
+ # ZMQ::RECONNECT_IVL - integer measured in milliseconds
373
+ # ZMQ::BACKLOG - integer
374
+ # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
375
+ #
376
+ # Returns 0 when the operation completed successfully.
377
+ # Returns -1 when this operation failed.
378
+ #
379
+ # With a -1 return code, the user must check ZMQ.errno to determine the
380
+ # cause.
381
+ #
382
+ # # retrieve high water mark
383
+ # array = []
384
+ # rc = socket.getsockopt(ZMQ::HWM, array)
385
+ # hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
386
+ #
387
+ def getsockopt name, array
388
+ rc = __getsockopt__ name, array
389
+
390
+ if Util.resultcode_ok?(rc) && (RCVMORE == name || MCAST_LOOP == name)
391
+ # convert to boolean
392
+ array[0] = 1 == array[0]
393
+ end
394
+
395
+ rc
396
+ end
397
+
398
+ # Queues the message for transmission. Message is assumed to conform to the
399
+ # same public API as #Message.
400
+ #
401
+ # +flags+ may take two values:
402
+ # * 0 (default) - blocking operation
403
+ # * ZMQ::NOBLOCK - non-blocking operation
404
+ # * ZMQ::SNDMORE - this message is part of a multi-part message
405
+ #
406
+ # Returns 0 when the message was successfully enqueued.
407
+ # Returns -1 under two conditions.
408
+ # 1. The message could not be enqueued
409
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
410
+ #
411
+ # With a -1 return code, the user must check ZMQ.errno to determine the
412
+ # cause.
413
+ #
414
+ # The application code is responsible for handling the +message+ object
415
+ # lifecycle when #send returns. Regardless of the return code, the user
416
+ # is responsible for calling message.close to free the memory in use.
417
+ #
418
+ def send message, flags = 0
419
+ LibZMQ.zmq_send @socket, message.address, flags
420
+ end
421
+
422
+ # Helper method to make a new #Message instance out of the +message_string+ passed
423
+ # in for transmission.
424
+ #
425
+ # +flags+ may be ZMQ::NOBLOCK.
426
+ #
427
+ # Returns 0 when the message was successfully enqueued.
428
+ # Returns -1 under two conditions.
429
+ # 1. The message could not be enqueued
430
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
431
+ #
432
+ # With a -1 return code, the user must check ZMQ.errno to determine the
433
+ # cause.
434
+ #
435
+ def send_string message_string, flags = 0
436
+ message = Message.new message_string
437
+ send_and_close message, flags
438
+ end
439
+
440
+ # Send a sequence of strings as a multipart message out of the +parts+
441
+ # passed in for transmission. Every element of +parts+ should be
442
+ # a String.
443
+ #
444
+ # +flags+ may be ZMQ::NOBLOCK.
445
+ #
446
+ # Returns 0 when the message was successfully enqueued.
447
+ # Returns -1 under two conditions.
448
+ # 1. The message could not be enqueued
449
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
450
+ #
451
+ # With a -1 return code, the user must check ZMQ.errno to determine the
452
+ # cause.
453
+ #
454
+ def send_strings parts, flags = 0
455
+ return -1 if !parts || parts.empty?
456
+
457
+ parts[0..-2].each do |part|
458
+ rc = send_string part, flags | ZMQ::SNDMORE
459
+ return rc unless Util.resultcode_ok?(rc)
460
+ end
461
+
462
+ send_string parts[-1], flags
463
+ end
464
+
465
+ # Send a sequence of messages as a multipart message out of the +parts+
466
+ # passed in for transmission. Every element of +parts+ should be
467
+ # a Message (or subclass).
468
+ #
469
+ # +flags+ may be ZMQ::NOBLOCK.
470
+ #
471
+ # Returns 0 when the message was successfully enqueued.
472
+ # Returns -1 under two conditions.
473
+ # 1. The message could not be enqueued
474
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
475
+ #
476
+ # With a -1 return code, the user must check ZMQ.errno to determine the
477
+ # cause.
478
+ #
479
+ def sendmsgs parts, flags = 0
480
+ return -1 if !parts || parts.empty?
481
+
482
+ parts[0..-2].each do |part|
483
+ rc = send part, flags | ZMQ::SNDMORE
484
+ return rc unless Util.resultcode_ok?(rc)
485
+ end
486
+
487
+ send parts[-1], flags
488
+ end
489
+
490
+ # Sends a message. This will automatically close the +message+ for both successful
491
+ # and failed sends.
492
+ #
493
+ # Returns 0 when the message was successfully enqueued.
494
+ # Returns -1 under two conditions.
495
+ # 1. The message could not be enqueued
496
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
497
+ #
498
+ # With a -1 return code, the user must check ZMQ.errno to determine the
499
+ # cause.
500
+ #
501
+ def send_and_close message, flags = 0
502
+ rc = send message, flags
503
+ message.close
504
+ rc
505
+ end
506
+
507
+ # Dequeues a message from the underlying queue. By default, this is a blocking operation.
508
+ #
509
+ # +flags+ may take two values:
510
+ # 0 (default) - blocking operation
511
+ # ZMQ::NOBLOCK - non-blocking operation
512
+ #
513
+ # Returns 0 when the message was successfully dequeued.
514
+ # Returns -1 under two conditions.
515
+ # 1. The message could not be dequeued
516
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
517
+ #
518
+ # With a -1 return code, the user must check ZMQ.errno to determine the
519
+ # cause.
520
+ #
521
+ # The application code is responsible for handling the +message+ object lifecycle
522
+ # when #recv returns an error code.
523
+ #
524
+ def recv message, flags = 0
525
+ LibZMQ.zmq_recv @socket, message.address, flags
526
+ end
527
+
528
+ # Converts the received message to a string and replaces the +string+ arg
529
+ # contents.
530
+ #
531
+ # +string+ should be an empty string, .e.g. ''
532
+ # +flags+ may be ZMQ::NOBLOCK.
533
+ #
534
+ # Returns 0 when the message was successfully dequeued.
535
+ # Returns -1 under two conditions.
536
+ # 1. The message could not be dequeued
537
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
538
+ #
539
+ # With a -1 return code, the user must check ZMQ.errno to determine the
540
+ # cause.
541
+ #
542
+ def recv_string string, flags = 0
543
+ message = @receiver_klass.new
544
+ rc = recv message, flags
545
+ string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
546
+ message.close
547
+ rc
548
+ end
549
+
550
+ # Receive a multipart message as a list of strings.
551
+ #
552
+ # +list+ should be an object that responds to #append or #<< so received
553
+ # strings can be appended to it
554
+ # +flag+ may be ZMQ::NOBLOCK. Any other flag will be removed
555
+ #
556
+ # Returns 0 when all messages were successfully dequeued.
557
+ # Returns -1 under two conditions.
558
+ # 1. A message could not be dequeued
559
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
560
+ #
561
+ # With a -1 return code, the user must check ZMQ.errno to determine the
562
+ # cause. Also, the +list+ will not be modified when there was an error.
563
+ #
564
+ def recv_strings list, flag = 0
565
+ array = []
566
+ rc = recvmsgs array, flag
567
+
568
+ if Util.resultcode_ok?(rc)
569
+ array.each do |message|
570
+ list << message.copy_out_string
571
+ message.close
572
+ end
573
+ end
574
+
575
+ rc
576
+ end
577
+
578
+ # Receive a multipart message as an array of objects
579
+ # (by default these are instances of Message).
580
+ #
581
+ # +list+ should be an object that responds to #append or #<< so received
582
+ # messages can be appended to it
583
+ # +flag+ may be ZMQ::NOBLOCK. Any other flag will be
584
+ # removed.
585
+ #
586
+ # Returns 0 when all messages were successfully dequeued.
587
+ # Returns -1 under two conditions.
588
+ # 1. A message could not be dequeued
589
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
590
+ #
591
+ # With a -1 return code, the user must check ZMQ.errno to determine the
592
+ # cause. Also, the +list+ will not be modified when there was an error.
593
+ #
594
+ def recvmsgs list, flag = 0
595
+ flag = NOBLOCK if noblock?(flag)
596
+
597
+ parts = []
598
+ message = @receiver_klass.new
599
+ rc = recv message, flag
600
+ parts << message
601
+
602
+ # check rc *first*; necessary because the call to #more_parts? can reset
603
+ # the zmq_errno to a weird value, so the zmq_errno that was set on the
604
+ # call to #recv gets lost
605
+ while Util.resultcode_ok?(rc) && more_parts?
606
+ message = @receiver_klass.new
607
+ rc = recv message, flag
608
+ parts << message
609
+ end
610
+
611
+ # only append the received parts if there were no errors
612
+ # FIXME:
613
+ # need to detect EAGAIN if flag is set; EAGAIN means we have read all that we
614
+ # can and should return whatever was already read; need a spec!
615
+ if Util.resultcode_ok?(rc)
616
+ parts.each { |part| list << part }
617
+ end
618
+
619
+ rc
620
+ end
621
+
622
+
623
+ private
624
+
625
+ def noblock? flags
626
+ (NOBLOCK & flags) == NOBLOCK
627
+ end
628
+
629
+ def int_option? name
630
+ super ||
631
+ RECONNECT_IVL_MAX == name
632
+ end
633
+
634
+ def long_long_option? name
635
+ super ||
636
+ HWM == name ||
637
+ SWAP == name ||
638
+ RATE == name ||
639
+ RECOVERY_IVL == name ||
640
+ RECOVERY_IVL_MSEC == name ||
641
+ MCAST_LOOP == name ||
642
+ SNDBUF == name ||
643
+ RCVBUF == name
644
+ end
645
+
646
+ # these finalizer-related methods cannot live in the CommonSocketBehavior
647
+ # module; they *must* be in the class definition directly
648
+
649
+ def define_finalizer
650
+ ObjectSpace.define_finalizer(self, self.class.close(@socket))
651
+ end
652
+
653
+ def remove_finalizer
654
+ ObjectSpace.undefine_finalizer self
655
+ end
656
+
657
+ def self.close socket
658
+ Proc.new { LibZMQ.zmq_close socket }
659
+ end
660
+ end # class Socket for version2
661
+
662
+ end # LibZMQ.version2?
663
+
664
+
665
+ if LibZMQ.version3? || LibZMQ.version4?
666
+ class Socket
667
+ include CommonSocketBehavior
668
+ include IdentitySupport
669
+
670
+ # Get the options set on this socket.
671
+ #
672
+ # +name+ determines the socket option to request
673
+ # +array+ should be an empty array; a result of the proper type
674
+ # (numeric, string, boolean) will be inserted into
675
+ # the first position.
676
+ #
677
+ # Valid +option_name+ values:
678
+ # ZMQ::RCVMORE - true or false
679
+ # ZMQ::HWM - integer
680
+ # ZMQ::SWAP - integer
681
+ # ZMQ::AFFINITY - bitmap in an integer
682
+ # ZMQ::IDENTITY - string
683
+ # ZMQ::RATE - integer
684
+ # ZMQ::RECOVERY_IVL - integer
685
+ # ZMQ::SNDBUF - integer
686
+ # ZMQ::RCVBUF - integer
687
+ # ZMQ::FD - fd in an integer
688
+ # ZMQ::EVENTS - bitmap integer
689
+ # ZMQ::LINGER - integer measured in milliseconds
690
+ # ZMQ::RECONNECT_IVL - integer measured in milliseconds
691
+ # ZMQ::BACKLOG - integer
692
+ # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
693
+ #
694
+ # Returns 0 when the operation completed successfully.
695
+ # Returns -1 when this operation failed.
696
+ #
697
+ # With a -1 return code, the user must check ZMQ.errno to determine the
698
+ # cause.
699
+ #
700
+ # # retrieve high water mark
701
+ # array = []
702
+ # rc = socket.getsockopt(ZMQ::HWM, array)
703
+ # hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
704
+ #
705
+ def getsockopt name, array
706
+ rc = __getsockopt__ name, array
707
+
708
+ if Util.resultcode_ok?(rc) && (RCVMORE == name)
709
+ # convert to boolean
710
+ array[0] = 1 == array[0]
711
+ end
712
+
713
+ rc
714
+ end
715
+
716
+ # The last message part received is tested to see if it is a label.
717
+ #
718
+ # Equivalent to calling Socket#getsockopt with ZMQ::RCVLABEL.
719
+ #
720
+ # Warning: if the call to #getsockopt fails, this method will return
721
+ # false and swallow the error.
722
+ #
723
+ # labels = []
724
+ # message_parts = []
725
+ # message = Message.new
726
+ # rc = socket.recv(message)
727
+ # if ZMQ::Util.resultcode_ok?(rc)
728
+ # label? ? labels.push(message) : message_parts.push(message)
729
+ # while more_parts?
730
+ # message = Message.new
731
+ # if ZMQ::Util.resulcode_ok?(socket.recv(message))
732
+ # label? ? labels.push(message) : message_parts.push(message)
733
+ # end
734
+ # end
735
+ # end
736
+ #
737
+ def label?
738
+ array = []
739
+ rc = getsockopt ZMQ::RCVLABEL, array
740
+
741
+ Util.resultcode_ok?(rc) ? array.at(0) : false
742
+ end
743
+
744
+ # Queues the message for transmission. Message is assumed to conform to the
745
+ # same public API as #Message.
746
+ #
747
+ # +flags+ may take two values:
748
+ # * 0 (default) - blocking operation
749
+ # * ZMQ::DONTWAIT - non-blocking operation
750
+ # * ZMQ::SNDMORE - this message is part of a multi-part message
751
+ #
752
+ # Returns 0 when the message was successfully enqueued.
753
+ # Returns -1 under two conditions.
754
+ # 1. The message could not be enqueued
755
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
756
+ #
757
+ # With a -1 return code, the user must check ZMQ.errno to determine the
758
+ # cause.
759
+ #
760
+ def sendmsg message, flags = 0
761
+ LibZMQ.zmq_sendmsg @socket, message.address, flags
762
+ end
763
+
764
+ # Helper method to make a new #Message instance out of the +string+ passed
765
+ # in for transmission.
766
+ #
767
+ # +flags+ may be ZMQ::DONTWAIT and ZMQ::SNDMORE.
768
+ #
769
+ # Returns 0 when the message was successfully enqueued.
770
+ # Returns -1 under two conditions.
771
+ # 1. The message could not be enqueued
772
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
773
+ #
774
+ # With a -1 return code, the user must check ZMQ.errno to determine the
775
+ # cause.
776
+ #
777
+ def send_string string, flags = 0
778
+ message = Message.new string
779
+ send_and_close message, flags
780
+ end
781
+
782
+ # Send a sequence of strings as a multipart message out of the +parts+
783
+ # passed in for transmission. Every element of +parts+ should be
784
+ # a String.
785
+ #
786
+ # +flags+ may be ZMQ::DONTWAIT.
787
+ #
788
+ # Returns 0 when the messages were successfully enqueued.
789
+ # Returns -1 under two conditions.
790
+ # 1. A message could not be enqueued
791
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
792
+ #
793
+ # With a -1 return code, the user must check ZMQ.errno to determine the
794
+ # cause.
795
+ #
796
+ def send_strings parts, flags = 0
797
+ return -1 if !parts || parts.empty?
798
+ flags = DONTWAIT if dontwait?(flags)
799
+
800
+ parts[0..-2].each do |part|
801
+ rc = send_string part, (flags | ZMQ::SNDMORE)
802
+ return rc unless Util.resultcode_ok?(rc)
803
+ end
804
+
805
+ send_string parts[-1], flags
806
+ end
807
+
808
+ # Send a sequence of messages as a multipart message out of the +parts+
809
+ # passed in for transmission. Every element of +parts+ should be
810
+ # a Message (or subclass).
811
+ #
812
+ # +flags+ may be ZMQ::DONTWAIT.
813
+ #
814
+ # Returns 0 when the messages were successfully enqueued.
815
+ # Returns -1 under two conditions.
816
+ # 1. A message could not be enqueued
817
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
818
+ #
819
+ # With a -1 return code, the user must check ZMQ.errno to determine the
820
+ # cause.
821
+ #
822
+ def sendmsgs parts, flags = 0
823
+ return -1 if !parts || parts.empty?
824
+ flags = DONTWAIT if dontwait?(flags)
825
+
826
+ parts[0..-2].each do |part|
827
+ rc = sendmsg part, (flags | ZMQ::SNDMORE)
828
+ return rc unless Util.resultcode_ok?(rc)
829
+ end
830
+
831
+ sendmsg parts[-1], flags
832
+ end
833
+
834
+ # Sends a message. This will automatically close the +message+ for both successful
835
+ # and failed sends.
836
+ #
837
+ # Returns 0 when the message was successfully enqueued.
838
+ # Returns -1 under two conditions.
839
+ # 1. The message could not be enqueued
840
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
841
+ #
842
+ # With a -1 return code, the user must check ZMQ.errno to determine the
843
+ # cause.
844
+ #
845
+ def send_and_close message, flags = 0
846
+ rc = sendmsg message, flags
847
+ message.close
848
+ rc
849
+ end
850
+
851
+ # Dequeues a message from the underlying queue. By default, this is a blocking operation.
852
+ #
853
+ # +flags+ may take two values:
854
+ # 0 (default) - blocking operation
855
+ # ZMQ::DONTWAIT - non-blocking operation
856
+ #
857
+ # Returns 0 when the message was successfully dequeued.
858
+ # Returns -1 under two conditions.
859
+ # 1. The message could not be dequeued
860
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
861
+ #
862
+ # With a -1 return code, the user must check ZMQ.errno to determine the
863
+ # cause.
864
+ #
865
+ # The application code is responsible for handling the +message+ object lifecycle
866
+ # when #recv returns an error code.
867
+ #
868
+ def recvmsg message, flags = 0
869
+ LibZMQ.zmq_recvmsg @socket, message.address, flags
870
+ end
871
+
872
+ # Helper method to make a new #Message instance and convert its payload
873
+ # to a string.
874
+ #
875
+ # +flags+ may be ZMQ::DONTWAIT.
876
+ #
877
+ # Returns 0 when the message was successfully dequeued.
878
+ # Returns -1 under two conditions.
879
+ # 1. The message could not be dequeued
880
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
881
+ #
882
+ # With a -1 return code, the user must check ZMQ.errno to determine the
883
+ # cause.
884
+ #
885
+ # The application code is responsible for handling the +message+ object lifecycle
886
+ # when #recv returns an error code.
887
+ #
888
+ def recv_string string, flags = 0
889
+ message = @receiver_klass.new
890
+ rc = recvmsg message, flags
891
+ string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
892
+ message.close
893
+ rc
894
+ end
465
895
 
466
- def remove_finalizer
467
- ObjectSpace.undefine_finalizer self
468
- end
896
+ # Receive a multipart message as a list of strings.
897
+ #
898
+ # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
899
+ # removed.
900
+ #
901
+ def recv_strings list, flag = 0
902
+ array = []
903
+ rc = recvmsgs array, flag
904
+
905
+ if Util.resultcode_ok?(rc)
906
+ array.each do |message|
907
+ list << message.copy_out_string
908
+ message.close
909
+ end
910
+ end
911
+
912
+ rc
913
+ end
914
+
915
+ # Receive a multipart message as an array of objects
916
+ # (by default these are instances of Message).
917
+ #
918
+ # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
919
+ # removed.
920
+ #
921
+ # Raises the same exceptions as Socket#recv.
922
+ #
923
+ def recvmsgs list, flag = 0
924
+ flag = DONTWAIT if dontwait?(flag)
925
+
926
+ parts = []
927
+ message = @receiver_klass.new
928
+ rc = recvmsg message, flag
929
+ parts << message
930
+
931
+ # check rc *first*; necessary because the call to #more_parts? can reset
932
+ # the zmq_errno to a weird value, so the zmq_errno that was set on the
933
+ # call to #recv gets lost
934
+ while Util.resultcode_ok?(rc) && more_parts?
935
+ message = @receiver_klass.new
936
+ rc = recvmsg message, flag
937
+ parts << message
938
+ end
939
+
940
+ # only append the received parts if there were no errors
941
+ if Util.resultcode_ok?(rc)
942
+ parts.each { |part| list << part }
943
+ end
944
+
945
+ rc
946
+ end
947
+
948
+
949
+ private
469
950
 
470
- def self.close socket
471
- Proc.new { LibZMQ.zmq_close socket }
472
- end
473
- end # class Socket
951
+ def dontwait? flags
952
+ (DONTWAIT & flags) == DONTWAIT
953
+ end
954
+ alias :noblock? :dontwait?
955
+
956
+ def int_option? name
957
+ super ||
958
+ RCVLABEL == name ||
959
+ RECONNECT_IVL_MAX == name ||
960
+ RCVHWM == name ||
961
+ SNDHWM == name ||
962
+ RATE == name ||
963
+ RECOVERY_IVL == name ||
964
+ SNDBUF == name ||
965
+ RCVBUF == name
966
+ end
967
+
968
+ # these finalizer-related methods cannot live in the CommonSocketBehavior
969
+ # module; they *must* be in the class definition directly
970
+
971
+ def define_finalizer
972
+ ObjectSpace.define_finalizer(self, self.class.close(@socket))
973
+ end
974
+
975
+ def remove_finalizer
976
+ ObjectSpace.undefine_finalizer self
977
+ end
978
+
979
+ def self.close socket
980
+ Proc.new { LibZMQ.zmq_close socket }
981
+ end
982
+ end # Socket for version3
983
+ end # LibZMQ.version3?
474
984
 
475
985
  end # module ZMQ