nng-ruby 0.1.1 → 0.1.2

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.
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # NNG + Protocol Buffers 示例 (使用线程)
5
+ # 演示 NNG 与 Protobuf 配合使用
6
+
7
+ require 'nng'
8
+ require 'google/protobuf'
9
+
10
+ puts "=" * 70
11
+ puts "NNG + Protocol Buffers 完整示例"
12
+ puts "=" * 70
13
+ puts
14
+
15
+ # ============================================================================
16
+ # 定义 Protobuf 消息
17
+ # ============================================================================
18
+
19
+ Google::Protobuf::DescriptorPool.generated_pool.build do
20
+ add_file("rpc.proto", syntax: :proto3) do
21
+ # RPC 请求
22
+ add_message "RpcRequest" do
23
+ optional :func_code, :int32, 1
24
+ optional :data, :bytes, 2
25
+ optional :request_id, :string, 3
26
+ end
27
+
28
+ # RPC 响应
29
+ add_message "RpcResponse" do
30
+ optional :status, :int32, 1
31
+ optional :data, :bytes, 2
32
+ optional :error_msg, :string, 3
33
+ end
34
+
35
+ # 联系人
36
+ add_message "Contact" do
37
+ optional :wxid, :string, 1
38
+ optional :name, :string, 2
39
+ end
40
+
41
+ # 联系人列表
42
+ add_message "ContactList" do
43
+ repeated :contacts, :message, 1, "Contact"
44
+ end
45
+ end
46
+ end
47
+
48
+ RpcRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("RpcRequest").msgclass
49
+ RpcResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("RpcResponse").msgclass
50
+ Contact = Google::Protobuf::DescriptorPool.generated_pool.lookup("Contact").msgclass
51
+ ContactList = Google::Protobuf::DescriptorPool.generated_pool.lookup("ContactList").msgclass
52
+
53
+ puts "✅ Protobuf 消息定义完成"
54
+ puts
55
+
56
+ # ============================================================================
57
+ # 示例: 获取联系人列表 (使用嵌套的 Protobuf 消息)
58
+ # ============================================================================
59
+
60
+ puts "示例: RPC 调用 - 获取联系人列表"
61
+ puts "=" * 70
62
+ puts
63
+
64
+ url = "inproc://contacts_demo" # 使用 inproc 避免端口冲突
65
+
66
+ # 创建 server socket (必须在 client 之前)
67
+ server_socket = NNG::Socket.new(:pair1)
68
+ server_socket.listen(url)
69
+ server_ready = true
70
+ puts "✅ 服务器监听: #{url}"
71
+ puts
72
+
73
+ # 服务器线程
74
+ server_thread = Thread.new do
75
+ begin
76
+ # 接收 RPC 请求
77
+ request_data = server_socket.recv
78
+ puts "📥 [服务器] 收到请求 (#{request_data.bytesize} bytes)"
79
+
80
+ # 反序列化 RPC 请求
81
+ rpc_request = RpcRequest.decode(request_data)
82
+ puts "📋 [服务器] 解析 RPC 请求:"
83
+ puts " Function Code: 0x#{rpc_request.func_code.to_s(16)}"
84
+ puts " Request ID: #{rpc_request.request_id}"
85
+ puts
86
+
87
+ # 构建联系人列表 (嵌套消息)
88
+ contacts = ContactList.new(
89
+ contacts: [
90
+ Contact.new(wxid: "wxid_001", name: "张三"),
91
+ Contact.new(wxid: "wxid_002", name: "李四"),
92
+ Contact.new(wxid: "wxid_003", name: "王五"),
93
+ Contact.new(wxid: "chatroom_001", name: "技术交流群")
94
+ ]
95
+ )
96
+
97
+ puts "🏗️ [服务器] 构建联系人列表:"
98
+ puts " 联系人数量: #{contacts.contacts.size}"
99
+ contacts.contacts.each_with_index do |contact, i|
100
+ puts " #{i + 1}. #{contact.name} (#{contact.wxid})"
101
+ end
102
+ puts
103
+
104
+ # 序列化联系人列表 (内层)
105
+ contacts_data = ContactList.encode(contacts)
106
+ puts "📦 [服务器] 序列化联系人列表: #{contacts_data.bytesize} bytes"
107
+
108
+ # 构建 RPC 响应 (外层)
109
+ rpc_response = RpcResponse.new(
110
+ status: 0,
111
+ data: contacts_data,
112
+ error_msg: ""
113
+ )
114
+
115
+ # 序列化 RPC 响应
116
+ response_data = RpcResponse.encode(rpc_response)
117
+ puts "📦 [服务器] 序列化 RPC 响应: #{response_data.bytesize} bytes"
118
+
119
+ # 发送响应
120
+ server_socket.send(response_data)
121
+ puts "📤 [服务器] 响应已发送"
122
+ puts
123
+
124
+ rescue => e
125
+ puts "❌ [服务器] 错误: #{e.message}"
126
+ end
127
+ end
128
+
129
+ # 等待服务器准备好
130
+ sleep 0.1
131
+
132
+ # ============================================================================
133
+ # 客户端
134
+ # ============================================================================
135
+
136
+ puts "📞 [客户端] 发起 RPC 调用..."
137
+ puts
138
+
139
+ begin
140
+ # 创建 client socket
141
+ client_socket = NNG::Socket.new(:pair1)
142
+ client_socket.dial(url)
143
+ puts "✅ [客户端] 连接: #{url}"
144
+ puts
145
+
146
+ # 构建 RPC 请求
147
+ rpc_request = RpcRequest.new(
148
+ func_code: 0x12, # 假设 0x12 = FUNC_GET_CONTACTS
149
+ data: "", # 本例无需参数
150
+ request_id: "req_#{Time.now.to_i}"
151
+ )
152
+
153
+ puts "🏗️ [客户端] 构建 RPC 请求:"
154
+ puts " Function Code: 0x#{rpc_request.func_code.to_s(16)}"
155
+ puts " Request ID: #{rpc_request.request_id}"
156
+ puts
157
+
158
+ # 序列化并发送
159
+ request_data = RpcRequest.encode(rpc_request)
160
+ puts "📦 [客户端] 序列化请求: #{request_data.bytesize} bytes"
161
+ client_socket.send(request_data)
162
+ puts "📤 [客户端] 请求已发送"
163
+ puts
164
+
165
+ # 接收响应
166
+ response_data = client_socket.recv
167
+ puts "📥 [客户端] 收到响应 (#{response_data.bytesize} bytes)"
168
+
169
+ # 反序列化 RPC 响应 (外层)
170
+ rpc_response = RpcResponse.decode(response_data)
171
+ puts "📋 [客户端] 解析 RPC 响应:"
172
+ puts " Status: #{rpc_response.status}"
173
+ puts " Error: #{rpc_response.error_msg}" unless rpc_response.error_msg.empty?
174
+ puts
175
+
176
+ if rpc_response.status == 0
177
+ # 反序列化联系人列表 (内层)
178
+ contacts = ContactList.decode(rpc_response.data)
179
+ puts "📋 [客户端] 解析联系人列表:"
180
+ puts " 总数: #{contacts.contacts.size}"
181
+ puts
182
+
183
+ contacts.contacts.each_with_index do |contact, i|
184
+ puts " #{i + 1}. #{contact.name}"
185
+ puts " WXID: #{contact.wxid}"
186
+ end
187
+ else
188
+ puts "❌ [客户端] RPC 调用失败: #{rpc_response.error_msg}"
189
+ end
190
+
191
+ # 关闭
192
+ client_socket.close
193
+
194
+ rescue => e
195
+ puts "❌ [客户端] 错误: #{e.message}"
196
+ puts e.backtrace.first(3)
197
+ end
198
+
199
+ # 等待服务器线程完成
200
+ server_thread.join
201
+ server_socket.close
202
+
203
+ puts
204
+ puts "=" * 70
205
+ puts "✅ 示例完成"
206
+ puts "=" * 70
207
+ puts
208
+ puts "技术要点:"
209
+ puts
210
+ puts "1. 消息嵌套:"
211
+ puts " RpcRequest/Response 包含 bytes 类型的 data 字段"
212
+ puts " data 字段可以存储任意 Protobuf 消息的序列化结果"
213
+ puts
214
+ puts "2. 序列化过程:"
215
+ puts " 内层: contacts → ContactList.encode → bytes"
216
+ puts " 外层: RpcRequest { data: bytes } → RpcRequest.encode → bytes"
217
+ puts " 发送: bytes → NNG socket.send"
218
+ puts
219
+ puts "3. 反序列化过程:"
220
+ puts " 接收: NNG socket.recv → bytes"
221
+ puts " 外层: bytes → RpcResponse.decode → RpcResponse"
222
+ puts " 内层: RpcResponse.data → ContactList.decode → contacts"
223
+ puts
224
+ puts "4. 优势:"
225
+ puts " • 类型安全 - 编译时检查消息结构"
226
+ puts " • 高效序列化 - 比 JSON 小 50%+"
227
+ puts " • 跨语言 - Python/Java/Go/C++ 互通"
228
+ puts " • 向后兼容 - 可安全添加字段"
229
+ puts " • 自动文档 - .proto 文件即文档"
230
+ puts
231
+ puts "5. 实际应用:"
232
+ puts " • 微服务 RPC 通信"
233
+ puts " • 分布式系统消息传递"
234
+ puts " • 客户端-服务器协议"
235
+ puts " • 消息队列数据格式"
236
+ puts
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ # This extconf.rb is used to capture gem install options
6
+ # It doesn't compile anything, just saves configuration
7
+
8
+ def save_config(config)
9
+ config_file = File.join(__dir__, 'nng_config.rb')
10
+ File.write(config_file, <<~RUBY)
11
+ # frozen_string_literal: true
12
+ # Auto-generated by extconf.rb during gem installation
13
+ # DO NOT EDIT THIS FILE MANUALLY
14
+
15
+ module NNG
16
+ module InstallConfig
17
+ CONFIG = #{config.inspect}
18
+ end
19
+ end
20
+ RUBY
21
+ puts "NNG configuration saved to #{config_file}"
22
+ end
23
+
24
+ # Parse installation options
25
+ config = {}
26
+
27
+ # --with-nng-dir=/path/to/nng
28
+ if nng_dir = with_config('nng-dir')
29
+ config[:nng_dir] = nng_dir
30
+ puts "Using NNG directory: #{nng_dir}"
31
+ end
32
+
33
+ # --with-nng-lib=/path/to/libnng.so
34
+ if nng_lib = with_config('nng-lib')
35
+ config[:nng_lib] = nng_lib
36
+ puts "Using NNG library: #{nng_lib}"
37
+ end
38
+
39
+ # --with-nng-include=/path/to/nng/include (for future use)
40
+ if nng_include = with_config('nng-include')
41
+ config[:nng_include] = nng_include
42
+ puts "Using NNG include directory: #{nng_include}"
43
+ end
44
+
45
+ # Save configuration
46
+ save_config(config)
47
+
48
+ # We don't actually compile anything since we use FFI
49
+ # Just create a dummy Makefile
50
+ File.write('Makefile', <<~MAKEFILE)
51
+ .PHONY: all install clean
52
+
53
+ all:
54
+ \t@echo "NNG Ruby uses FFI, no compilation needed"
55
+
56
+ install:
57
+ \t@echo "NNG configuration saved"
58
+
59
+ clean:
60
+ \t@echo "Nothing to clean"
61
+ MAKEFILE
62
+
63
+ puts "\nNNG Ruby gem configuration complete!"
64
+ puts "No native compilation needed (using FFI)"
65
+ puts ""
66
+ if config.any?
67
+ puts "Custom configuration:"
68
+ config.each { |k, v| puts " #{k}: #{v}" }
69
+ else
70
+ puts "Using default bundled libnng.so.1.8.0"
71
+ end
data/lib/nng/ffi.rb CHANGED
@@ -7,33 +7,102 @@ module NNG
7
7
  module FFI
8
8
  extend ::FFI::Library
9
9
 
10
- # Try to load libnng from various locations
11
- @lib_paths = [
12
- File.expand_path('../../ext/nng/libnng.so.1.8.0', __dir__),
13
- '/usr/local/lib/libnng.so',
14
- '/usr/lib/libnng.so',
15
- '/usr/lib/x86_64-linux-gnu/libnng.so'
16
- ]
10
+ # Load install-time configuration if available
11
+ @install_config = {}
12
+ config_file = File.expand_path('../../ext/nng/nng_config.rb', __dir__)
13
+ if File.exist?(config_file)
14
+ require config_file
15
+ @install_config = NNG::InstallConfig::CONFIG rescue {}
16
+ end
17
+
18
+ # Build library search paths with priority order:
19
+ # 1. ENV['NNG_LIB_PATH'] - Direct path to library file
20
+ # 2. ENV['NNG_LIB_DIR'] - Directory containing libnng.so*
21
+ # 3. Install config (from gem install --with-nng-*)
22
+ # 4. Bundled library
23
+ # 5. System default paths
24
+ def self.build_lib_paths
25
+ paths = []
26
+
27
+ # Priority 1: Direct library path from environment
28
+ if ENV['NNG_LIB_PATH'] && !ENV['NNG_LIB_PATH'].empty?
29
+ paths << ENV['NNG_LIB_PATH']
30
+ puts "NNG: Using library from NNG_LIB_PATH: #{ENV['NNG_LIB_PATH']}" if ENV['NNG_DEBUG']
31
+ end
32
+
33
+ # Priority 2: Library directory from environment
34
+ if ENV['NNG_LIB_DIR'] && !ENV['NNG_LIB_DIR'].empty?
35
+ dir = ENV['NNG_LIB_DIR']
36
+ # Search for libnng.so* in the directory
37
+ Dir.glob(File.join(dir, 'libnng.so*')).each { |lib| paths << lib }
38
+ puts "NNG: Searching in NNG_LIB_DIR: #{dir}" if ENV['NNG_DEBUG']
39
+ end
40
+
41
+ # Priority 3: Install-time configuration
42
+ if @install_config[:nng_lib]
43
+ paths << @install_config[:nng_lib]
44
+ puts "NNG: Using library from install config: #{@install_config[:nng_lib]}" if ENV['NNG_DEBUG']
45
+ elsif @install_config[:nng_dir]
46
+ nng_dir = @install_config[:nng_dir]
47
+ # Try common subdirectories
48
+ %w[lib lib64 lib/x86_64-linux-gnu].each do |subdir|
49
+ lib_dir = File.join(nng_dir, subdir)
50
+ Dir.glob(File.join(lib_dir, 'libnng.so*')).each { |lib| paths << lib }
51
+ end
52
+ puts "NNG: Searching in install config dir: #{nng_dir}" if ENV['NNG_DEBUG']
53
+ end
17
54
 
55
+ # Priority 4: Bundled library
56
+ bundled_lib = File.expand_path('../../ext/nng/libnng.so.1.8.0', __dir__)
57
+ paths << bundled_lib
58
+
59
+ # Priority 5: System default paths
60
+ paths += [
61
+ '/usr/local/lib/libnng.so',
62
+ '/usr/lib/libnng.so',
63
+ '/usr/lib/x86_64-linux-gnu/libnng.so',
64
+ '/usr/lib/aarch64-linux-gnu/libnng.so'
65
+ ]
66
+
67
+ paths.uniq
68
+ end
69
+
70
+ @lib_paths = build_lib_paths
18
71
  @loaded_lib_path = nil
72
+
19
73
  @lib_paths.each do |path|
20
74
  if File.exist?(path)
21
75
  begin
22
76
  ffi_lib path
23
77
  @loaded_lib_path = path
78
+ puts "NNG: Successfully loaded library from: #{path}" if ENV['NNG_DEBUG']
24
79
  break
25
- rescue LoadError
80
+ rescue LoadError => e
81
+ puts "NNG: Failed to load #{path}: #{e.message}" if ENV['NNG_DEBUG']
26
82
  next
27
83
  end
28
84
  end
29
85
  end
30
86
 
31
- raise LoadError, "Could not find libnng.so in any of: #{@lib_paths.join(', ')}" unless @loaded_lib_path
87
+ unless @loaded_lib_path
88
+ error_msg = "Could not find libnng.so in any of the following locations:\n"
89
+ error_msg += @lib_paths.map { |p| " - #{p}" }.join("\n")
90
+ error_msg += "\n\nYou can specify a custom path using:\n"
91
+ error_msg += " - Environment variable: export NNG_LIB_PATH=/path/to/libnng.so\n"
92
+ error_msg += " - Environment variable: export NNG_LIB_DIR=/path/to/lib\n"
93
+ error_msg += " - Gem install option: gem install nng-ruby -- --with-nng-dir=/path/to/nng\n"
94
+ error_msg += " - Gem install option: gem install nng-ruby -- --with-nng-lib=/path/to/libnng.so"
95
+ raise LoadError, error_msg
96
+ end
32
97
 
33
98
  def self.loaded_lib_path
34
99
  @loaded_lib_path
35
100
  end
36
101
 
102
+ def self.install_config
103
+ @install_config
104
+ end
105
+
37
106
  # ============================================================================
38
107
  # Constants
39
108
  # ============================================================================
data/lib/nng/socket.rb CHANGED
@@ -108,7 +108,10 @@ module NNG
108
108
  def get_option(name, type: :int)
109
109
  check_closed
110
110
 
111
- value_ptr = ::FFI::MemoryPointer.new(type)
111
+ # :ms is actually :int32 (nng_duration)
112
+ ptr_type = type == :ms ? :int32 : type
113
+ value_ptr = ::FFI::MemoryPointer.new(ptr_type)
114
+
112
115
  ret = case type
113
116
  when :bool
114
117
  FFI.nng_getopt_bool(@socket, name, value_ptr)
@@ -134,7 +137,7 @@ module NNG
134
137
  FFI.nng_strfree(str_ptr)
135
138
  result
136
139
  else
137
- value_ptr.read(type)
140
+ value_ptr.read(ptr_type)
138
141
  end
139
142
  end
140
143
 
data/lib/nng/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NNG
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
data/nng.gemspec CHANGED
@@ -5,8 +5,8 @@ require_relative 'lib/nng/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'nng-ruby'
7
7
  spec.version = NNG::VERSION
8
- spec.authors = ['Claude Code']
9
- spec.email = ['noreply@anthropic.com']
8
+ spec.authors = ['QingYi']
9
+ spec.email = ['qingyi.mail@gmail.com']
10
10
 
11
11
  spec.summary = 'Ruby bindings for NNG (nanomsg-next-generation)'
12
12
  spec.description = 'Complete Ruby bindings for NNG, a lightweight messaging library. ' \
@@ -41,9 +41,11 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency 'rake', '~> 13.0'
42
42
  spec.add_development_dependency 'rspec', '~> 3.0'
43
43
  spec.add_development_dependency 'yard', '~> 0.9'
44
+ spec.add_development_dependency 'rake-compiler', '~> 1.0'
44
45
 
45
- # Extensions (none - we use FFI)
46
- # spec.extensions = []
46
+ # Extension configuration (for capturing install-time options)
47
+ # We use FFI so no actual compilation, but extconf.rb captures --with-nng-* options
48
+ spec.extensions = ['ext/nng/extconf.rb']
47
49
 
48
50
  # Post-install message
49
51
  spec.post_install_message = <<~MSG
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nng-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
- - Claude Code
7
+ - QingYi
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -79,14 +79,29 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0.9'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rake-compiler
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
82
96
  description: Complete Ruby bindings for NNG, a lightweight messaging library. Supports
83
97
  all scalability protocols (Pair, Push/Pull, Pub/Sub, Req/Rep, Surveyor/Respondent,
84
98
  Bus) and transports (TCP, IPC, Inproc, WebSocket, TLS). Includes bundled libnng
85
99
  shared library.
86
100
  email:
87
- - noreply@anthropic.com
101
+ - qingyi.mail@gmail.com
88
102
  executables: []
89
- extensions: []
103
+ extensions:
104
+ - ext/nng/extconf.rb
90
105
  extra_rdoc_files: []
91
106
  files:
92
107
  - CHANGELOG.md
@@ -95,8 +110,14 @@ files:
95
110
  - README.md
96
111
  - Rakefile
97
112
  - examples/pair.rb
113
+ - examples/protobuf_advanced.rb
114
+ - examples/protobuf_demo.rb
115
+ - examples/protobuf_example.rb
116
+ - examples/protobuf_simple.rb
117
+ - examples/protobuf_thread.rb
98
118
  - examples/pubsub.rb
99
119
  - examples/reqrep.rb
120
+ - ext/nng/extconf.rb
100
121
  - ext/nng/libnng.so.1.8.0
101
122
  - lib/nng.rb
102
123
  - lib/nng/errors.rb
@@ -119,7 +140,7 @@ post_install_message: |
119
140
  │ Thank you for installing nng-ruby gem! │
120
141
  │ │
121
142
  │ NNG (nanomsg-next-generation) Ruby bindings │
122
- │ Version: 0.1.1
143
+ │ Version: 0.1.2
123
144
  │ │
124
145
  │ Quick start: │
125
146
  │ require 'nng' │