Joe's "deep trickery"
Chris Pressey
cpressey@REDACTED
Fri Feb 28 09:03:24 CET 2003
Good day, eh?
OK, in Joe's latest tutorial
(http://www.sics.se/~joe/tutorials/web_server/web_server.html) he states:
"We also need some deep trickery to arrange that one
instance of this process is started for each incoming request"
And by the looks of tcp_server.erl, the trickery is quite complex.
But I've been using a very much simpler method for starting generic TCP/IP
servers, roughly:
server(Module, Function, Args, Port, Options) ->
case gen_tcp:listen(Port, Options) of
{ok, LSock} ->
spawn_link(?MODULE, server_loop,
[Module, Function, Args, LSock, Port, Options])
end.
server_loop(Module, Function, Args, LSock, Port, Options) ->
case gen_tcp:accept(LSock) of
{ok, Socket} ->
Pid = spawn_link(Module, Function, [Socket] ++ Args),
gen_tcp:controlling_process(Socket, Pid),
server_loop(Module, Function, Args, LSock, Port, Options);
{error, closed} ->
server(Module, Function, Args, Port, Options);
N ->
server_loop(Module, Function, Args, LSock, Port, Options)
end.
I realize my version doesn't limit the number of connections like Joe's
does, but that seems straightforward to implement. Also, if you want
{active, true}, you have to pass the option {active, false} initially,
then use inet:setopts to set it to {active, true} while in
Module:Function, or risk a race condition - a minor inconvenience.
This version seems to work ok for me, and even with those improvements, it
could hardly be called deep trickery; so in deference to Joe's wisdom, and
knowing how much of a fan he is of simplicity, I must assume either:
a) mine has a fatal flaw or flaws that I can't presently perceive, or
b) Joe forgot the word "efficiently" before "arrange" in his sentence
My concern is mainly that an intimidating module like tcp_server.erl could
scare off new Erlang programmers by giving them the impression that
working with TCP/IP in Erlang is a bit nasty. It's not nasty at all, at
least I don't think it is unless you want to push the envelope.
This is otherwise a fine tutorial. I like it.
Middlemen gets me thinking: are there generic consumer/producer patterns
that can be packaged? I find I'm writing a lot of processes along the
lines of: accept data in arbitrary-sized chunks from one process and
dispense it in chunks of some calculated size to another process,
sometimes reformatting it on the way. Is there something like a
gen_stream that I've overlooked in OTP?
But then I start thinking: why the hell do I want more gen_* modules when
I rarely ever use the existing ones? For better or worse, I usually build
my own with receive and !, which I find easier to read (at least while
coding,) with the assumption that some day, if it becomes really
important, I'll rewrite them to be gen_*'s. So I sat myself down the
other day and forced myself to write one each gen_server, gen_event and
gen_fsm, to practice.
I learned more about them, but unfortunately I learned more about why I
never end up using them. I totally understand Suresh's confusion over
gen_event:call. It's unintuitive until you think about how Erlang grew up
- in an ideal world, if you wanted an event handler you could treat like a
server, you'd say -behaviour([gen_event, gen_server]). Clearly, you can't
do that, and gen_event:call smells like the workaround for it.
Also, it would be really nice if the event handler could actually *handle*
events and not just react to them after they've happened - i.e. if
gen_event:notify took, and returned, a term to represent state, perhaps
modified by the event handlers. (That way you wouldn't need
gen_event:call either; you could configure an event handler using events.)
Anyway, sorry that got off on sort of a tangent.
-Chris
More information about the erlang-questions
mailing list