Skip to content
/ inv_tcps Public

A high-performance TCP acceptor library for Erlang.

License

Apache-2.0, Unknown licenses found

Licenses found

Apache-2.0
LICENSE
Unknown
LICENSE.MochiWeb
Notifications You must be signed in to change notification settings

impl/inv_tcps

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

inv_tcps

inv_tcps is a fairly fast TCP acceptor library for Erlang applications. It consists of a gen_server that creates a TCP listening socket and several sub-processes for handling connections.

Connection handling is performed in the acceptor process itself, so a common use case will be to send messages to other processes from within the acceptor for most asynchronous calls; this will allow the acceptor to handle new connections extremely quickly.

inv_tcps includes a rudimentary algorithm for spawning new child processes on-demand: upon creation of a new listener, an initial and maximum number of acceptors can be specified. The number of available acceptors never drops below the initial amount and never exceeds the maximum amount.

Compiling

inv_tcps uses the excellent rebar tool. To compile inv_tcps, execute ./rebar compile in the project root.

Playing around

Head into the ebin directory and start up an Erlang shell:

$ erl +K true +A 20
1> application:load(inv_tcps), application:start(inv_tcps).

Before we continue, we need to define a callback function. The callback can be a fun() or a tuple containing {Module, Function}.

A callback accepts a socket() (as returned by gen_tcp:accept/1,2). You can perform all the usual operations on the socket, including gen_tcp:recv/2,3, gen_tcp:send/2 and gen_tcp:close/1. If the callback function returns without closing the socket, it will automatically be closed by inv_tcps; socket finalization will also occur if the callback throws an exception.

Let's create a simple echo server, since it's one of the more straightforward examples:

2> EchoServer = fun(Socket) ->
2>     Runner = fun(Self) ->
2>         case gen_tcp:recv(Socket, 0) of
2>             {ok, Packet} ->
2>                 io:format("~p received ~s~n", [self(), Packet]),
2>                 gen_tcp:send(Socket, Packet),
2>                 Self(Self);
2>             Error ->
2>                 ok
2>         end
2>     end,
2>     Runner(Runner)
2> end.

We exploit a bit of anonymous function trickery to get the callback to call itself for more data; obviously if we were not using anonymous functions we could avoid jumping through that hoop. Otherwise, the code is fairly straightforward: receive data on the socket, print out an informational line, and then send the data back. Rinse and repeat.

Now let's create a listening socket for our server:

3> {ok, Pid} = inv_tcps:start([{port, 8080}, {callback, EchoServer}]).

With a little luck, you should get a response of something like {ok, <0.41.0>}. Test out your server by running telnet localhost 8080 and typing a line or two; you should receive a reply. Open another telnet session (while the first one is still running) and you should also receive replies immediately in that session.

You can get a list of active acceptors by calling inv_tcps:active(Pid) (likewise, a list of inactive acceptors can be obtained by calling inv_tcps:idle(Pid)).

Pretty cool, huh?

Tuning

The main tuning options for inv_tcps are the initial_pool_size and maximum_pool_size arguments used when creating an acceptor pool. These default to 1 and infinity respectively, which is not terribly efficient.

After some very basic testing, using 20 asynchronous threads with an initial_pool_size of 4 and a maximum_pool_size of 16 yielded fairly good results (a Hello-World-style Web server handled between 7,000 and 10,000 requests per second in a VM). The syntax for this is as follows:

4> {ok, Pid2} = inv_tcps:start([{port, 8081}, {callback, YourCallback},
                                {initial_pool_size, 4},
                                {maximum_pool_size, 16}]).

Using inv_tcps in your project

inv_tcps is effectively a gen_server, so it should be properly supervised in your application. The functions inv_tcps:start_link/1,2 call and return the value of gen_server:start_link/1,2 respectively.

Your supervisor's init/1 might look like

init([]) ->
    Listener = {myapp_test, {myapp_test, start_link, []},
                permanent, 5000, worker, [myapp_test]},
    Processes = [Listener],
    {ok, {{one_for_one, 5, 10}, Processes}}.

And myapp_test should be something like the following (exports removed for brevity):

start_link() ->
    inv_tcps:start_link({local, ?MODULE},
                        [{port, 8080}, {callback, {?MODULE, accept}}]).

accept(Socket) ->
    %% Process connection
    ok.

There's also a working sample project which could serve as a starting point for your own application!

About

A high-performance TCP acceptor library for Erlang.

Resources

License

Apache-2.0, Unknown licenses found

Licenses found

Apache-2.0
LICENSE
Unknown
LICENSE.MochiWeb

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages