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, ...)
      local f=assert(CMD[cmd])
      f(source,...)
    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程序的运行结果