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,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
|