===== Туториал: многопользовательский режим (чат-комната) =====
Концепция логических машин платформы Лямбда-Мю 3 позволяет с легкостью решать задачи работы с многими пользователями:
* авторизация
* выделение ресурсов
* обработка запросов
* статистика
* пр.
Разберем взаимодействие платформы с несколькими клиентами.
==== Пример реализации ====
Чат-комната. Пользователи находятся в одной виртуальной комнате и обмениваются текстовыми сообщениями.
==== Репозитарий ====
Файловая структура проекта представлена ниже.
Необходимые скрипты туториала находятся в репозитарии [[https://gitlab.com/lambda-mu/lm.ver3/lm3.examples/chat|lm3.examples/chat]].
Чтобы запустить платформу скачайте бинарный файл и необходимые библиотеки из репозитария [[https://gitlab.com/lambda-mu/lm.ver3/lm3.engine.ce|lm3.engine.ce]] и скопируйте в корень проекта.
==== Исходные данные ====
Сторону клиента реализует Android-приложение с
* запрограммированным модулем связи с платформой
* протоколом связи, который рассмотрен ниже
==== Цель проекта ====
Разработка взаимодействия платформы Лямбда-Мю 3 с многими клиентами на конкретном примере.
==== Протокол запросов и ответов ====
Запрос:
{ Command = "DoAuthByName", Data = }
Ответ (всем пользователям):
{ Command = "OnSendAll", Data = "Member connected: ".. }
Запрос:
{ Command = "DoSendAll", Data = }
Ответ (всем пользователям):
{ Command = "OnSendAll", Data = "Member: ".. }
==== Файловая структура проекта ====
{{ :lm3:ce:chat_struct.png?direct |}}
==== Взаимосвязи ====
Объекты основной логической машины:
* TCP-server server: прием и передача данных клиенту
* DataPack packer: распаковка и запаковка данных клиенту
* Auth: авторизация клиента; вызов создания ЛМ; связь клиента с соответствующей ЛМ
* Сlients (наследник LMList): управление ЛМ
Диаграмма связей:
{{ :lm3:ce:chat_client.png?direct |}}
На логических машинах запросы обрабатываются следующим образом:
* команда авторизации DoAuthByName присваивает переменной userName значение и вызывает OnSendAll
* команда сообщения DoSend вызывает OnSendAll, если пользователь авторизирован
==== Скрипты ====
**main / start.lua**
lm3:Log("Starting server")
lm3:LoadLibrary( {Alias = "sys", FileName = "lm3system"} )
lm3:Include("main_lib.lua")
lm3:CreateLObject( { LibAlias = "sys", Type = "TCPServer", Name = "server" } )
server:SetAttr("SocCount", lmconf.MaxClientCount)
lm3:CreateLObject( { LibAlias = "sys", Type = "DataPack", Name = "packer" } )
packer:SetAttr("BufCount", lmconf.MaxClientCount)
lm3:CreateObject( { Type = "LMList", Name = "clients" } )
init_Auth("Auth")
lm3:AddHandler("OnStart", function(o, ev)
-- проверка конфигурации ЛМ
if clients:DoPrepare(lmconf.BasePath.."client_lm/config.lua") == false then
lm3:Log("client_lm prepare failed")
end
end)
Auth:AddHandler("OnReply", function(o, ev)
lm3:Log("Auth: OnReply: tag: "..tostring(ev.Tag).." : "..lm3:DataToString(ev.Data))
end)
-- связи объектов
server:Connect("OnConnect", "Auth", "DoConnect")
server:Connect("OnDisconnect", "Auth", "DoDisconnect")
server:Connect("OnIncoming", "packer", "DoUnpack")
packer:Connect("OnPack", "server", "DoSend")
packer:Connect("OnUnpack", "Auth", "DoIncoming")
Auth:Connect("OnReply", "packer", "DoPackTagged")
Auth:Connect("OnStartClientLM", "clients", "DoStart")
Auth:Connect("OnStopClientLM", "clients", "DoStop")
Auth:Connect("OnClientEvent", "clients", "DoEvent")
clients:Connect("OnEvent", "Auth", "DoClientEvent")
**main / main_lib.lua**
-- объект Auth
--
function init_Auth(_name)
local obj = lm3:CreateObject( { Type = "Base", Name = _name } )
obj.ClientList = {}
for i = 1, lmconf.MaxClientCount do
obj.ClientList[i] = {
isConnected = false,
LMName = "",
}
end
function obj.TagFromLMName(_name)
return tonumber(_name:sub(5))
end
-- подключение нового клиента
-- старт ЛМ
obj:AddInEvent("DoConnect", "int", function(o, ev)
lm3:Log("New client connected: "..tostring(ev))
obj.ClientList[ev].isConnected = true
obj.ClientList[ev].LMName = "clm_"..tostring(ev)
lm3:Log("Start client LM:")
if obj:OnStartClientLM( { Name = obj.ClientList[ev].LMName } ) == true then
lm3:Log("Start client LM complete.")
else
lm3:Log("ERROR: Start client LM failed.")
obj.ClientList[ev].isConnected = false
end
end)
-- отключение клиента
-- остановка ЛМ
obj:AddInEvent("DoDisconnect", "int", function(o,ev)
lm3:Log("Client disconnected: "..tostring(ev))
obj:OnStopClientLM(obj.ClientList[ev].LMName)
obj.ClientList[ev].isConnected = false
end)
-- распределение данных от клиента
obj:AddInEvent("DoIncoming", "table[Tag:int,Data:table[Command:string,Data:string]]", function(o, ev)
if obj.ClientList[ev.Tag].isConnected ~= true then
lm3:Log("ERROR: Incoming data from not connected client: "..tostring(ev.Tag))
else
lm3:Log("Incoming data from client with tag: "..tostring(ev.Tag).." : "..lm3:DataToString(ev.Data))
obj:OnClientEvent({ LMName = obj.ClientList[ev.Tag].LMName, EventName = "OnExtEvent", Data = ev.Data })
end
end)
-- обработка данных от ЛМ
obj:AddInEvent("DoClientEvent", "table[LMName:string,EventName:string,Data:table[Command:string,Data:string]]", function(o, ev)
if ev.Data.Command == "OnSendAll" then
for i = 1, lmconf.MaxClientCount do
if obj.ClientList[i].isConnected then
obj:OnReply( { Tag = i, Data = ev.Data } )
end
end
else
local iClient = obj.TagFromLMName(ev.LMName)
obj:OnReply( { Tag = iClient, Data = ev.Data } )
end
end)
-- отправить данные клиенту
obj:AddOutEvent("OnReply", "table[Tag:int,Data:table[Command:string,Data:string]]")
-- отправить данные в ЛМ
obj:AddOutProc("OnClientEvent", "table[LMName:string,EventName:string,Data:any]", "bool")
obj:AddOutProc("OnStartClientLM", "table[Name:string]", "bool")
obj:AddOutProc("OnStopClientLM", "string", "bool")
return obj
end
**main / client_lm / start.lua**
lm3:LoadLibrary( {Alias = "sys", FileName = "lm3system"} )
-- объект Timer для alive сообщение
lm3:CreateObject( { Type = "Timer", Name = "timer" } )
timer:SetAttr("Interval", 30*1000)
lm3:AddHandler("OnStart", function(o, ev)
timer:DoEnable(true)
end)
userName = nil
-- обработчик ивента с основной логической машины
lm3:AddHandler("OnExtEvent", function(o, ev)
if ev.Command == "DoAuthByName" then
userName = ev.Data
lm3:DoExtEvent( { Command = "OnSendAll", Data = "Member connected: "..userName } )
elseif ev.Command == "DoSendAll" then
if userName == nil then
lm3:Log("Error: userName == nil")
else
lm3:DoExtEvent( { Command = "OnSendAll", Data = "Member "..userName..": "..ev.Data } )
end
end
end)
timer:AddHandler("OnTick", function(o, ev)
--lm3:Log("OnTick: send Alive")
lm3:DoExtEvent( { Command = "Alive", Data = "" } )
end)
==== Логи ====
**[client 1]**
-- получение уникального имени
2020-11-27 19:02:14.044 1940-1940/com.example.lm3_client D/lm3: application: onCreate: uniqueName: 3360d
2020-11-27 19:02:14.060 1940-1984/com.example.lm3_client D/lm3: application: setNetworkStatus: 2
-- подключение к серверу
2020-11-27 19:02:14.060 1940-1984/com.example.lm3_client D/lm3: connect: startServerConnection
2020-11-27 19:02:14.061 1940-1985/com.example.lm3_client D/lm3: connect: start
2020-11-27 19:02:14.084 1940-1940/com.example.lm3_client D/lm3: activity: main: OnCreate
2020-11-27 19:02:14.188 1940-1985/com.example.lm3_client D/lm3: connect: setServerConnection: true
2020-11-27 19:02:14.189 1940-1985/com.example.lm3_client D/lm3: connect: done
2020-11-27 19:02:14.189 1940-1985/com.example.lm3_client D/lm3: connect: end
2020-11-27 19:02:14.189 1940-2027/com.example.lm3_client D/lm3: connect: start listen
2020-11-27 19:02:14.234 1940-1940/com.example.lm3_client D/lm3: activity: main: onResume
-- авторизация
2020-11-27 19:02:15.189 1940-2027/com.example.lm3_client D/lm3: listen: DoAuthByName: 3360d
2020-11-27 19:02:15.190 1940-2027/com.example.lm3_client D/lm3: listen: serverSend
2020-11-27 19:02:15.252 1940-2027/com.example.lm3_client D/lm3: listen: OnSendAll: Member connected: 3360d
2020-11-27 19:02:24.392 1940-1940/com.example.lm3_client D/lm3: activity: main:
-- отправка сообщения
button OnClick: message: Hello
2020-11-27 19:02:24.392 1940-1940/com.example.lm3_client D/lm3: listen: DoSendAll: Hello
2020-11-27 19:02:24.392 1940-1940/com.example.lm3_client D/lm3: listen: serverSend
2020-11-27 19:02:24.424 1940-2027/com.example.lm3_client D/lm3: listen: OnSendAll: Member 3360d: Hello
2020-11-27 19:02:34.153 1940-2027/com.example.lm3_client D/lm3: listen: OnSendAll: Member 65c8c: Hi
**[client 2]**
-- получение уникального имени
2020-11-27 19:01:55.396 14523-14523/com.example.lm3_client D/lm3: application: onCreate: uniqueName: 65c8c
2020-11-27 19:01:55.527 14523-14556/com.example.lm3_client D/lm3: application: setNetworkStatus: 2
-- подключение к серверу
2020-11-27 19:01:55.527 14523-14556/com.example.lm3_client D/lm3: connect: startServerConnection
2020-11-27 19:01:55.529 14523-14557/com.example.lm3_client D/lm3: connect: start
2020-11-27 19:01:55.544 14523-14557/com.example.lm3_client D/lm3: connect: setServerConnection: true
2020-11-27 19:01:55.545 14523-14557/com.example.lm3_client D/lm3: connect: done
2020-11-27 19:01:55.545 14523-14557/com.example.lm3_client D/lm3: connect: end
2020-11-27 19:01:55.545 14523-14559/com.example.lm3_client D/lm3: connect: start listen
2020-11-27 19:01:55.573 14523-14523/com.example.lm3_client D/lm3: activity: main: OnCreate
2020-11-27 19:01:55.716 14523-14523/com.example.lm3_client D/lm3: activity: main: onResume
-- авторизация
2020-11-27 19:01:56.546 14523-14559/com.example.lm3_client D/lm3: listen: DoAuthByName: 65c8c
2020-11-27 19:01:56.546 14523-14559/com.example.lm3_client D/lm3: listen: serverSend
2020-11-27 19:01:56.580 14523-14559/com.example.lm3_client D/lm3: listen: OnSendAll: Member connected: 65c8c
2020-11-27 19:02:14.398 14523-14559/com.example.lm3_client D/lm3: listen: OnSendAll: Member connected: 3360d
2020-11-27 19:02:23.602 14523-14559/com.example.lm3_client D/lm3: listen: OnSendAll: Member 3360d: Hello
-- отправка сообщения
2020-11-27 19:02:33.103 14523-14523/com.example.lm3_client D/lm3: activity: main: button OnClick: message: Hi
2020-11-27 19:02:33.103 14523-14523/com.example.lm3_client D/lm3: listen: DoSendAll: Hi
2020-11-27 19:02:33.103 14523-14523/com.example.lm3_client D/lm3: listen: serverSend
2020-11-27 19:02:33.139 14523-14559/com.example.lm3_client D/lm3: listen: OnSendAll: Member 65c8c: Hi
**[server] LM_main.log**
[2020-11-27 19:01:30][INFO:LMInit][Start logic machine v3.3(Build:555) ----------------------------------]
[2020-11-27 19:01:30][LUA:LMCore][Starting server]
[2020-11-27 19:01:30][TRACE:LMInit][Create library object: server]
[2020-11-27 19:01:30][TRACE:LMInit][Create library object: packer]
[2020-11-27 19:01:30][TRACE:LMInit][Create object: clients]
[2020-11-27 19:01:30][TRACE:LMInit][Create object: Auth]
[2020-11-27 19:01:30][INFO:LMInit][Initialization complete.]
[2020-11-27 19:01:48][LUA:LMCore][New client connected: 1]
[2020-11-27 19:01:48][LUA:LMCore][Start client LM:]
[2020-11-27 19:01:48][LUA:LMCore][Start client LM complete.]
-- авторизация первого клиента
[2020-11-27 19:01:49][LUA:LMCore][Incoming data from client with tag: 1 : [Table:2]
"Command" = [String] "DoAuthByName"
"Data" = [String] "65c8c"]
[2020-11-27 19:01:49][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member connected: 65c8c"]
[2020-11-27 19:02:06][LUA:LMCore][New client connected: 2]
[2020-11-27 19:02:06][LUA:LMCore][Start client LM:]
[2020-11-27 19:02:06][LUA:LMCore][Start client LM complete.]
-- авторизация второго клиента
[2020-11-27 19:02:07][LUA:LMCore][Incoming data from client with tag: 2 : [Table:2]
"Command" = [String] "DoAuthByName"
"Data" = [String] "3360d"]
[2020-11-27 19:02:07][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member connected: 3360d"]
[2020-11-27 19:02:07][LUA:LMCore][Auth: OnReply: tag: 2 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member connected: 3360d"]
-- сообщение от первого клиента
[2020-11-27 19:02:16][LUA:LMCore][Incoming data from client with tag: 2 : [Table:2]
"Command" = [String] "DoSendAll"
"Data" = [String] "Hello"]
[2020-11-27 19:02:16][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member 3360d: Hello"]
[2020-11-27 19:02:16][LUA:LMCore][Auth: OnReply: tag: 2 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member 3360d: Hello"]
[2020-11-27 19:02:18][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "Alive"
"Data" = [String] ""]
-- сообщение от второго клиента
[2020-11-27 19:02:26][LUA:LMCore][Incoming data from client with tag: 1 : [Table:2]
"Command" = [String] "DoSendAll"
"Data" = [String] "Hi"]
[2020-11-27 19:02:26][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member 65c8c: Hi"]
[2020-11-27 19:02:26][LUA:LMCore][Auth: OnReply: tag: 2 : [Table:2]
"Command" = [String] "OnSendAll"
"Data" = [String] "Member 65c8c: Hi"]
[2020-11-27 19:02:36][LUA:LMCore][Auth: OnReply: tag: 2 : [Table:2]
"Command" = [String] "Alive"
"Data" = [String] ""]
[2020-11-27 19:02:48][LUA:LMCore][Auth: OnReply: tag: 1 : [Table:2]
"Command" = [String] "Alive"
"Data" = [String] ""]
[2020-11-27 19:03:06][INFO:LMCore][Stop logic machine.]