在打开的连接中从 Elixir 中的 tcp 服务器发送消息到 tcp 客户端

Sending messages from a tcp server in Elixir to a tcp client within an open connection

我使用 Erlang :gen_tcp 模块的实现在 Phoenixframwork 中开发了一个 TCP 服务器。

我可以通过调用 :gen_tcp.listen(port) 启动服务器,然后在该端口上侦听新连接。

一个客户是一个药房自动拣货系统(基本上是一个自动配药机器人)。

因此,作为 tcp 客户端,机器人能够打开到我的 tcp 服务器的连接。服务器通过 handle_info-回调方法侦听机器人的新消息,并且还能够在此请求中响应客户端 (:gen_tcp.send)。

我面临的问题是我不知道如何使用此连接并在没有客户端请求的情况下将数据发送回机器人。

由于机器人是一个 tcp 客户端(机器人背后的公司说目前没有办法让机器人充当服务器),所以没有我可以向其发送消息的开放端口/机器人服务器地址。所以我必须使用客户端初始化的已经建立的连接。

设置

pharmacy_ui > pharmacy_api (Phoenix) > 机器人(供应商软件)

工作流:

  1. 机器人通过 tcp
  2. 初始化与 api 的连接
  3. 机器人将状态信息发送到api并得到响应
  4. 在某些时候(见更新 1),api 必须向机器人发送分配请求(通过使用在#1 中初始化的连接)

第 1 步和第 2 步有效,第 3 部分无效。

这看起来像是一个相当简单的关于 Elixir/Phoenix 中的 tcp 连接的问题,但是非常感谢任何正确方向的提示:)

到目前为止我想出了这个实现(基于这个blog post):

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:

在某个时候 = 用户(例如药剂师)在 ui 前端下订单。前端触发 post 到 api,api 处理 OrderController 中的 post。 OrderController 必须转换订单(以便机器人理解)并将其传递给保持与机器人连接的 TcpServer。此工作流程每天会发生多次。

{ :ok, _sock } = :gen_tcp.accept state.lsock

_sock 是您不使用的套接字。但它是您实际可以发送数据的套接字。 IE。 :gen_tcp.send(_sock, data) 会将数据推送到您的机器人。您将需要确保您正在监视此套接字是否断开连接,并确保您可以访问它以供以后使用。这意味着您需要创建一个拥有该套接字并包含对该套接字的引用的进程,以便您的服务器代码可以在稍后的某个时间点将数据发送到该套接字。 IE。最简单的做法是创建 gen_server。

但是,您正在做的是创建您自己的接受器代码。已经有一个广泛使用的接受器池实现。它被称为牧场(https://github.com/ninenines/ranch)。您可以使用它而不是自己滚动。它提供了比您拥有的更优化的方法。例如,它创建了一个受体池。它还将允许更好地抽象 gen_server,它只负责与机器人通信,根本不用担心侦听器套接字。