├── LICENSE ├── Makefile ├── README.md ├── examples ├── echo_client.lua ├── echo_server.lua ├── epoll_integration.lua ├── pair.lua ├── pipeline.lua ├── pub_test.lua ├── reqrep.lua └── sub_test.lua ├── nanomsg-ffi.lua ├── nanomsg-ffi.test.lua └── rockspec └── luajit-nanomsg-scm-1.rockspec /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2013 Evan Wies 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom 9 | the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | LMODNAME=nanomsg-ffi 3 | 4 | LUA=luajit 5 | LMODFILE=$(LMODNAME).lua 6 | 7 | ABIVER=5.1 8 | INSTALL_SHARE=$(PREFIX)/share 9 | INSTALL_LMOD=$(INSTALL_SHARE)/lua/$(ABIVER) 10 | 11 | all: 12 | @echo "This is a pure module. Nothing to make :)" 13 | 14 | test: 15 | $(LUA) $(LMODNAME).test.lua 16 | 17 | install: 18 | install -m0644 $(LMODFILE) $(INSTALL_LMOD)/$(LMODFILE) 19 | 20 | uninstall: 21 | rm -f $(INSTALL_LMOD)/$(LMODFILE) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | This is a LuaJIT FFI-based binding to the [nanomsg library](http://nanomsg.org). 5 | 6 | It exposes the raw API, as well as a higher-level convenience layer. 7 | 8 | 9 | Installation 10 | ============ 11 | 12 | First, make sure that the [nanomsg](https://github.com/250bpm/nanomsg) 13 | shared library is available on your `LD_LIBRARY_PATH`. 14 | 15 | Then, you can either: 16 | 17 | - simply put [nanomsg-ffi.lua](https://github.com/nanomsg/luajit-nanomsg/blob/master/nanomsg-ffi.lua) somewhere on your `LUA_PATH`; 18 | - clone this repository and run `make install`; 19 | - install the latest release with the [LuaRocks](http://luarocks.org/) package manager: 20 | 21 | luarocks install "https://raw.github.com/nanomsg/luajit-nanomsg/master/rockspec/luajit-nanomsg-scm-1.rockspec" 22 | 23 | Usage 24 | ===== 25 | 26 | ```lua 27 | local nn = require 'nanomsg-ffi' 28 | 29 | -- nn.C exposes all of the nanomsg C API functions 30 | print( nn.C.nn_errno() ) 31 | 32 | -- But there is a friendlier API available directly from nn: 33 | print( nn.errno() ) 34 | print( nn.EAGAIN ) 35 | 36 | -- For convenience, names for errno's are available in nn.E 37 | assert( nn.E[ nn.EAGAIN ] == 'EAGAIN' ) 38 | 39 | -- Use the nn.socket class rather than the functions in nn.C 40 | local req, id, rc, err 41 | 42 | req, err = nn.socket( nn.REQ ) 43 | assert( req, nn.strerror(err) ) 44 | 45 | id, err = req:connect( "tcp://127.0.0.1:555" ) 46 | assert( id, nn.strerror(err) ) 47 | 48 | local msg = "hello world" 49 | rc, err = req:send( msg, #msg ) 50 | assert( rc > 0, nn.strerror(err) ) 51 | 52 | ``` 53 | 54 | Socket object 55 | ============= 56 | 57 | Socket is nanomsg's primary object. Socket object can instantiated with `nn.socket( protocol, options)`, where `protocol` is the nanomsg enum, such as `nn.SUB`, and `options` is a table with the following optional fields: 58 | 59 | * `close_on_gc`: Default is true. When true, the socket's `close` method will be invoked upon garbage collection. `close` may block, so one may to wish to invoke it manually at a determined time. 60 | 61 | * `domain`: Default is `nn.AF_SP`. The domain of the socket. Options are `nn.AF_SP` and `nn.AF_SP_RAW`. 62 | 63 | 64 | TODO: generate LDoc documentation and link to it 65 | 66 | 67 | 68 | Running the test suite 69 | ====================== 70 | 71 | Test require [cwtest](https://github.com/catwell/cwtest). Either install it on your 72 | system or just put [cwtest.lua](https://github.com/catwell/cwtest/blob/master/cwtest.lua) 73 | somewhere on your LUA_PATH, then run `make test`. 74 | 75 | 76 | Other nanomsg Lua Bindings 77 | ========================== 78 | 79 | If you are looking for a nanomsg binding that can work with plain Lua, 80 | as well as LuaJIT, check out [lua-nanomsg](https://github.com/Neopallium/lua-nanomsg). 81 | 82 | 83 | Roadmap 84 | ======= 85 | 86 | This is usable now. The future holds: 87 | 88 | * more documentation 89 | * more examples and performance tests 90 | * more hand-holding classes for nanomsg concepts 91 | * bind nn_sendmsg and nn_recvmsg and associated control structures 92 | 93 | 94 | License 95 | ======= 96 | 97 | ``` 98 | Copyright (c) 2013 Evan Wies 99 | 100 | Permission is hereby granted, free of charge, to any person obtaining a copy 101 | of this software and associated documentation files (the "Software"), 102 | to deal in the Software without restriction, including without limitation 103 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 104 | and/or sell copies of the Software, and to permit persons to whom 105 | the Software is furnished to do so, subject to the following conditions: 106 | 107 | The above copyright notice and this permission notice shall be included 108 | in all copies or substantial portions of the Software. 109 | 110 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 111 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 112 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 113 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 114 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 115 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 116 | IN THE SOFTWARE. 117 | ``` 118 | -------------------------------------------------------------------------------- /examples/echo_client.lua: -------------------------------------------------------------------------------- 1 | 2 | local ffi = require 'ffi' 3 | local nn = require 'nanomsg-ffi' 4 | 5 | local ADDRESS = "tcp://127.0.0.1:5556" 6 | 7 | if #arg == 0 then 8 | io.stdout:write("usage: echo_client.lua msg\n") 9 | os.exit(-1) 10 | end 11 | 12 | local echo_str = '' 13 | for i, v in ipairs(arg) do 14 | echo_str = echo_str .. v 15 | if i ~= #arg then 16 | echo_str = echo_str .. ' ' 17 | end 18 | end 19 | 20 | print( '...client started...' ) 21 | 22 | local req, err = nn.socket( nn.REQ ) 23 | assert( req, nn.strerror(err) ) 24 | 25 | local eid, err = req:connect( ADDRESS ) 26 | assert( eid, nn.strerror(err) ) 27 | 28 | local rc, err = req:send( echo_str, #echo_str ) 29 | assert( rc >= 0, nn.strerror(err) ) 30 | 31 | --TODO: local msg, err = req:recv_zc() 32 | --assert( msg, nn.strerror(err) ) 33 | local buf = ffi.new("char [100]") 34 | local sz, err = req:recv( buf, 100 ) 35 | assert( sz >= 0, nn.strerror(err) ) 36 | 37 | print( sz, ffi.string(buf,100) ) -- msg:tostring() ) 38 | 39 | print( '...client done...' ) 40 | -------------------------------------------------------------------------------- /examples/echo_server.lua: -------------------------------------------------------------------------------- 1 | 2 | local nn = require 'nanomsg-ffi' 3 | 4 | local ADDRESS = "tcp://127.0.0.1:5556" 5 | 6 | local rep = nn.socket( nn.REP ) 7 | 8 | print("...starting server loop...") 9 | local echo 10 | while echo ~= "EXIT" do 11 | local cid, err = rep:bind( ADDRESS ) 12 | assert( cid, nn.strerror(err) ) 13 | 14 | local msg, sz 15 | msg, err = rep:recv_zc() 16 | assert( msg, nn.strerror(err) ) 17 | 18 | echo = msg:tostring() 19 | print( "GOT:", '"' .. echo .. '"' ) 20 | 21 | sz, err = rep:send( echo, #echo ) -- TODO msg.ptr, msg.size ) 22 | assert( sz > 0, nn.strerror(err) ) 23 | 24 | rep:shutdown( cid ) 25 | end 26 | 27 | print("...done...") 28 | 29 | -------------------------------------------------------------------------------- /examples/epoll_integration.lua: -------------------------------------------------------------------------------- 1 | 2 | -- listens for data on a nanomsg SUB using epoll, 3 | -- and writes the data to stdout 4 | 5 | local nn = require 'nanomsg-ffi' 6 | local S = require 'syscall' 7 | 8 | local SUB_ADDRESS = 'tcp://127.0.0.1:5557' 9 | 10 | -- setup nanomsg SUB 11 | local sub, sid, sub_fd, err, rc 12 | sub, err = nn.socket( nn.SUB ) 13 | assert( sub, nn.strerror(err) ) 14 | 15 | sid, err = sub:connect( SUB_ADDRESS ) 16 | assert( sid >= 0, nn.strerror(err) ) 17 | 18 | rc, err = sub:setsockopt( nn.SUB, nn.SUB_SUBSCRIBE, '' ) 19 | assert( rc >= 0, nn.strerror(err) ) 20 | 21 | sub_fd, err = sub:getsockopt( nn.SOL_SOCKET, nn.RCVFD ) 22 | assert( sub_fd, nn.strerror(err) ) 23 | 24 | 25 | -- setup the epoll 26 | local ep = S.epoll_create() 27 | assert( ep, 'epoll_create failed' ) 28 | assert( ep:epoll_ctl('add', sub_fd, 'in') ) 29 | 30 | local maxevents = 1024 31 | local events = S.t.epoll_events( maxevents ) 32 | 33 | 34 | -- run epoll loop 35 | local terminated = false 36 | while not terminated do 37 | local replies = ep:epoll_wait( events, maxevents ) 38 | for _, event in ipairs(replies) do 39 | if event.fd == sub_fd then 40 | -- read from the nanomsg socket 41 | local msg, err = sub:recv_zc() 42 | if msg then 43 | io.stdout:write( msg:tostring(), '\n' ) 44 | else 45 | io.stderr:write('SUB: error ', nnstrerror(err), '\n' ) 46 | end 47 | else 48 | io.stderr:write('EPOLL: unknown fd: ', fd, '\n' ) 49 | end 50 | end 51 | end 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/pair.lua: -------------------------------------------------------------------------------- 1 | -- Pair 2 | -- Ported from: https://github.com/dysinger/nanomsg-examples 3 | 4 | local usage = [[ 5 | USAGE: 6 | luajit pair.lua node0 7 | luajit pair.lua node1 8 | EXAMPLE: 9 | luajit pair.lua node0 ipc:///tmp/pair.ipc & node0=$! 10 | luajit pair.lua node1 ipc:///tmp/pair.ipc & node1=$! 11 | sleep 3 12 | kill $node0 $node1 13 | ]] 14 | 15 | local nn = require "nanomsg-ffi" 16 | local sleep = assert((require "socket").sleep) 17 | 18 | local send_name = function(sock, name) 19 | print(string.format("%s: RECEIVED \"%s\"", name, name)) 20 | sock:send(name, #name) 21 | end 22 | 23 | local recv_name = function(sock, name) 24 | local result = sock:recv_zc() 25 | if result then 26 | print(string.format("%s: RECEIVED \"%s\"", name, result:tostring())) 27 | result:free() 28 | end 29 | end 30 | 31 | local send_recv = function(sock, name) 32 | sock:setsockopt(nn.SOL_SOCKET, nn.RCVTIMEO, 100) 33 | while true do 34 | recv_name(sock, name) 35 | sleep(1) 36 | send_name(sock, name) 37 | end 38 | end 39 | 40 | local node0 = function(url) 41 | local sock, err = nn.socket( nn.PAIR ) 42 | assert( sock, nn.strerror(err) ) 43 | local _id, err = sock:bind( url ) 44 | assert( _id, nn.strerror(err) ) 45 | send_recv(sock, "node0") 46 | sock:shutdown(0) 47 | end 48 | 49 | local node1 = function(url) 50 | local sock, err = nn.socket( nn.PAIR ) 51 | assert( sock, nn.strerror(err) ) 52 | local _id, err = sock:connect( url ) 53 | assert( _id, nn.strerror(err) ) 54 | send_recv(sock, "node1") 55 | sock:shutdown(0) 56 | end 57 | 58 | if (arg[1] == "node0") and (#arg == 2) then 59 | node0(arg[2]) 60 | elseif (arg[1] == "node1") and (#arg == 2) then 61 | node1(arg[2]) 62 | else 63 | io.stderr:write(usage) 64 | end 65 | -------------------------------------------------------------------------------- /examples/pipeline.lua: -------------------------------------------------------------------------------- 1 | -- Pipeline 2 | -- Ported from: https://github.com/dysinger/nanomsg-examples 3 | 4 | local usage = [[ 5 | USAGE: 6 | luajit pipeline.lua node0 7 | luajit pipeline.lua node1 8 | EXAMPLE: 9 | luajit pipeline.lua node0 'ipc:///tmp/pipeline.ipc' & node0=$! && sleep 1 10 | luajit pipeline.lua node1 'ipc:///tmp/pipeline.ipc' 'Hello, World!' 11 | luajit pipeline.lua node1 'ipc:///tmp/pipeline.ipc' 'Goodbye.' 12 | kill $node0 13 | ]] 14 | 15 | local nn = require "nanomsg-ffi" 16 | 17 | local node0 = function(url) 18 | local sock, err = nn.socket( nn.PULL ) 19 | assert( sock, nn.strerror(err) ) 20 | local cid, err = sock:bind( url ) 21 | assert( cid, nn.strerror(err) ) 22 | while true do 23 | local msg, err = sock:recv_zc() 24 | assert( msg, nn.strerror(err) ) 25 | print(string.format("NODE0: RECEIVED \"%s\"", msg:tostring())) 26 | msg:free() 27 | end 28 | end 29 | 30 | local node1 = function(url, msg) 31 | local sock, err = nn.socket( nn.PUSH ) 32 | assert( sock, nn.strerror(err) ) 33 | local eid, err = sock:connect( url ) 34 | assert( eid, nn.strerror(err) ) 35 | print(string.format("NODE1: SENDING \"%s\"", msg)) 36 | assert( sock:send( msg, #msg ) == #msg ) 37 | sock:shutdown(0) 38 | end 39 | 40 | if (arg[1] == "node0") and (#arg == 2) then 41 | node0(arg[2]) 42 | elseif (arg[1] == "node1") and (#arg == 3) then 43 | node1(arg[2], arg[3]) 44 | else 45 | io.stderr:write(usage) 46 | end 47 | -------------------------------------------------------------------------------- /examples/pub_test.lua: -------------------------------------------------------------------------------- 1 | 2 | local nn = require 'nanomsg-ffi' 3 | 4 | local ADDRESS = 'tcp://127.0.0.1:5557' 5 | 6 | if #arg ~= 2 then 7 | io.stdout:write('usage: pub_test.lua channel1_name channel2_name\n\n') 8 | io.stdout:write('takes stdin and publishes it, alternating lines between\n') 9 | io.stdout:write('channel1_name and channel2_name\n') 10 | os.exit(-1) 11 | end 12 | 13 | local channels = { arg[1], arg[2] } 14 | 15 | local pub, err = nn.socket( nn.PUB ) 16 | assert( pub, nn.strerror(err) ) 17 | 18 | local pid, err = pub:bind( ADDRESS ) 19 | assert( pid >= 0 , nn.strerror(err) ) 20 | 21 | print(string.format('...publisher started... with channels 1=%s, 2=%s', 22 | channels[1], channels[2])) 23 | 24 | local next_channel = 1 25 | local line_count = 0 26 | for line in io.lines() do 27 | local msg = channels[next_channel] .. '|' .. line 28 | next_channel = (next_channel == 1) and 2 or 1 29 | 30 | local rc, err = pub:send( msg, #msg ) 31 | assert( rc > 0, 'send failed' ) 32 | line_count = line_count + 1 33 | end 34 | 35 | print(string.format('...publisher done..., sent %d lines', line_count)) 36 | 37 | -------------------------------------------------------------------------------- /examples/reqrep.lua: -------------------------------------------------------------------------------- 1 | -- Request/Reply 2 | -- Ported from: https://github.com/dysinger/nanomsg-examples 3 | 4 | local usage = [[ 5 | USAGE: 6 | luajit reqrep.lua node0 7 | luajit reqrep.lua node1 8 | EXAMPLE: 9 | luajit reqrep.lua node0 'ipc:///tmp/reqrep.ipc' & node0=$! && sleep 1 10 | luajit reqrep.lua node1 'ipc:///tmp/reqrep.ipc' 11 | kill $node0 12 | ]] 13 | 14 | local nn = require "nanomsg-ffi" 15 | 16 | local node0 = function(url) 17 | local sock, err = nn.socket( nn.REP ) 18 | assert( sock, nn.strerror(err) ) 19 | local cid, err = sock:bind( url ) 20 | assert( cid, nn.strerror(err) ) 21 | while true do 22 | local msg, err = sock:recv_zc() 23 | assert( msg, nn.strerror(err) ) 24 | if msg:tostring():sub(1,4) == "DATE" then 25 | print("NODE0: RECEIVED DATE REQUEST") 26 | local d = os.date("%c") 27 | print(string.format("NODE0: SENDING DATE %s", d)) 28 | assert( sock:send( d, #d ) == #d ) 29 | end 30 | msg:free() 31 | end 32 | end 33 | 34 | local node1 = function(url) 35 | local DATE = "DATE" 36 | local sock, err = nn.socket( nn.REQ ) 37 | assert( sock, nn.strerror(err) ) 38 | local eid, err = sock:connect( url ) 39 | assert( eid, nn.strerror(err) ) 40 | print(string.format("NODE1: SENDING DATE REQUEST %s", DATE)) 41 | assert( sock:send( DATE, #DATE ) == #DATE ) 42 | local msg, err = sock:recv_zc() 43 | print(string.format("NODE1: RECEIVED DATE %s", msg:tostring())) 44 | msg:free() 45 | sock:shutdown(0) 46 | end 47 | 48 | if (arg[1] == "node0") and (#arg == 2) then 49 | node0(arg[2]) 50 | elseif (arg[1] == "node1") and (#arg == 2) then 51 | node1(arg[2]) 52 | else 53 | io.stderr:write(usage) 54 | end 55 | -------------------------------------------------------------------------------- /examples/sub_test.lua: -------------------------------------------------------------------------------- 1 | local nn = require 'nanomsg-ffi' 2 | 3 | local ADDRESS = 'tcp://127.0.0.1:5557' 4 | 5 | if #arg ~= 1 then 6 | io.stdout:write('usage: sub_test.lua channel_name\n') 7 | io.stdout:write('listens to publisher, subscribing to `channel_name`\n') 8 | os.exit(-1) 9 | end 10 | 11 | local sub, err = nn.socket( nn.SUB ) 12 | assert( sub, nn.strerror(err) ) 13 | 14 | local sid, err = sub:connect( ADDRESS ) 15 | assert( sid >= 0, nn.strerror(err) ) 16 | 17 | local rc, err = sub:setsockopt( nn.SUB, nn.SUB_SUBSCRIBE, arg[1] ) 18 | assert( rc >= 0, nn.strerror(err) ) 19 | 20 | print(string.format('...subscriber started... on channel %s\n', arg[1])) 21 | 22 | local line_count = 0 23 | while true do 24 | local msg, err = sub:recv_zc() 25 | if not msg then 26 | print(string.format("...stopping with code:%d err:%s\n", 27 | err, nn.strerror(err))) 28 | break 29 | end 30 | 31 | print( msg:tostring() ) 32 | line_count = line_count + 1 33 | end 34 | 35 | print(string.format('...subscriber done..., recv\'d %d lines\n', line_count)) 36 | -------------------------------------------------------------------------------- /nanomsg-ffi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | LuaJIT FFI-based binding to the nanomsg library 4 | 5 | Copyright (c) 2013 Evan Wies 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- 26 | TODO: DOCUMENTATION 27 | 28 | TODO: zero-copy interface 29 | TODO: device 30 | TODO: nicer interfaces (especially for sockopts, a la ljsyscall?) 31 | 32 | --]] 33 | 34 | local ffi = require 'ffi' 35 | local libnn = ffi.load('nanomsg') 36 | 37 | local int_sz = ffi.sizeof('int') 38 | local int_1_t = ffi.typeof('int[1]') 39 | local size_1_t = ffi.typeof("size_t[1]") 40 | local charptr_1_t = ffi.typeof('char *[1]') 41 | local voidptr_1_t = ffi.typeof('void*[1]') 42 | 43 | -- Public API 44 | local nn = {} 45 | 46 | 47 | -- Bind nn_symbol to extract the nanomsg public symbols 48 | ffi.cdef([[ 49 | const char* nn_symbol (int i, int *value); 50 | ]]) 51 | nn.E = {} 52 | do 53 | local symbol = ffi.new( 'struct { int value[1]; const char* name; }' ) 54 | local i = 0 55 | while true do 56 | symbol.name = libnn.nn_symbol( i, symbol.value ) 57 | if symbol.name == nil then break end 58 | local name = ffi.string( symbol.name ) 59 | 60 | -- convert NN_FOO to just FOO, since nn.FOO is nicer than nn.NN_FOO 61 | name = name:match('NN_([%w_]*)') or name 62 | nn[ name ] = symbol.value[0] 63 | 64 | -- store mapping of error value -> symbol in nn.E 65 | if name:match('^E([%w_]*)') then 66 | nn.E[ symbol.value[0] ] = name 67 | end 68 | i = i + 1 69 | end 70 | end 71 | 72 | -- nanomsg ABI check 73 | -- we match the cdef's to the nanomsg library version 74 | if (nn.VERSION_CURRENT - nn.VERSION_AGE) == 0 then 75 | ffi.cdef([[ 76 | int nn_errno (void); 77 | const char *nn_strerror (int errnum); 78 | void nn_term (void); 79 | 80 | void *nn_allocmsg (size_t size, int type); 81 | int nn_freemsg (void *msg); 82 | 83 | int nn_socket (int domain, int protocol); 84 | int nn_close (int s); 85 | int nn_setsockopt (int s, int level, int option, const void *optval, size_t optvallen); 86 | int nn_getsockopt (int s, int level, int option, void *optval, size_t *optvallen); 87 | int nn_bind (int s, const char *addr); 88 | int nn_connect (int s, const char *addr); 89 | int nn_shutdown (int s, int how); 90 | int nn_send (int s, const void *buf, size_t len, int flags); 91 | int nn_recv (int s, void *buf, size_t len, int flags); 92 | 93 | int nn_device (int s1, int s2); 94 | 95 | // nn_socket_t doesn't exist in nanomsg; it is a metatype anchor for nn.socket 96 | struct nn_socket_t { int fd; bool close_on_gc; }; 97 | 98 | // nn_msg_t doesn't exist in nanomsg; it is a metatype anchor for nn.msg 99 | struct nn_msg_t { void *ptr; size_t size; }; 100 | ]]) 101 | else 102 | error( "unknown nanomsg version: " .. tostring(nn.VERSION) ) 103 | end 104 | 105 | 106 | -- NN_MSG doesn't come through the symbol interface properly due to its use of size_t 107 | nn.MSG = ffi.typeof('size_t')( -1 ) 108 | 109 | 110 | -- give clients access to the C API 111 | nn.C = libnn 112 | 113 | 114 | --- terminates the nanomsg library 115 | function nn.term() 116 | libnn.nn_term() 117 | end 118 | 119 | 120 | --- returns the current errno, as known by the nanomsg library 121 | function nn.errno() 122 | return libnn.nn_errno() 123 | end 124 | 125 | 126 | --- returns a Lua string associated with the passed err code 127 | --- if called without argument, err is retrieved from nn_errno() 128 | function nn.strerror( err ) 129 | err = err or libnn.nn_errno() 130 | return ffi.string( libnn.nn_strerror( err ) ) 131 | end 132 | 133 | 134 | --- nanomsg socket class 135 | nn.socket = ffi.metatype( 'struct nn_socket_t', { 136 | 137 | --- Construct a new nanomsg socket 138 | -- 139 | -- nn.socket( protocol, options ) 140 | -- @tparam number protocol The nanomsg protocol 141 | -- @tparam table options An options table 142 | -- @return a socket object or nil on error 143 | -- @return error code if the first argument is nil 144 | -- 145 | -- The following options are available: 146 | -- close_on_gc invoke nn_close when the socket is gc'd (default is true) 147 | -- domain either nn.AF_SP or nn.AF_SP_RAW (default is nn.AF_SP) 148 | -- 149 | __new = function( socket_ct, protocol, options ) 150 | if not protocol then return nil, nn.EINVAL end 151 | local close_on_gc, domain 152 | if not options then 153 | close_on_gc = true 154 | domain = nn.AF_SP 155 | else 156 | if type(options) ~= 'table' then return nil, nn.EINVAL end 157 | close_on_gc = (options.close_on_gc or options.close_on_gc == nil) and true or false 158 | domain = options.domain or nn.AF_SP 159 | end 160 | 161 | local fd = libnn.nn_socket( domain, protocol ) 162 | if fd < 0 then return nil, libnn.nn_errno() end 163 | return ffi.new( socket_ct, fd, close_on_gc ) 164 | end, 165 | 166 | -- garbage collection destructor, not invoked by user 167 | __gc = function( s ) 168 | -- make sure the socket is closed 169 | if s.close_on_gc and s.fd >= 0 then 170 | libnn.nn_close( s.fd ) 171 | end 172 | end, 173 | 174 | -- methods 175 | __index = { 176 | 177 | setsockopt = function( s, level, opt, optval, optvallen ) 178 | if not optvallen and type(optval) == 'boolean' then 179 | optval = int_1_t(optval and 1 or 0) 180 | optvallen = int_sz 181 | end 182 | if not optvallen and type(optval) == 'number' then 183 | optval = int_1_t(optval) 184 | optvallen = int_sz 185 | end 186 | if not optvallen and type(optval) == 'string' then 187 | optvallen = #optval 188 | end 189 | local rc = libnn.nn_setsockopt( s.fd, level, opt, optval, optvallen ) 190 | if rc == 0 then return rc else return nil, libnn.nn_errno() end 191 | end, 192 | 193 | -- currently assumes that the returned value is a number 194 | getsockopt = function( s, level, opt ) 195 | local optval, optvallen = int_1_t(), size_1_t(int_sz) 196 | local rc = libnn.nn_getsockopt( s.fd, level, opt, optval, optvallen ) 197 | if rc == 0 then return optval[0] else return nil, libnn.nn_errno() end 198 | end, 199 | 200 | bind = function( s, addr ) 201 | local rc = libnn.nn_bind( s.fd, addr ) 202 | if rc >= 0 then return rc else return nil, libnn.nn_errno() end 203 | end, 204 | 205 | connect = function( s, addr ) 206 | local rc = libnn.nn_connect( s.fd, addr ) 207 | if rc >= 0 then return rc else return nil, libnn.nn_errno() end 208 | end, 209 | 210 | shutdown = function( s, how ) 211 | local rc = libnn.nn_shutdown( s.fd, how ) 212 | if rc == 0 then return rc else return nil, libnn.nn_errno() end 213 | end, 214 | 215 | close = function( s ) 216 | local rc = libnn.nn_close( s.fd ) 217 | if rc < 0 then return nil, libnn.nn_errno() end 218 | s.fd = -1 219 | return rc 220 | end, 221 | 222 | send = function( s, buf, len, flags ) 223 | flags = flags or 0 224 | local sz = libnn.nn_send( s.fd, buf, len, flags ) 225 | if sz >= 0 then 226 | return sz 227 | else 228 | local err = libnn.nn_errno() 229 | if err ~= nn.EAGAIN then return nil, err end 230 | return -1 231 | end 232 | end, 233 | 234 | recv = function( s, buf, len, flags ) 235 | flags = flags or 0 236 | local sz = libnn.nn_recv( s.fd, buf, len, flags ) 237 | if sz >= 0 then 238 | return sz 239 | else 240 | local err = libnn.nn_errno() 241 | if err ~= nn.EAGAIN then return nil, err end 242 | return -1 243 | end 244 | end, 245 | 246 | -- Sends the passed nn.msg on the socket using zero-copy 247 | -- The msg is not usable after passed to this function 248 | -- On success, returns the number of bytes sent and clears msg.ptr 249 | -- If the message cannot be sent right away, returns -1. 250 | -- Otherwise, returns nil, nn_errorno() 251 | send_zc = function( s, msg, flags ) 252 | flags = flags or 0 253 | local msg_ptr_addr = voidptr_1_t(msg.ptr) 254 | local sz = libnn.nn_send( s.fd, msg_ptr_addr, nn.MSG, flags ) 255 | if sz >= 0 then 256 | msg.ptr = nil 257 | return sz 258 | else 259 | local err = libnn.nn_errno() 260 | if err ~= nn.EAGAIN then return nil, err end 261 | return -1 262 | end 263 | end, 264 | 265 | -- Calls nn_recv on the socket using zero-copy 266 | -- Returns a nn.msg to access the data 267 | -- If nn_recv fails, returns nil, nn_errorno() 268 | recv_zc = function( s, flags ) 269 | flags = flags or 0 270 | local ptr = charptr_1_t() 271 | local sz = libnn.nn_recv( s.fd, ptr, nn.MSG, flags ) 272 | if sz < 0 then return nil, libnn.nn_errno() end 273 | return nn.msg( ptr[0], sz ) 274 | end, 275 | } 276 | }) 277 | 278 | 279 | --- nanomsg msg class 280 | -- this class sets up automatic management of messages that are allocated 281 | -- by nanomsg, be it through nn_allocmsg or from send/recv with nn.MSG 282 | nn.msg = ffi.metatype( 'struct nn_msg_t', { 283 | 284 | -- constructor 285 | __new = function( msg_ct, ptr, size ) 286 | return ffi.new( msg_ct, ptr, size ) 287 | end, 288 | 289 | -- destructor 290 | __gc = function( m ) 291 | if m.ptr ~= nil then 292 | libnn.nn_freemsg( m.ptr ) 293 | end 294 | end, 295 | 296 | __len = function( m ) 297 | return m.size 298 | end, 299 | 300 | -- methods 301 | __index = { 302 | --- free the buffer associated with this msg, instead of waiting for GC 303 | -- sets ptr to NULL 304 | free = function( m ) 305 | if m.ptr ~= nil then 306 | libnn.nn_freemsg( m.ptr ) 307 | m.ptr = nil 308 | end 309 | end, 310 | 311 | tostring = function( m ) 312 | return ffi.string( m.ptr, m.size ) 313 | end, 314 | }, 315 | }) 316 | 317 | 318 | -- Allocates a buffer for zero-copy using nn_allocmsg 319 | -- This is managed by the Lua GC. 320 | -- Do not invoke nn_freemsg on it directly, but rather nn.msg:free() 321 | nn.allocmsg = function( msg_size, msg_type ) 322 | msg_type = msg_type or 0 323 | local ptr = libnn.nn_allocmsg( msg_size, msg_type or 0 ) 324 | if ptr == nil then return nil, libnn.nn_errno() end 325 | return nn.msg( ptr, msg_size ) 326 | end 327 | 328 | 329 | -- Return public API 330 | return nn 331 | 332 | -------------------------------------------------------------------------------- /nanomsg-ffi.test.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local cwtest = require "cwtest" 3 | local nn = require "nanomsg-ffi" 4 | local T = cwtest.new() 5 | 6 | T.posint = function(self,x) 7 | local r 8 | if (type(x) ~= "number") or (math.floor(x) ~= x) then 9 | r = self.fail_tpl(self, " (%s is not an integer)", tostring(x)) 10 | elseif x >= 0 then 11 | r = self.pass_tpl(self, " (%d >= 0)", x) 12 | else 13 | r = self.fail_tpl(self, " (%d < 0)", x) 14 | end 15 | return r 16 | end 17 | 18 | T:start("REQ/REP re-sending of request"); do 19 | -- from tests/reqrep.c 20 | local ADDRESS = "inproc://a" 21 | local rep, req, err 22 | rep, err = nn.socket(nn.REP) 23 | T:yes( rep ) 24 | T:posint( rep:bind(ADDRESS) ) 25 | req, err = nn.socket(nn.REQ) 26 | T:yes( req ) 27 | T:posint( req:connect(ADDRESS) ) 28 | local resend_ivl = ffi.new("int[1]", 100) 29 | T:eq( 30 | req:setsockopt(nn.REQ, nn.REQ_RESEND_IVL, resend_ivl, ffi.sizeof("int")), 31 | 0 32 | ) 33 | local buf = ffi.new("char[32]") 34 | T:eq( req:send("ABC", 3, 0), 3 ) 35 | T:eq( rep:recv(buf, ffi.sizeof(buf), 0), 3 ) 36 | T:eq( rep:recv(buf, ffi.sizeof(buf), 0), 3 ) 37 | T:eq( req:close(), 0 ) 38 | T:eq( rep:close(), 0 ) 39 | end; T:done() 40 | 41 | T:start("TCP close socket while not connected"); do 42 | -- from tests/tcp.c 43 | local ADDRESS = "tcp://127.0.0.1:5555" 44 | local sc, err = nn.socket(nn.PAIR) 45 | T:yes( sc ) 46 | T:posint( sc:connect(ADDRESS) ) 47 | T:eq( sc:close(sc), 0 ) 48 | end; T:done() 49 | 50 | T:start("TCP PAIR ping-pong"); do 51 | -- from tests/tcp.c 52 | local ADDRESS = "tcp://127.0.0.1:5555" 53 | local sc, sb, err 54 | sc, err = nn.socket(nn.PAIR) 55 | T:yes( sc ) 56 | T:posint( sc:connect(ADDRESS) ) 57 | sb, err = nn.socket(nn.PAIR) 58 | T:yes( sb ) 59 | T:posint( sb:bind(ADDRESS) ) 60 | local buf = ffi.new("char[32]") 61 | for i=1,10 do 62 | T:eq( sc:send("ABC", 3, 0), 3 ) 63 | T:eq( sb:recv(buf, ffi.sizeof(buf), 0), 3 ) 64 | T:eq( sb:send("DEF", 3, 0), 3 ) 65 | T:eq( sc:recv(buf, ffi.sizeof(buf), 0), 3 ) 66 | end 67 | T:eq( sc:close(), 0 ) 68 | T:eq( sb:close(), 0 ) 69 | end; T:done() 70 | 71 | T:start("TCP PAIR batch transfer"); do 72 | -- from tests/tcp.c 73 | local ADDRESS = "tcp://127.0.0.1:5555" 74 | local sc, sb, err 75 | sc, err = nn.socket(nn.PAIR) 76 | T:yes( sc ) 77 | T:posint( sc:connect(ADDRESS) ) 78 | sb, err = nn.socket(nn.PAIR) 79 | T:yes( sb ) 80 | T:posint( sb:bind(ADDRESS) ) 81 | local buf = ffi.new("char[32]") 82 | for i=1,10 do 83 | T:eq( sc:send("XYZ", 3, 0), 3 ) 84 | T:eq( sb:recv(buf, ffi.sizeof(buf), 0), 3 ) 85 | end 86 | T:eq( sc:close(), 0 ) 87 | T:eq( sb:close(), 0 ) 88 | end; T:done() 89 | 90 | T:start("TCP REQ/REP socket reuse"); do 91 | local ADDRESS = "tcp://127.0.0.1:5555" 92 | local req_buf, rep_buf = ffi.new("char[32]"), ffi.new("char[32]") 93 | local req, rep, err 94 | for i=1,10 do 95 | rep, err = nn.socket(nn.REP) 96 | T:yes( rep ) 97 | T:posint( rep:bind(ADDRESS) ) 98 | req, err = nn.socket(nn.REQ) 99 | T:yes( req ) 100 | T:posint( req:connect(ADDRESS) ) 101 | T:eq( req:send("REQ", 3, 0), 3 ) 102 | T:eq( rep:recv(rep_buf, ffi.sizeof(rep_buf), 0), 3 ) 103 | T:eq( rep:send("REP", 3, 0), 3 ) 104 | T:eq( req:recv(req_buf, ffi.sizeof(req_buf), 0), 3 ) 105 | T:eq( req:close(req), 0 ) 106 | T:eq( rep:close(rep), 0 ) 107 | end 108 | end; T:done() 109 | -------------------------------------------------------------------------------- /rockspec/luajit-nanomsg-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajit-nanomsg" 2 | version = "scm-1" 3 | 4 | source = { 5 | url = "git://github.com/nanomsg/luajit-nanomsg.git", 6 | } 7 | 8 | description = { 9 | summary = "FFI-based nanomsg binding", 10 | detailed = [[ 11 | luajit-nanomsg is a FFI-based binding 12 | for the nanomsg library 13 | ]], 14 | homepage = "http://github.com/nanomsg/luajit-nanomsg", 15 | license = "MIT/X11", 16 | } 17 | 18 | dependencies = { 19 | "lua >= 5.1", -- "luajit >= 2.0.0" 20 | } 21 | 22 | build = { 23 | type = "none", 24 | install = { 25 | lua = { 26 | ["nanomsg-ffi"] = "nanomsg-ffi.lua", 27 | }, 28 | }, 29 | copy_directories = {}, 30 | } 31 | --------------------------------------------------------------------------------