ffi-rzmq 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +92 -0
- data/README.rdoc +162 -0
- data/Rakefile +19 -0
- data/examples/local_lat.rb +43 -0
- data/examples/local_lat_zerocopy.rb +24 -0
- data/examples/publish_subscribe.rb +52 -0
- data/examples/remote_lat.rb +53 -0
- data/examples/remote_lat_zerocopy.rb +35 -0
- data/examples/reqrep_poll.rb +49 -0
- data/examples/request_response.rb +23 -0
- data/ffi-rzmq.gemspec +43 -0
- data/lib/ffi-rzmq.rb +71 -0
- data/lib/ffi-rzmq/context.rb +99 -0
- data/lib/ffi-rzmq/exceptions.rb +145 -0
- data/lib/ffi-rzmq/message.rb +210 -0
- data/lib/ffi-rzmq/poll.rb +186 -0
- data/lib/ffi-rzmq/poll_items.rb +98 -0
- data/lib/ffi-rzmq/socket.rb +344 -0
- data/lib/ffi-rzmq/wrapper.rb +120 -0
- data/lib/ffi-rzmq/zmq.rb +147 -0
- data/spec/context_spec.rb +96 -0
- data/spec/reqrep_spec.rb +56 -0
- data/spec/socket_spec.rb +111 -0
- data/spec/spec_helper.rb +38 -0
- data/version.txt +1 -0
- metadata +113 -0
@@ -0,0 +1,344 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
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
|
14
|
+
include ZMQ::Util
|
15
|
+
|
16
|
+
attr_reader :socket
|
17
|
+
|
18
|
+
# By default, this class uses ZMQ::Message for regular Ruby
|
19
|
+
# memory management.
|
20
|
+
#
|
21
|
+
# +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
|
22
|
+
# ZMQ::SUB, ZMQ::PAIR, ZMQ::UPSTREAM, ZMQ::DOWNSTREAM,
|
23
|
+
# ZMQ::XREQ or ZMQ::XREP.
|
24
|
+
#
|
25
|
+
# Can raise two kinds of exceptions depending on the error.
|
26
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
27
|
+
# #Context. See #ContextError.
|
28
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
29
|
+
#
|
30
|
+
def initialize context_ptr, type
|
31
|
+
# maybe at some point we'll want to allow users to override this with their
|
32
|
+
# own classes? Or is this a YAGNI mistake?
|
33
|
+
@sender_klass = ZMQ::Message
|
34
|
+
@receiver_klass = ZMQ::Message
|
35
|
+
|
36
|
+
unless context_ptr.null?
|
37
|
+
@socket = LibZMQ.zmq_socket context_ptr, type
|
38
|
+
error_check ZMQ_SOCKET_STR, @socket.null? ? 1 : 0
|
39
|
+
else
|
40
|
+
raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Context pointer was null"
|
41
|
+
end
|
42
|
+
|
43
|
+
define_finalizer
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set the queue options on this socket.
|
47
|
+
#
|
48
|
+
# Valid +option_name+ values that take a numeric +option_value+ are:
|
49
|
+
# ZMQ::HWM
|
50
|
+
# ZMQ::SWAP
|
51
|
+
# ZMQ::AFFINITY
|
52
|
+
# ZMQ::RATE
|
53
|
+
# ZMQ::RECOVERY_IVL
|
54
|
+
# ZMQ::MCAST_LOOP
|
55
|
+
#
|
56
|
+
# Valid +option_name+ values that take a string +option_value+ are:
|
57
|
+
# ZMQ::IDENTITY
|
58
|
+
# ZMQ::SUBSCRIBE
|
59
|
+
# ZMQ::UNSUBSCRIBE
|
60
|
+
#
|
61
|
+
# Can raise two kinds of exceptions depending on the error.
|
62
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
63
|
+
# #Context. See #ContextError.
|
64
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
65
|
+
#
|
66
|
+
def setsockopt option_name, option_value, option_len = nil
|
67
|
+
begin
|
68
|
+
case option_name
|
69
|
+
when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP
|
70
|
+
option_value_ptr = LibC.malloc option_value.size
|
71
|
+
option_value_ptr.write_long option_value
|
72
|
+
|
73
|
+
when IDENTITY, SUBSCRIBE, UNSUBSCRIBE
|
74
|
+
# note: not checking errno for failed memory allocations :(
|
75
|
+
option_value_ptr = LibC.malloc option_value.size
|
76
|
+
option_value_ptr.write_string option_value
|
77
|
+
|
78
|
+
else
|
79
|
+
# we didn't understand the passed option argument
|
80
|
+
# will force a raise due to EINVAL being non-zero
|
81
|
+
error_check ZMQ_SETSOCKOPT_STR, EINVAL
|
82
|
+
end
|
83
|
+
|
84
|
+
result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len || option_value.size
|
85
|
+
error_check ZMQ_SETSOCKOPT_STR, result_code
|
86
|
+
ensure
|
87
|
+
LibC.free option_value_ptr unless option_value_ptr.nil? || option_value_ptr.null?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get the options set on this socket. Returns a value dependent upon
|
92
|
+
# the +option_name+ requested.
|
93
|
+
#
|
94
|
+
# Valid +option_name+ values and their return types:
|
95
|
+
# ZMQ::RCVMORE - boolean
|
96
|
+
# ZMQ::HWM - integer
|
97
|
+
# ZMQ::SWAP - integer
|
98
|
+
# ZMQ::AFFINITY - bitmap in an integer
|
99
|
+
# ZMQ::IDENTITY - string
|
100
|
+
# ZMQ::RATE - integer
|
101
|
+
# ZMQ::RECOVERY_IVL - integer
|
102
|
+
# ZMQ::MCAST_LOOP - boolean
|
103
|
+
# ZMQ::SNDBUF - integer
|
104
|
+
# ZMQ::RCVBUF - integer
|
105
|
+
#
|
106
|
+
# Can raise two kinds of exceptions depending on the error.
|
107
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
108
|
+
# #Context. See #ContextError.
|
109
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
110
|
+
#
|
111
|
+
def getsockopt option_name
|
112
|
+
begin
|
113
|
+
option_value = FFI::MemoryPointer.new :pointer
|
114
|
+
option_length = FFI::MemoryPointer.new :size_t
|
115
|
+
|
116
|
+
unless [RCVMORE, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP,
|
117
|
+
IDENTITY, SNDBUF, RCVBUF].include? option_name
|
118
|
+
# we didn't understand the passed option argument
|
119
|
+
# will force a raise
|
120
|
+
error_check ZMQ_SETSOCKOPT_STR, -1
|
121
|
+
end
|
122
|
+
|
123
|
+
option_value, option_length = alloc_temp_sockopt_buffers option_name
|
124
|
+
|
125
|
+
result_code = LibZMQ.zmq_getsockopt @socket, option_name, option_value, option_length
|
126
|
+
error_check ZMQ_GETSOCKOPT_STR, result_code
|
127
|
+
ret = 0
|
128
|
+
|
129
|
+
case option_name
|
130
|
+
when RCVMORE, MCAST_LOOP
|
131
|
+
# boolean return
|
132
|
+
ret = option_value.read_long_long != 0
|
133
|
+
when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF
|
134
|
+
ret = option_value.read_long_long
|
135
|
+
when IDENTITY
|
136
|
+
ret = option_value.read_string(option_length.read_long_long)
|
137
|
+
end
|
138
|
+
|
139
|
+
ret
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Convenience method for checking on additional message parts.
|
144
|
+
#
|
145
|
+
# Equivalent to Socket#getsockopt ZMQ::RCVMORE
|
146
|
+
#
|
147
|
+
def more_parts?
|
148
|
+
getsockopt ZMQ::RCVMORE
|
149
|
+
end
|
150
|
+
|
151
|
+
# Convenience method for getting the value of the socket IDENTITY.
|
152
|
+
#
|
153
|
+
def identity
|
154
|
+
getsockopt ZMQ::IDENTITY
|
155
|
+
end
|
156
|
+
|
157
|
+
# Convenience method for setting the value of the socket IDENTITY.
|
158
|
+
#
|
159
|
+
def identity= value
|
160
|
+
setsockopt ZMQ::IDENTITY, value.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
# Can raise two kinds of exceptions depending on the error.
|
164
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
165
|
+
# #Context. See #ContextError.
|
166
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
167
|
+
#
|
168
|
+
def bind address
|
169
|
+
result_code = LibZMQ.zmq_bind @socket, address
|
170
|
+
error_check ZMQ_BIND_STR, result_code
|
171
|
+
end
|
172
|
+
|
173
|
+
# Can raise two kinds of exceptions depending on the error.
|
174
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
175
|
+
# #Context. See #ContextError.
|
176
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
177
|
+
#
|
178
|
+
def connect address
|
179
|
+
result_code = LibZMQ.zmq_connect @socket, address
|
180
|
+
error_check ZMQ_CONNECT_STR, result_code
|
181
|
+
end
|
182
|
+
|
183
|
+
# Closes the socket. Any unprocessed messages in queue are dropped.
|
184
|
+
#
|
185
|
+
def close
|
186
|
+
remove_finalizer
|
187
|
+
result_code = LibZMQ.zmq_close @socket
|
188
|
+
error_check ZMQ_CLOSE_STR, result_code
|
189
|
+
end
|
190
|
+
|
191
|
+
# Queues the message for transmission. Message is assumed to be an instance or
|
192
|
+
# subclass of #Message.
|
193
|
+
#
|
194
|
+
# +flags+ may take two values:
|
195
|
+
# * 0 (default) - blocking operation
|
196
|
+
# * ZMQ::NOBLOCK - non-blocking operation
|
197
|
+
# * ZMQ::SNDMORE - this message is part of a multi-part message
|
198
|
+
#
|
199
|
+
# Returns true when the message was successfully enqueued.
|
200
|
+
# Returns false when the message could not be enqueued *and* +flags+ is set
|
201
|
+
# with ZMQ::NOBLOCK.
|
202
|
+
#
|
203
|
+
# The application code is *not* responsible for handling the +message+ object
|
204
|
+
# lifecycle when #send return ZMQ::NOBLOCK or it raises an exception. The
|
205
|
+
# #send method takes ownership of the +message+ and its associated buffers.
|
206
|
+
# A failed call will release the +message+ data buffer.
|
207
|
+
#
|
208
|
+
# Again, once a +message+ object has been passed to this method,
|
209
|
+
# do not try to access its #data buffer anymore. The 0mq library now owns it.
|
210
|
+
#
|
211
|
+
# Can raise two kinds of exceptions depending on the error.
|
212
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
213
|
+
# #Context. See #ContextError.
|
214
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
215
|
+
#
|
216
|
+
def send message, flags = 0
|
217
|
+
begin
|
218
|
+
result_code = LibZMQ.zmq_send @socket, message.address, flags
|
219
|
+
|
220
|
+
# when the flag isn't set, do a normal error check
|
221
|
+
# when set, check to see if the message was successfully queued
|
222
|
+
queued = flags != NOBLOCK ? error_check(ZMQ_SEND_STR, result_code) : error_check_nonblock(result_code)
|
223
|
+
ensure
|
224
|
+
message.close
|
225
|
+
end
|
226
|
+
|
227
|
+
# true if sent, false if failed/EAGAIN
|
228
|
+
queued
|
229
|
+
end
|
230
|
+
|
231
|
+
# Helper method to make a new #Message instance out of the +message_string+ passed
|
232
|
+
# in for transmission.
|
233
|
+
#
|
234
|
+
# +flags+ may be ZMQ::NOBLOCK.
|
235
|
+
#
|
236
|
+
# Can raise two kinds of exceptions depending on the error.
|
237
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
238
|
+
# #Context. See #ContextError.
|
239
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
240
|
+
#
|
241
|
+
def send_string message_string, flags = 0
|
242
|
+
message = @sender_klass.new
|
243
|
+
message.copy_in_string message_string
|
244
|
+
result = send message, flags
|
245
|
+
result
|
246
|
+
end
|
247
|
+
|
248
|
+
# Dequeues a message from the underlying queue. By default, this is a blocking operation.
|
249
|
+
#
|
250
|
+
# +flags+ may take two values:
|
251
|
+
# 0 (default) - blocking operation
|
252
|
+
# ZMQ::NOBLOCK - non-blocking operation
|
253
|
+
#
|
254
|
+
# Returns a true when it successfully dequeues one from the queue. Also, the +message+
|
255
|
+
# object is populated by the library with a data buffer containing the received
|
256
|
+
# data.
|
257
|
+
#
|
258
|
+
# Returns nil when a message could not be dequeued *and* +flags+ is set
|
259
|
+
# with ZMQ::NOBLOCK. The +message+ object is not modified in this situation.
|
260
|
+
#
|
261
|
+
# The application code is *not* responsible for handling the +message+ object lifecycle
|
262
|
+
# when #recv raises an exception. The #recv method takes ownership of the
|
263
|
+
# +message+ and its associated buffers. A failed call will
|
264
|
+
# release the data buffers assigned to the +message+.
|
265
|
+
#
|
266
|
+
# Can raise two kinds of exceptions depending on the error.
|
267
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
268
|
+
# #Context. See #ContextError.
|
269
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
270
|
+
#
|
271
|
+
def recv message, flags = 0
|
272
|
+
begin
|
273
|
+
dequeued = _recv message, flags
|
274
|
+
rescue ZeroMQError
|
275
|
+
message.close
|
276
|
+
raise
|
277
|
+
end
|
278
|
+
|
279
|
+
dequeued ? true : nil
|
280
|
+
end
|
281
|
+
|
282
|
+
# Helper method to make a new #Message instance and convert its payload
|
283
|
+
# to a string.
|
284
|
+
#
|
285
|
+
# +flags+ may be ZMQ::NOBLOCK.
|
286
|
+
#
|
287
|
+
# Can raise two kinds of exceptions depending on the error.
|
288
|
+
# ContextError:: Raised when a socket operation is attempted on a terminated
|
289
|
+
# #Context. See #ContextError.
|
290
|
+
# SocketError:: See all of the possibilities in the docs for #SocketError.
|
291
|
+
#
|
292
|
+
def recv_string flags = 0
|
293
|
+
message = @receiver_klass.new
|
294
|
+
|
295
|
+
begin
|
296
|
+
dequeued = _recv message, flags
|
297
|
+
|
298
|
+
if dequeued
|
299
|
+
message.copy_out_string
|
300
|
+
else
|
301
|
+
nil
|
302
|
+
end
|
303
|
+
ensure
|
304
|
+
message.close
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def _recv message, flags = 0
|
311
|
+
result_code = LibZMQ.zmq_recv @socket, message.address, flags
|
312
|
+
|
313
|
+
flags != NOBLOCK ? error_check(ZMQ_RECV_STR, result_code) : error_check_nonblock(result_code)
|
314
|
+
end
|
315
|
+
|
316
|
+
def alloc_temp_sockopt_buffers option_name
|
317
|
+
length = FFI::MemoryPointer.new :int64
|
318
|
+
|
319
|
+
case option_name
|
320
|
+
when RCVMORE, MCAST_LOOP, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF
|
321
|
+
# int64_t
|
322
|
+
length.write_long_long 8
|
323
|
+
[FFI::MemoryPointer.new(:int64), length]
|
324
|
+
when IDENTITY
|
325
|
+
# could be a string of up to 255 bytes
|
326
|
+
length.write_long_long 255
|
327
|
+
[FFI::MemoryPointer.new(255), length]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def define_finalizer
|
332
|
+
ObjectSpace.define_finalizer(self, self.class.close(@socket))
|
333
|
+
end
|
334
|
+
|
335
|
+
def remove_finalizer
|
336
|
+
ObjectSpace.undefine_finalizer self
|
337
|
+
end
|
338
|
+
|
339
|
+
def self.close socket
|
340
|
+
Proc.new { LibZMQ.zmq_close socket }
|
341
|
+
end
|
342
|
+
end # class Socket
|
343
|
+
|
344
|
+
end # module ZMQ
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'ffi' unless RBX # external gem
|
2
|
+
|
3
|
+
module LibC
|
4
|
+
extend FFI::Library
|
5
|
+
# figures out the correct libc for each platform including Windows
|
6
|
+
ffi_lib FFI::Library::LIBC unless RBX
|
7
|
+
|
8
|
+
# memory allocators
|
9
|
+
attach_function :malloc, [:size_t], :pointer
|
10
|
+
attach_function :calloc, [:size_t], :pointer
|
11
|
+
attach_function :valloc, [:size_t], :pointer
|
12
|
+
attach_function :realloc, [:pointer, :size_t], :pointer
|
13
|
+
attach_function :free, [:pointer], :void
|
14
|
+
|
15
|
+
# memory movers
|
16
|
+
attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
|
17
|
+
attach_function :bcopy, [:pointer, :pointer, :size_t], :void
|
18
|
+
|
19
|
+
end # module LibC
|
20
|
+
|
21
|
+
module LibZMQ
|
22
|
+
extend FFI::Library
|
23
|
+
LINUX = ["libzmq", "/usr/local/lib/libzmq", "/opt/local/lib/libzmq"]
|
24
|
+
OSX = ["libzmq", "/usr/local/lib/libzmq", "/opt/local/lib/libzmq"]
|
25
|
+
WINDOWS = []
|
26
|
+
RBX ? ffi_lib(*(LINUX + OSX + WINDOWS)) : ffi_lib(LINUX + OSX + WINDOWS)
|
27
|
+
|
28
|
+
# Misc
|
29
|
+
attach_function :zmq_version, [:pointer, :pointer, :pointer], :void
|
30
|
+
|
31
|
+
# Context and misc api
|
32
|
+
attach_function :zmq_init, [:int], :pointer
|
33
|
+
attach_function :zmq_socket, [:pointer, :int], :pointer
|
34
|
+
attach_function :zmq_term, [:pointer], :int
|
35
|
+
attach_function :zmq_errno, [], :int
|
36
|
+
attach_function :zmq_strerror, [:int], :pointer
|
37
|
+
|
38
|
+
# Message api
|
39
|
+
attach_function :zmq_msg_init, [:pointer], :int
|
40
|
+
attach_function :zmq_msg_init_size, [:pointer, :size_t], :int
|
41
|
+
attach_function :zmq_msg_init_data, [:pointer, :pointer, :size_t, :pointer, :pointer], :int
|
42
|
+
attach_function :zmq_msg_close, [:pointer], :int
|
43
|
+
attach_function :zmq_msg_data, [:pointer], :pointer
|
44
|
+
attach_function :zmq_msg_size, [:pointer], :size_t
|
45
|
+
attach_function :zmq_msg_copy, [:pointer, :pointer], :int
|
46
|
+
attach_function :zmq_msg_move, [:pointer, :pointer], :int
|
47
|
+
|
48
|
+
unless RBX
|
49
|
+
MessageDeallocator = FFI::Function.new(:void, [:pointer, :pointer]) do |data_ptr, hint_ptr|
|
50
|
+
LibC.free data_ptr
|
51
|
+
end
|
52
|
+
MessageDeallocator.autorelease = false
|
53
|
+
end
|
54
|
+
|
55
|
+
# Used for casting pointers back to the struct
|
56
|
+
class Msg < FFI::Struct
|
57
|
+
layout :content, :pointer,
|
58
|
+
:flags, :uint8,
|
59
|
+
:vsm_size, :uint8,
|
60
|
+
:vsm_data, [:uint8, 30]
|
61
|
+
end # class Msg
|
62
|
+
|
63
|
+
|
64
|
+
# Socket api
|
65
|
+
# @blocking = true is a hint to FFI that the following (and only the following)
|
66
|
+
# function may block, therefore it should release the GIL before calling it.
|
67
|
+
# This can aid in situations where the function call will/may block and another
|
68
|
+
# thread within the lib may try to call back into the ruby runtime. Failure to
|
69
|
+
# release the GIL will result in a hang; the hint *may* allow things to run
|
70
|
+
# smoothly for Ruby runtimes hampered by a GIL.
|
71
|
+
attach_function :zmq_setsockopt, [:pointer, :int, :pointer, :int], :int
|
72
|
+
attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int
|
73
|
+
attach_function :zmq_bind, [:pointer, :string], :int
|
74
|
+
attach_function :zmq_connect, [:pointer, :string], :int
|
75
|
+
@blocking = true
|
76
|
+
attach_function :zmq_send, [:pointer, :pointer, :int], :int
|
77
|
+
@blocking = true
|
78
|
+
attach_function :zmq_recv, [:pointer, :pointer, :int], :int
|
79
|
+
attach_function :zmq_close, [:pointer], :int
|
80
|
+
|
81
|
+
# Poll api
|
82
|
+
@blocking = true
|
83
|
+
attach_function :zmq_poll, [:pointer, :int, :long], :int
|
84
|
+
|
85
|
+
module PollItemLayout
|
86
|
+
def self.included(base)
|
87
|
+
base.class_eval do
|
88
|
+
layout :socket, :pointer,
|
89
|
+
:fd, :int,
|
90
|
+
:events, :short,
|
91
|
+
:revents, :short
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end # module PollItemLayout
|
95
|
+
|
96
|
+
class PollItem < FFI::Struct
|
97
|
+
include PollItemLayout
|
98
|
+
|
99
|
+
def socket() self[:socket]; end
|
100
|
+
|
101
|
+
def readable?
|
102
|
+
(self[:revents] & ZMQ::POLLIN) > 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def writable?
|
106
|
+
(self[:revents] & ZMQ::POLLOUT) > 0
|
107
|
+
end
|
108
|
+
|
109
|
+
def both_accessible?
|
110
|
+
readable? && writable?
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"socket [#{socket}], fd [#{self[:fd]}], events [#{self[:events]}], revents [#{self[:revents]}]"
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s; inspect; end
|
118
|
+
end # class PollItem
|
119
|
+
|
120
|
+
end # module ZMQ
|