[Skynet] Skynet introduction example

Posted by nareshrevoori on Tue, 21 Dec 2021 11:43:45 +0100

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:

LuaAPIexplain
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:

LuaAPIexplain
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:

LuaAPIexplain
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:

LuaAPIexplain
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
  1. 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.
  2. mem: used to display the memory occupied by all Lua services. If a service occupies high memory, it can be optimized.
  3. stat: used to list the CPU time, total messages processed, message queue length (mqlen), number of pending requests (task s) of all Lua services.
  4. 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:

  1. Customer initiated deposit request (phase 1)
  2. agent relay request to bank (phase 2)
  3. 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

Topics: git lua