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

时间:2016-09-14 15:46:51

标签: sockets tcp elixir phoenix-framework gen-tcp

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

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

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

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

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

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

设置

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

工作流

  1. 机器人通过tcp初始化与api的连接
  2. 机器人向api发送状态信息并获得响应
  3. 在某些时候(参见更新1),api必须向机器人发送一个分配请求(通过使用在#1中初始化的连接)
  4. 第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前端下订单。前端触发api的帖子,api处理OrderController中的帖子。 OrderController必须转换顺序(以便机器人理解它)并将其传递给保存与机器人的连接的TcpServer。此工作流程每天会发生多次。

1 个答案:

答案 0 :(得分:2)

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

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

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