Я разработал сервер TCP в Phoenixframework, используя реализацию Erlang : модуль gen_tcp.
Я могу запустить сервер, вызвав :gen_tcp.listen(port)
, который затем прослушивает новые соединения на этом порту.
Один клиент — автоматизированная система комплектования для аптек (по сути, автоматизированный робот для выдачи лекарств).
Таким образом, как TCP-клиент, робот может открыть соединение с моим TCP-сервером. Сервер прослушивает новые сообщения робота с помощью метода handle_info
-обратного вызова, а также может ответить клиенту в рамках этого запроса (:gen_tcp.send
).
Проблема, с которой я столкнулся, заключается в том, что я понятия не имею, как использовать это соединение и отправлять данные обратно роботу без запроса клиента.
Поскольку робот является tcp-клиентом (компания, стоящая за роботом, говорит, что в настоящее время робот не может действовать как сервер), у него нет открытого порта/адреса сервера робота, на который я мог бы отправлять сообщения. Поэтому я должен использовать уже установленное соединение, инициализированное клиентом.
Настройка
Pharma_ui > Pharma_api (Phoenix) > robot (программное обеспечение поставщика)
Рабочий процесс:
- робот инициализирует подключение к API через TCP
- робот отправляет информацию о статусе в API и получает ответ
- в какой-то момент (см. обновление 1) API должен отправить роботу запрос на выдачу (используя соединение, инициализированное в #1)
Шаги 1 и 2 работают, часть 3 — нет.
Это выглядит как довольно простая проблема с tcp-соединениями в Elixir/Phoenix, но любой намек в правильном направлении приветствуется :)
На данный момент я придумал эту реализацию (на основе этого сообщения в блоге):
defmodule MyApi.TcpServerClean do
use GenServer
defmodule State do
defstruct port: nil, lsock: nil, request_count: 0
end
def start_link(port) do
:gen_server.start_link({ :local, :my_api }, __MODULE__, port, [])
end
def start_link() do
start_link 9876 # Default Port if non provided at startup
end
def get_count() do # test call from my_frontend
:gen_server.call(:my_api, :get_count)
end
def stop() do
:gen_server.cast(:my_api, :stop)
end
def init (port) do
{ :ok, lsock } = :gen_tcp.listen(port, [{ :active, true }])
{ :ok, %State{lsock: lsock, port: port}, 0 }
end
def handle_call(:get_count, _from, state) do
{ :reply, { :ok, state.request_count }, state }
end
def handle_cast(:stop , state) do
{ :noreply, state }
end
# handles client tcp requests
def handle_info({ :tcp, socket, raw_data}, state) do
do_rpc(socket, raw_data) # raw_data = data from robot
{ :noreply, %{ state | request_count: state.request_count + 1 } } # count for testing states
end
def handle_info(:timeout, state) do
{ :ok, _sock } = :gen_tcp.accept state.lsock
{ :noreply, state }
end
def handle_info(:tcp_closed, state) do
# do something
{ :noreply, state }
end
def do_rpc(socket, raw_data) do
try do
# process data from robot and do something with it
resp = "My tcp server response ..." # test
:gen_tcp.send(socket, :io_lib.fwrite(resp, []))
catch
error -> :gen_tcp.send(socket, :io_lib.fwrite("~p~n", [error]))
end
end
end
Обновление 1:
В какой-то момент = пользователь (например, фармацевт) размещает заказ в пользовательском интерфейсе. Интерфейс инициирует публикацию в API, а API обрабатывает публикацию в OrderController. OrderController должен преобразовать заказ (чтобы его понял робот) и передать его на TcpServer, который держит соединение с роботом. Этот рабочий процесс будет происходить много раз в день.
GenServer
), который хранит сокет, ждет триггера, а затем отправляет ответ, если вы хотите, чтобыTcpServerClean
мог обрабатывать более одного клиента одновременно. - person Dogbert   schedule 14.09.2016