nng-ruby 0.1.2 → 1.0.1
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 +4 -4
- data/CHANGELOG.md +78 -45
- data/Gemfile +10 -10
- data/LICENSE +21 -21
- data/README.md +1201 -1200
- data/Rakefile +22 -22
- data/examples/pair.rb +48 -48
- data/examples/protobuf_advanced.rb +322 -322
- data/examples/protobuf_demo.rb +340 -340
- data/examples/protobuf_example.rb +374 -374
- data/examples/protobuf_simple.rb +191 -191
- data/examples/protobuf_thread.rb +236 -236
- data/examples/pubsub.rb +51 -51
- data/examples/reqrep.rb +54 -54
- data/ext/nng/extconf.rb +80 -71
- data/ext/nng/libnng.so.1.11.0 +0 -0
- data/ext/nng/nng.dll +0 -0
- data/lib/nng/errors.rb +48 -48
- data/lib/nng/ffi.rb +508 -445
- data/lib/nng/message.rb +151 -151
- data/lib/nng/protocols.rb +96 -96
- data/lib/nng/socket.rb +196 -196
- data/lib/nng/version.rb +5 -5
- data/lib/nng.rb +52 -52
- data/nng.gemspec +67 -67
- metadata +5 -4
- data/ext/nng/libnng.so.1.8.0 +0 -0
data/README.md
CHANGED
@@ -1,1200 +1,1201 @@
|
|
1
|
-
# NNG Ruby Bindings
|
2
|
-
|
3
|
-
[](https://badge.fury.io/rb/nng-ruby)
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
5
|
-
|
6
|
-
Ruby bindings for [NNG (nanomsg-next-generation)](https://nng.nanomsg.org/), a lightweight messaging library.
|
7
|
-
|
8
|
-
## Features
|
9
|
-
|
10
|
-
- ✅ Complete FFI bindings for NNG 1.
|
11
|
-
- ✅
|
12
|
-
- ✅ All
|
13
|
-
- ✅
|
14
|
-
- ✅
|
15
|
-
- ✅
|
16
|
-
- ✅
|
17
|
-
- ✅
|
18
|
-
- ✅
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
server.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
client.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
rep.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
req.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
pub.
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
sub1.
|
115
|
-
sub1.
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
sub2.
|
120
|
-
sub2.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
puts
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
require '
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
push.
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
pull1.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
pull2.
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
workers
|
163
|
-
workers
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
workers
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
node1.
|
201
|
-
|
202
|
-
|
203
|
-
node2.
|
204
|
-
node2.
|
205
|
-
|
206
|
-
|
207
|
-
node3.
|
208
|
-
node3.dial("tcp://127.0.0.1:
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
puts
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
puts
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
surveyor.
|
236
|
-
surveyor.
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
resp1.
|
241
|
-
|
242
|
-
|
243
|
-
resp2.
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
resp1.
|
253
|
-
|
254
|
-
|
255
|
-
resp2.
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
puts surveyor.recv # => "Respondent
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
- **
|
274
|
-
- **
|
275
|
-
- **
|
276
|
-
- **
|
277
|
-
- **
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
- **
|
283
|
-
- **
|
284
|
-
- **
|
285
|
-
- **
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
- Which
|
356
|
-
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
socket
|
363
|
-
socket.
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
socket.
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
msg.append("
|
397
|
-
|
398
|
-
puts msg.
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
puts msg.
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
msg.
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
server.
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
client.
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
msg.
|
438
|
-
msg.
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
msg_ptr.
|
443
|
-
|
444
|
-
NNG::FFI.
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
NNG::FFI.
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
recv_msg
|
455
|
-
recv_msg.instance_variable_set(:@
|
456
|
-
recv_msg.instance_variable_set(:@
|
457
|
-
|
458
|
-
|
459
|
-
puts "
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
server.
|
475
|
-
|
476
|
-
|
477
|
-
client.
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
socket.set_option("
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
socket.set_option("tcp-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
socket.
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
socket.set_option_ms("
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
#
|
526
|
-
|
527
|
-
sub.
|
528
|
-
sub.set_option("sub:subscribe", "
|
529
|
-
sub.set_option("sub:
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
socket.
|
547
|
-
socket.dial("tcp://
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
socket.
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
socket.
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
socket.
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
socket.
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
- `examples/
|
575
|
-
- `examples/
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
- `examples/
|
584
|
-
- `examples/
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
require '
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
optional :
|
606
|
-
optional :
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
optional :
|
611
|
-
optional :
|
612
|
-
|
613
|
-
|
614
|
-
end
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
server.
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
client.
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
Server
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
require '
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
optional :
|
684
|
-
optional :
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
optional :
|
692
|
-
|
693
|
-
|
694
|
-
end
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
Contact.new(wxid: "
|
705
|
-
Contact.new(wxid: "
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
server.
|
716
|
-
|
717
|
-
|
718
|
-
client.
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
received_contacts.contacts.
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
-
|
746
|
-
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
require '
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
value :
|
763
|
-
value :
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
optional :
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
optional :
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
optional :
|
779
|
-
|
780
|
-
|
781
|
-
end
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
server.
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
client.
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
Send
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
require '
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
MyProto.
|
895
|
-
|
896
|
-
|
897
|
-
end
|
898
|
-
|
899
|
-
|
900
|
-
#
|
901
|
-
#
|
902
|
-
#
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
- `NNG.
|
942
|
-
- `NNG.
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
socket = NNG::Socket.new(:
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
- `
|
957
|
-
- `
|
958
|
-
- `
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
- `
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
- `
|
969
|
-
- `
|
970
|
-
- `
|
971
|
-
- `
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
- `
|
989
|
-
- `
|
990
|
-
- `
|
991
|
-
- `
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
- `
|
997
|
-
- `
|
998
|
-
- `
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
- `
|
1004
|
-
- `
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
├── NNG::
|
1054
|
-
├── NNG::
|
1055
|
-
├── NNG::
|
1056
|
-
├── NNG::
|
1057
|
-
├── NNG::
|
1058
|
-
├── NNG::
|
1059
|
-
├── NNG::
|
1060
|
-
├── NNG::
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
end
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
end
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
socket.
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
end
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
- [
|
1173
|
-
- [
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
-
|
1181
|
-
- Example
|
1182
|
-
- Example
|
1183
|
-
-
|
1184
|
-
- Added
|
1185
|
-
- Added
|
1186
|
-
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
-
|
1191
|
-
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
-
|
1196
|
-
-
|
1197
|
-
-
|
1198
|
-
-
|
1199
|
-
-
|
1200
|
-
-
|
1
|
+
# NNG Ruby Bindings
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/nng-ruby)
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
5
|
+
|
6
|
+
Ruby bindings for [NNG (nanomsg-next-generation)](https://nng.nanomsg.org/), a lightweight messaging library.
|
7
|
+
|
8
|
+
## Features
|
9
|
+
|
10
|
+
- ✅ Complete FFI bindings for NNG 1.11.0 (300+ functions)
|
11
|
+
- ✅ **Cross-platform support**: Windows, macOS, and Linux
|
12
|
+
- ✅ All scalability protocols: Pair, Push/Pull, Pub/Sub, Req/Rep, Surveyor/Respondent, Bus
|
13
|
+
- ✅ All transports: TCP, IPC, Inproc, WebSocket, TLS
|
14
|
+
- ✅ High-level Ruby API with automatic resource management
|
15
|
+
- ✅ Message-based and byte-based communication
|
16
|
+
- ✅ Bundled NNG 1.11.0 libraries (nng.dll for Windows, libnng.so for Linux)
|
17
|
+
- ✅ Thread-safe
|
18
|
+
- ✅ Full async I/O support
|
19
|
+
- ✅ Automated testing and publishing via GitHub Actions
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'nng-ruby'
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
bundle install
|
33
|
+
```
|
34
|
+
|
35
|
+
Or install it yourself as:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
gem install nng-ruby
|
39
|
+
```
|
40
|
+
|
41
|
+
## Quick Start
|
42
|
+
|
43
|
+
### Pair Protocol (Bidirectional)
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
require 'nng'
|
47
|
+
|
48
|
+
# Server
|
49
|
+
server = NNG::Socket.new(:pair1)
|
50
|
+
server.listen("tcp://127.0.0.1:5555")
|
51
|
+
|
52
|
+
# Client
|
53
|
+
client = NNG::Socket.new(:pair1)
|
54
|
+
client.dial("tcp://127.0.0.1:5555")
|
55
|
+
|
56
|
+
# Send and receive
|
57
|
+
client.send("Hello, NNG!")
|
58
|
+
puts server.recv # => "Hello, NNG!"
|
59
|
+
|
60
|
+
server.send("Hello back!")
|
61
|
+
puts client.recv # => "Hello back!"
|
62
|
+
|
63
|
+
# Cleanup
|
64
|
+
server.close
|
65
|
+
client.close
|
66
|
+
```
|
67
|
+
|
68
|
+
### Request/Reply Protocol
|
69
|
+
|
70
|
+
The request/reply pattern ensures exactly one reply for each request:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require 'nng'
|
74
|
+
|
75
|
+
# Server (replier) - Must reply to each request
|
76
|
+
rep = NNG::Socket.new(:rep)
|
77
|
+
rep.listen("tcp://127.0.0.1:5556")
|
78
|
+
|
79
|
+
# Client (requester) - Must wait for reply before sending next request
|
80
|
+
req = NNG::Socket.new(:req)
|
81
|
+
req.dial("tcp://127.0.0.1:5556")
|
82
|
+
sleep 0.1 # Allow connection to establish
|
83
|
+
|
84
|
+
# Request-reply cycle
|
85
|
+
req.send("What is the answer?")
|
86
|
+
question = rep.recv
|
87
|
+
puts "Server received: #{question}"
|
88
|
+
|
89
|
+
rep.send("42")
|
90
|
+
answer = req.recv
|
91
|
+
puts "Client received: #{answer}"
|
92
|
+
|
93
|
+
# Another request-reply cycle
|
94
|
+
req.send("What is 2+2?")
|
95
|
+
rep.send("4")
|
96
|
+
puts req.recv # => "4"
|
97
|
+
|
98
|
+
rep.close
|
99
|
+
req.close
|
100
|
+
```
|
101
|
+
|
102
|
+
### Publish/Subscribe Protocol
|
103
|
+
|
104
|
+
Pub/Sub allows one publisher to broadcast to multiple subscribers:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
require 'nng'
|
108
|
+
|
109
|
+
# Publisher
|
110
|
+
pub = NNG::Socket.new(:pub)
|
111
|
+
pub.listen("tcp://127.0.0.1:5557")
|
112
|
+
|
113
|
+
# Subscriber 1 - Subscribe to all topics
|
114
|
+
sub1 = NNG::Socket.new(:sub)
|
115
|
+
sub1.dial("tcp://127.0.0.1:5557")
|
116
|
+
sub1.set_option("sub:subscribe", "") # Subscribe to everything
|
117
|
+
|
118
|
+
# Subscriber 2 - Subscribe to specific topic
|
119
|
+
sub2 = NNG::Socket.new(:sub)
|
120
|
+
sub2.dial("tcp://127.0.0.1:5557")
|
121
|
+
sub2.set_option("sub:subscribe", "ALERT:") # Only "ALERT:" messages
|
122
|
+
|
123
|
+
sleep 0.1 # Allow subscriptions to propagate
|
124
|
+
|
125
|
+
# Publish to all subscribers
|
126
|
+
pub.send("ALERT: System update available")
|
127
|
+
puts sub1.recv # => "ALERT: System update available"
|
128
|
+
puts sub2.recv # => "ALERT: System update available"
|
129
|
+
|
130
|
+
# Publish general message (only sub1 receives)
|
131
|
+
pub.send("INFO: Normal operation")
|
132
|
+
puts sub1.recv # => "INFO: Normal operation"
|
133
|
+
# sub2 won't receive this (topic doesn't match)
|
134
|
+
|
135
|
+
pub.close
|
136
|
+
sub1.close
|
137
|
+
sub2.close
|
138
|
+
```
|
139
|
+
|
140
|
+
### Push/Pull Protocol (Pipeline)
|
141
|
+
|
142
|
+
Push/Pull creates a load-balanced pipeline for distributing work:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
require 'nng'
|
146
|
+
require 'thread'
|
147
|
+
|
148
|
+
# Producer (Push)
|
149
|
+
push = NNG::Socket.new(:push)
|
150
|
+
push.listen("tcp://127.0.0.1:5558")
|
151
|
+
|
152
|
+
# Worker 1 (Pull)
|
153
|
+
pull1 = NNG::Socket.new(:pull)
|
154
|
+
pull1.dial("tcp://127.0.0.1:5558")
|
155
|
+
|
156
|
+
# Worker 2 (Pull)
|
157
|
+
pull2 = NNG::Socket.new(:pull)
|
158
|
+
pull2.dial("tcp://127.0.0.1:5558")
|
159
|
+
|
160
|
+
sleep 0.1 # Allow connections
|
161
|
+
|
162
|
+
# Start workers in threads
|
163
|
+
workers = []
|
164
|
+
workers << Thread.new do
|
165
|
+
3.times do
|
166
|
+
task = pull1.recv
|
167
|
+
puts "Worker 1 processing: #{task}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
workers << Thread.new do
|
172
|
+
3.times do
|
173
|
+
task = pull2.recv
|
174
|
+
puts "Worker 2 processing: #{task}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Distribute tasks (round-robin to workers)
|
179
|
+
6.times do |i|
|
180
|
+
push.send("Task #{i+1}")
|
181
|
+
sleep 0.01
|
182
|
+
end
|
183
|
+
|
184
|
+
# Wait for workers to complete
|
185
|
+
workers.each(&:join)
|
186
|
+
|
187
|
+
push.close
|
188
|
+
pull1.close
|
189
|
+
pull2.close
|
190
|
+
```
|
191
|
+
|
192
|
+
### Bus Protocol (Many-to-Many)
|
193
|
+
|
194
|
+
Bus protocol allows all peers to communicate with each other:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
require 'nng'
|
198
|
+
|
199
|
+
# Create three bus nodes
|
200
|
+
node1 = NNG::Socket.new(:bus)
|
201
|
+
node1.listen("tcp://127.0.0.1:5559")
|
202
|
+
|
203
|
+
node2 = NNG::Socket.new(:bus)
|
204
|
+
node2.listen("tcp://127.0.0.1:5560")
|
205
|
+
node2.dial("tcp://127.0.0.1:5559")
|
206
|
+
|
207
|
+
node3 = NNG::Socket.new(:bus)
|
208
|
+
node3.dial("tcp://127.0.0.1:5559")
|
209
|
+
node3.dial("tcp://127.0.0.1:5560")
|
210
|
+
|
211
|
+
sleep 0.1 # Allow mesh to form
|
212
|
+
|
213
|
+
# Any node can send to all others
|
214
|
+
node1.send("Message from Node 1")
|
215
|
+
puts node2.recv # => "Message from Node 1"
|
216
|
+
puts node3.recv # => "Message from Node 1"
|
217
|
+
|
218
|
+
node2.send("Message from Node 2")
|
219
|
+
puts node1.recv # => "Message from Node 2"
|
220
|
+
puts node3.recv # => "Message from Node 2"
|
221
|
+
|
222
|
+
node1.close
|
223
|
+
node2.close
|
224
|
+
node3.close
|
225
|
+
```
|
226
|
+
|
227
|
+
### Surveyor/Respondent Protocol
|
228
|
+
|
229
|
+
Surveyor sends a survey, and all respondents reply:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
require 'nng'
|
233
|
+
|
234
|
+
# Surveyor
|
235
|
+
surveyor = NNG::Socket.new(:surveyor)
|
236
|
+
surveyor.listen("tcp://127.0.0.1:5561")
|
237
|
+
surveyor.send_timeout = 1000 # 1 second to collect responses
|
238
|
+
|
239
|
+
# Respondents
|
240
|
+
resp1 = NNG::Socket.new(:respondent)
|
241
|
+
resp1.dial("tcp://127.0.0.1:5561")
|
242
|
+
|
243
|
+
resp2 = NNG::Socket.new(:respondent)
|
244
|
+
resp2.dial("tcp://127.0.0.1:5561")
|
245
|
+
|
246
|
+
sleep 0.1 # Allow connections
|
247
|
+
|
248
|
+
# Send survey
|
249
|
+
surveyor.send("What is your status?")
|
250
|
+
|
251
|
+
# Respondents reply
|
252
|
+
question1 = resp1.recv
|
253
|
+
resp1.send("Respondent 1: OK")
|
254
|
+
|
255
|
+
question2 = resp2.recv
|
256
|
+
resp2.send("Respondent 2: OK")
|
257
|
+
|
258
|
+
# Collect responses
|
259
|
+
begin
|
260
|
+
puts surveyor.recv # => "Respondent 1: OK" or "Respondent 2: OK"
|
261
|
+
puts surveyor.recv # => "Respondent 2: OK" or "Respondent 1: OK"
|
262
|
+
rescue NNG::TimeoutError
|
263
|
+
puts "Survey complete"
|
264
|
+
end
|
265
|
+
|
266
|
+
surveyor.close
|
267
|
+
resp1.close
|
268
|
+
resp2.close
|
269
|
+
```
|
270
|
+
|
271
|
+
## Supported Protocols
|
272
|
+
|
273
|
+
- **Pair** - Bidirectional 1:1 communication (`pair0`, `pair1`)
|
274
|
+
- **Push/Pull** - Unidirectional pipeline (`push0`, `pull0`)
|
275
|
+
- **Pub/Sub** - One-to-many distribution (`pub0`, `sub0`)
|
276
|
+
- **Req/Rep** - Request/reply pattern (`req0`, `rep0`)
|
277
|
+
- **Surveyor/Respondent** - Survey pattern (`surveyor0`, `respondent0`)
|
278
|
+
- **Bus** - Many-to-many (`bus0`)
|
279
|
+
|
280
|
+
## Supported Transports
|
281
|
+
|
282
|
+
- **TCP** - `tcp://host:port`
|
283
|
+
- **IPC** - `ipc:///path/to/socket`
|
284
|
+
- **Inproc** - `inproc://name`
|
285
|
+
- **WebSocket** - `ws://host:port/path`
|
286
|
+
- **TLS** - `tls+tcp://host:port`
|
287
|
+
|
288
|
+
## Advanced Usage
|
289
|
+
|
290
|
+
### Custom Library Configuration
|
291
|
+
|
292
|
+
By default, nng-ruby uses the bundled NNG 1.11.0 library (platform-specific: nng.dll on Windows, libnng.so.1.11.0 on Linux). However, you can specify a custom NNG library in several ways:
|
293
|
+
|
294
|
+
#### Option 1: At install time
|
295
|
+
|
296
|
+
Use gem install options to specify a custom NNG library location:
|
297
|
+
|
298
|
+
```bash
|
299
|
+
# Specify NNG installation directory (will search lib/, lib64/, etc.)
|
300
|
+
gem install nng-ruby -- --with-nng-dir=/opt/nng
|
301
|
+
|
302
|
+
# Specify exact library path
|
303
|
+
gem install nng-ruby -- --with-nng-lib=/opt/nng/lib/libnng.so
|
304
|
+
|
305
|
+
# Specify include directory (for future use)
|
306
|
+
gem install nng-ruby -- --with-nng-include=/opt/nng/include
|
307
|
+
```
|
308
|
+
|
309
|
+
#### Option 2: At runtime with environment variables
|
310
|
+
|
311
|
+
Set environment variables before requiring the gem:
|
312
|
+
|
313
|
+
```bash
|
314
|
+
# Specify exact library file path
|
315
|
+
export NNG_LIB_PATH=/usr/local/lib/libnng.so.1.9.0
|
316
|
+
ruby your_script.rb
|
317
|
+
|
318
|
+
# Or specify library directory (will search for libnng.so*)
|
319
|
+
export NNG_LIB_DIR=/usr/local/lib
|
320
|
+
ruby your_script.rb
|
321
|
+
```
|
322
|
+
|
323
|
+
In Ruby code:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
# Set before requiring nng
|
327
|
+
ENV['NNG_LIB_PATH'] = '/custom/path/libnng.so'
|
328
|
+
require 'nng'
|
329
|
+
|
330
|
+
# Or use directory
|
331
|
+
ENV['NNG_LIB_DIR'] = '/custom/path/lib'
|
332
|
+
require 'nng'
|
333
|
+
```
|
334
|
+
|
335
|
+
#### Priority Order
|
336
|
+
|
337
|
+
The library is loaded in this priority order:
|
338
|
+
|
339
|
+
1. **Environment variable** `NNG_LIB_PATH` (highest priority)
|
340
|
+
2. **Environment variable** `NNG_LIB_DIR`
|
341
|
+
3. **Install-time configuration** (gem install --with-nng-*)
|
342
|
+
4. **Bundled library** (ext/nng/nng.dll or ext/nng/libnng.so.1.11.0)
|
343
|
+
5. **System paths** (/usr/local/lib, /usr/lib, etc.)
|
344
|
+
|
345
|
+
#### Debugging
|
346
|
+
|
347
|
+
Enable debug output to see which library is being loaded:
|
348
|
+
|
349
|
+
```bash
|
350
|
+
export NNG_DEBUG=1
|
351
|
+
ruby your_script.rb
|
352
|
+
```
|
353
|
+
|
354
|
+
This will print messages showing:
|
355
|
+
- Which paths are being searched
|
356
|
+
- Which library was successfully loaded
|
357
|
+
- Any load failures encountered
|
358
|
+
|
359
|
+
### Setting Timeouts
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
socket = NNG::Socket.new(:pair1)
|
363
|
+
socket.send_timeout = 5000 # 5 seconds
|
364
|
+
socket.recv_timeout = 5000 # 5 seconds
|
365
|
+
```
|
366
|
+
|
367
|
+
### Non-blocking I/O
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
require 'nng'
|
371
|
+
|
372
|
+
socket = NNG::Socket.new(:pair1)
|
373
|
+
socket.listen("tcp://127.0.0.1:5555")
|
374
|
+
|
375
|
+
begin
|
376
|
+
data = socket.recv(flags: NNG::FFI::NNG_FLAG_NONBLOCK)
|
377
|
+
puts "Received: #{data}"
|
378
|
+
rescue NNG::Error => e
|
379
|
+
puts "No data available: #{e.message}"
|
380
|
+
end
|
381
|
+
```
|
382
|
+
|
383
|
+
### Using Messages (NNG::Message API)
|
384
|
+
|
385
|
+
The Message API provides more control over message headers and bodies:
|
386
|
+
|
387
|
+
#### Basic Message Operations
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
require 'nng'
|
391
|
+
|
392
|
+
# Create a new message
|
393
|
+
msg = NNG::Message.new
|
394
|
+
|
395
|
+
# Append data to message body
|
396
|
+
msg.append("Hello, ")
|
397
|
+
msg.append("World!")
|
398
|
+
puts msg.body # => "Hello, World!"
|
399
|
+
puts msg.length # => 13
|
400
|
+
|
401
|
+
# Insert data at the beginning
|
402
|
+
msg.insert("Say: ")
|
403
|
+
puts msg.body # => "Say: Hello, World!"
|
404
|
+
|
405
|
+
# Add header information
|
406
|
+
msg.header_append("Content-Type: text/plain")
|
407
|
+
puts msg.header # => "Content-Type: text/plain"
|
408
|
+
puts msg.header_length # => 24
|
409
|
+
|
410
|
+
# Clear body or header
|
411
|
+
msg.clear # Clears body
|
412
|
+
msg.header_clear # Clears header
|
413
|
+
|
414
|
+
# Duplicate message
|
415
|
+
msg2 = msg.dup
|
416
|
+
|
417
|
+
# Free messages when done
|
418
|
+
msg.free
|
419
|
+
msg2.free
|
420
|
+
```
|
421
|
+
|
422
|
+
#### Sending and Receiving Messages
|
423
|
+
|
424
|
+
```ruby
|
425
|
+
require 'nng'
|
426
|
+
|
427
|
+
# Server side
|
428
|
+
server = NNG::Socket.new(:pair1)
|
429
|
+
server.listen("tcp://127.0.0.1:5555")
|
430
|
+
|
431
|
+
# Client side
|
432
|
+
client = NNG::Socket.new(:pair1)
|
433
|
+
client.dial("tcp://127.0.0.1:5555")
|
434
|
+
sleep 0.1 # Give time to establish connection
|
435
|
+
|
436
|
+
# Send a message with header and body
|
437
|
+
msg = NNG::Message.new
|
438
|
+
msg.header_append("RequestID: 12345")
|
439
|
+
msg.append("Hello from client!")
|
440
|
+
|
441
|
+
# Send message using low-level API
|
442
|
+
msg_ptr = ::FFI::MemoryPointer.new(:pointer)
|
443
|
+
msg_ptr.write_pointer(msg.to_ptr)
|
444
|
+
ret = NNG::FFI.nng_sendmsg(client.socket, msg_ptr.read_pointer, 0)
|
445
|
+
NNG::FFI.check_error(ret, "Send message")
|
446
|
+
# Note: Message is freed automatically after sending
|
447
|
+
|
448
|
+
# Receive message
|
449
|
+
recv_msg_ptr = ::FFI::MemoryPointer.new(:pointer)
|
450
|
+
ret = NNG::FFI.nng_recvmsg(server.socket, recv_msg_ptr, 0)
|
451
|
+
NNG::FFI.check_error(ret, "Receive message")
|
452
|
+
|
453
|
+
# Wrap received message
|
454
|
+
recv_msg = NNG::Message.allocate
|
455
|
+
recv_msg.instance_variable_set(:@msg, recv_msg_ptr.read_pointer)
|
456
|
+
recv_msg.instance_variable_set(:@msg_ptr, recv_msg_ptr)
|
457
|
+
recv_msg.instance_variable_set(:@freed, false)
|
458
|
+
|
459
|
+
puts "Header: #{recv_msg.header}" # => "RequestID: 12345"
|
460
|
+
puts "Body: #{recv_msg.body}" # => "Hello from client!"
|
461
|
+
|
462
|
+
recv_msg.free
|
463
|
+
server.close
|
464
|
+
client.close
|
465
|
+
```
|
466
|
+
|
467
|
+
#### Simple String-based Send/Receive (Recommended for most use cases)
|
468
|
+
|
469
|
+
For simpler use cases, use the high-level Socket#send and Socket#recv methods:
|
470
|
+
|
471
|
+
```ruby
|
472
|
+
require 'nng'
|
473
|
+
|
474
|
+
server = NNG::Socket.new(:pair1)
|
475
|
+
server.listen("tcp://127.0.0.1:5555")
|
476
|
+
|
477
|
+
client = NNG::Socket.new(:pair1)
|
478
|
+
client.dial("tcp://127.0.0.1:5555")
|
479
|
+
|
480
|
+
# Simple send/receive (no need to manage Message objects)
|
481
|
+
client.send("Hello, Server!")
|
482
|
+
data = server.recv
|
483
|
+
puts data # => "Hello, Server!"
|
484
|
+
|
485
|
+
server.close
|
486
|
+
client.close
|
487
|
+
```
|
488
|
+
|
489
|
+
### Socket Options
|
490
|
+
|
491
|
+
NNG sockets support various options to control behavior:
|
492
|
+
|
493
|
+
```ruby
|
494
|
+
require 'nng'
|
495
|
+
|
496
|
+
socket = NNG::Socket.new(:pub)
|
497
|
+
|
498
|
+
# Set buffer sizes
|
499
|
+
socket.set_option("send-buffer", 8192) # Send buffer size in bytes
|
500
|
+
socket.set_option("recv-buffer", 8192) # Receive buffer size in bytes
|
501
|
+
|
502
|
+
# Set TCP options
|
503
|
+
socket.set_option("tcp-nodelay", true) # Disable Nagle's algorithm
|
504
|
+
socket.set_option("tcp-keepalive", true) # Enable TCP keepalive
|
505
|
+
|
506
|
+
# Set timeouts
|
507
|
+
socket.send_timeout = 1000 # 1 second send timeout
|
508
|
+
socket.recv_timeout = 5000 # 5 second receive timeout
|
509
|
+
|
510
|
+
# Or use set_option_ms
|
511
|
+
socket.set_option_ms("send-timeout", 1000)
|
512
|
+
socket.set_option_ms("recv-timeout", 5000)
|
513
|
+
|
514
|
+
# Get options
|
515
|
+
buffer_size = socket.get_option("send-buffer", type: :int)
|
516
|
+
puts "Send buffer: #{buffer_size} bytes"
|
517
|
+
|
518
|
+
nodelay = socket.get_option("tcp-nodelay", type: :bool)
|
519
|
+
puts "TCP NoDelay: #{nodelay}"
|
520
|
+
|
521
|
+
send_timeout = socket.get_option("send-timeout", type: :ms)
|
522
|
+
puts "Send timeout: #{send_timeout} ms"
|
523
|
+
|
524
|
+
# Protocol-specific options
|
525
|
+
if socket.socket.id # For pub/sub
|
526
|
+
# Subscriber can set subscription topics
|
527
|
+
sub = NNG::Socket.new(:sub)
|
528
|
+
sub.set_option("sub:subscribe", "news/") # Subscribe to "news/*"
|
529
|
+
sub.set_option("sub:subscribe", "alerts/") # Subscribe to "alerts/*"
|
530
|
+
sub.set_option("sub:unsubscribe", "news/") # Unsubscribe from "news/*"
|
531
|
+
end
|
532
|
+
|
533
|
+
socket.close
|
534
|
+
```
|
535
|
+
|
536
|
+
### Transport-Specific URLs
|
537
|
+
|
538
|
+
Different transports have different URL formats:
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
require 'nng'
|
542
|
+
|
543
|
+
socket = NNG::Socket.new(:pair1)
|
544
|
+
|
545
|
+
# TCP transport
|
546
|
+
socket.listen("tcp://0.0.0.0:5555") # Listen on all interfaces
|
547
|
+
socket.dial("tcp://192.168.1.100:5555") # Connect to specific IP
|
548
|
+
socket.dial("tcp://localhost:5555") # Connect to localhost
|
549
|
+
|
550
|
+
# IPC transport (Unix domain sockets)
|
551
|
+
socket.listen("ipc:///tmp/test.sock") # Unix socket
|
552
|
+
socket.dial("ipc:///tmp/test.sock")
|
553
|
+
|
554
|
+
# Inproc transport (in-process, same memory space)
|
555
|
+
socket.listen("inproc://my-channel")
|
556
|
+
socket.dial("inproc://my-channel")
|
557
|
+
|
558
|
+
# WebSocket transport
|
559
|
+
socket.listen("ws://0.0.0.0:8080/path")
|
560
|
+
socket.dial("ws://localhost:8080/path")
|
561
|
+
|
562
|
+
# TLS transport
|
563
|
+
socket.listen("tls+tcp://0.0.0.0:5556")
|
564
|
+
socket.dial("tls+tcp://server.example.com:5556")
|
565
|
+
|
566
|
+
socket.close
|
567
|
+
```
|
568
|
+
|
569
|
+
## Examples
|
570
|
+
|
571
|
+
See the `examples/` directory for complete working examples:
|
572
|
+
|
573
|
+
### Protocol Examples
|
574
|
+
- `examples/pair.rb` - Pair protocol
|
575
|
+
- `examples/reqrep.rb` - Request/Reply protocol
|
576
|
+
- `examples/pubsub.rb` - Publish/Subscribe protocol
|
577
|
+
|
578
|
+
### Protocol Buffers Integration
|
579
|
+
|
580
|
+
NNG-ruby works seamlessly with Google Protocol Buffers for efficient binary serialization. This is perfect for RPC systems, microservices, and cross-language communication.
|
581
|
+
|
582
|
+
**Available Examples:**
|
583
|
+
- `examples/protobuf_demo.rb` - Complete demo with nested messages and RPC patterns
|
584
|
+
- `examples/protobuf_simple.rb` - Simple client-server with Protobuf
|
585
|
+
- `examples/proto/message.proto` - Protobuf message definitions
|
586
|
+
|
587
|
+
**Installation:**
|
588
|
+
|
589
|
+
```bash
|
590
|
+
gem install google-protobuf
|
591
|
+
```
|
592
|
+
|
593
|
+
#### Example 1: Basic RPC with Protobuf
|
594
|
+
|
595
|
+
This example shows a simple request-response RPC system using NNG + Protobuf:
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
require 'nng'
|
599
|
+
require 'google/protobuf'
|
600
|
+
|
601
|
+
# Define Protobuf messages inline (or load from .proto file)
|
602
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
603
|
+
add_file("rpc.proto", syntax: :proto3) do
|
604
|
+
add_message "RpcRequest" do
|
605
|
+
optional :func_code, :int32, 1
|
606
|
+
optional :data, :bytes, 2
|
607
|
+
optional :request_id, :string, 3
|
608
|
+
end
|
609
|
+
add_message "RpcResponse" do
|
610
|
+
optional :status, :int32, 1
|
611
|
+
optional :data, :bytes, 2
|
612
|
+
optional :error_msg, :string, 3
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
RpcRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("RpcRequest").msgclass
|
618
|
+
RpcResponse = Google::Protobuf::DescriptorPool.generated_pool.msgclass("RpcResponse")
|
619
|
+
|
620
|
+
# Server
|
621
|
+
server = NNG::Socket.new(:rep)
|
622
|
+
server.listen("tcp://127.0.0.1:5555")
|
623
|
+
puts "RPC Server listening on tcp://127.0.0.1:5555"
|
624
|
+
|
625
|
+
# Client
|
626
|
+
client = NNG::Socket.new(:req)
|
627
|
+
client.dial("tcp://127.0.0.1:5555")
|
628
|
+
sleep 0.1
|
629
|
+
|
630
|
+
# Client sends request
|
631
|
+
request = RpcRequest.new(
|
632
|
+
func_code: 100,
|
633
|
+
data: "Get user info",
|
634
|
+
request_id: "req-001"
|
635
|
+
)
|
636
|
+
client.send(RpcRequest.encode(request))
|
637
|
+
puts "Client sent: #{request.inspect}"
|
638
|
+
|
639
|
+
# Server receives and processes
|
640
|
+
req_data = server.recv
|
641
|
+
received_req = RpcRequest.decode(req_data)
|
642
|
+
puts "Server received: func=#{received_req.func_code}, id=#{received_req.request_id}"
|
643
|
+
|
644
|
+
# Server sends response
|
645
|
+
response = RpcResponse.new(
|
646
|
+
status: 0,
|
647
|
+
data: '{"name": "Alice", "age": 30}',
|
648
|
+
error_msg: ""
|
649
|
+
)
|
650
|
+
server.send(RpcResponse.encode(response))
|
651
|
+
puts "Server sent: status=#{response.status}"
|
652
|
+
|
653
|
+
# Client receives response
|
654
|
+
resp_data = client.recv
|
655
|
+
received_resp = RpcResponse.decode(resp_data)
|
656
|
+
puts "Client received: status=#{received_resp.status}, data=#{received_resp.data}"
|
657
|
+
|
658
|
+
server.close
|
659
|
+
client.close
|
660
|
+
```
|
661
|
+
|
662
|
+
**Output:**
|
663
|
+
```
|
664
|
+
RPC Server listening on tcp://127.0.0.1:5555
|
665
|
+
Client sent: <RpcRequest: func_code: 100, data: "Get user info", request_id: "req-001">
|
666
|
+
Server received: func=100, id=req-001
|
667
|
+
Server sent: status=0
|
668
|
+
Client received: status=0, data={"name": "Alice", "age": 30}
|
669
|
+
```
|
670
|
+
|
671
|
+
#### Example 2: Nested Messages (Message in Message)
|
672
|
+
|
673
|
+
A common pattern is sending complex data structures by nesting Protobuf messages:
|
674
|
+
|
675
|
+
```ruby
|
676
|
+
require 'nng'
|
677
|
+
require 'google/protobuf'
|
678
|
+
|
679
|
+
# Define nested message structure
|
680
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
681
|
+
add_file("nested.proto", syntax: :proto3) do
|
682
|
+
add_message "Contact" do
|
683
|
+
optional :wxid, :string, 1
|
684
|
+
optional :name, :string, 2
|
685
|
+
optional :remark, :string, 3
|
686
|
+
end
|
687
|
+
add_message "ContactList" do
|
688
|
+
repeated :contacts, :message, 1, "Contact"
|
689
|
+
end
|
690
|
+
add_message "RpcResponse" do
|
691
|
+
optional :status, :int32, 1
|
692
|
+
optional :data, :bytes, 2 # Will contain encoded ContactList
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
Contact = Google::Protobuf::DescriptorPool.generated_pool.lookup("Contact").msgclass
|
698
|
+
ContactList = Google::Protobuf::DescriptorPool.generated_pool.lookup("ContactList").msgclass
|
699
|
+
RpcResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("RpcResponse").msgclass
|
700
|
+
|
701
|
+
# Server prepares nested data
|
702
|
+
contacts = ContactList.new(
|
703
|
+
contacts: [
|
704
|
+
Contact.new(wxid: "wxid_001", name: "Alice", remark: "Friend"),
|
705
|
+
Contact.new(wxid: "wxid_002", name: "Bob", remark: "Colleague"),
|
706
|
+
Contact.new(wxid: "wxid_003", name: "Charlie", remark: "Family")
|
707
|
+
]
|
708
|
+
)
|
709
|
+
|
710
|
+
# Encode inner message first, then wrap in outer message
|
711
|
+
contacts_data = ContactList.encode(contacts)
|
712
|
+
response = RpcResponse.new(status: 0, data: contacts_data)
|
713
|
+
|
714
|
+
# Setup sockets
|
715
|
+
server = NNG::Socket.new(:pair1)
|
716
|
+
server.listen("tcp://127.0.0.1:5556")
|
717
|
+
|
718
|
+
client = NNG::Socket.new(:pair1)
|
719
|
+
client.dial("tcp://127.0.0.1:5556")
|
720
|
+
sleep 0.1
|
721
|
+
|
722
|
+
# Send nested message
|
723
|
+
server.send(RpcResponse.encode(response))
|
724
|
+
puts "Server sent: #{contacts.contacts.size} contacts"
|
725
|
+
|
726
|
+
# Receive and decode nested message
|
727
|
+
resp_data = client.recv
|
728
|
+
received_resp = RpcResponse.decode(resp_data)
|
729
|
+
|
730
|
+
# Decode inner message
|
731
|
+
received_contacts = ContactList.decode(received_resp.data)
|
732
|
+
puts "Client received #{received_contacts.contacts.size} contacts:"
|
733
|
+
received_contacts.contacts.each do |c|
|
734
|
+
puts " - #{c.name} (#{c.wxid}): #{c.remark}"
|
735
|
+
end
|
736
|
+
|
737
|
+
server.close
|
738
|
+
client.close
|
739
|
+
```
|
740
|
+
|
741
|
+
**Output:**
|
742
|
+
```
|
743
|
+
Server sent: 3 contacts
|
744
|
+
Client received 3 contacts:
|
745
|
+
- Alice (wxid_001): Friend
|
746
|
+
- Bob (wxid_002): Colleague
|
747
|
+
- Charlie (wxid_003): Family
|
748
|
+
```
|
749
|
+
|
750
|
+
#### Example 3: Real-World RPC Pattern (WeChatFerry Style)
|
751
|
+
|
752
|
+
This shows a complete RPC system similar to WeChatFerry's architecture:
|
753
|
+
|
754
|
+
```ruby
|
755
|
+
require 'nng'
|
756
|
+
require 'google/protobuf'
|
757
|
+
|
758
|
+
# Define protocol (matching WeChatFerry style)
|
759
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
760
|
+
add_file("wcf_rpc.proto", syntax: :proto3) do
|
761
|
+
add_enum "Functions" do
|
762
|
+
value :FUNC_IS_LOGIN, 0x01
|
763
|
+
value :FUNC_SEND_TEXT, 0x20
|
764
|
+
value :FUNC_GET_CONTACTS, 0x12
|
765
|
+
end
|
766
|
+
|
767
|
+
add_message "Request" do
|
768
|
+
optional :func, :enum, 1, "Functions"
|
769
|
+
optional :data, :bytes, 2
|
770
|
+
end
|
771
|
+
|
772
|
+
add_message "Response" do
|
773
|
+
optional :status, :int32, 1
|
774
|
+
optional :data, :bytes, 2
|
775
|
+
end
|
776
|
+
|
777
|
+
add_message "TextMsg" do
|
778
|
+
optional :receiver, :string, 1
|
779
|
+
optional :message, :string, 2
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
Functions = Google::Protobuf::DescriptorPool.generated_pool.lookup("Functions").enummodule
|
785
|
+
Request = Google::Protobuf::DescriptorPool.generated_pool.lookup("Request").msgclass
|
786
|
+
Response = Google::Protobuf::DescriptorPool.generated_pool.lookup("Response").msgclass
|
787
|
+
TextMsg = Google::Protobuf::DescriptorPool.generated_pool.lookup("TextMsg").msgclass
|
788
|
+
|
789
|
+
# RPC Server
|
790
|
+
def start_rpc_server
|
791
|
+
server = NNG::Socket.new(:rep)
|
792
|
+
server.listen("tcp://127.0.0.1:10086")
|
793
|
+
puts "RPC Server started on port 10086"
|
794
|
+
|
795
|
+
loop do
|
796
|
+
# Receive request
|
797
|
+
req_data = server.recv
|
798
|
+
request = Request.decode(req_data)
|
799
|
+
|
800
|
+
puts "Received: func=#{request.func}"
|
801
|
+
|
802
|
+
# Process request
|
803
|
+
response = case request.func
|
804
|
+
when :FUNC_IS_LOGIN
|
805
|
+
Response.new(status: 1) # Logged in
|
806
|
+
when :FUNC_SEND_TEXT
|
807
|
+
text_msg = TextMsg.decode(request.data)
|
808
|
+
puts " Send to #{text_msg.receiver}: #{text_msg.message}"
|
809
|
+
Response.new(status: 0) # Success
|
810
|
+
when :FUNC_GET_CONTACTS
|
811
|
+
Response.new(status: 0, data: "[contacts data]")
|
812
|
+
else
|
813
|
+
Response.new(status: -1) # Unknown function
|
814
|
+
end
|
815
|
+
|
816
|
+
# Send response
|
817
|
+
server.send(Response.encode(response))
|
818
|
+
end
|
819
|
+
ensure
|
820
|
+
server&.close
|
821
|
+
end
|
822
|
+
|
823
|
+
# RPC Client
|
824
|
+
def rpc_call(client, func, data = nil)
|
825
|
+
request = Request.new(func: func, data: data)
|
826
|
+
client.send(Request.encode(request))
|
827
|
+
|
828
|
+
resp_data = client.recv
|
829
|
+
Response.decode(resp_data)
|
830
|
+
end
|
831
|
+
|
832
|
+
# Run example
|
833
|
+
server_thread = Thread.new { start_rpc_server rescue nil }
|
834
|
+
sleep 0.5 # Wait for server to start
|
835
|
+
|
836
|
+
client = NNG::Socket.new(:req)
|
837
|
+
client.dial("tcp://127.0.0.1:10086")
|
838
|
+
puts "Client connected"
|
839
|
+
|
840
|
+
# Call 1: Check login
|
841
|
+
resp = rpc_call(client, :FUNC_IS_LOGIN)
|
842
|
+
puts "Login status: #{resp.status == 1 ? 'Logged in' : 'Not logged in'}"
|
843
|
+
|
844
|
+
# Call 2: Send text message
|
845
|
+
text_msg = TextMsg.new(receiver: "wxid_friend", message: "Hello from Ruby!")
|
846
|
+
resp = rpc_call(client, :FUNC_SEND_TEXT, TextMsg.encode(text_msg))
|
847
|
+
puts "Send text result: #{resp.status == 0 ? 'Success' : 'Failed'}"
|
848
|
+
|
849
|
+
# Call 3: Get contacts
|
850
|
+
resp = rpc_call(client, :FUNC_GET_CONTACTS)
|
851
|
+
puts "Contacts: #{resp.data}"
|
852
|
+
|
853
|
+
client.close
|
854
|
+
Thread.kill(server_thread)
|
855
|
+
```
|
856
|
+
|
857
|
+
**Output:**
|
858
|
+
```
|
859
|
+
RPC Server started on port 10086
|
860
|
+
Client connected
|
861
|
+
Received: func=FUNC_IS_LOGIN
|
862
|
+
Login status: Logged in
|
863
|
+
Received: func=FUNC_SEND_TEXT
|
864
|
+
Send to wxid_friend: Hello from Ruby!
|
865
|
+
Send text result: Success
|
866
|
+
Received: func=FUNC_GET_CONTACTS
|
867
|
+
Contacts: [contacts data]
|
868
|
+
```
|
869
|
+
|
870
|
+
#### Why Use Protobuf with NNG?
|
871
|
+
|
872
|
+
1. **Binary Efficiency**: Protobuf is 3-10x smaller than JSON, faster to parse
|
873
|
+
2. **Type Safety**: Strong typing prevents errors
|
874
|
+
3. **Cross-Language**: Works with Python, Go, Java, C++, etc.
|
875
|
+
4. **Schema Evolution**: Add fields without breaking old clients
|
876
|
+
5. **Perfect for RPC**: Natural request/response pattern
|
877
|
+
|
878
|
+
#### Performance Comparison
|
879
|
+
|
880
|
+
```ruby
|
881
|
+
# Benchmark: Protobuf vs JSON
|
882
|
+
require 'benchmark'
|
883
|
+
require 'json'
|
884
|
+
|
885
|
+
data = { name: "Alice", age: 30, contacts: ["Bob", "Charlie"] }
|
886
|
+
|
887
|
+
Benchmark.bm(10) do |x|
|
888
|
+
x.report("JSON:") do
|
889
|
+
10000.times { JSON.parse(data.to_json) }
|
890
|
+
end
|
891
|
+
|
892
|
+
x.report("Protobuf:") do
|
893
|
+
10000.times {
|
894
|
+
msg = MyProto.new(data)
|
895
|
+
MyProto.decode(MyProto.encode(msg))
|
896
|
+
}
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
# Typical result:
|
901
|
+
# user system total real
|
902
|
+
# JSON: 0.850000 0.010000 0.860000 ( 0.862341)
|
903
|
+
# Protobuf: 0.280000 0.000000 0.280000 ( 0.283127)
|
904
|
+
```
|
905
|
+
|
906
|
+
#### More Examples
|
907
|
+
|
908
|
+
See the `examples/` directory for complete runnable code:
|
909
|
+
|
910
|
+
```bash
|
911
|
+
# Run comprehensive demo
|
912
|
+
ruby examples/protobuf_demo.rb
|
913
|
+
|
914
|
+
# Run simple client-server
|
915
|
+
ruby examples/protobuf_simple.rb
|
916
|
+
|
917
|
+
# View proto definitions
|
918
|
+
cat examples/proto/message.proto
|
919
|
+
```
|
920
|
+
|
921
|
+
#### Loading .proto Files
|
922
|
+
|
923
|
+
You can also define messages in `.proto` files and compile them:
|
924
|
+
|
925
|
+
```bash
|
926
|
+
# Install protoc compiler
|
927
|
+
gem install grpc-tools
|
928
|
+
|
929
|
+
# Compile proto file
|
930
|
+
grpc_tools_ruby_protoc -I ./proto --ruby_out=./lib proto/message.proto
|
931
|
+
|
932
|
+
# Use in Ruby
|
933
|
+
require_relative 'lib/message_pb'
|
934
|
+
msg = MyMessage.new(field: "value")
|
935
|
+
```
|
936
|
+
|
937
|
+
## API Documentation
|
938
|
+
|
939
|
+
### NNG Module
|
940
|
+
|
941
|
+
- `NNG.version` - Gem version
|
942
|
+
- `NNG.lib_version` - NNG library version
|
943
|
+
- `NNG.fini` - Cleanup NNG (called automatically)
|
944
|
+
|
945
|
+
### NNG::Socket
|
946
|
+
|
947
|
+
#### Creating Sockets
|
948
|
+
|
949
|
+
```ruby
|
950
|
+
socket = NNG::Socket.new(:pair1)
|
951
|
+
socket = NNG::Socket.new(:req, raw: false)
|
952
|
+
```
|
953
|
+
|
954
|
+
#### Connection Methods
|
955
|
+
|
956
|
+
- `listen(url, flags: 0)` - Listen on address
|
957
|
+
- `dial(url, flags: 0)` - Connect to address
|
958
|
+
- `close` - Close socket
|
959
|
+
- `closed?` - Check if closed
|
960
|
+
|
961
|
+
#### Send/Receive Methods
|
962
|
+
|
963
|
+
- `send(data, flags: 0)` - Send data
|
964
|
+
- `recv(flags: NNG::FFI::NNG_FLAG_ALLOC)` - Receive data
|
965
|
+
|
966
|
+
#### Option Methods
|
967
|
+
|
968
|
+
- `set_option(name, value)` - Set socket option
|
969
|
+
- `get_option(name, type: :int)` - Get socket option
|
970
|
+
- `set_option_ms(name, ms)` - Set timeout option
|
971
|
+
- `send_timeout=(ms)` - Set send timeout
|
972
|
+
- `recv_timeout=(ms)` - Set receive timeout
|
973
|
+
|
974
|
+
#### Information Methods
|
975
|
+
|
976
|
+
- `id` - Get socket ID
|
977
|
+
|
978
|
+
### NNG::Message
|
979
|
+
|
980
|
+
#### Creating Messages
|
981
|
+
|
982
|
+
```ruby
|
983
|
+
msg = NNG::Message.new(size: 0)
|
984
|
+
```
|
985
|
+
|
986
|
+
#### Body Methods
|
987
|
+
|
988
|
+
- `append(data)` - Append to body
|
989
|
+
- `insert(data)` - Insert at beginning
|
990
|
+
- `body` - Get body content
|
991
|
+
- `length` / `size` - Get body length
|
992
|
+
- `clear` - Clear body
|
993
|
+
|
994
|
+
#### Header Methods
|
995
|
+
|
996
|
+
- `header_append(data)` - Append to header
|
997
|
+
- `header` - Get header content
|
998
|
+
- `header_length` - Get header length
|
999
|
+
- `header_clear` - Clear header
|
1000
|
+
|
1001
|
+
#### Other Methods
|
1002
|
+
|
1003
|
+
- `dup` - Duplicate message
|
1004
|
+
- `free` - Free message
|
1005
|
+
- `freed?` - Check if freed
|
1006
|
+
|
1007
|
+
### Error Handling
|
1008
|
+
|
1009
|
+
NNG-ruby provides specific exception classes for different error conditions:
|
1010
|
+
|
1011
|
+
```ruby
|
1012
|
+
require 'nng'
|
1013
|
+
|
1014
|
+
socket = NNG::Socket.new(:req)
|
1015
|
+
|
1016
|
+
begin
|
1017
|
+
# Set a short timeout for demonstration
|
1018
|
+
socket.recv_timeout = 100 # 100ms
|
1019
|
+
|
1020
|
+
# Try to receive (will timeout if no data)
|
1021
|
+
socket.dial("tcp://127.0.0.1:9999")
|
1022
|
+
data = socket.recv
|
1023
|
+
|
1024
|
+
rescue NNG::TimeoutError => e
|
1025
|
+
puts "Operation timed out: #{e.message}"
|
1026
|
+
# Retry logic here
|
1027
|
+
|
1028
|
+
rescue NNG::ConnectionRefused => e
|
1029
|
+
puts "Cannot connect to server: #{e.message}"
|
1030
|
+
# Server might be down
|
1031
|
+
|
1032
|
+
rescue NNG::AddressInUse => e
|
1033
|
+
puts "Port already in use: #{e.message}"
|
1034
|
+
# Try a different port
|
1035
|
+
|
1036
|
+
rescue NNG::StateError => e
|
1037
|
+
puts "Invalid state for operation: #{e.message}"
|
1038
|
+
# Check socket state
|
1039
|
+
|
1040
|
+
rescue NNG::Error => e
|
1041
|
+
puts "NNG error (#{e.class}): #{e.message}"
|
1042
|
+
# Generic error handling
|
1043
|
+
|
1044
|
+
ensure
|
1045
|
+
socket.close
|
1046
|
+
end
|
1047
|
+
```
|
1048
|
+
|
1049
|
+
#### Complete Error Hierarchy
|
1050
|
+
|
1051
|
+
```ruby
|
1052
|
+
NNG::Error # Base class for all NNG errors
|
1053
|
+
├── NNG::TimeoutError # Operation timed out
|
1054
|
+
├── NNG::ConnectionRefused # Connection refused by peer
|
1055
|
+
├── NNG::ConnectionAborted # Connection aborted
|
1056
|
+
├── NNG::ConnectionReset # Connection reset by peer
|
1057
|
+
├── NNG::Closed # Socket/resource already closed
|
1058
|
+
├── NNG::AddressInUse # Address already in use
|
1059
|
+
├── NNG::NoMemory # Out of memory
|
1060
|
+
├── NNG::MessageSize # Message size invalid
|
1061
|
+
├── NNG::ProtocolError # Protocol error
|
1062
|
+
└── NNG::StateError # Invalid state for operation
|
1063
|
+
```
|
1064
|
+
|
1065
|
+
#### Practical Error Handling Examples
|
1066
|
+
|
1067
|
+
**1. Retry on Timeout:**
|
1068
|
+
|
1069
|
+
```ruby
|
1070
|
+
def send_with_retry(socket, data, max_retries: 3)
|
1071
|
+
retries = 0
|
1072
|
+
begin
|
1073
|
+
socket.send(data)
|
1074
|
+
rescue NNG::TimeoutError
|
1075
|
+
retries += 1
|
1076
|
+
if retries < max_retries
|
1077
|
+
puts "Timeout, retrying (#{retries}/#{max_retries})..."
|
1078
|
+
sleep 0.1
|
1079
|
+
retry
|
1080
|
+
else
|
1081
|
+
raise "Failed after #{max_retries} retries"
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
```
|
1086
|
+
|
1087
|
+
**2. Graceful Degradation:**
|
1088
|
+
|
1089
|
+
```ruby
|
1090
|
+
def connect_with_fallback(socket, primary_url, fallback_url)
|
1091
|
+
begin
|
1092
|
+
socket.dial(primary_url)
|
1093
|
+
puts "Connected to primary server"
|
1094
|
+
rescue NNG::ConnectionRefused, NNG::TimeoutError
|
1095
|
+
puts "Primary server unavailable, trying fallback..."
|
1096
|
+
socket.dial(fallback_url)
|
1097
|
+
puts "Connected to fallback server"
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
```
|
1101
|
+
|
1102
|
+
**3. Non-blocking Receive with Error Handling:**
|
1103
|
+
|
1104
|
+
```ruby
|
1105
|
+
socket = NNG::Socket.new(:pull)
|
1106
|
+
socket.listen("tcp://127.0.0.1:5555")
|
1107
|
+
|
1108
|
+
loop do
|
1109
|
+
begin
|
1110
|
+
# Non-blocking receive
|
1111
|
+
data = socket.recv(flags: NNG::FFI::NNG_FLAG_NONBLOCK)
|
1112
|
+
puts "Received: #{data}"
|
1113
|
+
process_data(data)
|
1114
|
+
rescue NNG::Error => e
|
1115
|
+
if e.message.include?("try again")
|
1116
|
+
# No data available, do other work
|
1117
|
+
sleep 0.01
|
1118
|
+
else
|
1119
|
+
puts "Error: #{e.message}"
|
1120
|
+
break
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
```
|
1125
|
+
|
1126
|
+
## Requirements
|
1127
|
+
|
1128
|
+
- Ruby 2.5 or later
|
1129
|
+
- FFI gem
|
1130
|
+
|
1131
|
+
The NNG shared library (v1.11.0) is bundled with the gem for all platforms, so no external installation is required.
|
1132
|
+
|
1133
|
+
## Development
|
1134
|
+
|
1135
|
+
```bash
|
1136
|
+
# Clone repository
|
1137
|
+
git clone https://github.com/Hola-QingYi/nng-ruby.git
|
1138
|
+
cd nng-ruby
|
1139
|
+
|
1140
|
+
# Install dependencies
|
1141
|
+
bundle install
|
1142
|
+
|
1143
|
+
# Run tests
|
1144
|
+
bundle exec rspec
|
1145
|
+
|
1146
|
+
# Build gem
|
1147
|
+
gem build nng.gemspec
|
1148
|
+
|
1149
|
+
# Run examples
|
1150
|
+
ruby examples/pair.rb
|
1151
|
+
```
|
1152
|
+
|
1153
|
+
## Contributing
|
1154
|
+
|
1155
|
+
1. Fork it
|
1156
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
1157
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
1158
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
1159
|
+
5. Create new Pull Request
|
1160
|
+
|
1161
|
+
## License
|
1162
|
+
|
1163
|
+
MIT License - see LICENSE file for details.
|
1164
|
+
|
1165
|
+
## Credits
|
1166
|
+
|
1167
|
+
- NNG library: https://nng.nanomsg.org/
|
1168
|
+
- Original nanomsg: https://nanomsg.org/
|
1169
|
+
|
1170
|
+
## Links
|
1171
|
+
|
1172
|
+
- [NNG Documentation](https://nng.nanomsg.org/man/)
|
1173
|
+
- [GitHub Repository](https://github.com/Hola-QingYi/nng-ruby)
|
1174
|
+
- [RubyGems Page](https://rubygems.org/gems/nng-ruby)
|
1175
|
+
|
1176
|
+
## Version History
|
1177
|
+
|
1178
|
+
### 0.1.2 (2025-10-03)
|
1179
|
+
- **Enhanced Protocol Buffers documentation**
|
1180
|
+
- Added 3 comprehensive Protobuf integration examples
|
1181
|
+
- Example 1: Basic RPC with Protobuf
|
1182
|
+
- Example 2: Nested messages (message in message pattern)
|
1183
|
+
- Example 3: Real-world RPC pattern (WeChatFerry style)
|
1184
|
+
- Added detailed explanation of Protobuf benefits with NNG
|
1185
|
+
- Added performance comparison (Protobuf vs JSON)
|
1186
|
+
- Added instructions for loading .proto files
|
1187
|
+
- Improved examples documentation
|
1188
|
+
|
1189
|
+
### 0.1.1 (2025-10-03)
|
1190
|
+
- Published to GitHub repository
|
1191
|
+
- Updated gem packaging to include source code
|
1192
|
+
- Added GitHub Actions workflows for CI/CD
|
1193
|
+
|
1194
|
+
### 0.1.0 (2025-10-03)
|
1195
|
+
- Initial release
|
1196
|
+
- Complete NNG API bindings
|
1197
|
+
- All protocols and transports supported
|
1198
|
+
- Bundled NNG library
|
1199
|
+
- High-level Ruby API
|
1200
|
+
- Message support
|
1201
|
+
- Examples and documentation
|