ffi-rzmq 0.5.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.
- 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,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
if ARGV.length < 3
|
5
|
+
puts "usage: remote_lat <connect-to> <message-size> <roundtrip-count>"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
connect_to = ARGV[0]
|
10
|
+
message_size = ARGV[1].to_i
|
11
|
+
roundtrip_count = ARGV[2].to_i
|
12
|
+
|
13
|
+
ctx = ZMQ::Context.new 1
|
14
|
+
s = ctx.socket ZMQ::REQ
|
15
|
+
s.connect connect_to
|
16
|
+
|
17
|
+
msg = ZMQ::Message.new "#{'3'*message_size}"
|
18
|
+
|
19
|
+
start_time = Time.now
|
20
|
+
|
21
|
+
roundtrip_count.times do
|
22
|
+
s.send msg, 0
|
23
|
+
result = s.recv msg, 0
|
24
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
25
|
+
end
|
26
|
+
|
27
|
+
end_time = Time.now
|
28
|
+
elapsed_secs = (end_time.to_f - start_time.to_f)
|
29
|
+
elapsed_usecs = elapsed_secs * 1000000
|
30
|
+
latency = elapsed_usecs / roundtrip_count / 2
|
31
|
+
|
32
|
+
puts "message size: %i [B]" % message_size
|
33
|
+
puts "roundtrip count: %i" % roundtrip_count
|
34
|
+
puts "throughput (msgs/s): %i" % (roundtrip_count / elapsed_secs)
|
35
|
+
puts "mean latency: %.3f [us]" % latency
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
|
5
|
+
link = "tcp://127.0.0.1:5555"
|
6
|
+
|
7
|
+
ctx = ZMQ::Context.new 1
|
8
|
+
s1 = ctx.socket ZMQ::REQ
|
9
|
+
s2 = ctx.socket ZMQ::REP
|
10
|
+
|
11
|
+
s1.connect link
|
12
|
+
s2.bind link
|
13
|
+
|
14
|
+
poller = ZMQ::Poller.new
|
15
|
+
poller.register_readable s2
|
16
|
+
poller.register_writable s1
|
17
|
+
|
18
|
+
start_time = Time.now
|
19
|
+
@unsent = true
|
20
|
+
|
21
|
+
until @done do
|
22
|
+
begin
|
23
|
+
poller.poll_nonblock
|
24
|
+
rescue ZMQ::PollError => e
|
25
|
+
puts "efault? [#{e.efault?}]"
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
# send the message after 5 seconds
|
30
|
+
if Time.now - start_time > 5 && @unsent
|
31
|
+
payload = "#{ '3' * 1024 }"
|
32
|
+
|
33
|
+
puts "sending payload nonblocking"
|
34
|
+
s1.send_string payload, ZMQ::NOBLOCK
|
35
|
+
@unsent = false
|
36
|
+
end
|
37
|
+
|
38
|
+
# check for messages after 1 second
|
39
|
+
if Time.now - start_time > 1
|
40
|
+
poller.readables.each do |sock|
|
41
|
+
received_msg = sock.recv_string ZMQ::NOBLOCK
|
42
|
+
|
43
|
+
puts "message received [#{received_msg}]"
|
44
|
+
@done = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "executed in [#{Time.now - start_time}] seconds"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
|
5
|
+
link = "tcp://127.0.0.1:5555"
|
6
|
+
|
7
|
+
ctx = ZMQ::Context.new 1
|
8
|
+
s1 = ctx.socket ZMQ::REQ
|
9
|
+
s2 = ctx.socket ZMQ::REP
|
10
|
+
|
11
|
+
s2.bind link
|
12
|
+
s1.connect link
|
13
|
+
|
14
|
+
payload = "#{ '3' * 2048 }"
|
15
|
+
sent_msg = ZMQ::Message.new payload
|
16
|
+
received_msg = ZMQ::Message.new
|
17
|
+
|
18
|
+
s1.send sent_msg
|
19
|
+
s2.recv received_msg
|
20
|
+
|
21
|
+
result = payload == received_msg.copy_out_string ? "Request received" : "Received wrong payload"
|
22
|
+
|
23
|
+
p result
|
data/ffi-rzmq.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{ffi-rzmq}
|
5
|
+
s.version = "0.5.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Chuck Remes"]
|
9
|
+
s.date = %q{2010-06-06}
|
10
|
+
s.description = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
|
11
|
+
function interface). It's a pure ruby wrapper so this gem can be loaded
|
12
|
+
and run by any ruby runtime that supports FFI. Right now that means
|
13
|
+
MRI 1.8.7, 1.9.1 and JRuby.
|
14
|
+
|
15
|
+
The impetus behind this library was to provide support for ZeroMQ in
|
16
|
+
JRuby which has native threads. Unlike MRI, MacRuby, IronRuby and
|
17
|
+
Rubinius which all have a GIL, JRuby allows for threaded access to ruby
|
18
|
+
code from outside extensions. ZeroMQ is heavily threaded, so until the
|
19
|
+
other runtimes remove their GIL, JRuby will likely be the best
|
20
|
+
environment to run this library.}
|
21
|
+
s.email = %q{cremes@mac.com}
|
22
|
+
s.extra_rdoc_files = ["History.txt", "README.rdoc", "version.txt"]
|
23
|
+
s.files = ["History.txt", "README.rdoc", "Rakefile", "examples/local_lat.rb", "examples/local_lat_zerocopy.rb", "examples/publish_subscribe.rb", "examples/remote_lat.rb", "examples/remote_lat_zerocopy.rb", "examples/reqrep_poll.rb", "examples/request_response.rb", "ffi-rzmq.gemspec", "lib/ffi-rzmq.rb", "lib/ffi-rzmq/context.rb", "lib/ffi-rzmq/exceptions.rb", "lib/ffi-rzmq/message.rb", "lib/ffi-rzmq/poll.rb", "lib/ffi-rzmq/poll_items.rb", "lib/ffi-rzmq/socket.rb", "lib/ffi-rzmq/wrapper.rb", "lib/ffi-rzmq/zmq.rb", "spec/context_spec.rb", "spec/reqrep_spec.rb", "spec/socket_spec.rb", "spec/spec_helper.rb", "version.txt"]
|
24
|
+
s.homepage = %q{http://github.com/chuckremes/ffi-rzmq}
|
25
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.rubyforge_project = %q{ffi-rzmq}
|
28
|
+
s.rubygems_version = %q{1.3.6}
|
29
|
+
s.summary = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign function interface)}
|
30
|
+
|
31
|
+
if s.respond_to? :specification_version then
|
32
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
33
|
+
s.specification_version = 3
|
34
|
+
|
35
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
36
|
+
s.add_development_dependency(%q<bones>, [">= 3.4.3"])
|
37
|
+
else
|
38
|
+
s.add_dependency(%q<bones>, [">= 3.4.3"])
|
39
|
+
end
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<bones>, [">= 3.4.3"])
|
42
|
+
end
|
43
|
+
end
|
data/lib/ffi-rzmq.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
# :stopdoc:
|
5
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
6
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
7
|
+
# :startdoc:
|
8
|
+
|
9
|
+
# Returns the version string for the library.
|
10
|
+
#
|
11
|
+
def self.version
|
12
|
+
@version ||= File.read(path('version.txt')).strip
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the library path for the module. If any arguments are given,
|
16
|
+
# they will be joined to the end of the libray path using
|
17
|
+
# <tt>File.join</tt>.
|
18
|
+
#
|
19
|
+
def self.libpath( *args, &block )
|
20
|
+
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
21
|
+
if block
|
22
|
+
begin
|
23
|
+
$LOAD_PATH.unshift LIBPATH
|
24
|
+
rv = block.call
|
25
|
+
ensure
|
26
|
+
$LOAD_PATH.shift
|
27
|
+
end
|
28
|
+
end
|
29
|
+
return rv
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the lpath for the module. If any arguments are given,
|
33
|
+
# they will be joined to the end of the path using
|
34
|
+
# <tt>File.join</tt>.
|
35
|
+
#
|
36
|
+
def self.path( *args, &block )
|
37
|
+
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
38
|
+
if block
|
39
|
+
begin
|
40
|
+
$LOAD_PATH.unshift PATH
|
41
|
+
rv = block.call
|
42
|
+
ensure
|
43
|
+
$LOAD_PATH.shift
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return rv
|
47
|
+
end
|
48
|
+
|
49
|
+
# Utility method used to require all files ending in .rb that lie in the
|
50
|
+
# directory below this file that has the same name as the filename passed
|
51
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
52
|
+
# the _filename_ does not have to be equivalent to the directory.
|
53
|
+
#
|
54
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
55
|
+
dir ||= ::File.basename(fname, '.*')
|
56
|
+
search_me = ::File.expand_path(
|
57
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
58
|
+
|
59
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
60
|
+
end
|
61
|
+
|
62
|
+
end # module ZMQ
|
63
|
+
|
64
|
+
# some code is conditionalized based upon what ruby engine we are
|
65
|
+
# executing
|
66
|
+
RBX = RUBY_ENGINE =~ /rbx/ ? true : false
|
67
|
+
|
68
|
+
# the order of files is important
|
69
|
+
%w(wrapper zmq exceptions context message socket poll_items poll).each do |file|
|
70
|
+
require ZMQ.libpath(['ffi-rzmq', file])
|
71
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
ZMQ_INIT_STR = 'zmq_init'.freeze
|
5
|
+
ZMQ_TERM_STR = 'zmq_term'.freeze
|
6
|
+
ZMQ_SOCKET_STR = 'zmq_socket'.freeze unless defined? ZMQ_SOCKET_STR
|
7
|
+
|
8
|
+
|
9
|
+
class Context
|
10
|
+
include ZMQ::Util
|
11
|
+
|
12
|
+
attr_reader :context, :pointer
|
13
|
+
|
14
|
+
# Recommended to just pass 1 for +io_threads+
|
15
|
+
# since most programs are not heavily threaded. The rule of thumb
|
16
|
+
# is to make +io_threads+ equal to the number of application
|
17
|
+
# threads that will be accessing 0mq sockets within this context.
|
18
|
+
# The +io_threads+ number specifies the size of the thread pool
|
19
|
+
# allocated by 0mq for processing incoming/outgoing messages.
|
20
|
+
#
|
21
|
+
# Returns a context object. It's necessary for passing to the
|
22
|
+
# #Socket constructor when allocating new sockets. All sockets
|
23
|
+
# live within a context. Sockets in one context may not be accessed
|
24
|
+
# from another context; doing so raises an exception.
|
25
|
+
#
|
26
|
+
# To connect sockets between contexts, use +inproc+ or +ipc+
|
27
|
+
# transport and set up a 0mq socket between them.
|
28
|
+
#
|
29
|
+
# May raise a #ContextError.
|
30
|
+
#
|
31
|
+
def initialize io_threads
|
32
|
+
@sockets ||= []
|
33
|
+
@context = LibZMQ.zmq_init io_threads
|
34
|
+
@pointer = @context
|
35
|
+
error_check ZMQ_INIT_STR, @context.null? ? 1 : 0
|
36
|
+
|
37
|
+
define_finalizer
|
38
|
+
end
|
39
|
+
|
40
|
+
# Call to release the context and any remaining data associated
|
41
|
+
# with past sockets. This will close any sockets that remain
|
42
|
+
# open; further calls to those sockets will raise failure
|
43
|
+
# exceptions.
|
44
|
+
#
|
45
|
+
# Returns nil.
|
46
|
+
#
|
47
|
+
# May raise a #ContextError.
|
48
|
+
#
|
49
|
+
def terminate
|
50
|
+
unless @context.nil? || @context.null?
|
51
|
+
result_code = LibZMQ.zmq_term @context
|
52
|
+
error_check ZMQ_TERM_STR, result_code
|
53
|
+
@context = nil
|
54
|
+
@sockets = nil
|
55
|
+
remove_finalizer
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
# Short-cut to allocate a socket for a specific context.
|
61
|
+
#
|
62
|
+
# Takes several +type+ values:
|
63
|
+
# #ZMQ::REQ
|
64
|
+
# #ZMQ::REP
|
65
|
+
# #ZMQ::PUB
|
66
|
+
# #ZMQ::SUB
|
67
|
+
# #ZMQ::PAIR
|
68
|
+
# #ZMQ::UPSTREAM
|
69
|
+
# #ZMQ::DOWNSTREAM
|
70
|
+
# #ZMQ::XREQ
|
71
|
+
# #ZMQ::XREP
|
72
|
+
#
|
73
|
+
# Returns a #ZMQ::Socket.
|
74
|
+
#
|
75
|
+
# May raise a #ContextError or #SocketError.
|
76
|
+
#
|
77
|
+
def socket type
|
78
|
+
sock = Socket.new @context, type
|
79
|
+
error_check ZMQ_SOCKET_STR, sock.nil? ? 1 : 0
|
80
|
+
sock
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def define_finalizer
|
87
|
+
ObjectSpace.define_finalizer(self, self.class.close(@context))
|
88
|
+
end
|
89
|
+
|
90
|
+
def remove_finalizer
|
91
|
+
ObjectSpace.undefine_finalizer self
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.close context
|
95
|
+
Proc.new { LibZMQ.zmq_term context unless context.null? }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end # module ZMQ
|
@@ -0,0 +1,145 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
class ZeroMQError < StandardError
|
5
|
+
attr_reader :source, :result_code, :error_code, :message
|
6
|
+
|
7
|
+
def initialize source, result_code, error_code, message
|
8
|
+
@source = source
|
9
|
+
@result_code = result_code
|
10
|
+
@error_code = error_code
|
11
|
+
@message = message
|
12
|
+
super message
|
13
|
+
end
|
14
|
+
end # call ZeroMQError
|
15
|
+
|
16
|
+
|
17
|
+
class ContextError < ZeroMQError
|
18
|
+
# True when the exception was raised due to the library
|
19
|
+
# returning EINVAL.
|
20
|
+
#
|
21
|
+
# Occurs when he number of app_threads requested is less
|
22
|
+
# than one, or the number of io_threads requested is
|
23
|
+
# negative.
|
24
|
+
#
|
25
|
+
def einval?() EINVAL == @error_code; end
|
26
|
+
|
27
|
+
# True when the exception was raised due to the library
|
28
|
+
# returning ETERM.
|
29
|
+
#
|
30
|
+
# The associated context was terminated.
|
31
|
+
#
|
32
|
+
def eterm?() ETERM == @error_code; end
|
33
|
+
|
34
|
+
end # class ContextError
|
35
|
+
|
36
|
+
|
37
|
+
class PollError < ZeroMQError
|
38
|
+
# True when the exception was raised due to the library
|
39
|
+
# returning EMTHREAD.
|
40
|
+
#
|
41
|
+
# At least one of the members of the items array refers
|
42
|
+
# to a socket belonging to a different application
|
43
|
+
# thread.
|
44
|
+
#
|
45
|
+
def efault?() EFAULT == @error_code; end
|
46
|
+
|
47
|
+
end # class PollError
|
48
|
+
|
49
|
+
|
50
|
+
class SocketError < ZeroMQError
|
51
|
+
# True when the exception was raised due to the library
|
52
|
+
# returning EMTHREAD.
|
53
|
+
#
|
54
|
+
# Occurs for #send and #recv operations.
|
55
|
+
# * When calling #send, non-blocking mode was requested
|
56
|
+
# and the message cannot be queued at the moment.
|
57
|
+
# * When calling #recv, non-blocking mode was requested
|
58
|
+
# and no messages are available at the moment.
|
59
|
+
#
|
60
|
+
def egain?() EAGAIN == @error_code; end
|
61
|
+
|
62
|
+
# True when the exception was raised due to the library
|
63
|
+
# returning ENOCOMPATPROTO.
|
64
|
+
#
|
65
|
+
# The requested transport protocol is not compatible
|
66
|
+
# with the socket type.
|
67
|
+
#
|
68
|
+
def enocompatproto?() ENOCOMPATPROTO == @error_code; end
|
69
|
+
|
70
|
+
# True when the exception was raised due to the library
|
71
|
+
# returning EPROTONOSUPPORT.
|
72
|
+
#
|
73
|
+
# The requested transport protocol is not supported.
|
74
|
+
#
|
75
|
+
def eprotonosupport?() EPROTONOSUPPORT == @error_code; end
|
76
|
+
|
77
|
+
# True when the exception was raised due to the library
|
78
|
+
# returning EADDRINUSE.
|
79
|
+
#
|
80
|
+
# The given address is already in use.
|
81
|
+
#
|
82
|
+
def eaddrinuse?() EADDRINUSE == @error_code; end
|
83
|
+
|
84
|
+
# True when the exception was raised due to the library
|
85
|
+
# returning EADDRNOTAVAIL.
|
86
|
+
#
|
87
|
+
# A nonexistent interface was requested or the
|
88
|
+
# requested address was not local.
|
89
|
+
#
|
90
|
+
def eaddrnotavail?() EADDRNOTAVAIL == @error_code; end
|
91
|
+
|
92
|
+
# True when the exception was raised due to the library
|
93
|
+
# returning EMTHREAD.
|
94
|
+
#
|
95
|
+
# Occurs under 2 conditions.
|
96
|
+
# * When creating a new #Socket, the requested socket
|
97
|
+
# type is invalid.
|
98
|
+
#
|
99
|
+
# * When setting socket options with #setsockopt, the
|
100
|
+
# requested option +option_name+ is unknown, or the
|
101
|
+
# requested +option_len+ or +option_value+ is invalid.
|
102
|
+
#
|
103
|
+
def einval?() EINVAL == @error_code; end
|
104
|
+
|
105
|
+
# True when the exception was raised due to the library
|
106
|
+
# returning EMTHREAD.
|
107
|
+
#
|
108
|
+
# The send or recv operation cannot be performed on this
|
109
|
+
# socket at the moment due to the socket not being in
|
110
|
+
# the appropriate state. This error may occur with socket
|
111
|
+
# types that switch between several states, such as ZMQ::REP.
|
112
|
+
#
|
113
|
+
def efsm?() EFSM == @error_code; end
|
114
|
+
|
115
|
+
# True when the exception was raised due to the library
|
116
|
+
# returning ENOTSUP.
|
117
|
+
#
|
118
|
+
# The send or recv operation is not supported by this socket
|
119
|
+
# type.
|
120
|
+
#
|
121
|
+
def enotsup?() super; end
|
122
|
+
|
123
|
+
# True when the exception was raised due to the library
|
124
|
+
# returning EMTHREAD.
|
125
|
+
#
|
126
|
+
# The number of application threads using sockets within
|
127
|
+
# this context has been exceeded. See the +app_threads+
|
128
|
+
# parameter of #Context.
|
129
|
+
#
|
130
|
+
def emthread?() EMTHREAD == @error_code; end
|
131
|
+
|
132
|
+
end # class SocketError
|
133
|
+
|
134
|
+
|
135
|
+
class MessageError < ZeroMQError
|
136
|
+
# True when the exception was raised due to the library
|
137
|
+
# returning ENOMEM.
|
138
|
+
#
|
139
|
+
# Only ever raised by the #Message class when it fails
|
140
|
+
# to allocate sufficient memory to send a message.
|
141
|
+
#
|
142
|
+
def enomem?() ENOMEM == @error_code; end
|
143
|
+
end
|
144
|
+
|
145
|
+
end # module ZMQ
|