Erlang accept incoming tcp connections dynamically
What I am trying to solve: have an Erlang TCP server that listens on a specific port (the code should reside in some kind of external facing interface/API) and each incoming co开发者_开发技巧nnection should be handled by a gen_server
(that is even the gen_tcp:accept
should be coded inside the gen_server
), but I don't actually want to initially spawn a predefined number of processes that accepts an incoming connection). Is that somehow possible ?
Basic Procedure
You should have one static process (implemented as a gen_server
or a custom process) that performs the following procedure:
- Listens for incoming connections using
gen_tcp:accept/1
- Every time it returns a connection, tell a supervisor to spawn of a worker process (e.g. another
gen_server
process) - Get the pid for this process
- Call
gen_tcp:controlling_process/2
with the newly returned socket and that pid - 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 thegen_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.
You might want to check out http://github.com/oscarh/gen_tcpd and use the handle_connection function to convert the process you get to a gen_server.
You should use "prim_inet:async_accept(Listen_socket, -1)" as said by Steve. Now the incoming connection would be accepted by your handle_info callback (assuming you interface is also a gen_server) as you have used an asynchronous accept call.
On accepting the connection you can spawn another ger_server(I would recommend gen_fsm) and make that as the "controlling process" by calling "gen_tcp:controlling_process(CliSocket, Pid of spwned process)".
After this all the data from socket would be received by that process rather than by your interface code. Like that a new controlling process would be spawned for another connection.
精彩评论