问题
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