在上一篇博客文章中笔者记录了使用openwrt编译一个运行于嵌入式ARM/Linux的“最小Lua开发环境”的过程,当时的制作的工具包没有上传,通过该文笔者尝试对上一篇博客文章进行补充,并将工具包上传;同时分享一些笔者基于Lua脚本进行ZeroMQ的开发的学习方法。虽然笔者此时仅对zeromq有浅薄的了解,但希望以后能够较深入地理解并应用该强大的线程间、进程间、远程通信的网络库。对于嵌入式应用软件的开发,可以说ZeroMQ可以较大程度上替代传统的网络编程方法,并有Lua/Python/NODEJS等等脚本语言的编程接口,从而增加软件的复用,加速应用开发。
除此之外,笔者还在GNU/Linux环境下编译了zeromq及其Lua的模块,这样就可以编写Lua脚本进行zeromq开发,让嵌入式设备与主机通信。下图是笔者在设备和主机上使用Lua解析器加载zeromq模块的结果:
笔者使用了一个简单的td.dump来导出zeromq的Lua主模块导出的变量及函数库,zmq表中共有194个元素:
下图是在笔者的旧手机上的运行结果:
上图与在主机上的结果没有什么差异。
为了测试zeromq的功能,笔者编写了下面的reply-server.lua,运行于主机上用作REP服务器:
#!/usr/bin/env lua
-- Created by xiaoqzye@qq.com
-- Simple ZeroMQ reply Server
-- 2020/08/09
-- load Lua modules
local zmq = require 'lzmq'
local czmq = require 'czmq'
-- global variables
local mcnt = 0
local zctx, zskt = nil, nil
local rok, errMsg = nil, nil
-- register signal handler
if not czmq.signal(czmq) then os.exit(1) end
-- create ZeroMQ context
rok, errMsg = zmq.context()
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 2)
end
zctx = rok
-- set the maximum number of sockets
rok, errMsg = zctx:set(zmq.MAX_SOCKETS, 3)
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 3)
end
-- create ZeroMQ socket
rok, errMsg = zctx:socket(zmq.REP)
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 4)
end
zskt = rok
-- set receive/send high water marks
rok, errMsg = zskt:set_rcvhwm(32)
if rok then rok, errMsg = zskt:set_sndhwm(32) end
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 5)
end
-- bind socket to a specific address
rok, errMsg = zskt:bind("tcp://*:4321")
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 6)
end
while not czmq.signaled(czmq) do
rok, errMsg = zskt:recv()
if not rok then
if not czmq.signaled(czmq) then print(czmq.errMsg(errMsg)) end
break
end
print(string.format("Received message [%08x]: %s", mcnt, rok))
local smsg = string.format("Hello there! [%08x]", mcnt)
rok, errMsg = zskt:send(smsg)
if not rok then
print(czmq.errMsg(errMsg))
break
end
mcnt = mcnt + 1
end
czmq.exit(zctx, zskt, 0)
此外也编写了request-client.lua,复制到手机上用作客户端:
#!/usr/bin/env lua
-- Created by xiaoqzye@qq.com
-- Simple ZeroMQ request client
-- 2020/08/09
-- load Lua modules
local zmq = require 'lzmq'
local czmq = require 'czmq'
local posix = require 'posix'
-- global variables
local mcnt, cid = 0, nil
local zctx, zskt = nil, nil
local rok, errMsg = nil, nil
-- register signal handler
if not czmq.signal(czmq) then os.exit(1) end
-- construct client ID
rok, errMsg = posix.getpid()
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 1)
end
cid = string.format("REQPID-%d", type(rok) == "table" and rok.pid or rok)
-- create ZeroMQ context
rok, errMsg = zmq.context()
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 2)
end
zctx = rok
-- set the maximum number of sockets
rok, errMsg = zctx:set(zmq.MAX_SOCKETS, 3)
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 3)
end
-- create ZeroMQ socket
rok, errMsg = zctx:socket(zmq.REQ)
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 4)
end
zskt = rok
-- set receive/send high water marks
rok, errMsg = zskt:set_rcvhwm(32)
if rok then rok, errMsg = zskt:set_sndhwm(32) end
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 5)
end
-- connect to server
rok, errMsg = zskt:connect(arg[1] or "tcp://127.0.0.1:4321")
if not rok then
print(czmq.errMsg(errMsg))
czmq.exit(zctx, zskt, 6)
end
local function recvMsg(what)
local rmsg = zskt:recv(zmq.DONTWAIT)
if not rmsg then return nil end
while rmsg do
print(string.format("[%s] received: %s", what, rmsg))
rmsg = zskt:recv(zmq.DONTWAIT)
end
return nil
end
while not czmq.signaled(czmq) do
local smsg = string.format("Hello from %s, count: %08x", cid, mcnt)
rok, errMsg = zskt:send(smsg)
if not rok then
print(czmq.errMsg(errMsg))
break
end
mcnt = mcnt + 1; recvMsg(cid)
posix.nanosleep{tv_sec = 1, tv_nsec = 0}
recvMsg(cid)
end
czmq.exit(zctx, zskt, 0)
以上两个Lua脚本分别运行在主机和手机上,二者均依赖一个czmq.lua的公共脚本,其内容如下:
#!/usr/bin/env lua
-- Created by xiaoqzye@qq.com
-- Common routines for ZeroMQ
-- 2020/08/09
local czmq = {}
local posix = require 'posix'
-- get default error message
czmq.errMsg = function (errmsg)
local typn = type(errmsg)
local unknown = "unknown error"
if typn ~= "string" or #errmsg == 0 then
return unknown
end
return errmsg
end
czmq.exit = function (zmqCtx, zmqSkt, eval)
if not eval then eval = 0 end
if zmqSkt then
zmqSkt:close()
zmqSkt = nil
end
if zmqCtx then
zmqCtx:destroy()
zmqCtx = nil
end
os.exit(eval)
end
-- local variable to signal if SIGINT has been delivered
czmq.sigint = false
-- return whether signal handler has been received
czmq.signaled = function (dzmq)
local typn = type(dzmq)
if typn ~= "table" then
print(string.format("Error, invalid argument type: %s", typn))
return false
end
return dzmq.sigint and true or false
end
-- register signal handler
czmq.signal = function (dzmq, signo)
local typn = type(dzmq)
if typn ~= "table" then
io.stderr:write(string.format("Error, invalid argument type: %s", typn))
return false
end
typn = type(dzmq.sigint)
if typn ~= "boolean" then
io.stderr:write(string.format("Error, invalid element type: %s", typn))
return false
end
if not signo then signo = posix.SIGINT end
local rok, errMsg = posix.signal(signo, function (sigNo)
local typn = type(sigNo)
if typn ~= "number" then
print(string.format("Error, invalid signal type: %s", typn))
return false
end
dzmq.sigint = true
return true
end)
if not rok then
print(czmq.errMsg(errMsg))
return false
end
return true
end
-- return the module
return czmq
下图是主机上运行reply-server.lua脚本的输出结果:
注意到上图中每一行的信息对应一个客户端发送的一条信息;而REQPID-XXXX用于区分该行消息是哪一个客户端发送的消息。笔者在手机上依次运行了三个request-client.lua的脚本进程(每开始运行一个脚本之间有几秒到十机秒的间隔),之后三个进程同时运行了一段时间,其运行结果分别如下:
第一个request-client.lua的部分运行结果:
/data/user # lua 00-request-client.lua 'tcp://192.168.1.104:4321'
[REQPID-5508] received: Hello there! [00000000]
[REQPID-5508] received: Hello there! [00000001]
[REQPID-5508] received: Hello there! [00000002]
[REQPID-5508] received: Hello there! [00000003]
[REQPID-5508] received: Hello there! [00000004]
[REQPID-5508] received: Hello there! [00000005]
[REQPID-5508] received: Hello there! [00000006]
[REQPID-5508] received: Hello there! [00000008]
[REQPID-5508] received: Hello there! [0000000a]
[REQPID-5508] received: Hello there! [0000000c]
[REQPID-5508] received: Hello there! [0000000e]
[REQPID-5508] received: Hello there! [00000010]
[REQPID-5508] received: Hello there! [00000013]
[REQPID-5508] received: Hello there! [00000016]
[REQPID-5508] received: Hello there! [00000019]
[REQPID-5508] received: Hello there! [0000001c]
[REQPID-5508] received: Hello there! [0000001f]
[REQPID-5508] received: Hello there! [00000022]
[REQPID-5508] received: Hello there! [00000025]
[REQPID-5508] received: Hello there! [00000028]
[REQPID-5508] received: Hello there! [0000002b]
[REQPID-5508] received: Hello there! [0000002e]
[REQPID-5508] received: Hello there! [00000031]
[REQPID-5508] received: Hello there! [00000034]
[REQPID-5508] received: Hello there! [00000037]
[REQPID-5508] received: Hello there! [0000003a]
[REQPID-5508] received: Hello there! [0000003d]
[REQPID-5508] received: Hello there! [00000040]
第二个request-client.lua的部分运行结果:
data/user # lua 00-request-client.lua 'tcp://192.168.1.104:4321'
[REQPID-5511] received: Hello there! [00000007]
[REQPID-5511] received: Hello there! [00000009]
[REQPID-5511] received: Hello there! [0000000b]
[REQPID-5511] received: Hello there! [0000000d]
[REQPID-5511] received: Hello there! [0000000f]
[REQPID-5511] received: Hello there! [00000011]
[REQPID-5511] received: Hello there! [00000014]
[REQPID-5511] received: Hello there! [00000017]
[REQPID-5511] received: Hello there! [0000001b]
[REQPID-5511] received: Hello there! [0000001d]
[REQPID-5511] received: Hello there! [00000020]
[REQPID-5511] received: Hello there! [00000023]
[REQPID-5511] received: Hello there! [00000026]
[REQPID-5511] received: Hello there! [0000002a]
[REQPID-5511] received: Hello there! [0000002d]
[REQPID-5511] received: Hello there! [0000002f]
[REQPID-5511] received: Hello there! [00000032]
[REQPID-5511] received: Hello there! [00000036]
[REQPID-5511] received: Hello there! [00000039]
[REQPID-5511] received: Hello there! [0000003b]
[REQPID-5511] received: Hello there! [0000003e]
[REQPID-5511] received: Hello there! [00000041]
[REQPID-5511] received: Hello there! [00000045]
[REQPID-5511] received: Hello there! [00000048]
[REQPID-5511] received: Hello there! [0000004a]
[REQPID-5511] received: Hello there! [0000004d]
[REQPID-5511] received: Hello there! [00000050]
第三个request-client.lua的部分运行结果:
/data/user # lua 00-request-client.lua 'tcp://192.168.1.104:4321'
[REQPID-5515] received: Hello there! [00000012]
[REQPID-5515] received: Hello there! [00000015]
[REQPID-5515] received: Hello there! [00000018]
[REQPID-5515] received: Hello there! [0000001a]
[REQPID-5515] received: Hello there! [0000001e]
[REQPID-5515] received: Hello there! [00000021]
[REQPID-5515] received: Hello there! [00000024]
[REQPID-5515] received: Hello there! [00000027]
[REQPID-5515] received: Hello there! [00000029]
[REQPID-5515] received: Hello there! [0000002c]
[REQPID-5515] received: Hello there! [00000030]
[REQPID-5515] received: Hello there! [00000033]
[REQPID-5515] received: Hello there! [00000035]
[REQPID-5515] received: Hello there! [00000038]
[REQPID-5515] received: Hello there! [0000003c]
[REQPID-5515] received: Hello there! [0000003f]
[REQPID-5515] received: Hello there! [00000042]
[REQPID-5515] received: Hello there! [00000044]
[REQPID-5515] received: Hello there! [00000047]
[REQPID-5515] received: Hello there! [0000004b]
[REQPID-5515] received: Hello there! [0000004e]
[REQPID-5515] received: Hello there! [00000051]
[REQPID-5515] received: Hello there! [00000053]
[REQPID-5515] received: Hello there! [00000057]
[REQPID-5515] received: Hello there! [0000005a]
[REQPID-5515] received: Hello there! [0000005d]
[REQPID-5515] received: Hello there! [00000060]
[REQPID-5515] received: Hello there! [00000062]
仔细观察运行结果可以得到一些有趣的现象,比如说对于REP/REQ协议,服务端发送的一条消息只有一个客户端能接收到,这说明其中有一个消息分发的机制。以上代码的编写及运行,就是笔者开始使用Lua脚本进行zeromq开发的开端了。ZeroMQ的官网提供了很多资料,不过zguide的Lua脚本几乎不能运行(请参考:http://zguide.zeromq.org/lua:all);但相关的说明文档仍是很有参考价值的。此外,我还建议经常查看ZeroMQ的Lua模块源码,可以在github上访问:https://github.com/zeromq/lzmq,该仓库中的zcontex.c/zsocket.c源码可以加深我们对lzmq的理解从而更好地使用它;该库中也有一些示例和文档,分别在examples/doc目录下。
为了进一步了解并使用C/C++开发zeromq的应用,就需要参考ZeroMQ的API,可以在http://api.zeromq.org/4-1:_start 查询。
注:LuaARM.tar.xz可在笔者的下载区获得;其在嵌入式ARM/Linux上的安装操作为:
mkdir -p /system ; cd /system ; xzcat path/to/LuaARM.tar.xz | tar -x -f - ; source ./LuaARM/envsetup.sh