Я люблю Lua. I love Lua.

Передача данных по сети с LuaSocket

Автор Валерий Блажнов.

Иногда требуется некоторую прикладную работу выполнить удаленно,
а придумывать для этого прикладной сетевой протокол не хочется.
В данном примере демонстрируется организация сетевого соединения
и удаленное выполнение прикладной функции с передачей по сети
исходных данных и результатов прямо на языке Lua.

В примере используется слегка модифицированный модуль Serializer
из Шага 8. Модифицированный модуль сериализует таблицу в строку,
а не в файл.

Для установления сетевого соединения используется известная
библиотека LuaSocket.
Для наглядности продемонстрируем сразу скрипты сервера и клиента.
Сервер:

local base = _G
module('Server')

-- Для сериализации таблиц подключаем модуль Serializer
serializer = base.require("Serializer")

-- Организуем прием соединения
local socket = base.require("socket")
host = host or "*"
port = port or 8080
if arg then
	host = arg[1] or host
	port = arg[2] or port
end
base.print("Binding to host '" ..host.. "' and port " ..port.. "...")
s = base.assert(socket.bind(host, port))
i, p   = s:getsockname()
base.assert(i, p)
base.print("Waiting connection from client on " .. i .. ":" .. p .. "...")
c = base.assert(s:accept())
base.print("Connected!")

-- Пример прикладной сервисной функции,
-- к которой будет удаленно обращаться клиент.
function SomeService(a, b)
    local tbl = {}
    tbl.a = a
    tbl.b = b
    tbl.result = a + b
    local rc = 0
    return rc, tbl
end

-- Сервер просто интерпретирует входной скрипт
-- и возвращает выходной скрипт.
function DoScript(strin)
	local fstrin = base.loadstring(strin)
	base.assert(fstrin)
	return fstrin()
end

-- Принимаем запрос
strin, e = c:receive()
while not e do
    -- Отладочная печать
	base.print("strin = "..strin.."\n")
    -- Интерпретируем запрос
    strout = DoScript(strin)
    if strout then
        -- Отладочная печать
        base.print("strout = "..strout.."\n")
        -- Возвращаем ответный скрипт
        base.assert(c:send(strout .. "\n"))
    end
    -- Принимаем следующий запрос
	strin, e = c:receive()
end
base.print(e)

Клиент:

-- Подключаемся к серверу
local socket = require("socket")
host = host or "localhost"
port = port or 8080
if arg then
	host = arg[1] or host
	port = arg[2] or port
end
print("Attempting connection to host '" ..host.. "' and port " ..port.. "...")
c = assert(socket.connect(host, port))
print("Connected!")

-- Исходные данные
local a = 11
local b = 6

-- Формируем скрипт для удаленного выполнения на сервере прикладной функции.
strin = 'local a ='..a..';local b ='..b..';local rc, tbl=Server.SomeService(a, b);' ..
    'local str_tbl = Server.serializer.save("tbl", tbl);' ..
    'local strout="local rc="..rc..";local "..str_tbl.."return rc, tbl";return strout'

-- Отладочная печать
print("strin = "..strin.."\n")

-- Отправляем скрипт на сервер и получаем ответный скрипт
assert(c:send(strin .. "\n"))
strout, e = c:receive()

-- Отладочная печать
print("strout = "..strout.."\n")

-- Интерпретируем ответный скрипт
local rc, tbl = loadstring(strout)()

-- Печатаем результаты
print('Remote call: rc, tbl = SomeService('..a..','..b..'); rc = '..rc)
print('tbl.a = '..tbl.a..', tbl.b = '..tbl.b..', tbl.result = '..tbl.result)

-- Отсоединяемся от сервера
assert(c:send("\n"))

Для интерпретации скриптов на сервере и клиенте используется штатная
функция loadstring(). Она на лету компилирует скрипт и возвращает Lua-чанк
в виде функции без аргументов. Вызов этой функции фактически выполняет
откомпилированный скрипт. В нашем случае функция на сервере всегда возвращает
строку ответного скрипта, которая затем и передается по сети от сервера
клиенту:

function DoScript(strin)
	local fstrin = base.loadstring(strin)
	base.assert(fstrin)
	return fstrin()
end

Запускаем сначала сервер:
lua ServerExample.lua

Binding to host '*' and port 8080...
Waiting connection from client on 0.0.0.0:8080...

Теперь в другом процессе запускаем клиента.
lua ClientExample.lua

На сервере видим:

Connected!
strin = local a =11;local b =6;local rc, tbl=Server.SomeService(a, b);local str_tbl = Server.serializer.save
("tbl", tbl);local strout="local rc="..rc..";local "..str_tbl.."return rc, tbl";return strout

strout = local rc=0;local tbl = {};tbl[«a»] = 11;tbl[«result»] = 17;tbl[«b»] = 6;return rc, tbl

closed

На стороне клиента видим:

Attempting connection to host 'localhost' and port 8080...
Connected!
strin = local a =11;local b =6;local rc, tbl=Server.SomeService(a, b);local str_tbl = Server.serializer.save("tbl", tbl);local strout="local rc="..rc..";local "..str_tbl.."return rc, tbl";return strout

strout = local rc=0;local tbl = {};tbl[«a»] = 11;tbl[«result»] = 17;tbl[«b»] = 6;return rc, tbl

Remote call: rc, tbl = SomeService(11,6); rc = 0
tbl.a = 11, tbl.b = 6, tbl.result = 17

Таким образом, сервер успешно интерпретировал удаленный функциональный вызов функции,
полученный от клиента и вернул ему результаты выполнения, которые, в свою очередь,
были успешно интерпретированы клиентом.

Serializer.lua:

local base = _G

-- Данный модуль модифицирован для сериализации данных
-- в возвращаемую строку, а не в файл!
-- Замененные строки закомментированы.

module('Serializer')
-- модуль для сохранения таблицы в строке
-- могут быть сохранены переменные типа число, строка или булевский тип
-- таблица сохраняется строке, которая возвращается в качестве результата

-- проверка, что переменная может быть сохранена как строка 
-- т.е это число, строка или булевский тип)
local function isValidType(valueType)
  return "number" == valueType or
         "boolean" == valueType or
         "string" == valueType
end

-- конвертация переменной в строку
local function valueToString (value)
  local valueType = base.type(value)

  if "number" == valueType or "boolean" == valueType then
    result = base.tostring(value)
  else  -- assume it is a string
    result = base.string.format("%q", value)
  end

  return result
end

function save (name, value, saved, result)
    result = result or ""
  saved = saved or {}       -- initial value
-- base.io.write(name, " = ")
    result = result..name.." = "
  local valueType = base.type(value)
  if isValidType(valueType) then
-- base.io.write(valueToString(value), "\n")
    result = result..valueToString(value)..";"
  elseif "table" == valueType then
    if saved[value] then    -- value already saved?
-- base.io.write(saved[value], "\n") -- use its previous name
    result = result..saved[value]..";"
    else
      saved[value] = name   -- save name for next time
-- base.io.write("{}\n") -- create a new table
        result = result.."{};"
      for k,v in base.pairs(value) do      -- save its fields
        -- добавляем проверку ключа таблицы
        local keyType = base.type(k)
        if isValidType(keyType) then
          local fieldname = base.string.format("%s[%s]", name, valueToString(k))
          result = save(fieldname, v, saved, result)
        else
          base.error("cannot save a " .. keyType)
        end
      end
    end
  else
    base.error("cannot save a " .. valueType)
  end
  return result
end

комментария 2

Subscribe to comments with RSS.

  1. mszerg said, on Июнь 6, 2014 at 9:47 дп

    Спасибо за хороший сайт. Я занимаюсь автоматизацией на роутере с прошивкой openwrt. LUA там встроен в прошивку. Задался целью поднять на роутере сокет, что бы считывать данные с датчиков. Сделал тестовую реализацию на php, хочется без него, т.к. он занимает много места и памяти.
    Вопрос в следующем, смогу ли я подключится по технологии Ajax к серверу из вашего примера? в lua пока не силен(

    • ilovelua said, on Июнь 6, 2014 at 10:26 дп

      Я в сокетах Lua не силен, сорри. Эта статья написана другим человеком. Про серверное взаимодействие с Lua мне тоже, к сожалению, ничего не известно.


Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: