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

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

问题描述:

我通过使用Erlang :gen_tcp模块的实现在Phoenixframwork中开发了一个TCP服务器。从Elixir的tcp服务器向打开的连接中的tcp客户端发送消息

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

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

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

我面临的问题是,我不知道如何使用这个连接,并发送数据回机器人没有客户端请求。

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

设置

pharmacy_ui> pharmacy_api(菲尼克斯)>机器人(供应商软件)

工作流

  1. 机器人初始化通过TCP到API的连接
  2. 机器人发送状态信息给API并得到响应
  3. 在某些点(见更新1),该API必须发送一个分配请求到机器人(通过使用在#1初始化的连接)

步骤1和2工作,第3部分没有按”吨。

这看起来像关于药剂/凤凰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

在某些点= A用户(例如药剂师)放置在UI前端的顺序。前端触发到api的帖子,并且api处理OrderController中的帖子。 OrderController必须转换顺序(以便机器人理解它)并将其传递给保存到机器人连接的TcpServer。这个工作流程每天会发生多次。

+1

你可以举一个“在某个时间点”的例子吗?谁会触发这个动作?一个简短的答案是你可以产生一个存储套接字的进程(像一个不同的'GenServer'),等待触发器,然后发送一个响应,如果你希望'TcpServerClean'能够同时处理多个客户端。 – Dogbert

+0

@Dogbert:我更新我的问题以举例说明“在某个时间点”可能是什么。如果在我更新后它仍然有效,您是否介意并给出一个简短的答案示例?谢谢你的帮助! – Pascal

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

_sock是您不使用的套接字。但它实际上可以发送数据的是套接字。即:调用gen_tcp。发送(_sock,数据)将推送数据到您的机器人。您将需要确保您正在监视此插槽以进行断开连接,并确保您有权访问它以备后用。这意味着您需要创建一个拥有该套接字的进程,并包含对套接字的引用,以便服务器代码可以在稍后的时间点将数据发送到套接字。即最简单的事情就是创建gen_server。

但是,你正在做的是创建你自己的接受者代码。有一个已经广泛使用的接受池实现。它被称为牧场(https://github.com/ninenines/ranch)。你可以使用它来代替滚动你自己的。它有更多的最佳做法比你有什么。例如,它创建了一个受体池。它也将允许更好地抽象gen_server,它只负责与机器人通信,而不用担心监听套接字。

+0

感谢您的牧场链接,不知道接受者代码!我认为你的回答的第一部分与Dagobert在他的评论中建议的方向一致。我会尽力回复你。我更新我的问题以举例说明“在某个时刻”意味着只是为了确保这不会改变您的答案。谢谢! – Pascal