In this blog, I will continue my discussion on ejabberd’s socket infrastructure.
For the sake of simplicity, let’s write a simple echo service module, which receives any packet from the client, and echo the packet back.
Register our listener
First, we must register our service in the ejabberd’s config file:
Quick and dirty echo service
Our echo service will be listening at 5555/tcp. Now let’s write a handler for this port, we start out with a gen_fsm skeleton:
Pretty simple, right? With only very few lines of the default gen_fsm code changed, we have a fully working echo service!
The first thing you may have noticed is the start/2 and socket_type/0 call. Why is this neccessary? Recall from ejabberd_socket:
As is shown above, if the Module:socket_type() returns the atom ‘raw’, then the module will be used without ejabberd_receiver, which is kinda what we want, because we want to take control over everything. After ejabberd_socket calls Module:start/2, passing the socket module(gen_tcp here) and the socket, it will call SockMod:controlling_process to direct any messages with the socket to the Pid returned by Module:start/2, which is, in our case, the echo_service gen_fsm process.
When the echo_service fsm starts, the socket is in passive mode, that is, it won’t get any data until the recv() function is called. As a result, we set the socket to be active once, and the data can be received. Everytime we receive a new packet, we generate and send response, and then set the socket to be active once again.
Finally, don’t forget to turn off the FSM when {tcp_closed, Sock} is received. That prevents you from leaking processes.
That should sound reasonable enough. But wait, what if we want our echo service to be working on UDP also?
Adding UDP Transport
Simple. Let’s see how UDP sockets in ejabberd are handled:
That’s pretty neat. Instead of spawning a new process for every new connection like the tcp, udp sockets are handled by only one process for each port.
To use UDP transport, we simply add udp_recv to our Module:
That’s enough for most purposes, but you must be careful: if the Module:udp_recv/5 call blocks, it blocks any other data to be handled. Hence, in real life applications, get ready to spawn multiple processes to handle the UDP requests!
Using customized socket options
The ejabberd_listener’s listen options fits our needs in most cases. What if we want customized socket options, other than what the default options, say, we want {packet, 4} to be set before we receive any data from the socket?
That’s easy. First add the options in the config file:
Then we add a setopts step in our echo service module:
We have added a filter to filter the options provided, allowing only valid options to be set. Now the echo service listening on 5556/tcp will require a 4-octet header stating the whole packet length, cool isn’t it?
To sum up
We have written a very simple echo service to learn how to use the ejabberd’s socket infrastructure. To write a simple TCP service, we only need to implement the socket_type() to return raw, and spawn a process handling the socket in Mod:start/2. To write a simple UDP service, we only need to provide a udp_recv/5 callback. Things we haven’t covered yet:
What about the TLS transport ? (hint:use the tls module included in ejabberd.)
How to separte socket receiving data and socket handling logic? (hint: start and return your own receiver in your Mod:start).
How to use the builtin ejabberd_receiver and ejabberd_socket? (hint: return xml_stream for socket_type/0).
The above questions are left out as an exercise. Hack on!