condor icon indicating copy to clipboard operation
condor copied to clipboard

A minimal library for building scalable TCP servers in Erlang

  • Condor

    Condor is a minimal library for building scalable TCP servers in Erlang.

** Quick Overview

#+BEGIN_SRC erlang -module(ping_server). -behaviour(condor_listener).

%% condor_listener callbacks -export([init/1]). -export([handle_packet/2]). -export([handle_info/2]). -export([terminate/2]).

init([]) -> {ok, undefined}.

handle_packet(<<"stop">>, State) -> {send_and_stop, <<"ok">>, normal, State}; handle_packet(<<"ping">>, State) -> {send, <<"pong">>, State}.

handle_info(_Msg, State) -> {ok, State}.

terminate(_Reason, _State) -> ok. #+END_SRC

** Features

  • Reusable supervised connection acceptors
  • Neat packet frames and buffers

** API

*** Start a Listener

#+BEGIN_SRC
condor:start_listener(Name, Opts, Module, InitialState) ->
    {ok, Pid} | {error, Reason}

Name = atom()
Opts = #{
         ip => inet:ip_address(),
         port => inet:port_number(),
         max_acceptors => non_neg_integer(),
         len => 1 | 2,
         timeout => non_neg_integer()
       }

Module = atom() InitialState = any() Pid = pid() Reason = any() #+END_SRC

~Opts~:

  • ~ip~: to what IP Condor should bind.
  • ~port~: on what port Condor should listen.
  • ~max_acceptors~: maximum number of socket acceptors, or in other words, maximum number of concurrent connections.
  • ~len~: length of the indicator of a packet's size.
  • ~timeout~: the time limit, in milliseconds, for a packet to be buffered.

Default ~Opts~:

#+BEGIN_SRC #{ ip => {127, 0, 0, 1}, len => 2, timeout => 10000, max_acceptors => 100 } #+END_SRC

*** Stop a Listener

#+BEGIN_SRC
condor:stop_listener(Name) -> ok

Name = atom()
#+END_SRC

*** Condor Listener Callbacks

Behaviour: ~condor_listener~

#+BEGIN_SRC
init(State) -> Result

handle_packet(Packet, State) -> Result

handle_info(Msg, State) -> Result

terminate(Reason, State) -> ok

Result = {ok, State}
       | {send, Packet, State}
       | {stop, Reason, State}
       | {send_and_stop, Packet, Reason, State}

Packet = binary()
State = any()
Reason = atom()
#+END_SRC

Events that invoke callbacks:

#+BEGIN_SRC
event                       callback module
-----                       ---------------
new connection         ---> Module:init/1
new packet             ---> Module:handle_packet/2
receiving a message    ---> Module:handle_info/2
packet timeout         ---> Module:handle_info/2
connection termination ---> Module:terminate/2
#+END_SRC

Condor takes care of framing packets. Each packet frame consists of two
segments: ~Len | Packet~.

~Len~ indicates the length of ~Packet~ in big-endian order.

The length of ~Len~ is two bytes by default, though it can be modified with
the ~len~ option.

Condor also takes care of buffering. So, ~Module:handle_packet/2~ is called
only when an entire packet is received. That is, when ~Packet~ is buffered
according to the value of ~Len~. ~Len~ will then be stripped off, and only
~Packet~ will be passed to ~Module:handle_packet/2~. However, if ~Packet~ is
not received completely in ~Timeout~ milliseconds (which would be specified
in ~Opts~), ~Module:handle_info/2~ will be invoked with ~Msg~ being
~{timeout, Buffer :: binary()}~.

A callback's ~Result~:

- ~{send, Packet, State}~ sends ~Packet~ (in the frame: ~Len | Packet~).
- ~{stop, Reason, State}~ terminates the connection.
- ~{send_and_stop, Packet, Reason, State}~ sends ~Packet~ (in the frame:
  ~Len | Packet~), and then terminates the connection.

** License

Apache License, Version 2.0