nng-ruby 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +383 -0
- data/Rakefile +22 -0
- data/examples/pair.rb +48 -0
- data/examples/pubsub.rb +51 -0
- data/examples/reqrep.rb +54 -0
- data/lib/nng/errors.rb +48 -0
- data/lib/nng/ffi.rb +376 -0
- data/lib/nng/message.rb +151 -0
- data/lib/nng/protocols.rb +96 -0
- data/lib/nng/socket.rb +193 -0
- data/lib/nng/version.rb +5 -0
- data/lib/nng.rb +52 -0
- data/nng.gemspec +65 -0
- metadata +148 -0
data/lib/nng/socket.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'protocols'
|
4
|
+
|
5
|
+
module NNG
|
6
|
+
# High-level socket interface
|
7
|
+
class Socket
|
8
|
+
attr_reader :socket
|
9
|
+
|
10
|
+
# Create a new socket
|
11
|
+
# @param protocol [Symbol] protocol name (:pair0, :pair1, :push, :pull, :pub, :sub, :req, :rep, :surveyor, :respondent, :bus)
|
12
|
+
# @param raw [Boolean] open in raw mode
|
13
|
+
def initialize(protocol, raw: false)
|
14
|
+
@socket = Protocols.open_socket(protocol, raw: raw)
|
15
|
+
@closed = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Listen on an address
|
19
|
+
# @param url [String] URL to listen on (e.g., "tcp://0.0.0.0:5555", "ipc:///tmp/test.sock")
|
20
|
+
# @param flags [Integer] optional flags
|
21
|
+
# @return [self]
|
22
|
+
def listen(url, flags: 0)
|
23
|
+
check_closed
|
24
|
+
ret = FFI.nng_listen(@socket, url, nil, flags)
|
25
|
+
FFI.check_error(ret, "Listen on #{url}")
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Dial (connect) to an address
|
30
|
+
# @param url [String] URL to connect to (e.g., "tcp://127.0.0.1:5555")
|
31
|
+
# @param flags [Integer] optional flags
|
32
|
+
# @return [self]
|
33
|
+
def dial(url, flags: 0)
|
34
|
+
check_closed
|
35
|
+
ret = FFI.nng_dial(@socket, url, nil, flags)
|
36
|
+
FFI.check_error(ret, "Dial to #{url}")
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Send data
|
41
|
+
# @param data [String] data to send
|
42
|
+
# @param flags [Integer] optional flags (e.g., FFI::NNG_FLAG_NONBLOCK)
|
43
|
+
# @return [self]
|
44
|
+
def send(data, flags: 0)
|
45
|
+
check_closed
|
46
|
+
data_str = data.to_s
|
47
|
+
data_ptr = ::FFI::MemoryPointer.new(:uint8, data_str.bytesize)
|
48
|
+
data_ptr.put_bytes(0, data_str)
|
49
|
+
|
50
|
+
ret = FFI.nng_send(@socket, data_ptr, data_str.bytesize, flags)
|
51
|
+
FFI.check_error(ret, "Send data")
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
# Receive data
|
56
|
+
# @param flags [Integer] optional flags (e.g., FFI::NNG_FLAG_NONBLOCK)
|
57
|
+
# @return [String] received data
|
58
|
+
def recv(flags: FFI::NNG_FLAG_ALLOC)
|
59
|
+
check_closed
|
60
|
+
|
61
|
+
buf_ptr = ::FFI::MemoryPointer.new(:pointer)
|
62
|
+
size_ptr = ::FFI::MemoryPointer.new(:size_t)
|
63
|
+
|
64
|
+
ret = FFI.nng_recv(@socket, buf_ptr, size_ptr, flags)
|
65
|
+
FFI.check_error(ret, "Receive data")
|
66
|
+
|
67
|
+
# Read the data
|
68
|
+
response_buf = buf_ptr.read_pointer
|
69
|
+
response_size = size_ptr.read(:size_t)
|
70
|
+
data = response_buf.read_bytes(response_size)
|
71
|
+
|
72
|
+
# Free NNG-allocated memory
|
73
|
+
FFI.nng_free(response_buf, response_size)
|
74
|
+
|
75
|
+
data
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set socket option
|
79
|
+
# @param name [String] option name
|
80
|
+
# @param value [Object] option value
|
81
|
+
# @return [self]
|
82
|
+
def set_option(name, value)
|
83
|
+
check_closed
|
84
|
+
|
85
|
+
case value
|
86
|
+
when true, false
|
87
|
+
ret = FFI.nng_setopt_bool(@socket, name, value)
|
88
|
+
when Integer
|
89
|
+
if value >= 0 && value <= 2**31 - 1
|
90
|
+
ret = FFI.nng_setopt_int(@socket, name, value)
|
91
|
+
else
|
92
|
+
ret = FFI.nng_setopt_uint64(@socket, name, value)
|
93
|
+
end
|
94
|
+
when String
|
95
|
+
ret = FFI.nng_setopt_string(@socket, name, value)
|
96
|
+
else
|
97
|
+
raise ArgumentError, "Unsupported option value type: #{value.class}"
|
98
|
+
end
|
99
|
+
|
100
|
+
FFI.check_error(ret, "Set option #{name}")
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get socket option
|
105
|
+
# @param name [String] option name
|
106
|
+
# @param type [Symbol] expected type (:bool, :int, :size, :uint64, :string, :ms)
|
107
|
+
# @return [Object] option value
|
108
|
+
def get_option(name, type: :int)
|
109
|
+
check_closed
|
110
|
+
|
111
|
+
value_ptr = ::FFI::MemoryPointer.new(type)
|
112
|
+
ret = case type
|
113
|
+
when :bool
|
114
|
+
FFI.nng_getopt_bool(@socket, name, value_ptr)
|
115
|
+
when :int
|
116
|
+
FFI.nng_getopt_int(@socket, name, value_ptr)
|
117
|
+
when :size
|
118
|
+
FFI.nng_getopt_size(@socket, name, value_ptr)
|
119
|
+
when :uint64
|
120
|
+
FFI.nng_getopt_uint64(@socket, name, value_ptr)
|
121
|
+
when :ms
|
122
|
+
FFI.nng_getopt_ms(@socket, name, value_ptr)
|
123
|
+
when :string
|
124
|
+
FFI.nng_getopt_string(@socket, name, value_ptr)
|
125
|
+
else
|
126
|
+
raise ArgumentError, "Unknown option type: #{type}"
|
127
|
+
end
|
128
|
+
|
129
|
+
FFI.check_error(ret, "Get option #{name}")
|
130
|
+
|
131
|
+
if type == :string
|
132
|
+
str_ptr = value_ptr.read_pointer
|
133
|
+
result = str_ptr.read_string
|
134
|
+
FFI.nng_strfree(str_ptr)
|
135
|
+
result
|
136
|
+
else
|
137
|
+
value_ptr.read(type)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set send timeout
|
142
|
+
# @param ms [Integer] timeout in milliseconds
|
143
|
+
# @return [self]
|
144
|
+
def send_timeout=(ms)
|
145
|
+
set_option_ms('send-timeout', ms)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Set receive timeout
|
149
|
+
# @param ms [Integer] timeout in milliseconds
|
150
|
+
# @return [self]
|
151
|
+
def recv_timeout=(ms)
|
152
|
+
set_option_ms('recv-timeout', ms)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Set timeout option
|
156
|
+
# @param name [String] option name
|
157
|
+
# @param ms [Integer] timeout in milliseconds
|
158
|
+
# @return [self]
|
159
|
+
def set_option_ms(name, ms)
|
160
|
+
check_closed
|
161
|
+
ret = FFI.nng_setopt_ms(@socket, name, ms)
|
162
|
+
FFI.check_error(ret, "Set option #{name}")
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get socket ID
|
167
|
+
# @return [Integer] socket ID
|
168
|
+
def id
|
169
|
+
FFI.nng_socket_id(@socket)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Close the socket
|
173
|
+
# @return [nil]
|
174
|
+
def close
|
175
|
+
return if @closed
|
176
|
+
FFI.nng_close(@socket)
|
177
|
+
@closed = true
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# Check if socket is closed
|
182
|
+
# @return [Boolean]
|
183
|
+
def closed?
|
184
|
+
@closed
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def check_closed
|
190
|
+
raise Closed, "Socket is closed" if @closed
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/nng/version.rb
ADDED
data/lib/nng.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'nng/version'
|
5
|
+
require_relative 'nng/ffi'
|
6
|
+
require_relative 'nng/socket'
|
7
|
+
require_relative 'nng/message'
|
8
|
+
require_relative 'nng/errors'
|
9
|
+
|
10
|
+
# NNG (nanomsg-next-generation) Ruby bindings
|
11
|
+
#
|
12
|
+
# @example Basic usage
|
13
|
+
# require 'nng'
|
14
|
+
#
|
15
|
+
# # Create a pair socket
|
16
|
+
# socket = NNG::Socket.new(:pair1)
|
17
|
+
# socket.listen("tcp://127.0.0.1:5555")
|
18
|
+
#
|
19
|
+
# # Send a message
|
20
|
+
# socket.send("Hello, NNG!")
|
21
|
+
#
|
22
|
+
# # Receive a message
|
23
|
+
# data = socket.recv
|
24
|
+
# puts data
|
25
|
+
#
|
26
|
+
# # Close the socket
|
27
|
+
# socket.close
|
28
|
+
#
|
29
|
+
module NNG
|
30
|
+
class Error < StandardError; end
|
31
|
+
|
32
|
+
# NNG library version
|
33
|
+
# @return [String] version string
|
34
|
+
def self.version
|
35
|
+
VERSION
|
36
|
+
end
|
37
|
+
|
38
|
+
# NNG library version (from C library)
|
39
|
+
# @return [String] version string from libnng
|
40
|
+
def self.lib_version
|
41
|
+
"#{FFI::NNG_MAJOR_VERSION}.#{FFI::NNG_MINOR_VERSION}.#{FFI::NNG_PATCH_VERSION}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Cleanup NNG library (optional, called automatically at exit)
|
45
|
+
def self.fini
|
46
|
+
FFI.nng_fini
|
47
|
+
end
|
48
|
+
|
49
|
+
at_exit do
|
50
|
+
fini
|
51
|
+
end
|
52
|
+
end
|
data/nng.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/nng/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'nng-ruby'
|
7
|
+
spec.version = NNG::VERSION
|
8
|
+
spec.authors = ['Claude Code']
|
9
|
+
spec.email = ['noreply@anthropic.com']
|
10
|
+
|
11
|
+
spec.summary = 'Ruby bindings for NNG (nanomsg-next-generation)'
|
12
|
+
spec.description = 'Complete Ruby bindings for NNG, a lightweight messaging library. ' \
|
13
|
+
'Supports all scalability protocols (Pair, Push/Pull, Pub/Sub, Req/Rep, ' \
|
14
|
+
'Surveyor/Respondent, Bus) and transports (TCP, IPC, Inproc, WebSocket, TLS). ' \
|
15
|
+
'Includes bundled libnng shared library.'
|
16
|
+
spec.homepage = 'https://github.com/yourusername/nng-ruby'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.required_ruby_version = '>= 2.5.0'
|
19
|
+
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/yourusername/nng-ruby'
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/yourusername/nng-ruby/blob/main/CHANGELOG.md'
|
23
|
+
spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/nng-ruby'
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
|
+
Dir.glob('{lib,ext}/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) } +
|
28
|
+
Dir.glob('examples/*.rb') +
|
29
|
+
%w[README.md LICENSE CHANGELOG.md nng.gemspec Rakefile Gemfile]
|
30
|
+
end
|
31
|
+
|
32
|
+
spec.bindir = 'exe'
|
33
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ['lib']
|
35
|
+
|
36
|
+
# Runtime dependencies
|
37
|
+
spec.add_dependency 'ffi', '~> 1.15'
|
38
|
+
|
39
|
+
# Development dependencies
|
40
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
41
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
42
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
43
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
44
|
+
|
45
|
+
# Extensions (none - we use FFI)
|
46
|
+
# spec.extensions = []
|
47
|
+
|
48
|
+
# Post-install message
|
49
|
+
spec.post_install_message = <<~MSG
|
50
|
+
┌───────────────────────────────────────────────────────────┐
|
51
|
+
│ Thank you for installing nng-ruby gem! │
|
52
|
+
│ │
|
53
|
+
│ NNG (nanomsg-next-generation) Ruby bindings │
|
54
|
+
│ Version: #{NNG::VERSION} │
|
55
|
+
│ │
|
56
|
+
│ Quick start: │
|
57
|
+
│ require 'nng' │
|
58
|
+
│ socket = NNG::Socket.new(:pair1) │
|
59
|
+
│ socket.listen("tcp://127.0.0.1:5555") │
|
60
|
+
│ │
|
61
|
+
│ Documentation: https://rubydoc.info/gems/nng-ruby │
|
62
|
+
│ Examples: https://github.com/yourusername/nng-ruby │
|
63
|
+
└───────────────────────────────────────────────────────────┘
|
64
|
+
MSG
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nng-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Claude Code
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ffi
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.15'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.15'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bundler
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rake
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '13.0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '13.0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rspec
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: yard
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.9'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.9'
|
82
|
+
description: Complete Ruby bindings for NNG, a lightweight messaging library. Supports
|
83
|
+
all scalability protocols (Pair, Push/Pull, Pub/Sub, Req/Rep, Surveyor/Respondent,
|
84
|
+
Bus) and transports (TCP, IPC, Inproc, WebSocket, TLS). Includes bundled libnng
|
85
|
+
shared library.
|
86
|
+
email:
|
87
|
+
- noreply@anthropic.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- CHANGELOG.md
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- examples/pair.rb
|
98
|
+
- examples/pubsub.rb
|
99
|
+
- examples/reqrep.rb
|
100
|
+
- lib/nng.rb
|
101
|
+
- lib/nng/errors.rb
|
102
|
+
- lib/nng/ffi.rb
|
103
|
+
- lib/nng/message.rb
|
104
|
+
- lib/nng/protocols.rb
|
105
|
+
- lib/nng/socket.rb
|
106
|
+
- lib/nng/version.rb
|
107
|
+
- nng.gemspec
|
108
|
+
homepage: https://github.com/yourusername/nng-ruby
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata:
|
112
|
+
homepage_uri: https://github.com/yourusername/nng-ruby
|
113
|
+
source_code_uri: https://github.com/yourusername/nng-ruby
|
114
|
+
changelog_uri: https://github.com/yourusername/nng-ruby/blob/main/CHANGELOG.md
|
115
|
+
documentation_uri: https://rubydoc.info/gems/nng-ruby
|
116
|
+
post_install_message: |
|
117
|
+
┌───────────────────────────────────────────────────────────┐
|
118
|
+
│ Thank you for installing nng-ruby gem! │
|
119
|
+
│ │
|
120
|
+
│ NNG (nanomsg-next-generation) Ruby bindings │
|
121
|
+
│ Version: 0.1.0 │
|
122
|
+
│ │
|
123
|
+
│ Quick start: │
|
124
|
+
│ require 'nng' │
|
125
|
+
│ socket = NNG::Socket.new(:pair1) │
|
126
|
+
│ socket.listen("tcp://127.0.0.1:5555") │
|
127
|
+
│ │
|
128
|
+
│ Documentation: https://rubydoc.info/gems/nng-ruby │
|
129
|
+
│ Examples: https://github.com/yourusername/nng-ruby │
|
130
|
+
└───────────────────────────────────────────────────────────┘
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.5.0
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubygems_version: 3.6.7
|
146
|
+
specification_version: 4
|
147
|
+
summary: Ruby bindings for NNG (nanomsg-next-generation)
|
148
|
+
test_files: []
|