Erlang服务器连接端口以发送和接收Json文件到Java应用程序

时间:2017-10-25 16:26:35

标签: erlang erlang-ports

我试图用我的Java应用程序实现一个带Erlang的服务器。 似乎我的服务器正在运行,但仍然充满了错误和死点。 我需要将Java应用程序解析的JSON文件接收到映射中,然后将其发送回所有客户端,包括上传文件的客户端。 同时,我需要跟踪发出请求的人以及发送消息的哪一部分,如果出现任何问题,客户端应该从这一点重新启动,而不是从头开始。除非客户端离开应用程序,否则它应该重新启动。

我的三段代码将在下面:

app.erl

-module(erlServer_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

%%%===================================================================
%%% Application callbacks
%%%===================================================================

start(_StartType, _StartArgs) ->
    erlServer_sup:start_link().


stop(_State) ->
    ok.

supervisor.erl:

-module(erlServer_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).

%% Supervisor callbacks
-export([init/1, start_socket/0, terminate_socket/0, empty_listeners/0]).

-define(SERVER, ?MODULE).

%%--------------------------------------------------------------------
%% @doc
%% Starts the supervisor
%%
%% @end
%%--------------------------------------------------------------------
start_link() ->
  supervisor:start_link({local, ?SERVER}, ?MODULE, []).

%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================

init([]) -> % restart strategy 'one_for_one': if one goes down only that one is restarted
  io:format("starting...~n"),
  spawn_link(fun() -> empty_listeners() end),
  {ok,
    {{one_for_one, 5, 30}, % The flag - 5 restart within 30 seconds
      [{erlServer_server, {erlServer_server, init, []}, permanent, 1000, worker, [erlServer_server]}]}}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

start_socket() ->
  supervisor:start_child(?MODULE, []).

terminate_socket() ->
  supervisor:delete_child(?MODULE, []).

empty_listeners() ->
  [start_socket() || _ <- lists:seq(1,20)],
  ok.

server.erl :(我有很多调试io:format。)

-module(erlServer_server).

%% API
-export([init/0, start_server/0]).

%% Defining the port used.
-define(PORT, 8080).

%%%===================================================================
%%% API
%%%===================================================================

init() ->
  start_server().

%%%===================================================================
%%% Server callbacks
%%%===================================================================

start_server() ->
  io:format("Server started.~n"),
  Pid = spawn_link(fun() ->
    {ok, ServerSocket} = gen_tcp:listen(?PORT, [binary, {packet, 0}, 
      {reuseaddr, true}, {active, true}]),
    io:format("Baba~p", [ServerSocket]),
    server_loop(ServerSocket) end),
  {ok, Pid}.

server_loop(ServerSocket) ->
  io:format("Oba~p", [ServerSocket]),
  {ok, Socket} = gen_tcp:accept(ServerSocket),
  Pid1 = spawn(fun() -> client() end),
  inet:setopts(Socket, [{packet, 0}, binary, 
    {nodelay, true}, {active, true}]),
  gen_tcp:controlling_process(Socket, Pid1), 
  server_loop(ServerSocket).

%%%===================================================================
%%% Internal functions
%%%===================================================================

client() ->
  io:format("Starting client. Enter \'quit\' to exit.~n"),
  Client = self(),
  {ok, Sock} = gen_tcp:connect("localhost", ?PORT, [{active, false}, {packet, 2}]),
  display_prompt(Client),
  client_loop(Client, Sock).

%%%===================================================================
%%% Client callbacks
%%%===================================================================

send(Sock, Packet) ->
  {ok, Sock, Packet} = gen_tcp:send(Sock, Packet),
  io:format("Sent ~n").

recv(Packet) ->
  {recv, ok, Packet} = gen_tcp:recv(Packet),
  io:format("Received ~n").

display_prompt(Client) ->
  spawn(fun () ->
    Packet = io:get_line("> "),
    Client ! {entered, Packet}
        end),
  Client ! {requested},
  ok.


client_loop(Client, Sock) ->
  receive
    {entered, "quit\n"} ->
      gen_tcp:close(Sock);
    {entered, Packet}        ->
      % When a packet is entered we receive it,
      recv(Packet),
      display_prompt(Client),
      client_loop(Client, Sock);
    {requested, Packet}        ->
      % When a packet is requested we send it,
      send(Sock, Packet),
      display_prompt(Client),
      client_loop(Client, Sock);
    {error, timeout} ->
      io:format("Send timeout, closing!~n", []),
      Client ! {self(),{error_sending, timeout}},
      gen_tcp:close(Sock);
    {error, OtherSendError} ->
      io:format("Some other error on socket (~p), closing", [OtherSendError]),
      Client ! {self(),{error_sending, OtherSendError}},
      gen_tcp:close(Sock)
  end.

这是我正在做的第一台服务器,我可能在中间迷路了。当我跑步它似乎工作,但挂。有人能帮我吗?我的localhost永远不会加载任何它永远加载的东西。

我的Java应用程序如何从同一端口接收它?

我必须使用Erlang,我必须使用端口连接到java应用程序。

感谢您的帮助!

1 个答案:

答案 0 :(得分:3)

让我们重新做一点......

首先:命名

我们在Erlang中不使用camelCase。这是令人困惑的,因为大写的变量名称和小写(或单引号)原子意味着不同的东西。此外,模块名称​​必须与文件名相同,这会导致不区分大小写的文件系统出现问题。

此外,我们真的想要一个比服务器&#34;更好的名字。服务器在这样的系统中可能意味着很多东西,虽然整个系统可能是用Erlang编写的服务,但它并不一定意味着我们可以调用服务器内的所有内容。没有得到超级暧昧!这令人困惑。我要将您的项目命名为&#34; ES&#34;目前。因此,您将拥有es_appes_sup等等。当我们想要开始定义新模块时,这将会派上用场,其中一些模块称为&#34; server&#34;无需编写&#34; server_server&#34;到处都是。

第二:输入数据

一般来说,我们希望将参数传递给函数,而不是在代码中隐藏文字(或更糟糕的是,宏重写)。如果我们要使用魔术数字和常量,我们会尽力将它们放入配置文件中,以便我们可以access them in a programmatic way更好,让&#39;在初始启动调用中将它们用作从属进程的参数,这样我们就可以通过搞乱主应用程序模块中的启动调用函数来重写系统的行为(一旦编写)。

-module(es).
-behaviour(application).

-export([listen/1, ignore/0]).
-export([start/0, start/1]).
-export([start/2, stop/1]).

listen(PortNum) ->
    es_client_man:listen(PortNum).

ignore() ->
    es_client_man:ignore().

start() ->
    ok = application:ensure_started(sasl),
    ok = application:start(?MODULE),
    io:format("Starting es...").


start(Port) ->
    ok = start(),
    ok = es_client_man:listen(Port),
    io:format("Startup complete, listening on ~w~n", [Port]).

start(normal, _Args) ->
    es_sup:start_link().

stop(_State) ->
    ok.

我在上面添加了一个start / 1函数以及一个start / 0,一个listen / 1和一个ignore / 0,你稍后会在es_client_man中再次看到它。这些主要是围绕你可以更明确地调用的东西的便利包装,但可能不会一直想要打字。

这个应用程序模块通过让应用程序主机为我们启动项目(通过调用application:start/1)和然后下一行调用erl_server_server来告诉它开始监听来解决问题。在早期开发中,我发现这种方法比将自动启动安装到整个地方的每个组件更有用,后来它为我们提供了一种非常简单的方法来编写可以打开各种组件的外部接口。关闭。

啊,也是......我们将把它作为真正的Erlang应用程序启动,因此我们需要ebin/中的应用程序文件(或者如果你需要的话)在src/)中使用erlang.mk或类似的app.src文件:

ebin / es.app看起来像这样:

{application,es,
             [{description,"An Erlang Server example project"},
              {vsn,"0.1.0"},
              {applications,[stdlib,kernel,sasl]},
              {modules,[es,
                        es_sup,
                          es_clients,
                            es_client_sup,
                              es_client,
                            es_client_man]},
              {mod,{es,[]}}]}.

modules下的列表反映了监督树的布局,实际上,如下所示。

上面的start / 2函数现在断言我们只会以normal模式启动(可能适合或不适合),并忽略启动参数(也可能适合或不合适)。

第三:The Supervision Tree

-module(es_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    RestartStrategy = {one_for_one, 1, 60},
    Clients   = {es_clients,
                 {es_clients, start_link, []},
                 permanent,
                 5000,
                 supervisor,
                 [es_clients]},
    Children  = [Clients],
    {ok, {RestartStrategy, Children}}.

然后......

-module(es_clients).
-behavior(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, none).

init(none) ->
    RestartStrategy = {rest_for_one, 1, 60},
    ClientSup = {es_client_sup,
                 {es_client_sup, start_link, []},
                 permanent,
                 5000,
                 supervisor,
                 [es_client_sup]},
    ClientMan = {es_client_man,
                 {es_client_man, start_link, []},
                 permanent,
                 5000,
                 worker,
                 [es_client_man]},
    Children  = [ClientSup, ClientMan],
    {ok, {RestartStrategy, Children}}.

哇! 这里发生了什么?!?嗯,es_sup是一个主管,而不是一次性的东西,只会产生其他一次性的东西。 (误解supervisors是你核心问题的一部分。)

主管很无聊。 主管应该很无聊。他们作为代码读者真正做的就是监督树的结构在里面。他们在OTP结构方面为我们所做的事情非常重要,但他们并不要求我们编写任何程序代码,只要声明它应该拥有什么样的子代码。我们在这里实现的是一个service -> worker结构。因此,我们为您的整个应用程序提供了名为es_sup的顶级主管。下面我们(现在)有一个名为es_clients的服务组件。

es_clients流程也​​是主管。这样做的原因是为客户端连接部件定义了一种明确的方式不影响以后系统其余部分可能存在的任何状态。 只是接受来自客户端的连接是没用的 - 当然有一些状态在其他地方很重要,比如长期连接到某个Java节点或其他任何东西。这将是一个单独的服务组件,可能称为es_java_nodes,该程序的一部分将以其自己独立的主管开始。这就是为什么它被称为监督树&#34;而不是监督名单&#34;。

回到客户......我们会让客户联系。这就是为什么我们称他们为#34;客户&#34;,因为从这个Erlang系统的角度来看连接的东西是客户端,接受这些连接的进程抽象客户端< / em>所以我们可以将每个连接处理程序视为客户端本身 - 因为它正是它所代表的。如果我们稍后连接到上游服务,我们会想要调用那些它们正在抽象的内容,以便我们在系统中的语义是理智的。

然后您可以根据&#34; es_client向es_java_node发送消息来查询[thingy]&#34;而不是试图让事情保持平稳,而不是像服务器一样,服务器向服务器请求java_server_client服务器服务器&#34; (如果你不从内部系统的角度来看,你的命名原则是直截了当的话,这实际上是多么愚蠢的事情。)

布拉赫等等......

所以,这是es_client_sup:

-module(es_client_sup).
-behaviour(supervisor).

-export([start_acceptor/1]).
-export([start_link/0]).
-export([init/1]).

start_acceptor(ListenSocket) ->
    supervisor:start_child(?MODULE, [ListenSocket]).

start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, none).

init(none) ->
    RestartStrategy = {simple_one_for_one, 1, 60},
    Client    = {es_client,
                 {es_client, start_link, []},
                 temporary,
                 brutal_kill,
                 worker,
                 [es_client]},
    {ok, {RestartStrategy, [Client]}}.

你正在挑选一种模式吗?当我说那些&#34;主管应该很无聊时,我不是在开玩笑......&#34; :-)请注意,这里我们实际上在中传递了一个参数,我们已经定义了一个接口函数。如果我们需要一个套接字接受器来启动它,那么我们就有了一个逻辑上可以调用的地方。

第四:客户服务本身

让我们看看客户经理:

-module(es_client_man).
-behavior(gen_server).

-export([listen/1, ignore/0]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         code_change/3, terminate/2]).

-record(s, {port_num = none :: none | inet:port_number(),
            listener = none :: none | gen_tcp:socket()}).

listen(PortNum) ->
    gen_server:call(?MODULE, {listen, PortNum}).

ignore() ->
    gen_server:cast(?MODULE, ignore).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).

init(none) ->
    ok = io:format("Starting.~n"),
    State = #s{},
    {ok, State}.

handle_call({listen, PortNum}, _, State) ->
    {Response, NewState} = do_listen(PortNum, State),
    {reply, Response, NewState};
handle_call(Unexpected, From, State) ->
    ok = io:format("~p Unexpected call from ~tp: ~tp~n", [self(), From, Unexpected]),
    {noreply, State}.

handle_cast(ignore, State) ->
    NewState = do_ignore(State),
    {noreply, NewState};
handle_cast(Unexpected, State) ->
    ok = io:format("~p Unexpected cast: ~tp~n", [self(), Unexpected]),
    {noreply, State}.

handle_info(Unexpected, State) ->
    ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]),
    {noreply, State}.

code_change(_, State, _) ->
    {ok, State}.

terminate(_, _) ->
    ok.

do_listen(PortNum, State = #s{port_num = none}) ->
    SocketOptions =
        [{active,    once},
         {mode,      binary},
         {keepalive, true},
         {reuseaddr, true}],
    {ok, Listener} = gen_tcp:listen(PortNum, SocketOptions),
    {ok, _} = es_client:start(Listener),
    {ok, State#s{port_num = PortNum, listener = Listener}};
do_listen(_, State = #s{port_num = PortNum}) ->
    ok = io:format("~p Already listening on ~p~n", [self(), PortNum]),
    {{error, {listening, PortNum}}, State}.

do_ignore(State = #s{listener = none}) ->
    State;
do_ignore(State = #s{listener = Listener}) ->
    ok = gen_tcp:close(Listener),
    State#s{listener = none}.
嗯,那是什么呢?这里的基本思想是我们在客户端的整个概念上有一个服务主管(es_clients,如上所述),然后我们有simple_one_for_one来处理刚刚发生的任何客户端(es_client_sup),这里我们有子系统的管理界面。这个管理器所做的就是跟踪我们正在监听的端口,拥有我们正在收听的套接字,如果此时正在打开的话。请注意,这可以很容易地重写,以允许同时收听任意数量的端口,或跟踪所有生活客户端,或其他任何东西。你可能想要做的事情没有限制。

那么我们如何启动可以接受连接的客户端呢?通过告诉他们产生并监听我们作为参数传入的侦听套接字 。再看看上面的es_client_sup。我们传入一个空列表作为其第一个参数。当我们调用它的start_link函数时会发生什么,无论我们传入的 else ,列表都会被添加到整个参数列表中。在这种情况下,我们将传入listen套接字,因此它将以参数[ListenSocket]开始。

每当客户端侦听器接受连接时,其下一步将是生成其后继者,并将原始ListenSocket参数交给它。啊,生命的奇迹。

-module(es_client).

-export([start/1]).
-export([start_link/1, init/2]).
-export([system_continue/3, system_terminate/4,
         system_get_state/1, system_replace_state/2]).

-record(s, {socket = none :: none | gen_tcp:socket()}).

start(ListenSocket) ->
    es_client_sup:start_acceptor(ListenSocket).

start_link(ListenSocket) ->
    proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).

init(Parent, ListenSocket) ->
    ok = io:format("~p Listening.~n", [self()]),
    Debug = sys:debug_options([]),
    ok = proc_lib:init_ack(Parent, {ok, self()}),
    listen(Parent, Debug, ListenSocket).

listen(Parent, Debug, ListenSocket) ->
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            {ok, _} = start(ListenSocket),
            {ok, Peer} = inet:peername(Socket),
            ok = io:format("~p Connection accepted from: ~p~n", [self(), Peer]),
            State = #s{socket = Socket},
            loop(Parent, Debug, State);
        {error, closed} ->
            ok = io:format("~p Retiring: Listen socket closed.~n", [self()]),
            exit(normal)
     end.

loop(Parent, Debug, State = #s{socket = Socket}) ->
    ok = inet:setopts(Socket, [{active, once}]),
    receive
        {tcp, Socket, <<"bye\r\n">>} ->
            ok = io:format("~p Client saying goodbye. Bye!~n", [self()]),
            ok = gen_tcp:send(Socket, "Bye!\r\n"),
            ok = gen_tcp:shutdown(Socket, read_write),
            exit(normal);
        {tcp, Socket, Message} ->
            ok = io:format("~p received: ~tp~n", [self(), Message]),
            ok = gen_tcp:send(Socket, ["You sent: ", Message]),
            loop(Parent, Debug, State);
        {tcp_closed, Socket} ->
            ok = io:format("~p Socket closed, retiring.~n", [self()]),
            exit(normal);
        {system, From, Request} ->
            sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State);
        Unexpected ->
            ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
            loop(Parent, Debug, State)
    end.

system_continue(Parent, Debug, State) ->
    loop(Parent, Debug, State).

system_terminate(Reason, _Parent, _Debug, _State) ->
    exit(Reason).

system_get_state(Misc) -> {ok, Misc}.

system_replace_state(StateFun, Misc) ->
    {ok, StateFun(Misc), Misc}.

请注意,上面我们编写了一个纯Erlang进程,它与gen_server的方式集成了OTP,但有一个更直接的循环,只处理套接字。这意味着我们没有gen_server调用/强制转换机制(可能需要自己实现,但通常asynch-only是一种更好的套接字处理方法)。这是started through the proc_lib module,专门用于引导任何类型的OTP兼容进程。

如果你打算使用主管那么你真的想要一路走下去并正确使用OTP

所以我们现在所拥有的是非常基本的Telnet echo服务。而不是在服务器模块中编写一个神奇的客户端进程来打结你的大脑(Erlangers并不喜欢他们的大脑绑在一起),你可以启动它,告诉它听一些端口,并自己telnet到它并看到结果。

我添加了一些脚本来自动启动,但基本上是codemake模块的铰链。您的项目布局如

es/
   Emakefile
   ebin/es.app
   src/*.erl

Emakefile的内容将使我们更轻松。在这种情况下,它只是一行:

enter code here {&#34; src / *&#34;,[debug_info,{i,&#34; include /&#34;},{outdir,&#34; ebin /&# 34;}]}

在主es/目录中,如果我们输入erl shell,我们现在可以做...

1> code:add_patha("ebin").
true
2> make:all().
up_to_date
3> es:start().

你会看到一堆SASL开始报告在屏幕上滚动。

从那里开始es:listen(5555)

4> es:listen(5555).
<0.94.0> Listening.
ok

酷!所以似乎事情正在发挥作用。让我们试着telnet到自己:

ceverett@changa:~/vcs/es$ telnet localhost 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello es thingy
You sent: Hello es thingy
Yay! It works!
You sent: Yay! It works!
bye
Bye!
Connection closed by foreign host.

另一方面看起来像什么?

<0.96.0> Listening.
<0.94.0> Connection accepted from: {{127,0,0,1},60775}
<0.94.0> received: <<"Hello es thingy\r\n">>
<0.94.0> received: <<"Yay! It works!\r\n">>
<0.94.0> Client saying goodbye. Bye!

啊,我们在这里看到&#34;倾听。&#34;来自第一个<0.96.0>生成的下一个听众<0.94.0>的消息。

并发连接怎么样?

<0.97.0> Listening.
<0.96.0> Connection accepted from: {{127,0,0,1},60779}
<0.98.0> Listening.
<0.97.0> Connection accepted from: {{127,0,0,1},60780}
<0.97.0> received: <<"Screen 1\r\n">>
<0.96.0> received: <<"Screen 2\r\n">>
<0.97.0> received: <<"I wonder if I could make screen 1 talk to screen 2...\r\n">>
<0.96.0> received: <<"Time to go\r\n">>
<0.96.0> Client saying goodbye. Bye!
<0.97.0> Client saying goodbye. Bye!

哦,neato。并发服务器!

从这里开始,您可以使用这个基本结构进行更改以完成您可能想象的任何事情。

请注意此代码中缺少很多内容。我删除了edoc符号和typespecs(由Dialyzer使用,这是一个大型项目中非常重要的工具) - 这对于生产系统来说是一件坏事。 / p>

有关生产型项目的示例,该项目小到可以包围(仅3个模块+完整文档),请参阅zuuid。它专门用作代码示例,尽管它恰好是一个功能齐全的UUID生成器。

原谅你(更短)问题的答案的巨大。这种情况经常出现,我想写一个简单的网络套接字服务的完整示例,我可以在将来推荐人们,当我读到你的问题时,恰好是渴望这样做。 :-)希望SO纳粹分子能够原谅这种严重的违规行为。