当前位置: 首页 > news >正文

skynet 使用protobuf

一、安装protobuf

    下面的操作方法都是在 centos 环境下操作

#下载 Protocol Buffers 源代码:
#您可以从 Protocol Buffers 的 GitHub 仓库中获取特定版本的源代码。使用以下命令克隆仓库
git clone -b v3.20.3 https://github.com/protocolbuffers/protobuf.git#编译和安装:
#进入克隆的目录,然后编译和安装 Protocol Buffers:
cd protobuf
./autogen.sh
./configure
make
sudo make install#验证安装:
protoc --version#您应该看到输出,指示安装的版本为 3.20.3。

二、安装pdb库

#第三方库安装在3rd目录下
cd skynet/3rd/
git clone https://github.com/cloudwu/pbc.gitcd pbc
make#编译成功后,打开skynet/3rd/pbc/binding/lua53/Makefile文件,修改里面的lua路径
CC = gccCFLAGS = -O2 -fPIC -WallLUADIR = ../../../lua   #这个路劲就是skynet/3rd/luaTARGET = protobuf.so.PHONY : all cleanall : $(TARGET)$(TARGET) : pbc-lua53.c$(CC) $(CFLAGS) -shared -o $@ -I../.. -I$(LUADIR) -L../../build $^ -lpbcclean :rm -f $(TARGET)#去到lua53目录,编译生成protobuf.so库
cd ./binding/lua53
sudo make

三、将依赖文件放到工程目录下

   将 protobuf.so 和 protobuf.lua 分别放入 luaclib 、lualib

cp protobuf.so ../../../../luaclib/  #将protobuf.so复制到存放C模块的lualib目录中    
cp protobuf.lua ../../../../lualib/  #将protobuf.lua复制到存放Lua模块的lualib目录中

四、创建protobuf目录

在skynet目录下创建protobuf目录,用来存放原始 .proto 描述文件

  这里举例 login.proto

mkdir protobuflogin.proto的内容如下代码所示:
syntax = "proto3";  
package Login;message login {string account=1;string passwd=2;int32 result=3;
}

五、编译proto文件

protoc --descriptor_set_out login.pb login.proto

   这里写了个gen_pb.sh脚本,将protobuf目录下的所有.sproto文件转成proto文件

#!/bin/bash  # 指定要遍历的目录,这里使用当前目录"."  
dir="."  # 使用find命令查找所有.proto文件,并调用basename和cut命令来截取文件名  
find "$dir" -type f -name "*.proto" | while read -r filepath; do  # 使用basename命令获取文件名部分,然后使用cut命令去除后缀  filename=$(basename "$filepath")  filename_without_extension="${filename%.*}"  # 输出截取后的文件名  echo "$filename, $filename_without_extension"  # 在这里可以添加其他操作,比如使用protoc编译等  protoc --descriptor_set_out "$filename_without_extension.pb" "$filename" 
done

六、了解protobuf.lua 关键函数

1、 protobuf.register

  • 功能:注册 Protobuf 
  • 参数:buffer  .pb文件读取出来的二进制字符串
  • 返回值:无。
function M.register(buffer)c._env_register(P, buffer) 
end

   备注: register需要自己去加载.pb文件内容,下面的register_file函数使用会更多

2、protobuf.register_file

  • 功能:注册 Protobuf 
  • 参数:filename 为  .pb文件名。  
  • 返回值:无。
function M.register_file(filename)local f = assert(io.open(filename , "rb"))local buffer = f:read "*a"c._env_register(P, buffer)f:close()
end

3、protobof.encode

  • 功能:将一个 Lua 表编码为 Protobuf 格式的二进制消息。
  • 参数:message 是注册的 Protobuf 定义的名称,msg 是要编码的 Lua 表。
  • 返回值:编码后的二进制数据。

function M.encode( message, t , func , ...)local encoder = c._wmessage_new(P, message)assert(encoder ,  message)encode_message(encoder, message, t)if func thenlocal buffer, len = c._wmessage_buffer(encoder)local ret = func(buffer, len, ...)c._wmessage_delete(encoder)return retelse    local s = c._wmessage_buffer_string(encoder)c._wmessage_delete(encoder)return send
end

4、protobof.decode

  • 功能:将一个 Protobuf 编码的二进制消息解码为 Lua 表。
  • 参数:typename 是注册的 Protobuf 定义的名称,buf 是包含 Protobuf 编码消息的二进制数据。
  • 返回值:解码后的 Lua 表。

function M.decode(typename, buffer, length)local ret = {}local ok = c._decode(P, decode_message_cb , ret , typename, buffer, length)if ok thenreturn setmetatable(ret , default_table(typename))elsereturn false , c._last_error(P)end
end

七、protobuf测试用例

   在examples 目录下新建 test_protobuf.lua 

package.path = package.path .. ";./lualib/?.lua"
package.cpath = package.cpath .. ";./luaclib/?.so"local protobuf = require "protobuf"      --引入文件protobuf.lua
protobuf.register_file "./protobuf/common.pb" --注册pb文件
protobuf.register_file "./protobuf/login.pb" --注册pb文件local loginInfo = { account = "test", passwd = "pw"} local encodeData = protobuf.encode("Login.login", loginInfo)
print("encodeData:", encodeData)local decodeData = protobuf.decode("Login.login", encodeData)
print("decodeData account:", decodeData.account)
print("decodeData passwd:", decodeData.passwd)

八、skynet 使用protobuf进行网络通信

1、 将数据打包成二进制数据

--[[big endianhead    2 byte body size2 byte protonamesizen byte protonamebody    n byte data@desc: 将lua格式的协议序列化为二进制数据
]] 
function protobufDataHelper.encode( name,data )local stringbuffer =  protobuf.encode(name, data)         -- protobuf序列化 返回lua stringlocal body = string.pack(">s2s",name,stringbuffer)       -- 打包包体 协议名 + 协议数据local head = string.pack(">I2",#body)                     -- 打包包体长度print("encode proto_name:", name, ",data_size:", #body, ",totalSize:", #head+#body)return head .. body                                       -- 包体长度 + 协议名 + 协议数据
end

2、将二进制数据解包

--[[@desc: 将二进制数据反序列化为lua string--@msg: C Point @return:协议名字,协议数据
]]
function protobufDataHelper.decode( msg  )--- 前两个字节在netpack.filter 已经解析print("msg size:", #msg)local proto_name,stringbuffer = string.unpack(">s2s",msg)print("proto_name", proto_name, "data:", stringbuffer)local body = protobuf.decode(proto_name, stringbuffer)return proto_name,body
end

3、skyent unpack类型指定为二进制字符串

     这里在agent.lua 注册消息协议类型时处理

skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,   unpack = skynet.tostring,   --- 将C point 转换为lua 二进制字符串
}

  在dispatch_message消息地方反序列化消息

--- 分发消息
local function  dispatch_message(msg)--- 反序列化二进制string数据local pack_name,data = dataHelper.decode(msg)     --   pack_name = c2s.testlocal sub_name = pack_name:match(".+%.(%w+)$")    --   sub_name = test......local f = REQUEST[sub_name]if f == nil thenprint("not function define handle package:", pack_name)returnendf(data)
end

agent.lua 改造测试代码如下

local skynet = require "skynet"
local socket = require "skynet.socket"
local sproto = require "sproto"
local sprotoloader = require "sprotoloader"
local login = require "login"
local tableutil = require "tableutil"local dataHelper = require "protobufDataHelper"local WATCHDOG
local host
local send_requestlocal CMD = {}
local REQUEST = {}
local client_fdfunction REQUEST:get()print("get", self.what)local r = skynet.call("SIMPLEDB", "lua", "get", self.what)return { result = r }
endfunction REQUEST:set()print("set", self.what, self.value)local r = skynet.call("SIMPLEDB", "lua", "set", self.what, self.value)
endfunction REQUEST:handshake()return { msg = "Welcome to skynet, I will send heartbeat every 5 sec." }
endfunction REQUEST:quit()skynet.call(WATCHDOG, "lua", "close", client_fd)
endlocal function send_data(name, args)local data = dataHelper.encode(name, args)-- 发送数据socket.write(client_fd,data)
endfunction REQUEST:login()print("login account,passwd:", self.account, self.passwd)local result = login.loginRequest(self.account, self.passwd)if result ~= 0 thenprint("kill client, client_fd:", client_fd)REQUEST:quit()endlocal loginInfo = { account = "kk", passwd = "haha"}send_data("Login.login", loginInfo)return {result = result}
endfunction REQUEST:loginTest()print("loginTest:", tableutil.tPrint(self))
endlocal function request(name, args, response)local f = assert(REQUEST[name])print("recieve request, protoName:", name, tableutil.tPrint(args))local r = f(args)if response thenreturn response(r)end
endlocal function send_package(pack)local package = string.pack(">s2", pack)socket.write(client_fd, package)
end--[[
skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,unpack = function (msg, sz)return host:dispatch(msg, sz)end,dispatch = function (fd, _, type, ...)assert(fd == client_fd)	-- You can use fd to reply messageskynet.ignoreret()	-- session is fd, don't call skynet.retskynet.trace()if type == "REQUEST" thenlocal ok, result  = pcall(request, ...)if ok thenif result thensend_package(result)endelseskynet.error(result)endelseassert(type == "RESPONSE")error "This example doesn't support request client"endend
}--]]skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,	unpack = skynet.tostring,   --- 将C point 转换为lua 二进制字符串
}function CMD.start(conf)local fd = conf.clientlocal gate = conf.gateWATCHDOG = conf.watchdog-- slot 1,2 set at main.luahost = sprotoloader.load(1):host "package"send_request = host:attach(sprotoloader.load(2))skynet.fork(function()local index = 1while true do--send_package(send_request "heartbeat")index = index + 1local loginInfo = { account = "kk"..index, passwd = "haha"}send_data("Login.login", loginInfo)skynet.sleep(500)endend)client_fd = fdskynet.call(gate, "lua", "forward", fd)
endfunction CMD.disconnect()-- todo: do something before exitskynet.exit()
end--- 分发消息
local function  dispatch_message(msg)--- 反序列化二进制string数据local pack_name,data = dataHelper.decode(msg)     --   pack_name = c2s.testlocal sub_name = pack_name:match(".+%.(%w+)$")    --   sub_name = testprint("recieve request, protoName:", pack_name, tableutil.tPrint(data))local f = REQUEST[sub_name]if f == nil thenprint("not function define handle package:", pack_name)returnendf(data)
endskynet.start(function()skynet.dispatch("lua", function(_,_, command, ...)skynet.trace()local f = CMD[command]skynet.ret(skynet.pack(f(...)))end)skynet.dispatch("client", function (session, address, msg)dispatch_message(msg)end)
end)

4、客户端测试代码

client_protobuf.lua

package.cpath = "luaclib/?.so;skynet/luaclib/?.so"
package.path = "lualib/common/?.lua;lualib/?.lua;skynet/lualib/?.lua;skynet/examples/?.lua"local tableutil = require "tableutil"if _VERSION ~= "Lua 5.4" thenerror "Use lua 5.4"
endlocal socket = require "client.socket"
local dataHelper = require "protobufDataHelper"local fd = assert(socket.connect("127.0.0.1", 8888))local function send_data(name, args)local data = dataHelper.encode(name, args)-- 发送数据socket.send(fd,data)
endlocal loginInfo = { account = "test", passwd = "haha"}
send_data("Login.login", loginInfo)
send_data("Common.heartbeat", {})local function unpack_package(text)local size = #textif size < 2 thenreturn nil, textendlocal s = text:byte(1) * 256 + text:byte(2)if size < s+2 thenreturn nil, textendreturn text:sub(3,2+s), text:sub(3+s)
endlocal function recv_package(last)local resultresult, last = unpack_package(last)if result thenreturn result, lastendlocal r = socket.recv(fd)if not r thenreturn nil, lastendif r == "" thenerror "Server closed"endreturn unpack_package(last .. r)
endlocal last = ""
local function dispatch_package()while true dolocal vv, last = recv_package(last)if not v thenbreakendlocal packName, data = dataHelper.decode(v)print("packName:", packName)print("data:", tableutil.tPrint(data) )end
endwhile true dodispatch_package()socket.usleep(1000000)
end

相关文章:

skynet 使用protobuf

一、安装protobuf 下面的操作方法都是在 centos 环境下操作 #下载 Protocol Buffers 源代码&#xff1a; #您可以从 Protocol Buffers 的 GitHub 仓库中获取特定版本的源代码。使用以下命令克隆仓库 git clone -b v3.20.3 https://github.com/protocolbuffers/protobuf.git#编译…...

Vue Router 4 与 Router 3 路由配置与区别

文章目录 路由安装路由配置vue-router 3.x版本写法配置路由使用路由 vue-router 4.x版本写法配置路由使用路由 Vue Router 4 与 Vue Router 3 区别 路由安装 Vue 2 (使用 Vue Router 3) &#xff1a;npm install vue-router3 Vue 3 (使用 Vue Router 4) &#xff1a;npm insta…...

python借助elasticsearch实现标签匹配计数

给定一组标签 [{“tag_id”: “1”, “value”: “西瓜”}, {“tag_id”: “1”, “value”: “苹果”}]&#xff0c;我想精准匹配到现有的标签库中存在的标签并记录匹配成功的数量。 标签id(tag_id)标签名(tag_name)标签值(tag_name )1水果西瓜1水果苹果1水果橙子2动物老虎 …...

Yolo-world+Python-OpenCV之摄像头视频实时目标检测

上一次介绍了如何使用最基本的 Yolo-word来做检测&#xff0c;现在我们在加opencv来做个实时检测的例子 基本思路 1、读取离线视频流 2、将视频帧给yolo识别 3、根据识别结果 对视频进行绘制边框、加文字之类的 完整代码如下&#xff1a; import datetimefrom ultralytics …...

vue-treeselect 的基本使用

vue-treeselect 的基本使用 1. 效果展示2. 安装 插件3. 引入组件4. 代码 1. 效果展示 2. 安装 插件 vue-treeselect是一个树形的下拉菜单&#xff0c;至于到底有多少节点那就要看你的数据源有多少层了&#xff0c;挺方便的。下面这个这个不用多说吧&#xff0c;下载依赖 npm in…...

Vue(二)

文章目录 1.条件渲染1.关于js中的false的判定2.基本介绍3.v-if1.需求分析2.代码实例 4.v-show实现5.v-if与v-show比较6.课后练习 2.列表渲染1.代码实例2.课后练习 3.组件化编程1.基本介绍2.实现方式一_普通方式2.实现方式二_全局组件方式3.实现方式三_局部组件方式 4.生命周期和…...

Python基于深度学习的车辆特征分析系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

推理还原的干货

故事的递进还原 从下层故事到上层故事 设定还原 还原的逻辑 隐藏信息拼凑、因果导致果推因、规则还原现象 设计思路&#xff1a; 真解答 真解答的关键信息 推理逻辑链 哪些环节可以被误导 如何把关键信息变成伪解答 解释变形信息 给出识别变形信息的方法或线索 其实看似一个…...

【Redis 神秘大陆】006 灾备方案

六、Redis 灾备方案 6.1 存储方案 6.1.1 基础对比 RDB持久化AOF持久化原理周期性fork子进程生成持久化文件每次写入记录命令日志文件类型二进制dump快照文件文本appendonly日志文件触发条件默认超过300s间隔且有1s内超过1kb数据变更永久性每秒fsync一次文件位置配置文件中指…...

【Java基础】17.异常处理

文章目录 前言一、异常的概念1.异常的3种类型2.支持异常处理的关键字和类 二、Exception 类的层次三、内置异常类1.非检查性异常2.检查性异常类 四、异常处理1.捕获异常2.多重捕获块3.throws/throw 关键字1.throw 关键字2.throws 关键字 3.finally关键字 五、编译时异常处理方式…...

【python】flask结合SQLAlchemy,在视图函数中实现对数据库的增删改查

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

APIGateway的认证

APIGateway的支持的认证如下&#xff1a; 我们从表格中可以看到&#xff0c;HTTP API 不支持资源策略的功能&#xff0c;另外是通过JWT的方式集成Cognito的。 对于REST API则是没有显示说明支持JWT认证&#xff0c;这个我们可以通过Lambda 自定义的方式来实现。 所以按照这个…...

MacOS Github Push项目 精简版步骤

大白菜教程&#xff1a;小白菜 macOS github提交代码-CSDN博客 步骤1&#xff1a;git init步骤2&#xff1a; touch .gitignore 创建ignore文件 open .gitignore 打开ignore文件 编写ignore文件.idea/ 是文件夹的意思.git/ 也是自动生成的文件夹 也不上传.DS_St…...

Eclipse的基本使用讲解(建项目,建包,建类,写代码(基本语法))新手入门必备

目录 一.介绍eclipse 二.操作Eclipse 1.选择工作空间 2.建项目&#xff0c;建包&#xff0c;建类 1.建项目(两种) 2.建包 3.建类 三.写代码(基本语法) 1.代码操作 2.代码规范 3.代码注释 一.介绍eclipse Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其…...

3D模型处理的并行化

今天我们将讨论如何使用 Python 多进程来处理大量3D数据。 我将讲述一些可能在手册中找到的一般信息&#xff0c;并分享我发现的一些小技巧&#xff0c;例如将 tqdm 与多处理 imap 结合使用以及并行处理存档。 那么我们为什么要诉诸并行计算呢&#xff1f; 使用数据有时会出现…...

盲人安全导航技巧:科技赋能让出行更自如

作为一名资深记者&#xff0c;长期关注并报道无障碍领域的发展动态。今日&#xff0c;我将聚焦盲人安全导航技巧&#xff0c;探讨这一主题下科技如何赋能视障人士实现更为安全、独立的出行。一款融合了实时避障、拍照识别物体及场景功能的盲人出行辅助应用叫做蝙蝠避障&#xf…...

问,由于java存在性能上,以及部分功能上的缺点,请问如何正确使用C,C++,Go,这三个语言,提升Java Web项目的性能?

拓展阅读&#xff1a;版本任你发&#xff0c;我用java8 我明白Java虽然在许多方面表现出色&#xff0c;但在某些特定场景下可能会遇到性能瓶颈或功能限制。为了提升Java Web项目的性能&#xff0c;可以考虑将C、C和Go这三种语言用于特定的组件或服务。以下是如何正确使用这些语…...

【信号与系统 - 9】傅里叶变换的性质习题

1 习题 已知 f ( t ) f(t) f(t) 的傅里叶变换为 F ( j w ) F(jw) F(jw) &#xff0c;求如下信号的傅里叶变换 &#xff08;1&#xff09; t ⋅ f ( 3 t ) t\cdot f(3t) t⋅f(3t) 解&#xff1a; f ( 3 t ) ↔ 1 3 F ( j w 3 ) f(3t)\leftrightarrow \frac{1}{3}F(j\frac{w}…...

C#探索之路基础夯实篇(5):语法糖概念解析

C#探索之路基础夯实篇(5)&#xff1a;语法糖概念解析 文章目录 C#探索之路基础夯实篇(5)&#xff1a;语法糖概念解析1、概念定义2、Lua中的语法糖3、C#中的语法糖4、C中的语法糖5、优缺点辨析6、适用范围7、总结 从之前一开始接触lua的时候开始&#xff0c;开始第一次接触到语法…...

SeaTunnel 与 DataX 、Sqoop、Flume、Flink CDC 对比

产品概述 Apache SeaTunnel 是一个非常易用的超高性能分布式数据集成产品,支持海量数据的离线及实时同步。每天可稳定高效同步万亿级数据,已应用于数百家企业生产,也是首个由国人主导贡献到 Apache 基金会的数据集成顶级项目。 SeaTunnel 主要解决数据集成领域的常见问题:…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...