1, Download and edit
CentOS7.7. Install corresponding software:
yum install git #git for downloading source code yum install gcc #For compiling source code yun install autoconf #For compiling source code yum install readline-devel #It will be used to compile Lua
Download skynet source code:
git clone https://github.com/cloudwu/skynet.git
compile:
cd skynet #Enter the skynet directory make linux #compile
Executing the command "make linux" will automatically download the third-party library "jemalloc" to the / skynet/3rd / directory. If the download process is too slow or fails, you can go there by yourself https://github.com/jemalloc/jemalloc/ Download it, then unzip it to the / skynet/3rd / directory, and then re execute the instruction "make linux" to compile.
Note: the GCC version needs to be greater than 4.9 (because the new features of c++11 are used), so lower versions such as Ubuntu 14 need to download versions above gcc-4.9, and then replace GCC and g + + soft connect to gcc-4.9 and g++-4.9.
2, Run analysis
To start skynet, you need to specify a configuration file, for example:
./skynet examples/config
The operation results are as follows:
[:01000002] LAUNCH snlua bootstrap [:01000003] LAUNCH snlua launcher [:01000004] LAUNCH snlua cmaster [:01000004] master listen socket 0.0.0.0:2013 [:01000005] LAUNCH snlua cslave [:01000005] slave connect to master 127.0.0.1:2013 [:01000004] connect from 127.0.0.1:40324 4 [:01000006] LAUNCH harbor 1 16777221 [:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526 [:01000005] Waiting for 0 harbors [:01000005] Shakehand ready [:01000007] LAUNCH snlua datacenterd [:01000008] LAUNCH snlua service_mgr [:01000009] LAUNCH snlua main [:01000009] Server start [:0100000a] LAUNCH snlua protoloader [:0100000b] LAUNCH snlua console [:0100000c] LAUNCH snlua debug_console 8000 [:0100000c] Start debug console at 127.0.0.1:8000 [:0100000d] LAUNCH snlua simpledb [:0100000e] LAUNCH snlua watchdog [:0100000f] LAUNCH snlua gate [:0100000f] Listen on 0.0.0.0:8888 [:01000009] Watchdog listen on 8888 [:01000009] KILL self [:01000002] KILL self
The output shows that skynet started bootstrap, launcher, cmaster, cslave, harbor, datacenter and service in turn_ mgr,main,protoloader,debug_console, simpledb, watchdog, gate and other services. The left [: 0100000x] represents which service generated the message. As you can see, the gate service monitors port 8888. debug_ The console service monitors 8000 ports.
skynet also contains supporting client examples, which are located in "examples/client.lua". Start with the following statement:
lua examples/client.lua
After skynet is compiled, it will include Lua program, which is located in "3rd/lua/lua". If Lua is not installed on the server, or the version of lua is less than 5.3, you can start the client with the following command:
./3rd/lua/lua examples/client.lua
3, Understanding skynet
Each skynet process (operating system process) is called a node, and each node can start thousands of services.
Different nodes can be deployed on different physical machines to provide distributed clustering capabilities.
3.1 description of configuration file:
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"
config:
include "config.path" -- preload = "./examples/preload.lua" -- run preload.lua before every lua service run thread = 8 -- How many threads are started logpath = "." harbor = 0 -- skynet Early versions provided“ master/slave"The cluster model later provided a more applicable model“ cluster"Cluster mode. Because“ master/slave"It is not complete, so it is not recommended. Just set it to 0. address = "127.0.0.1:2526" master = "127.0.0.1:2013" start = "main" -- main script,Main service entry, path is config The files are in the same directory bootstrap = "snlua bootstrap" -- The service for bootstrap,(Fixed) first service started standalone = "0.0.0.0:2013" -- snax_interface_g = "snax_g" cpath = root.."cservice/?.so" -- use c Location of service modules written -- daemon = "./skynet.pid" -- Daemon logger = nil --Save output log to logger Item in the specified file
3.2 directory structure:
- 3rd: store third-party codes, such as Lua, jemalloc, lpeg, etc.
- cservice: store built-in services written in c language, such as gate, harbor, snlua, etc.
- Examples: examples.
- luaclib: program library written in c language, such as bson parsing, md5 parsing, etc
- lualib: a library written in Lua.
- Lualib SRC: the source code of the library file in the luaclib directory.
- service: contains some services built into skynet. Services written in Lua.
- Service SRC: the source code of the program generation in the cservice directory.
- skynet SRC: skynet core code written in c.
- Test: test code.
4, Skynet API
The 8 most important API s in skynet:
LuaAPI | explain |
---|---|
skynet.newservice(name, ...) | Start a new service named (type) name and return the address of the new service. Services in the same node will have a unique address. For example, local ping1 = newservice("Ping") means to start a service of Ping type and store the address in ping1. |
skynet.start(func) | Initialize the service with func function. When writing a service, you will write a sentence Skynet Start and write some initialization code in func. |
skynet.dispatch(type, func) | Set the handler func for messages of type. skynet supports multiple message types. Since the message type between Lua services is "Lua", it is temporarily fixed as "Lua". Func refers to the processing function after receiving a message. When a service receives a new message, skynet will open a new process and call it. The form of func is function (session, source, cmd,...) end. The parameter session represents the unique id of the message. Source stands for the message source and refers to the service address where the message is sent. cmd represents the message name. " "Is a variable parameter, and the content is specified by the sender's skynet.send or skynet.call. The following fixed forms are generally used to write services. Indicates that Skynet is written as an anonymous function The parameter func of start is called and dispatch is called in func. skynet.start(function() skynet.dispatch("lua", function (parameter omitted)) ... end end) |
skynet.send(addr, type, cmd, ...) | Send a message of type cmd to the service with address addr. The sender uses Skynet Send sends a message, and the receiver uses Skynet Dispatch receives messages, and their parameters correspond to each other. If it is used for inter service communication, the type is generally fixed as "lua". For example, use the following statement to send a message to service ping1 skynet.send(ping1, "lua", "ping", 1, 2) In the dispatch callback of ping1, the parameter values are as follows function(session, source, cmd, p1, p2, p3) –cmd = "ping" –p1=1 –p2=2 –p3=nil end |
skynet.call(addr, type, cmd, ...) | Send a message of type cmd to the service with address addr, and wait for the other party's response. skynet.call is a blocking method. |
skynet.exit() | End current service |
skynet.self() | Returns the address of the current service |
skynet.error(msg) | Send a message to the log service, that is, print the log |
API for handling network messages:
LuaAPI | explain |
---|---|
socket.listen(host, port) | Listen to the client connection, where host represents the IP address and port represents the port. It will return the ID of the listening socket for example local listenfd = socket.listen("0.0.0.0", 8888) On behalf of listening to 8888 port, "0.0 0.0 "means that the IP of the client is not limited, and listenfd stores the ID of the listening socket. |
socket.start(fd, connect) | When a new client connects, the callback method connect will be called. The parameter fd is socket The ID returned by listen; The callback method connect has two parameters. The first parameter represents the flag of the new connection and the second parameter represents the address of the new connection In addition, after connect obtains a new connection, it will not receive its data immediately. You need to call socket again Start (FD) will start receiving Generally, the complete writing method of enabling monitoring is function connect(fd, addr) socket.start(fd) print(fd..." connected addr:" ...addr) end |
socket.read(fd) | Read data from the specified socket, which is a blocking method |
socket.write(fd, data) | Put the data into the write queue, and the skynet framework will send it when the socket is writable |
socket.close(fd) | Close the connection, which is a blocking method |
API for connecting to MySQL database:
LuaAPI | explain |
---|---|
mysql.connect(args) | Connect to the database. The parameter args is a Lua table, which contains database address, user name, password and other information. The API will return database objects for subsequent operations for example local db = mysql.connect({ host = "127.0.0.1", port = 3306, database = "message_board", user = "root", password = "123456", max_packet_size = 1024*1024, on_connect = nil }) The representative connection address is 127.0 0.1, port 3306, database name message_board, MySQL database with user name of root and password of 123456 |
db:query(sql) | Execute SQL statements. db stands for MySQL The object returned by connect. The SQL parameter represents the SQL statement For example, local res = db:query("select * from msgs") Represents the query database table msgs, and the return value res represents the query result db:query("insert into msgs(text) values('hello')") Represents inserting the string "hello" into the text field of the msgs table |
cluster API:
LuaAPI | explain |
---|---|
cluster.reload(cfg) | Let this node (reload) the node configuration. The parameter cfg is a Lua table indicating the address of each node in the cluster for example cluster.reload({ node1 = "127.0.0.1:7001" node2 = "127.0.0.1:7002" }) Indicate the two nodes named "node1" and "node2" in the cluster. Node1 listens to the local 7001 port and node2 listens to the local 7002 port. |
cluster.open(node) | Start the node. Node 1 needs to call cluster Open ("node1"), node 2 needs to call cluster Open ("node2"), so that they can know that they are a cluster Which item in reload and enable the corresponding port listening. |
cluster.send(node, address, cmd, ...) | Push a message to the service named node and address, where the parameter cmd represents the message name |
cluster.call(node, address, cmd, ...) | It works with cluster The function of send is similar. Both push messages to another service. The difference is that it is a blocking method and will wait for the other party's response. All messages sent through the cluster are of "lua" type and do not need to be specified |
cluster.proxy(node, address) | Create a local proxy service for the service on the remote node, which will return the proxy object, and then use Skynet send,skynet.call the agent |
5, skynet instance program
4.1 PingPong
examples/Pmain.lua:
local skynet = require "skynet" skynet.start(function() --skynet.start with function Initialize service skynet.error("[Pmain] start") local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping") skynet.send(ping1, "lua", "start", ping2) skynet.exit() end)
examples/ping.lua:
local skynet = require "skynet" local CMD = {} 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 skynet.start(function() skynet.dispatch("lua", function(session, source, cmd , ...) --skynet.dispatch Specify the processing method of parameter type I message (here is“ lua"Type, Lua The message type between services is“ lua"),Immediate processing lua Messages between services local f = assert(CMD[cmd]) f(source, ...) end) end)
examples/Pconfig:
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 = "Pmain" -- 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"
function:
./skynet examples/Pconfig
4.2 chat room (upgrade of Echo and broadcast the received information to all online players)
examples/Pmain.lua:
local skynet = require "skynet" local socket = require "skynet.socket" local clients = {} function connect(fd, addr) --Enable connection and start waiting to receive client messages print(fd .. " connected addr:" .. addr) socket.start(fd) clients[fd] = {} --Message processing while true do local readdata = socket.read(fd) --Using coprocessing to realize blocking mode --Normal reception if readdata ~= nil then print(fd .. " recv " .. readdata) for k,v in pairs(clients) do --radio broadcast socket.write(k, readdata) end --Disconnect else print(fd .. " close ") socket.close(fd) clients[fd] = nil end end end skynet.start(function() local listenfd = socket.listen("0.0.0.0", 8888) --Monitor all ip,Port 8888 socket.start(listenfd, connect) --When a new client initiates a connection, conncet Method will be called. end)
examples/Pconfig:
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 = "Pmain" -- 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"
function:
./skynet examples/Pconfig
4.3 make a message board and use the database
To install mysql on ubuntu linux:
sudo apt-get install mysql-server-5.6
ubuntu linux logs in to the mysql service as root, - p is the password:
mysql -u root -p
Display the libraries in the mysql database:
show databases;
Exit mysql:
exit
examples/Pmain.lua:
local skynet = require "skynet" local mysql = require "skynet.db.mysql" local socket = require "skynet.socket" local clients = {} local db = nil function connect(fd, addr) --Enable connection and start waiting to receive client messages print(fd .. " connected addr:" .. addr) socket.start(fd) clients[fd] = {} --Message processing while true do local readdata = socket.read(fd) --Using coprocessing to realize blocking mode --Normal reception if readdata ~= nil then if readdata == "get\r\n" then local res = db:query("select * from msgs") --implement SQL sentence. for k,v in pairs(res) do socket.write(fd, v.id .. " " .. v.text .. "\r\n") end --Leaving a message. else local data = string.match(readdata, "set (.-)\r\n") db:query("insert into msgs(text) values(\'"..data.."\')") --implement SQL sentence. end --Disconnect else print(fd .. " close ") socket.close(fd) clients[fd] = nil end end end skynet.start(function() --Connect to database db = mysql.connect({ host="192.168.184.130", --ip port=3306, --port database="message_board", --Database used user="root", --user name password="123456", --password max_packet_size=1024*1024, --Maximum packet size on_connect=nil }) --Network monitoring local listenfd = socket.listen("0.0.0.0", 8888) socket.start(listenfd, connect) end)
examples/Pconfig:
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 = "Pmain" -- 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"
function:
./skynet examples/Pconfig
Run client:
telnet 127.0.0.1 8888 //Connect to the local ip port 8888, that is, connect to the port service opened by the skynet process get set lrh
4.4 monitoring service status
skynet comes with a debugging console service debug_console, after starting it, you can view the internal status of the node.
examples/Pmain.lua:
local skynet = require "skynet" skynet.start(function() --skynet.start with function Initialize service skynet.error("[Pmain] start") skynet.newservice("debug_console", 8000) local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping") local ping3 = skynet.newservice("ping") skynet.send(ping1, "lua", "start", ping2) skynet.send(ping2, "lua", "start", ping3) skynet.exit() end)
examples/ping.lua:
local skynet = require "skynet" local CMD = {} 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 skynet.start(function() skynet.dispatch("lua", function(session, source, cmd , ...) --skynet.dispatch Specify the processing method of parameter type I message (here is“ lua"Type, Lua The message type between services is“ lua"),Immediate processing lua Messages between services local f = assert(CMD[cmd]) f(source, ...) end) end)
function:
./skynet examples/Pconfig
Run client:
telnet 127.0.0.1 8000
- List: lists all services started by skynet and the parameters for starting the service. In the process of programming, if you suspect that some services are not started successfully, you can use the list command to check.
- mem: used to display the memory occupied by all Lua services. If a service occupies high memory, it can be optimized.
- stat: used to list the CPU time, total messages processed, message queue length (mqlen), number of pending requests (task s) of all Lua services.
- netstat: used to list an overview of network connections.
4.5 establishing distributed system using node cluster
The following figure shows skynet's cluster mode. In this mode, the user needs to configure cluster listening ports (7001 and 7002 on the way) for each node. skynet will automatically start gate, cluster and other services to handle inter node communication functions.
If ping1 in Figure 2-23 wants to send a message to another node ping3, the process is: node 1 first establishes a TCP connection with node 2, the message is transmitted to the cluster service of node 2 through skynet, and then the cluster forwards it to ping3 in the node.
Node configuration:
examples/.Pconfig.c1 The new contents in the are as follows: node = "node1" examples/Pconfig.c2 The new contents in the are as follows: node = "node2"
Code implementation:
examples/Pmain.lua:
local skynet = require "skynet" local cluster = require "skynet.cluster" require "skynet.manager" skynet.start(function () cluster.reload({ node1 = "127.0.0.1:7001", node2 = "127.0.0.1:7002" }) local mynode = skynet.getenv("node") --obtain./skynet Start configured node variable if mynode == "node1" then cluster.open("node1") local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping") skynet.send(ping1, "lua", "start", "node2", "pong") skynet.send(ping2, "lua", "start", "node2", "pong") --Using a proxy, you can then treat it as a local service local pong = cluster.proxy("node2", "pong") skynet.send(pong, "lua", "ping", "node1", "ping1", 1) skynet.send(pong, "lua", "ping", "node1", "ping2", 1) elseif mynode == "node2" then cluster.open("node2") local ping3 = skynet.newservice("ping") skynet.name("pong", ping3) --Modify service name end end)
examples/ping.lua:
local skynet = require "skynet" local cluster = require "skynet.cluster" local mynode = skynet.getenv("node") --obtain./skynet Start configured node variable local CMD = {} function CMD.ping(source, source_node, source_srv, count) local id = skynet.self() skynet.error("["..id.."] recv ping count=" .. count) skynet.sleep(100) cluster.send(source_node, source_srv, "ping", mynode, skynet.self(), count+1) end function CMD.start(source, target_node, target) cluster.send(target_node, target, "ping", mynode, skynet.self(), 1) end skynet.start(function () skynet.dispatch("lua", function(session, source, cmd , ...) --skynet.dispatch Specify the processing method of parameter type I message (here is“ lua"Type, Lua The message type between services is“ lua"),Immediate processing lua Messages between services local f = assert(CMD[cmd]) f(source, ...) end) end)
function:
Physical machine 1: ./skynet examples/Pconfig.c1 Physical machine 2: ./skynet examples/Pconfig.c2
6, Precautions for using skynet
skynet's biggest feature is to "make full use of the processing power of multi-core CPU on the same machine", but the resulting timing problem deserves special attention.
5.1 role of collaborative process
When the Skynet service receives a message, it will create a collaboration, In the association process, the message processing method (the callback method set up by skynet.dispatch) is run. This means that if the blocking API (such as skynet.call, skynet.sleep, skynet.read) is called in the message processing method, the service will not be jammed (only the processing of the message's Association is jammed), the execution efficiency can be improved, but the execution sequence of the program will not be guaranteed.
As shown in Figure 2-36, there are multiple messages in the message queue of a service. The processing function of the first message is OnMsg1 and the second is OnMsg2. OnMsg1 called the blocking method skynet,sleep. Although the program will call OnMsg1, OnMsg2. However, when the blocking function is executed, the coroutine will hang. The actual execution order may be "statement 1, Skynet. 1" shown on the right in Figure 2-36 Sleep, statement 3, statement 4, statement 2 ".
5.2 Bug deducting gold coins
Assuming that the game has a "deposit" function, players can deposit a certain amount of gold coins in the bank to obtain interest. Relevant services are shown in figure 2-37. The agent service represents the role controlled by the player, and the bank represents the bank.
The deposit process is as follows:
- Customer initiated deposit request (phase 1)
- agent relay request to bank (phase 2)
- bank returns the result of the operation (phase 3)
Writing method 1 (with Bug):
local coin = 20 --Number of gold coins on the character function CMD.deposit(source) if coin < 20 then --Suppose you save 20 gold coins at a time return end local isok = skynet.call(bank, "lua", "deposit", 20) if isok then coin = coin -20 end end
There is a possibility that the player quickly clicks the deposit button twice, and the message sequence is executed in the order of ① ② ③ in figure 2-37. If there are only 20 gold coins left on the character, there are still 20 gold coins left in the first operation and 20 gold coins left in the second operation. Both operations are successful. The player deposits a total of 40 gold coins and the remaining "- 20" gold coins, which is obviously unreasonable.
Write 2 (fix bug s):
function CMD.deposit(source) if coin < 20 then --Suppose you save 20 gold coins at a time return end coin = coin - 20 local isok = skynet.call(bank, "lua", "deposit", 20) if isok then coin = coin + 20 end end