2016-09-14 73 views
0

我通過使用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

回答

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

_sock是您不使用的套接字。但它實際上可以發送數據的是套接字。即:調用gen_tcp。發送(_sock,數據)將推送數據到您的機器人。您將需要確保您正在監視此插槽以進行斷開連接,並確保您有權訪問它以備後用。這意味着您需要創建一個擁有該套接字的進程,幷包含對套接字的引用,以便服務器代碼可以在稍後的時間點將數據發送到套接字。即最簡單的事情就是創建gen_server。

但是,你正在做的是創建你自己的接受者代碼。有一個已經廣泛使用的接受池實現。它被稱爲牧場(https://github.com/ninenines/ranch)。你可以使用它來代替滾動你自己的。它有更多的最佳做法比你有什麼。例如,它創建了一個受體池。它也將允許更好地抽象gen_server,它只負責與機器人通信,而不用擔心監聽套接字。

+0

感謝您的牧場鏈接,不知道接受者代碼!我認爲你的回答的第一部分與Dagobert在他的評論中建議的方向一致。我會盡力回覆你。我更新我的問題以舉例說明「在某個時刻」意味着只是爲了確保這不會改變您的答案。謝謝! – Pascal

相關問題