Erlang accept incoming tcp connections dynamically

Basic Procedure

You should have one static process (implemented as a gen_server or a custom process) that performs the following procedure:

  1. Listens for incoming connections using gen_tcp:accept/1
  2. Every time it returns a connection, tell a supervisor to spawn of a worker process (e.g. another gen_server process)
  3. Get the pid for this process
  4. Call gen_tcp:controlling_process/2 with the newly returned socket and that pid
  5. Send the socket to that process

Note: You must do it in that order, otherwise the new process might use the socket before ownership has been handed over. If this is not done, the old process might get messages related to the socket when the new process has already taken over, resulting in dropped or mishandled packets.

The listening process should only have one responsibility, and that is spawning of workers for new connections. This process will block when calling gen_tcp:accept/1, which is fine because the started workers will handle ongoing connections concurrently. Blocking on accept ensure the quickest response time when new connections are initiated. If the process needs to do other things in-between, gen_tcp:accept/2 could be used with other actions interleaved between timeouts.

Scaling

  • You can have multiple processes waiting with gen_tcp:accept/1 on a single listening socket, further increasing concurrency and minimizing accept latency.

  • Another optimization would be to pre-start some socket workers to further minimize latency after accepting the new socket.

  • Third and final, would be to make your processes more lightweight by implementing the OTP design principles in your own custom processes using proc_lib (more info). However, this you should only do if you benchmark and come to the conclusion that it is the gen_server behavior that slows you down.


The issue with gen_tcp:accept is that it blocks, so if you call it within a gen_server, you block the server from receiving other messages. You can try to avoid this by passing a timeout but that ultimately amounts to a form of polling which is best avoided. Instead, you might try Kevin Smith's gen_nb_server instead; it uses an internal undocumented function prim_inet:async_accept and other prim_inet functions to avoid blocking.