%%% BEGIN openflax/tcp.erl %%% %%% %%% openflax - Open Source web server for Erlang/OTP %%% Copyright (c)2004 Cat's Eye Technologies. All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% %%% Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% %%% Neither the name of Cat's Eye Technologies nor the names of its %%% contributors may be used to endorse or promote products derived %%% from this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE %%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, %%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, %%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, %%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON %%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, %%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY %%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF AsDVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. %% @doc TCP/IP socket library for OpenFlax. %% %% @end -module(openflax.tcp). -vsn('$Id: tcp.erl 31 2004-04-23 07:00:11Z catseye $'). -author('catseye@catseye.mb.ca'). -copyright('Copyright (c)2004 Cat`s Eye Technologies. All rights reserved.'). -export([server/4]). -export([get_iface_ip_addr/1]). -import(gen_tcp). -import(inet). -import(inet_udp). -import(prim_inet). -import(io). %% @spec server(Fun::fun(), Port::integer(), Opts::[option()], %% MaxCon::integer()) -> pid() | {error, reason()} %% @doc Implements a generic TCP/IP socket server. %% Opens a socket with the given options on the given port and spawns %% Fun(Socket) to handle each incoming connection, up to %% the specified maximum number of connections. server(Fun, Port, Options, MaxCon) -> {ok, ListenSocket} = gen_tcp:listen(Port, Options), spawn_link(fun() -> server_setup(Fun, ListenSocket, Port, Options, MaxCon) end). %% @spec server_setup(Fun::fun(), LSock::listen_socket(), Port::integer(), %% Opts::[option()], MaxCon::integer()) -> never_returns %% @doc Used by server/6 to handle the listen socket. server_setup(Fun, ListenSocket, Port, Options, 0) -> % When the maximum number of connections has been reached, wait % until a socket connection dies before starting any more accepts. receive {'EXIT', _Pid, _Reason} -> server_setup(Fun, ListenSocket, Port, Options, 1) end; server_setup(Fun, ListenSocket, Port, Options, MaxCon) -> % By setting the process flag trap_exit to true, we will recieve % {'EXIT', Pid, Reason} messages for any linked processes that die process_flag(trap_exit, true), % Therefore, this call is not a spawn_link because we only want to % get exit messages when the user socket handlers die Looper = self(), Accepter = spawn(fun() -> asynch_accept(Looper, ListenSocket) end), server_loop(Fun, ListenSocket, Port, Options, MaxCon, Accepter). %% @spec server_loop(Fun::fun(), LSock::listen_socket(), Port::integer(), %% Opts::[option()], MaxCon::integer(), Accepter::pid()) -> never_returns %% @doc Called by server_setup/5 to handle the listen socket. %% This function waits around for something to happen: %% either for a message to arrive from the previous asynch_accept/2 %% (meaning there is now one more connection), or for the exit signal of an %% existing socket process (meaning there is now one less connection.) In the %% former case, a new asynchronous accept call is set up by %% looping back to server_setup/5. server_loop(Fun, LSock, Port, Options, MaxCon, Accepter) -> receive {Accepter, {ok, Socket}} -> {ok, {RemoteAddress, _}} = inet:peername(Socket), Pid = spawn_link(fun() -> case catch Fun(Socket, RemoteAddress) of {'EXIT', Reason} -> io:fwrite("process ~p died unexpectedly: ~p~n", [self(), Reason]), exit(Reason); _ -> exit(normal) end end), gen_tcp:controlling_process(Socket, Pid), server_setup(Fun, LSock, Port, Options, MaxCon - 1); {Accepter, {error, closed}} -> server_setup(Fun, LSock, Port, Options, MaxCon); {Accepter, {error, _Reason}} -> server_setup(Fun, LSock, Port, Options, MaxCon); {'EXIT', _Pid, _Reason} -> server_loop(Fun, LSock, Port, Options, MaxCon + 1, Accepter) end. %% @spec asynch_accept(Parent::pid(), LSock::listen_socket()) -> never_returns %% @doc Used by server_loop/6 to handle the listen socket. %% This function waits for a connection using gen_tcp:accept/1 %% and notifies the parent process which spawned it when a connection happens, %% allowing the parent to process other messages in the meantime. asynch_accept(Parent, ListenSocket) -> Result = gen_tcp:accept(ListenSocket), case Result of {ok, Socket} -> gen_tcp:controlling_process(Socket, Parent); _ -> ok end, Parent ! {self(), Result}. %% @spec get_iface_ip_addr(Interface::string()) -> ip_addr() %% @doc Acquires the IP address of a given interface. %% Thanks to Samuel Rivas for this code. get_iface_ip_addr(Interface) -> {ok, Socket} = inet_udp:open(0, []), {ok, [{addr, Address}]} = prim_inet:ifget(Socket, Interface, [addr]), inet_udp:close(Socket), Address. %%% END of openflax/tcp.erl %%%