2.3 第一个程序PingPong
服务向另一个服务发送消息,是Skynet的最核心功能。PingPong是个很简单的程序,下面用它来学习如何开启服务、如何发送消息。
2.3.1 功能需求
如图2-10所示,开启两个ping类型的服务ping1和ping2,让ping1给ping2发消息,ping2收到后回应ping1,ping1收到再回应ping2,不断循环。PingPong与1.7.2节的Actor程序“相互督促工作,努力赚钱”很相似。
图2-10 PingPong程序示意图
2.3.2 学习服务模块
Skynet提供了开启服务和发送消息的API,必先掌握它们。表2-5列出了Skynet中8个最重要的API,PingPong程序会用到它们。更多API可以参见https://github.com/cloudwu/skynet/wiki/APIList,此处暂不列举太多,用到时再做介绍。
表2-5 Skynet中8个最重要的API
图2-11 skynet.call的示意图
2.3.3 代码实现
初看API文档可能一头雾水,结合代码才能融会贯通。按照2.3.1节的需求,PingPong程序必须包含主服务和ping服务。
1.主服务
新建文件examples/Pmain.lua,主服务如代码2-2所示。
代码2-2 examples/Pmain.lua中的主服务代码
(资源:Chapter2/2_pingpong_main.lua)
local skynet = require "skynet" skynet.start(function() skynet.error("[Pmain] start") local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping") skynet.send(ping1, "lua", "start", ping2) skynet.exit() end)
说明:可以用Vim等工具直接在Linux上编辑文档,也可以使用WinSCP、Samba等工具在Windows上编辑。
图2-12是代码2-2的示意图,主服务启动服务后,会先打印“[Pmain]start”(没特别的作用,用于验证程序是否运行到skynet.start的回调函数了),然后开启两个ping类型的服务,它们的地址分别存为ping1和ping2。再调用skynet.send,让主服务向ping1发送名为“start”的消息(图中的阶段①),附带一个参数ping2。最后,主服务完成使命,退出。
图2-12 代码2-2的示意图
为使Skynet启动Pmain,需设置配置文件。在examples中新建配置文件Pconfig,可以复制原先的Config文件,并将其中的start="main"改为start="Pmain"。Skynet会找到Pmian.lua作为主服务。也可以复制2.2.2节的配置模板,同样,设置主服务为Pmain。
2.ping服务
新建文件examples/ping.lua,编写ping服务。Skynet服务的基础结构如代码2-3所示(主服务功能单一,因此使用更简单的写法)。
代码2-3 examples/ping.lua中的ping服务代码
(资源:Chapter2/2_pingpong_ping.lua)
local skynet = require "skynet" local CMD = {} skynet.start(function() skynet.dispatch("lua", function(session, source, cmd, ...)end) end)
在代码2-3中,先用skynet.start初始化服务,然后在回调方法中调用skynet.dispatch,指定lua类型消息的处理方法。为使代码简洁,两个回调方法都使用了匿名函数。代码中带底纹的两句值得重点关注,其含义是:收到其他服务的消息后,查找CMD[cmd]这个方法是否存在,如果存在就调用它。例如,当ping1服务收到主服务的“start”消息时,程序会调用CMD.start(source, ...)。其中,参数source代表消息来源,其他参数由发送方传送。
ping服务可以接收两种消息:一种是主服务发来的start消息;另一种是其他ping服务发来的ping消息。如代码2-4展示了这两种消息的处理方法。
代码2-4 examples/ping.lua中的ping服务消息处理
function CMD.start(source, target) skynet.send(target, "lua", "ping", 1) end function CMD.ping(source, count) local id = skynet.self() skynet.error("["..id.."] recv ping count="..count) skynet.sleep(100) skynet.send(source, "lua", "ping", count+1) end
主服务会在启动两个ping服务后给ping1发送start消息,语句是“skynet.send(ping1, "lua", "start", ping2)”,最后一个参数对应CMD.start的参数target,代表要让ping1发消息给谁。ping1收到后,会给ping2发送一条ping消息,附带参数“1”。ping2收到后,执行CMD.ping,参数“1”对应参数count。ping2也会给ping1(发送方source)发送ping,并把记数值count加1,如此往复。
代码中的skynet.sleep(100)指让协程(ping方法)暂停1秒,这仅仅为了降低程序运行速度,让读者可以看清日志。
2.3.4 运行结果
执行./skynet examples/Pconfig运行程序,结果如图2-13所示。其中0100000b和16777227代表ping2的地址(一个十六进制一个十进制,它们是相同的值,根据不同配置,读者看到的数值可能不同),0100000a和16777226代表ping1的地址。ping2先打印出计数值1,接着ping1打印出计数值2,然后ping2再打印出计数值3,以此类推。
图2-13 PingPong程序的运行结果