Simulation of Thread Pool for a web-server implemented by Erlang doesn't work

旧时模样 提交于 2019-12-13 01:36:20

问题


The code is as follows:

-module(rudy).
-export([init/1,handler/1,request/1,reply/1, start/1, stop/0]).

start(Port) ->
  register(rudy, spawn(fun() ->
    init(Port) end)).

stop() ->
  exit(whereis(rudy), "time to die").

init(Port) ->
  Opt = [list, {active, false}, {reuseaddr, true}],
  case gen_tcp:listen(Port, Opt) of         % opens a listening socket
    {ok, Listen} ->
      spawn_many(3,Listen),
%%       handler(Listen),
      ok;
    {error, _Error} -> error
  end.

handler(Listen) ->
  case gen_tcp:accept(Listen) of            % listen to the socket
    {ok, Client} ->
      request(Client),
      gen_tcp:close(Client),
      handler(Listen);
    {error, _Error} -> error
  end.
%%   gen_tcp:close(Listen).            % close the socket

request(Client) ->
  Recv = gen_tcp:recv(Client, 0),
  case Recv of
    {ok, Str} ->
      Request = http:parse_request(Str),
      Response = reply(Request),
      gen_tcp:send(Client, Response);
    {error, Error} ->
      io:format("rudy: error: ~w~n", [Error])
  end,
  gen_tcp:close(Client).

reply({{get, URI, _}, _, _}) ->
  timer:sleep(40),
  http:ok(URI).

spawn_many(0, _Listen)-> ok;
spawn_many(N, Listen)->
  spawn(rudy,handler,[Listen]),
  spawn_many(N - 1, Listen).

I intend to create 3 listening socket for client to connect to, but this code doesn't work when executing rudy:start(8027). and then accessing http://localhost:8027/ from web browser.

Where's the culprit? Many thanks.


回答1:


One thing to know about Erlang sockets is that a process that opens one controls it; when that process dies, the runtime closes the socket.

Consider your start/1 function:

start(Port) ->
  register(rudy, spawn(fun() ->
    init(Port) end)).

It spawns the init/1 function, registers a name for it, and then returns. This means init/1 is running in a new process, so let's look at init/1:

init(Port) ->
  Opt = [list, {active, false}, {reuseaddr, true}],
  case gen_tcp:listen(Port, Opt) of         % opens a listening socket
    {ok, Listen} ->
      spawn_many(3,Listen),
%%       handler(Listen),
      ok;
    {error, _Error} -> error
  end.

The newly-spawned process running init/1 first calls gen_tcp:listen/2. If it succeeds, it calls spawn_many/2 to set up some acceptors; if it fails, it essentially ignores the error. But here's the key to your problem: regardless of success or failure, init/1 ends, and therefore so does the process in which it was spawned, and because this process, the controlling process of the listen socket, dies, the listen socket is closed. Any acceptors trying to use that socket fail because of that, which you would see if you were to print out the error condition in the handler/1 function.

The way to fix this is to make the init/1 process wait until all processes using the listen socket die off.

One way to do this is to make init/1 pass its pid to spawn_many (thus changing it from spawn_many/2 to spawn_many/3), have init/1 wait for 3 messages before exiting, and change handler/1 to handler/2, taking the pid as an additional argument and sending a message there when it's finished. Th easiest way to have init/1 wait for all the messages is to have it call a recursive function like the one below:

init(Port) ->
  Opt = [list, {active, false}, {reuseaddr, true}],
  case gen_tcp:listen(Port, Opt) of         % opens a listening socket
    {ok, Listen} ->
      Count = 3,
      spawn_many(Count,Listen,self()),
      wait_for_threads(Count);
      %%       handler(Listen),
    {error, _Error} ->
      error
  end.

wait_for_threads(0) ->
  ok;
wait_for_threads(Count) ->
  receive
    handler_done ->
      wait_for_threads(Count-1)
  end.

Then change handler/1 to handler/2 and have it send the message:

handler(Listen, Pid) ->
  case gen_tcp:accept(Listen) of            % listen to the socket
    {ok, Client} ->
      request(Client),
      gen_tcp:close(Client),
      handler(Listen, Pid);
    {error, _Error} ->
      error
  end,
  Pid ! handler_done.

Don't forget to accept the additional pid argument to spawn_many/3:

spawn_many(0, _Listen, _Pid)-> ok;
spawn_many(N, Listen, Pid)->
    spawn(rudy,handler,[Listen, Pid]),
    spawn_many(N - 1, Listen, Pid).

All this is enough to keep the listen socket alive for all the spawned acceptors.



来源:https://stackoverflow.com/questions/32416651/simulation-of-thread-pool-for-a-web-server-implemented-by-erlang-doesnt-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!