【游戏开发实战】手把手教你从零跑一个Skynet,详细教程,含案例讲解(服务端 | Skynet | Ubuntu)
文章目录
- 一、前言
- 二、关于Skynet
- 三、Ubuntu虚拟机
- 1、Ubuntu系统镜像下载
- 2、VirtualBox虚拟机软件
- 2.1、VirtualBox下载
- 2.2、VirtualBox安装
- 2.3、创建虚拟机
- 3、载入Ubuntu iso镜像
- 4、Ubuntu系统安装过程
- 四、安装必要的工具
- 1、安装git
- 2、安装autoconf
- 3、安装gcc
- 五、下载Skynet源码
- 六、编译Skynet源码
- 七、运行Skynet案例
- 八、写个Demo
- 1、配置文件
- 2、规范目录结构
- 3、自己写个配置文件
- 4、主服务
- 5、写个打工服务
- 6、在主服务中启动打工服务
- 7、在主服务中给打工服务发消息
- 8、封装服务类
- 9、重写打工服务
- 10、买猫粮服务
- 九、补充
- 1、网络模块
- 2、节点集群
- 3、数据库模块
- 3.1、安装MySQL
- 3.2、启动MySQL
- 3.3、关闭MySQL
- 3.4、登录MySQL
- 3.5、在skynet中操作数据库
- 十、完毕
一、前言
嗨,大家好,我是新发。
认识我的朋友都知道我是一名Unity
游戏开发工程师,也就是我平时做的是客户端部分的开发,其实以前我是一名服务端开发工程师,后来因为工作原因,转岗做了Unity
客户端开发,然后就一直干到现在。
最近,我在搞服务端的skynet
框架,看看以后自己做些作品(skynet
框架服务端+Unity
客户端)。今天呢,我就先把skynet
环境搞一下,讲讲流程,也方便想学习的同学,话不多说,我们开始吧~
二、关于Skynet
skynet
是一个轻量级的网络游戏框架,也可用于许多其他领域。
建议大家看下云风的《Skynet设计综述》,这里我不过多赘述,主要讲讲操作流程~
三、Ubuntu虚拟机
skynet
需要运行在linux
或macos
系统中,这里作为演示,我使用Ubuntu
虚拟机。下面我讲下Ubuntu
虚拟机的安装过程。
1、Ubuntu系统镜像下载
首先我们需要先下载Ubuntu
系统的iso
文件,下面这些地址都可以下载,大家选择一个即可:
网易开源镜像:http://mirrors.163.com/ubuntu-releases/
Ubuntu
官方:http://releases.ubuntu.com/
Ubuntu
中国官网:https://ubuntu.com/download/alternative-downloads
中科开源镜像:http://mirrors.ustc.edu.cn/ubuntu-releases/
阿里开源镜像:http://mirrors.aliyun.com/ubuntu-releases/
浙江大学开源镜像:http://mirrors.zju.edu.cn/ubuntu-releases/
我以Ubuntu 16.04.7
版本为例,地址:http://mirrors.163.com/ubuntu-releases/16.04.7/
把iso
文件下载到本地,
2、VirtualBox虚拟机软件
有了iso
文件,需要将其安装到虚拟机中,而虚拟机需要运行在虚拟机软件上,所以,我们还需要先安装一个虚拟机软件。
虚拟机软件大家常用的是VMWare
,这里我强烈推荐另一款虚拟机软件:VirtualBox
,它轻量、开源免费,对于个人学习使用完全足够,五星推荐~
关于VirtualBox:
VirtualBox
是一款开源虚拟机软件。VirtualBox
是由德国Innotek
公司开发,由Sun Microsystems
公司出品的软件,使用Qt
编写,在Sun
被Oracle
收购后正式更名成Oracle VM VirtualBox
。
VirtualBox
号称是最强的免费虚拟机软件,它不仅具有丰富的特色,而且性能也很优异!它简单易用,可虚拟的系统包括Windows
(从Windows 3.1
到Windows 10
、Windows Server 2012
,所有的Windows
系统都支持)、Mac OS X
、Linux
、OpenBSD
、Solaris
、IBM OS2
甚至Android
等操作系统!使用者可以在VirtualBox
上安装并且运行上述的这些操作系统!
2.1、VirtualBox下载
VirtualBox
我们可以从官网下载到,地址:https://www.virtualbox.org/
选择windows
版本,点击下载,
下载完毕,
2.2、VirtualBox安装
双击安装包运行安装,过程没有什么特别的,这里不赘述~
安装成功后打开VirtualBox
,界面如下:
2.3、创建虚拟机
点击菜单控制/新建
,
填写虚拟机名称,设置虚拟机保存路径,如下,我设置为E:\ubuntu16
,
设置内存大小,建议分配2G
内存,
创建虚拟硬盘,
建议分配10G
的虚拟硬盘空间,
虚拟机创建完成,如下
3、载入Ubuntu iso镜像
点击启动虚拟机,会提示选择启动盘,点击下面的小按钮,
点击注册,
选择我们刚刚下载的iso
系统镜像文件,打开,可以看到列表中出现了我们的镜像,选中它,
点击启动,即可进入系统安装。
4、Ubuntu系统安装过程
点击Install Ubuntu
,
点击Continue
,
点击Install Now
,
此时会弹个提示框,点击Continue
,
时区填写China Time
,然后点击Continue
,
语言默认English
,点击Continue
,
接着输入账号密码,后面进入系统的时候要用到,这里提示我的密码弱(Weak password
),由于只是自己学习使用,密码弱也没什么关系,点击Continue
,
接着就是耐心等待它安装,
完成后,会提示需要重启,点击Restart Now
,
进入下面这个界面时,按一下回车键,
输入刚刚设置的密码,
顺利进入系统,
四、安装必要的工具
1、安装git
我们需要通过git
来下载skynet
,所以必须先安装git
,我们先打开终端,如下:
在终端输入下面的命令:(注意按回车后需要输入一次密码,并且密码不会显示出来,不要怀疑你的键盘)
sudo apt-get install git
如下:
安装完毕后,输入下面的命令检查下git
是否安装成功了,
git --version
如果输出了版本号,则说明git
已经安装成功了,
2、安装autoconf
编译skynet
需要用到autoconf
,在终端输入下面的命令来安装autoconf
,
sudo apt-get install autoconf
如下:
安装完毕后,输入autoconf --version
按回车,如果能输出版本号,则说明安装成功了,
3、安装gcc
编译skynet
需要用到gcc
,因为Ubuntu
默认安装了gcc
,所以我们这里就不用重复安装了,可以在终端中输入gcc --version
,如果能输出版本号,则说明已经安装了gcc
,
如果提示没有gcc
,则执行sudo apt-get install gcc
进行安装即可。
五、下载Skynet源码
在终端中执行下面的命令,
git clone https://gitee.com/mirrors/skynet.git
如下:
下载完毕后,我们打开文件夹浏览器,可以在Home
目录中看到多了一个skynet
文件夹,
进入skynet
文件夹,就可以看到框架源码啦,
六、编译Skynet源码
在终端中进入skynet
目录,
cd skynet
如下:
然后执行下面的命令:
make linux
此处你可能会报错,如下:
这是因为过程中去gitub
下载jemalloc
失败了,可以多试几次,编译成功后显示的输出内容如下:
我们可以在skynet
目录中看到生成了一个可执行文件:skynet
,如下:
七、运行Skynet案例
在终端中进入skynet
目录后,执行下面的命令,
./skynet example/config
如下,可以看到服务启动成功了,
接下来,我们开启一个新的终端,
运行一个客户端来测试一下,先cd skynet
进入目录,
然后执行如下命令:
./3rd/lua/lua example/client.lua
如下,每隔5秒就会给服务端发送一个heartbeat
心跳包,
我们可以输入hello
,服务器会回应一个world
,如下:
八、写个Demo
想要自己写个Demo
,得先知道skynet
是如何工作的。
1、配置文件
运行skynet
时,需要制定一个配置文件,例:
./skynet example/config
我们先看看这个config
文件里面是啥,进入examples
目录,打开config
文件,
config
文件内容如下:
include "config.path"-- preload = "./examples/preload.lua" -- run preload.lua before every lua service run
thread = 8
logger = nil
logpath = "."
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main" -- main script
bootstrap = "snlua bootstrap" -- The service for bootstrap
standalone = "0.0.0.0:2013"
-- snax_interface_g = "snax_g"
cpath = root.."cservice/?.so"
-- daemon = "./skynet.pid"
第一行引用了config.path
文件,我们打开config.path
文件,
内容如下:
root = "./"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua"
lua_cpath = root .. "luaclib/?.so"
snax = root.."examples/?.lua;"..root.."test/?.lua"
现在,我们把两个文件合在一起看,如下:
root = "./"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua"
lua_cpath = root .. "luaclib/?.so"
snax = root.."examples/?.lua;"..root.."test/?.lua"-- preload = "./examples/preload.lua" -- run preload.lua before every lua service run
thread = 8
logger = nil
logpath = "."
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main" -- main script
bootstrap = "snlua bootstrap" -- The service for bootstrap
standalone = "0.0.0.0:2013"
-- snax_interface_g = "snax_g"
cpath = root.."cservice/?.so"
-- daemon = "./skynet.pid"
我这里对几个重要的参数做一下说明:
参数 | 描述 |
---|---|
luaservice
|
服务脚本路径,包括skynet 框架自带的一些服务和自己写的服务
|
lualoader
|
lua 脚本加载器,指定skynet 的loader.lua
|
lua_path
|
程序加载lua 脚本时,会搜索这个lua_path 配置的路径
|
lua_cpath
|
用C 语言编写的程序库(.so 文件)的路径
|
thread
|
启用的工作线程数量,一般配置为CPU 核心数
|
harbor
|
主从节点模式。skynet 初期提供了master/slave 集群模式,后来提供了更适用的cluster 集群模式,建议使用cluster 模式,配0
|
start
|
主服务入口 |
cpath
|
用C 语言编写的服务模块的路径
|
画个图,强化记忆:
2、规范目录结构
上面介绍了配置文件,现在我们可以自己写一个配置文件啦,不过,实际项目中,一般会先规范一下目录结构,我们把根目录重命名为game
,新建一些子文件夹:
文件夹 | 说明 |
---|---|
etc
|
存放配置文件 |
luaclib
|
存放一些C 模块(.so 文件)
|
lualib
|
存放lua 模块
|
service
|
存放各服务的lua 代码
|
skynet
|
存放skynet 框架(存放我们刚刚下载的skynet 框架源码)
|
最终如下:
3、自己写个配置文件
我们在etc
目录中新建一个config.node1
配置,如下:
因为我们把skynet
框架源码丢在了skynet
子目录中,所以我们配置路径的时候需要加多一层skynet
,最终config.node1
配置如下:
thread = 8
cpath = "./skynet/cservice/?.so"
bootstrap = "snlua bootstrap"start = "main"
harbor = 0lualoader = "./skynet/lualib/loader.lua"luaservice = "./service/?.lua;" .. "./service/?/init.lua;" .. "./skynet/service/?.lua;"lua_path = "./etc/?.lua;" .. "./lualib/?.lua;" .. "./skynet/lualib/?.lua;" .. "./skynet/lualib/?/init.lua;"lua_cpath = "./luaclib/?.so;" .. "./skynet/luaclib/?.so"
4、主服务
上面我们配置的主服务是main
,
start = "main"
它会去配置的luaservice
路径中查找一个main.lua
脚本,
luaservice = "./service/?.lua;" .. "./service/?/init.lua;" .. "./skynet/service/?.lua;"
框架会去启动这个main
服务,我们现在还没有这个main.lua
脚本,现在我们就来写这个main.lua
脚本吧~
进入service
目录,创建main.lua
脚本,代码如下:
local skynet = require "skynet"
skynet.start(function()skynet.error("[start main] hello world")-- TODO 启动其他服务skynet.exit()
end)
上面我们用到了skynet
的三个API
,如下:
有同学会问了,明明说三个API
,怎么列了四个,那个newservice(name, ...)
没看到呀!
因为main
是主服务,它是由框架来启动的,所以是框架帮我们调用了newservice
,如果我们想在主服务中启动其他服务,就要自己调用newservice
了。
现在我们测试一下,打开终端,进入game
目录,然后执行命令:
./skynet/skynet etc/config.node1
运行效果如下,可以看到main.lua
脚本被执行了,输出了[start main] hello world
,
5、写个打工服务
服务脚本统一放在service
目录中,以服务名为文件夹名字创建子目录,打工服务我们取名为worker
,所以,我们在service
文件夹中新建一个worker
目录,
进入worker
目录,新建一个init.lua
脚本,
init.lua
脚本需要实现服务的逻辑,
init.lua
代码如下,
-- service/worker/init.lua脚本local skynet = require "skynet"-- 消息响应函数表
local CMD = {}
-- 服务名
local worker_name = ""
-- 服务id
local worker_id = ""
-- 工钱
local money = 0
-- 是否在工作
local isworking = false-- 每帧调用,一帧的时间是0.2秒
local function update(frame)if isworking thenmoney = money + 1skynet.error(worker_name .. tostring(worker_id) .. ", money: " .. tostring(money))end
end-- 定时器,每隔0.2秒调用一次update函数
local function timer()local stime = skynet.now()local frame = 0while true doframe = frame + 1local isok, err = pcall(update, frame)if not isok thenskynet.error(err)endlocal etime = skynet.now()-- 保证0.2秒local waittime = frame * 20 - (etime - stime)if waittime <= 0 thenwaittime = 2endskynet.sleep(waittime)end
end-- 初始化
local function init(name, id)worker_name = nameworker_id = id
end-- 开始工作
function CMD.start_work(source)isworking = true
end-- 停止工作
function CMD.stop_work(source)isworking = false
end-- 调用初始化函数,...是不定参数,会从skynet.newservice的第二个参数开始透传过来
init(...)skynet.start(function ()-- 消息分发skynet.dispatch("lua", function (session, source, cmd, ...)-- 从CMD这个表中查找是否有定义响应函数,如果有,则触发响应函数local func = CMD[cmd]if func thenfunc(source, ...)endend)-- 启动定时器skynet.fork(timer)
end)
注:这里对代码说明一下,
timer
定时器函数中,waittime
代表每次循环等待的时间,由于程序有可能会卡住,我们很难保证 “每隔0.2秒调用一次update
” 是精确的,update
函数本身执行也需要时间,所以等待时间是0.2
减去执行时间,执行时间就是etime - stime
。
6、在主服务中启动打工服务
我们回到主服务main.lua
脚本中,添加一句skynet.newservice
调用,如下:
-- main.lua脚本local skynet = require "skynet"skynet.start(function ()skynet.error("[start main] hello world")-- 启动打工服务,其中第二个参数和第三个参数会透传给service/worker/init.lua脚本local worker1 = skynet.newservice("worker", "worker", 1)skynet.exit()
end)
现在我们测试一下,在game
目录中执行命令
./skynet/skynet etc/config.node1
运行效果如下,可以看到启动了一个worker
服务,
有同学可能会问了,我们调用skynet.newservice
时第一个参数是worker
,框架怎么知道会去执行service/worker/init.lua
脚本呢?
还记得我们的config.node1
配置吗,里面的luaservice
我们配置了"./service/?/init.lua;"
,如下:
-- config.node1配置luaservice = "./service/?.lua;" .. "./service/?/init.lua;" .. "./skynet/service/?.lua;"
其中,?
符号会匹配服务名,也就是说,当我们调用skynet.newservice("worker")
时,框架先去检查./service/worker.lua
脚本是否存在,发现不存在,于是接着检查./service/worker/init.lua
脚本,发现存在,于是执行./service/worker/init.lua
脚本作为worker
服务,当然,如果找不到,它就会去检查./skynet/service/worker.lua
是否存在了。
另外,newservice
的函数原型是newservice(name, ...)
,我们调用skynet.newservice
时可以透传一些参数给服务,比如我们上面的
-- main.lua脚本local worker1 = skynet.newservice("worker", "worker", 1)
第二个参数和第三个参数就会透传给init.lua
脚本,我们在init.lua
脚本中可以取出来缓存起来,如下:
-- service/worker/init.lua脚本-- 服务名
local worker_name = ""
-- 服务id
local worker_id = ""local function init(name, id)worker_name = nameworker_id = id
endinit(...)
7、在主服务中给打工服务发消息
打工服务中我们定义了两个消息:start_work
和stop_work
,现在我们在主服务中给打工服务发送消息,添加skynet.send
调用,如下:
local skynet = require "skynet"skynet.start(function ()skynet.error("[start main] hello world")-- 启动打工服务,其中第二个参数和第三个参数会透传给service/worker/init.lua脚本local worker1 = skynet.newservice("worker", "worker", 1)-- 开始工作skynet.send(worker1, "lua", "start_work")-- 主服务休息2秒,注意,这里是主服务休息2秒,并不会卡住worker服务skynet.sleep(200)-- 停止工作skynet.send(worker1, "lua", "stop_work")skynet.exit()
end)
我们再次执行命令
./skynet/skynet etc/config.node1
运行效果如下,可以看到打工服务开始工作了,2秒
赚了10
块钱~
8、封装服务类
假设我们现在要再写一个买猫粮的服务,这个时候,可以按照上面的打工服务写一个服务。事实上,每个服务都有一些通用的变量和方法,我们可以封装一个service
类,方便复用减少代码量。
我们在lualib
目录中新建一个service.lua
脚本,
service.lua
代码如下,
local skynet = require "skynet"
local cluster = require "skynet.cluster"-- 封装服务类
local M = {-- 服务名name = "",-- 服务idid = 0,-- 退出exit = nil,-- 初始化init = nil,-- 消息响应函数表resp = {},
}-- 输出堆栈
local function tracback(err)skynet.error(tostring(err))skynet.error(debug.traceback())
end-- 消息分发
local dispatch = function (session, address, cmd, ...)-- 从resp表中查找是否存在消息的响应函数local func = M.resp[cmd]if not func thenskynet.ret()returnend-- 调用响应函数local ret = table.pack(xpcall(func, tracback, address, ...))local isok = ret[1]if not isok thenskynet.ret()returnendskynet.retpack(table.unpack(ret, 2))
end-- 初始化
local function init()skynet.error(M.name .. " " .. M.id .. " init")skynet.dispatch("lua", dispatch)if M.init thenM.init()end
end-- 启动服务
function M.start(name, id, ...)M.name = nameM.id = tonumber(id)skynet.start(init)
endreturn M
9、重写打工服务
有了service
类,我们写服务的时候,只需要按下面这个模板写就可以了,
local skynet = require "skynet"
local s = require "service"s.init = function()-- 初始化
ends.resp.协议1 = function(source, ...)-- TODO
ends.resp.协议2 = function(source, ...)-- TODO
ends.start(...)
现在,我们重新写一下打工服务,改造后代码如下,我们后面新写服务也按照这个格式来写,统一,方便维护,
local skynet = require "skynet"
local s = require "service"s.money = 0
s.isworking = falses.update = function(frame)if s.isworking thens.money = s.money + 1skynet.error(s.name .. tostring(s.id) .. ", money: " .. tostring(s.money))end
ends.init = function ()skynet.fork(s.timer)
ends.timer = function()local stime = skynet.now()local frame = 0while true doframe = frame + 1local isok, err = pcall(s.update, frame)if not isok thenskynet.error(err)endlocal etime = skynet.now()local waittime = frame * 20 - (etime - stime)if waittime <= 0 thenwaittime = 2endskynet.sleep(waittime)end
ends.resp.start_work = function(source)s.isworking = true
ends.resp.stop_work = function(source)s.isworking = false
ends.start(...)
10、买猫粮服务
打工挣钱,有了钱我们就可以买猫粮啦,我们来写一个买猫粮的服务脚本。
我们在service
文件夹中新建一个buy
文件夹,如下,
进入buy
目录,然后创建一个init.lua
脚本,
代码如下:
-- service/buy/init.lua脚本local skynet = require "skynet"
local s = require "service"s.cat_food_price = 5
s.cat_food_cnt = 0s.resp.buy = function (source)-- 先扣费local left_money = skynet.call("worker1", "lua", "change_money", -s.cat_food_price)if left_money >= 0 thens.cat_food_cnt = s.cat_food_cnt + 1skynet.error("buy cat food ok, current cnt: " .. tostring(s.cat_food_cnt))return trueend-- 购买失败,把钱加回去skynet.error("buy failed, money not enough")skynet.call("worker1", "lua", "change_money", s.cat_food_price)return false
ends.start(...)
然后我们给打工服务加多一个消息,
-- service/worker/init.lua脚本s.resp.change_money = function(source, delta)s.money = s.money + deltareturn s.money
end
最后,在主服务中加上买猫粮服务的启动和消息发送,如下:
local skynet = require "skynet"skynet.start(function ()skynet.error("[start main] hello world")-- 启动打工服务local worker1 = skynet.newservice("worker", "worker", 1)-- 启动买猫粮服务local buy1 = skynet.newservice("buy", "buy", 1)-- 开始打工skynet.send(worker1, "lua", "start_work")skynet.sleep(200)-- 结束打工skynet.send(worker1, "lua", "stop_work")-- 买猫粮skynet.send(buy1, "lua", "buy")-- 退出主服务skynet.exit()
end)
好啦,现在我们测试一下,运行效果如下:
可以看到,最后买猫粮成功啦~
九、补充
1、网络模块
写服务端,肯定需要涉及到网络模块,需要用到skynet.socket
,案例:
-- main.lualocal skynet = require "skynet"
local socket = require "skynet.socket"local function on_connect(fd, addr)socket.start(fd)while true dolocal readdata = socket.read(fd)if readdata then-- TODO 处理消息-- 回应客户端,把readdata返回给客户端-- socket.write(fd, "server get data: " .. readdata)else-- 连接断开了socket.close(fd)endend
endskynet.start(function()local listenfd = socket.listen("0.0.0.0", 8888)socket.start(listenfd, on_connect)
end)
如果要写一个多人聊天功能的话,只需要把消息广播出去即可,例:
-- main.lualocal skynet = require "skynet"
local socket = require "skynet.socket"local clients = {}local function on_connect(fd, addr)clients[fd] = {}socket.start(fd)while true dolocal readdata = socket.read(fd)if readdata then-- 广播for client_fd, _ in pairs(clients) dosocket.write(client_fd, readdata)end else-- 连接断开了socket.close(fd)clients[fd] = nilendend
endskynet.start(function()local listenfd = socket.listen("0.0.0.0", 8888)socket.start(listenfd, on_connect)
end)
2、节点集群
我上面写的打工赚钱买猫粮,都在同一个节点中,
实际项目中,可能会开启多个节点,两个服务如果在同一个节点中,则通过skynet.send
或skynet.call
来传递消息,
注:
send
是发送消息,不会阻塞调用方;call
是阻塞调用。
如果在不同的节点中,则需要使用cluster.send
或cluster.call
,
注:
send
是发送消息,不会阻塞调用方;call
是阻塞调用。
对此,我们可以优化一下上文中的service.lua
,封装send
和call
方法,
-- lualib/service.lualocal cluster = require "skynet.cluster"...function M.call(node, srv, ...)local mynode = skynet.getenv("node")if node == mynode then return skynet.call(srv, "lua", ...)elsereturn cluster.call(node, srv, ...)end
endfunction M.send(node, srv, ...)local mynode = skynet.getenv("node")if node == mynode then return skynet.send(srv, "lua", ...)elsereturn cluster.send(node, srv, ...)end
end
我们在etc/config.node1
的末尾加多一行
node = "node1"
拷贝一份,重命名为config.node2
,把末尾一行改为
node = "node2"
然后,我们改下main.lua
脚本,让它在node1
节点开启打工服务,在node2
节点开启买猫粮节点,最终main.lua
脚本如下:
local skynet = require "skynet"
local cluster = require "skynet.cluster"
require "skynet.manager"skynet.start(function ()skynet.error("[start main] hello world")-- 集群配置cluster.reload({node1 = "127.0.0.1:7001",node2 = "127.0.0.1:7002",})local mynode = skynet.getenv("node")if "node1" == mynode then-- 启动集群节点cluster.open("node1")-- node1节点,开启打工服务local worker1 = skynet.newservice("worker", "worker", 1)skynet.name("worker1", worker1)skynet.send(worker1, "lua", "start_work")skynet.sleep(200)skynet.send(worker1, "lua", "stop_work")elseif "node2" == mynode then-- 启动集群节点cluster.open("node2")-- node2节点,开启买猫粮服务local buy1 = skynet.newservice("buy", "buy", 1)-- 请求买猫粮,买三次skynet.send(buy1, "lua", "buy")skynet.send(buy1, "lua", "buy")skynet.send(buy1, "lua", "buy")endskynet.exit()
end)
最后,我们改下buy/init.lua
脚本,把skynet.call
改成s.call
,如下:
local skynet = require "skynet"
local s = require "service"s.cat_food_price = 5
s.cat_food_cnt = 0s.resp.buy = function (source)local left_money = s.call("node1", "worker1", "change_money", -s.cat_food_price)if left_money >= 0 thens.cat_food_cnt = s.cat_food_cnt + 1skynet.error("buy cat food ok, current cnt: " .. tostring(s.cat_food_cnt))return trueendskynet.error("buy cat food failed, money not enough")s.call("node1", "worker1", "change_money", s.cat_food_price)return false
ends.start(...)
好了,现在我们先开启节点1
,在终端执行命令
./skynet/skynet etc/config.node1
可以看到节点1
开启了打工服务,赚了10块钱
,
现在我们开启节点2
,在终端执行命令
./skynet/skynet etc/config.node2
因为猫粮价格是5块钱
一包,所以只能买两次,执行结果如下,可以看到第三次买猫粮失败,因为钱不够了,
3、数据库模块
3.1、安装MySQL
在终端执行命令
sudo apt-get install mysql-server
执行过程中会弹出框让你输入MySQL
的root
账号的密码,如下,需要输入两次,
安装完毕后,执行mysql --version
,如果输出版本号,则说明安装成功了,
3.2、启动MySQL
在终端执行命令
service mysql start
此时会弹出一个框,注意此处输入Ubuntu
的开机密码,而不是MySQL
的root
账号密码哦,点击Authenticate
,
启动成功后,我们可以通过下面的命令看是否有mysql
的进程,
ps -axj |grep mysql
可以看到有mysql
进程,说明MySQL
服务已经成功启动了,
3.3、关闭MySQL
在终端执行命令
service mysql stop
此时会弹出一个框,此处输入Ubuntu
的开机密码,点击Authenticate
,
执行完毕后,同理,我们可以通过下面的命令查看是否已经没有mysql
进程了,
ps -axj |grep mysql
3.4、登录MySQL
在终端执行命令
mysql -h127.0.0.1 -uroot -p你的密码
如下,登录成功,
现在我们可以愉快地使用mysql
啦,执行show databases;
,查看所有的数据库
3.5、在skynet中操作数据库
我们先在终端手动创建一个数据库,
create database test_db;
如下,
选择test_db
,
接着再创建一个表,
create table users (id int not null auto_increment,name varchar(30) not null,primary key (id));
如下:
好了,现在我们在skynet
中来操作数据库吧,例:
local skynet = require "skynet"
local mysql = require "skynet.db.mysql"skynet.start(function ()skynet.error("[start main] hello world")local db = mysql.connect({host = "127.0.0.1",port = 3306,database = "test_db",user = "root",password = "123456",max_packet_size = 1024 * 1024,on_connect = nil})-- 插入数据local res = db:query("insert into users(name) values (\'linxinfa\')")-- 查询数据res = db:query('select * from users')for i, v in pairs(res) doprint(i, " " .. v.id .. " " .. v.name)endskynet.exit()
end)
我们执行两次,结果如下,可以看到,数据正常写入到数据库中了,成功~
十、完毕
好了,就先写这么多吧,关于skynet
还有很多很多内容,本文只是一个入门,希望可以帮助到新手同学~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~
【游戏开发实战】手把手教你从零跑一个Skynet,详细教程,含案例讲解(服务端 | Skynet | Ubuntu)相关推荐
- 手把手教你从零跑一个Skynet
一.前言 最近,我在搞服务端的skynet框架,看看以后自己做些作品(skynet框架服务端+Unity客户端).今天呢,我就先把skynet环境搞一下,讲讲流程,也方便想学习的同学,话不多说,我们开 ...
- 【游戏开发实战】教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)
文章目录 一.前言 二.ShaderGraph环境准备 三.模型准备:原神角色模型 四.实现思路 1.效果一的实现思路 2.效果二的实现思路 五.ShaderGraph具体实现 1.效果一 1.1.创 ...
- 《Unity 5.x游戏开发实战》一1.9 添加一个水平面
本节书摘来异步社区<Unity 5.x游戏开发实战>一书中的第1章,第1.9节,作者: Alan Thorn 译者: 李华峰 责编: 胡俊英,更多章节内容可以访问云栖社区"异步社 ...
- 手把手教你从零写一个日志框架
点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Sometimes you have to accept the ...
- 手把手教你搭建自己的Java Web(Android)项目(SpringMVC + Mybatis服务端,Html5 Web端, Android客户端实现)
刚工作不久的时候,学到了几点内容:软件产品挣的是大家的钱:内容整合是一个比较好的产品形态:可以通过广告的方式挣钱.但是就怀着这个想法,从去年12月份开始,一直想着自己搞点东西出来,即使最终没有人使用, ...
- 手把手教你搭建Pytest+Allure2.X环境详细教程 - 01
缺陷聚集的位置,执行时间表的外观以及许多其他方便的事情.魅力的模块化和可扩展性确保您始终可以微调某些东西,以使魅力更适合您. 一睹Allure风采 在展开Allure详述前,先上一份测试报告,报告主要 ...
- java最新版安装教程_手把手教你安装Eclipse最新版本的详细教程 (非常详细,非常实用)...
简介 首先声明此篇文章主要是针对测试菜鸟或者刚刚入门的小伙们或者童鞋们,大佬就没有必要往下看了. 写这篇文章的由来是因为后边要用这个工具,但是由于某些原因有部分小伙伴和童鞋们可能不会安装此工具,为了方 ...
- 【游戏开发实战】用Go语言写一个服务器,实现与Unity客户端通信(Golang | Unity | Socket | 通信 | 教程 | 附工程源码)
文章目录 一.前言 二.Go开发环境搭建(Windows系统) 1.安装Go命令行工具 2.创建GoWorkspace目录 3.配置GOPATH环境变量 4.配置GOPROXY代理 5.安装VSCod ...
- 今天用Java开发主机IP扫描神器,零基础Socket编程详细
目录 一.开发背景 二.准备工作 三.远程主机 IP 探测 四.核心算法 1.IP地址转化为十进制数 2.十进制数转化为IP地址 五.主机 IP 扫描神器界面 六.各功能代码及IP扫描演示 1.主机扫 ...
最新文章
- Numpy的广播机制详解(broadcasting)
- DAY7-Python学习笔记
- python解非线性规划问题讲析_python中线性规划中的单纯形法、scipy库与非线性规划求解问题...
- docker学习笔记-为容器配置重启策略
- c++ why can't class template hide its implementation in cpp file?
- Iirf安装配置(图文)
- GitHub提速方法大揭秘,10M速度使用无忧
- android tv nugat,GitHub - GongXunYoung/Android-tv-widget: Android tv,盒子,投影仪 控件
- 三星Galaxy Z Flip 3渲染图:更窄边框 铰链升级
- 012、JVM实战总结:案例实战:每日百万交易的支付系统,JVM栈内存与永久代大小又该如何设置?
- CentOS通过 liveCD 进入救援模式-重装 grub 修复损坏的
- 深入浅出GAMP算法(下):MMSE估计和AWGN场景
- java struts2教程_Struts2教程
- 基于RFID定位技术的文物仓库管理--新导智能
- wordpress 后台 文章管理列表 添加自定义栏
- nmos导通流向_MOS管知识详细说明!结构,原理,技术参数详解,一定要进来看下...
- ORCAD中occurences 和 instances的区别
- ERNIE(二妮儿)模型初探
- 日语流行口语极短句2
- 网易云商-七鱼客服使用感受