Accepting Sockets Asynchronously with prim_inet
September 6, 2015
As I was going over my HTTP/2 in Erlang talk for StrangeLoop, Sean Cribbs showed me a way to listen asynchronously for a socket. It's a pretty clean method, but I had to do some digging in order to get it working, because it uses the prim_inet
module, which isn't very well documented. I think that might be because we're not supposed to use it. Oops.
So, suppose you've already opened a socket with gen_tcp:listen
. It'a actually very important that you use gen_tcp:listen
and not ssl:listen
. We'll upgrade to SSL later if that's important to you, and it should be.
{ok, Ref} = prim_inet:async_accept(ListenSocket, -1),
First of all, Ref
isn't a reference()
. Sorry about that. I actually have no idea what it is, but it'll be the same value that we bind to Ref
again later. When's later? When a client tries to connect. When that happens you'll get a process message that looks like this:
{inet_async, ListenSocket, Ref, {ok, CliSocket}}
ListenSocket
is the same one you passed in. Ref
, like I mentioned, is the same value that was returned from prim_inet:async_accept/2
. It's {ok, CliSocket}
that we want to work with.
We're certainly not done. When you try to perform any gen_tcp
functions on CliSocket
, it all explodes. gen_tcp:accept
and ssl:accept
regester the port as a socket with the inet_db for you, but since we went around those, we're going to have to register it manually:
inet_db:register_socket(CliSocket, inet_tcp),
Great. Now all these gen_tcp
options work! But, you'll need to do a little more if you want this to be a SSL connection.
{ok, AcceptSocket} = ssl:ssl_accept(CliSocket, SSLOptions),
Once this is done, you can perform ssl
functions on the socket.
How I Used It
In chatterbox, each http2_connection
FSM started like this:
- Supervisor passes an open socket (started with either
ssl:listen
orgen_tcp:listen
) toinit/1
- In order to not hang in the
init/1
callback, add the Listener to the FSM state and transition to anaccept
state with a0
timeout - use a case statement to call either
gen_tcp:accept/1
orssl:transport_accept/1
, and hang waiting on a connection.
That's how it was before this commit when I moved over to the prim_inet
solution. Now it works more like:
- Supervisor still passes an open socket, but now it's always opened with
gen_tcp:listen
init/1
callsprim_inet:async_accept/2
- The half of my
accept/2
callback was refactored intohandle_info/2
, because now it's reacting to a message fromprim_inet
, and not a0
timeout. - The remaining half of my
accpet/2
callback was renamed tohandshake/2
to more accurately reflect it's role in the HTTP/2 spec.
This way, my FSM is no longer blocking on gen_tcp:accpet/1
or ssl:transport_accept/1
, and can now receive messages. Now, I don't actually need to receive messages in this particular case, but it seems cleaner overall.