skynet框架应用 (七) 本地服务间消息通信

本文深入探讨Skynet框架中的服务间通信,包括消息类型、注册处理函数、打包解包、发送和响应消息的方法,以及session的重要性和使用skynet.response的技巧。此外,还介绍了服务重入问题、服务临界区的处理以及代理服务的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

7 服务间消息通信

​ skynet中的每一个服务都有一个独立的lua虚拟机,逻辑上服务之间是相互隔离的,那么你就不能使用传统意义上的LUA全局变量来进行服务间通信了。

​ 在skynet中服务之间可以通过skynet消息调度机制来完成通信。skynet中的服务是基于actor模型设计出来的,每个服务都可以接收消息,处理消息,发送应答消息。

​ 每条 skynet 消息由 6 部分构成:消息类型、session 、发起服务地址 、接收服务地址 、消息 C 指针、消息长度。

7.1消息类型

​ 在 skynet 中消息分为多种类别,对应的也有不同的编码方式(即协议),消息类型的宏定义可以查看 skynet.h 中:

#define PTYPE_TEXT 0   
#define PTYPE_RESPONSE 1    //表示一个回应包
#define PTYPE_MULTICAST 2   //广播消息
#define PTYPE_CLIENT 3      //用来处理网络客户端的请求消息
#define PTYPE_SYSTEM 4      //系统消息
#define PTYPE_HARBOR 5      //跨节点消息
#define PTYPE_SOCKET 6    //套接字消息
#define PTYPE_ERROR 7     //错误消息,一般服务退出的时候会发送error消息给关联的服务
#define PTYPE_QUEUE 8
#define PTYPE_DEBUG 9
#define PTYPE_LUA 10   //lua类型的消息,最常用
#define PTYPE_SNAX 11  //snax服务消息

#define PTYPE_TAG_DONTCOPY 0x10000
#define PTYPE_TAG_ALLOCSESSION 0x20000

​ 上面的消息类型有多种,但是最常用的是PTYPE_LUA,对应到lua层,叫做lua消息 ,大部分服务一般使用这种消息,默认情况下,PTYPE_REPSONSE、PTYPE_ERROR、PTYPE_LUA三种消息类型已经注册(查看源码了解情况),如果想使用其他的消息类型,需要自己显示注册消息 类型。

7.2 注册消息处理函数

​ 当我们需要在一个服务中监听指定类型的消息,就需要在服务启动的时候先注册该类型的消息的监听,通常是在服务的入口函数 skynet.start 处通过调用 skynet.dispatch 来注册绑定:

--服务启动入口
skynet.start(function()
    --注册"lua"类型消息的回调函数
    skynet.dispatch("lua", function(session, address, ...)
        dosomething(...)
    end)
end)

​ 一旦注册成功,那么只要是发送给这个服务的消息是lua类型消息,那么都会调用我们注册的function进行处理。

例如testluamsg.lua:

skynet = require "skynet"
require "skynet.manager"

local function dosomething(session, address, ...)
    skynet.error("session", session)
    skynet.error("address", skynet.address(address))
    local args = {...}
    for i,v in pairs(args) do
         skynet.error("arg"..i..":", v)
    end
end
skynet.start(function()
    --注册"lua"类型消息的回调函数
    skynet.dispatch("lua", function(session, address, ...)
          dosomething(session, address, ...)
    end)
    skynet.register(".testluamsg")
end)

7.3 打包与解包消息

​ skynet中的消息在发送之前必须要把参数进行打包,然后才发送,接受方收到消息后会自动根据指定的解包函数进行解包,最常用的打包解包函数为skynet.pack与skynet.unpack.

skynet.pack(...)打包后,会返回两个参数,一个是C指针msg指向数据包的起始地址,sz一个是数据包的长度。msg指针的内存区域是动态申请的。

skynet.unpack(msg, sz)解包后,会返回一个参数列表。需要注意这个时候C指针msg指向的内存不会释放掉。如果msg有实际的用途,skynet框架会帮你在合适的地方释放掉,如果没有实际的用途,自己想释放掉可以使用skynet.trash(msg, sz)释放掉。

例如:

local msg, sz = skynet.pack("nengzhong", 8.8, false)
local arg1, arg2, arg3 = skynet.unpack(msg, sz)
skynet.error(arg1, arg2, arg3)

local arglist = {
  
  skynet.unpack(msg, sz)}
for i,v in pairs(arglist) do
      skynet.error("arg"..i..":", v)
end
skynet.trash(msg, sz) --没有用到skynet框架中,所以用完了需要自己释放一下

注意:skynet.pack返回的msg与sz只用在skynet.unpack中使用才有意义。不要这么使用table.unpack(msg, sz).

7.4 发送消息的方法

7.4.1 发送无需响应的消息

 --用 type 类型向 addr 发送未打包的消息。该函数会自动把...参数列表进行打包,默认情况下lua消息使用skynet.pack打包。addr可以是服务句柄也可以是别名。
skynet.send(addr, type, ...)

--用 type 类型向 addr 发送一个打包好的消息。addr可以是服务句柄也可以是别名。
skynet.rawsend(addr, type, msg, sz) 

例如testsendmsg.lua:

skynet = require "skynet"

skynet.start(function()
    skynet.register(".testsendmsg")
    local testluamsg = skynet.localname(".testluamsg")
    --发送lua类型的消息给testluamsg,发送成功后立即返回,r的值为0
    local r = skynet.send(testluamsg, "lua", 1, "nengzhong", true) --申请了C内存(msg,sz)已经用与发送,所以不用自己再释放内存了。
    skynet.error("skynet.send return value:", r)   
        
    --通过skynet.pack来打包发送
    r = skynet.rawsend(testluamsg, "lua", skynet.pack(2, "nengzhong", false)) --申请了C内存(msg,sz)已经用与发送,所以不用自己再释放内存了。
    skynet.error("skynet.rawsend return value:", r)   
end)

先运行testluamsg.lua再运行testsendmsg.lua, 结果如下:

$ ./skynet examples/config
testluamsg
[:0100000a] LAUNCH snlua testluamsg
testsendmsg
[:0100000b] LAUNCH snlua testsendmsg
[:0100000b] skynet.send return value: 0  #发送完消息马上返回
[:0100000b] skynet.rawsend return value: 0 #发送完消息马上返回
[:0100000a] session 0      #接收端接到消息
[:0100000a] address :0100000b
[:0100000a] arg1: 1
[:0100000a] arg2: nengzhong
[:0100000a] arg3: true
[:0100000a] session 0
[:0100000a] address :0100000b
[:0100000a] arg1: 2
[:0100000a] arg2: nengzhong
[:0100000a] arg3: false

​ 上面的代码我们隐式或显示调用了skynet.pack,一共申请了两段C内存,但是并不需要我们释放C内存。因为已经把这段内存用于发送了,skynet会等到该消息处理完后,自动释放掉它的内存。

7.4.2 发送必须响应的消息

--用默认函数打包消息,向addr发送type类型的消息并等待返回响应,并对回应信息进行解包。(自动打包与解包。)
skynet.call(addr, type, ...) 
--直接向addr发送type类型的msg,sz并等待返回响应,不对回应信息解包。(需要自己打包与解包)
skynet.rawcall(addr, type, msg, sz) 

例如testcallmsg.lua:

skynet = require "skynet"
skynet.start(function()
    skynet.register(".testcallmsg")
    --发送lua类型的消息给service,发送成功,该函数将阻塞等待响应返回,r的值为响应的返回值
    local r = skynet.call(".testluamsg", "lua", 1, "nengzhong", true)
    skynet.error("skynet.call return value:", r)   
        
   --通过skynet.pack来打包发送,返回的值也需要自己解包
    r = skynet.unpack(skynet.rawcall(".testluamsg", "lua", skynet.pack(2, "nengzhong", false)))
    skynet.error("skynet.rawcall return value:", r)   
end)

先运行testluamsg.lua再运行testcallmsg.lua, 结果如下:

$ ./skynet examples/config
testluamsg
[:0100000a] LAUNCH snlua testluamsg
testcallmsg
[:0100000b] LAUNCH snlua testcallmsg
[:0100000a] session 2   #只发送出第一个消息,现在已经阻塞住,由于testluamsg并没有返回应答。
[:0100000a] address :0100000b
[:0100000a] arg1: 1
[:0100000a] arg2: nengzhong
[:0100000a] arg3: true
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值